Использование «IN» в предложении WHERE, где количество элементов в наборе очень большое - PullRequest
14 голосов
/ 10 февраля 2009

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

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

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

Ответы [ 10 ]

15 голосов
/ 10 февраля 2009

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

8 голосов
/ 10 февраля 2009

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

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

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

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

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

4 голосов
/ 10 февраля 2009

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

Если есть другой оператор SELECT, который генерирует эти значения, вы можете просто вставить это в ОБНОВЛЕНИЕ следующим образом:

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
             )
2 голосов
/ 10 февраля 2009

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

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

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

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

2 голосов
/ 10 февраля 2009

Я бы использовал переменную таблицы / временную таблицу; вставить значения в это, и присоединиться к нему. Тогда вы можете использовать один и тот же набор несколько раз. Это работает особенно хорошо, если вы (например) передаете CSV идентификаторов как 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
2 голосов
/ 10 февраля 2009

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

1 голос
/ 10 февраля 2009

Если бы вы работали в 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;
1 голос
/ 10 февраля 2009

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

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

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

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

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

0 голосов
/ 24 сентября 2018

Существует несколько способов размещения большого набора значений в условии where

  1. Использование временных таблиц

    Вставить значения во временную таблицу с одним столбцом.

    Создать УНИКАЛЬНЫЙ ИНДЕКС для этого конкретного столбца.

    ВНУТРЕННИЙ СОЕДИНИТСЯ с необходимой таблицей с вновь созданной временной таблицей

  2. Использование функции массива в SQL Server

    SQL поддерживает массив как функциональность

    проверьте эту ссылку для полной документации.

ОБРАЗЕЦ СИНТАКСА:

Create TABLE #IDs (id int NOT NULL)
DECLARE @x varchar(max) = '' 
DECLARE @xParam XML;
SELECT @xParam = CAST('<i>' + REPLACE(@x, ',', '</i><i>') + '</i>' AS XML)
INSERT into #IDs
SELECT x.i.value('.','NVARCHAR(100)') as key FROM @xParam .nodes('//i') x(i)
CREATE UNIQUE INDEX IX_#IDs ON #IDs (ID ASC) 

Запрос с использованием

SELECT A.Name, A.Age from Table A 

INNER JOIN # ID ID на id.id = A.Key

0 голосов
/ 12 февраля 2009

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

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

Или, если список NOT IN все еще будет длинным, обработайте список и сгенерируйте несколько операторов BETWEEN:

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

и т. Д.

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

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