Использование "IN" в предложении WHERE, где количество элементов в наборе очень велико

У меня есть ситуация, когда мне нужно сделать обновление в очень большом наборе строк, которые я могу идентифицировать только по их идентификатору (так как целевые записи выбираются пользователем и не имеют ничего общего, кроме набора записи, которые пользователь хотел изменить). Это же свойство обновляется во всех этих записях, поэтому я хотел бы сделать один вызов UPDATE.

Это плохая практика или есть лучший способ сделать это обновление, чем использовать "WHERE IN (1,2,3,4,..... 10000)" в инструкции UPDATE?

Будет ли смысл использовать отдельные операторы обновления для каждой записи и вставлять их в одну транзакцию? Сейчас я работаю с SQL Server и Access, но, если это возможно, я бы хотел услышать более широкие передовые практики в любой реляционной базе данных.

Ответ 1

Я всегда использовал бы

WHERE id IN (1,2,3,4,.....10000)

если ваше предложение не было глупо большим, что не должно происходить из пользовательского ввода.

edit: Например, Rails делает это за кулисами

Конечно, было бы не лучше делать отдельные операторы обновления в одной транзакции.

Ответ 2

Другой альтернативой является сохранение этих чисел в таблице temp и использование их в соединении для обновления. Если вы можете выполнить один оператор обновления, определенно лучше, чем выполнение одного оператора на запись.

Ответ 3

Как вы создаете предложение IN?

Если есть еще одна инструкция SELECT, которая генерирует эти значения, вы можете просто подключить ее к UPDATE следующим образом:

UPDATE TARGET_TABLE T
SET
  SOME_VALUE = 'Whatever'
WHERE T.ID_NUMBER IN(
                    SELECT ID_NUMBER  --this SELECT generates your ID #s.
                    FROM SOURCE_TABLE
                    WHERE SOME_CONDITIONS
                    )

В некоторых RDBMses вы получите лучшую производительность, используя синтаксис EXISTS, который будет выглядеть следующим образом:

UPDATE TARGET_TABLE T
SET
  SOME_VALUE = 'Whatever'
WHERE EXISTS (
             SELECT ID_NUMBER  --this SELECT generates your ID #s.
             FROM SOURCE_TABLE S
             WHERE SOME_CONDITIONS
               AND S.ID_NUMBER =  T.ID_NUMBER
             )

Ответ 4

В Oracle существует предел значений, которые вы можете поместить в предложение IN. Поэтому вам лучше использовать OR, x = 1 или x = 2... они не ограничены, насколько я знаю.

Ответ 5

Я бы использовал таблицу-переменную/temp-table; вставьте в него значения и присоединитесь к нему. Затем вы можете использовать один и тот же набор несколько раз. Это работает особенно хорошо, если вы (например) передаете CSV ID в качестве varchar. В качестве примера SQL Server:

DECLARE @ids TABLE (id int NOT NULL)

INSERT @ids
SELECT value
FROM dbo.SplitCsv(@arg) // need to define separately

UPDATE t
SET    t. // etc
FROM   [TABLE] t
INNER JOIN @ids #i ON #i.id = t.id

Ответ 6

Не зная, что такое "очень большое" количество ID, я бы рискнул предположить.;-)

Поскольку вы используете Access как базу данных, количество ID не может быть таким высоким. Предполагая, что мы говорим о менее чем, скажем, 10 000 номеров, и мы должны знать ограничения контейнеров для хранения идентификатора (какой язык используется для интерфейса?), Я бы придерживался одной инструкции UPDATE; если это наиболее читаемо и проще всего выполнять обслуживание позже. В противном случае я бы разделил их на несколько утверждений, используя некоторую умную логику. Что-то вроде разделить утверждение на несколько операторов с одним, десятью, сотнями, тысячами... идентификатором для каждого оператора.

Тогда я оставил бы его оптимизатору БД, чтобы выполнить оператор как можно эффективнее. Я бы, вероятно, сделал "объяснение" в запросе/запросах, чтобы убедиться, что ничего глупого не происходит.

Но, по моему опыту, довольно часто удается оставить эту оптимизацию самому менеджеру базы данных. Единственное, что занимает больше всего времени, - это, как правило, фактическое подключение к базе данных, поэтому, если вы можете выполнять все запросы в одном и том же соединении, то обычно проблем нет. Убедитесь, что вы отсылаете все операторы UPDATE, прежде чем приступать к просмотру и ждать, пока все возвращаемые наборы результатов вернутся.: -)

Ответ 7

В целом есть несколько вещей, которые следует учитывать.

  • Оператор анализирует кеш в БД. Каждое утверждение с другим количеством элементов в предложении IN должно анализироваться отдельно. Вы используете связанные переменные вместо литералов, правильно?
  • Некоторые базы данных имеют ограничение на количество элементов в предложении IN. Для Oracle это 1000.
  • При обновлении блокировки записей. Если у вас несколько отдельных операторов обновления, у вас могут быть взаимоблокировки. Это означает, что вы должны быть осторожны с порядком, в котором вы публикуете свои обновления.
  • Задержка в обратном направлении для базы данных может быть высокой, даже для очень быстрого оператора. Это означает, что часто лучше манипулировать множеством записей одновременно, чтобы сохранить время отключения.

Недавно мы изменили нашу систему, чтобы ограничить размер внутренних предложений и всегда использовать связанные переменные, поскольку это уменьшило количество различных операторов SQL и, таким образом, улучшило производительность. В основном мы генерируем наши операторы SQL и выполняем несколько операторов, если in-clause превышает определенный размер. Мы не делаем этого для обновлений, поэтому нам не пришлось беспокоиться о блокировке. Вы будете.

Использование таблицы temp может не улучшить производительность, поскольку вам нужно заполнить таблицу temp идентификаторами. Эксперименты и тесты производительности могут сообщить вам ответ здесь.

Один пункт IN очень прост для понимания и поддержки. Вероятно, это то, о чем вам следует беспокоиться в первую очередь. Если вы обнаружите, что производительность запросов невелика, вы можете попробовать другую стратегию и посмотреть, помогает ли она, но не оптимизируйте ее преждевременно. Предложение IN является семантически правильным, поэтому оставьте его в покое, если оно не сломано.

Ответ 8

Если бы вы были в Oracle, я бы рекомендовал использовать функции таблицы, похожие на пост Марка Гравелла.

-- first create a user-defined collection type, a table of numbers
create or replace type tbl_foo as table of number;

declare
  temp_foo tbl_foo;
begin
  -- this could be passed in as a parameter, for simplicity I am hardcoding it
  temp_foo := tbl_foo(7369, 7788);

  -- here I use a table function to treat my temp_foo variable as a table, 
  -- and I join it to the emp table as an alternative to a massive "IN" clause
  select e.*
    from emp e,
         table(temp_foo) foo
   where e.empno = foo.column_value;
end;

Ответ 9

Я не знаю тип значений в вашем списке IN. Если они составляют большинство значений от 1 до 10 000, вы можете обработать их, чтобы получить что-то вроде:

WHERE MyID BETWEEN 1 AND 10000 AND MyID NOT IN (3,7,4656,987)

Или, если список NOT IN будет длинным, обработайте список и создайте кучу операторов МЕЖДУ:

WHERE MyID BETWEEN 1 AND 343 AND MyID BETWEEN 344 AND 400 ...

И так далее.

В конце концов, вам не нужно беспокоиться о том, как Jet обработает предложение IN, если вы используете запрос PASSE. Вы не можете сделать это в коде, но у вас может быть сохраненный QueryDef, который определяется как сквозной проход и изменить предложение WHERE в коде во время выполнения, чтобы использовать ваш список IN. Затем все это передается SQL Server, и SQL Server лучше решает, как обрабатывать его.