SQL WHERE ID IN (id1, id2,…, idn)

170

Мне нужно написать запрос, чтобы получить большой список идентификаторов.

Мы поддерживаем множество бэкэндов (MySQL, Firebird, SQLServer, Oracle, PostgreSQL ...), поэтому мне нужно написать стандартный SQL.

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

1) Написание запроса с использованием IN

SELECT * FROM TABLE WHERE ID IN (id1, id2, ..., idn)

Мой вопрос здесь Что будет, если n очень большое? Кроме того, как насчет производительности?

2) Написание запроса с использованием ИЛИ

SELECT * FROM TABLE WHERE ID = id1 OR ID = id2 OR ... OR ID = idn

Я думаю, что у этого подхода нет предела n, но как насчет производительности, если n очень велико?

3) Написание программного решения:

  foreach (var id in myIdList)
  {
      var item = GetItemByQuery("SELECT * FROM TABLE WHERE ID = " + id);
      myObjectList.Add(item);
  }

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

Каково было бы правильное решение этой проблемы?

Даниэль Пеньальба
источник
1
Вариант 1 значительно сокращает время отклика сервера SQL, выбирая идентификаторы 7k, некоторые из которых не существовали. Обычно запрос занимал около 1300 мс, он сокращается до 80 мс при использовании IN! Я выбрал в качестве вашего решения 1 + 3. Просто последний запрос был одной длинной строкой запроса, отправленной в SQL для выполнения.
Петр Кула

Ответы:

108

Вариант 1 является единственным хорошим решением.

Зачем?

  • Вариант 2 делает то же самое, но вы повторяете имя столбца много раз; Кроме того, механизм SQL не сразу знает, что вы хотите проверить, является ли значение одним из значений в фиксированном списке. Тем не менее, хороший движок SQL может оптимизировать его, чтобы иметь равную производительность, как с IN. Там все еще проблема читабельности, хотя ...

  • Вариант 3 просто ужасен с точки зрения производительности. Он отправляет запрос каждый цикл и забивает базу данных небольшими запросами. Это также предотвращает использование каких-либо оптимизаций для «значение является одним из тех, что в данном списке»

ThiefMaster
источник
2
Я согласен, но обратите внимание, что список во многих RDMS ограничен, и поэтому вам нужно, чтобы мы использовали решение @Ed Guiness, но здесь временные таблицы отличаются между RDBMS. (Эффективно для сложных задач, вы не можете использовать только чистый стандартный SQL)
mmmmmm
28

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

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

Обновление : хотя вы можете использовать временную таблицу, я не имел в виду, что вы должны или даже должны. Постоянная таблица, используемая для временных данных, является распространенным решением с достоинствами, помимо описанных здесь.

Эд Гинесс
источник
1
Но как бы вы передали список идентификаторов, которые вам нужны? (Видя, что вы не можете выбрать диапазон или что-то в этом роде).
raam86
1
@ raam86: список идентификаторов мог быть получен с помощью selectоператора в другой таблице. Список передается как другая таблица, с которой вы работаете inner join.
bdforbes,
19

То, что предложил Эд Гиннес, действительно повышает производительность, у меня был такой запрос

select * from table where id in (id1,id2.........long list)

что я сделал :

DECLARE @temp table(
            ID  int
            )
insert into @temp 
select * from dbo.fnSplitter('#idlist#')

Затем внутренний соединяется с основной таблицей:

select * from table inner join temp on temp.id = table.id

И производительность значительно улучшилась.

Риту
источник
1
Привет, fnSplitter - это функция из MSSQL? Потому что я не смог его найти.
WiiMaxx
Это не стандартная вещь. Они должны означать, что они написали эту функцию для этой цели или, например, имели приложение, которое уже предоставило ее.
underscore_d
fnSplitter - это функция, созданная Ritu, которую вы можете найти в интернете / Google, похожая на нее
Башар Абу Шамаа
9

Первый вариант, безусловно, лучший вариант.

SELECT * FROM TABLE WHERE ID IN (id1, id2, ..., idn)

Однако, учитывая, что список идентификаторов очень велик , скажем, миллионы, вы должны учитывать размеры блоков, как показано ниже:

  • Разделите список идентификаторов на куски с фиксированным числом, скажем, 100
  • Размер куска следует выбирать исходя из объема памяти вашего сервера
  • Предположим, у вас есть 10000 идентификаторов, у вас будет 10000/100 = 100 блоков
  • Обрабатывать один блок за раз, что приводит к 100 вызовам базы данных для выбора

Почему вы должны разделить на куски?

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

Это всегда работало как очарование для меня. Надеюсь, это сработает и для моих коллег-разработчиков :)

Адарш Кумар
источник
4

Выполнение команды SELECT * FROM MyTable where id in () для таблицы SQL Azure с 500 миллионами записей привело к времени ожидания> 7 минут!

Выполнение этого вместо этого немедленно возвратило результаты:

select b.id, a.* from MyTable a
join (values (250000), (2500001), (2600000)) as b(id)
ON a.id = b.id

Используйте соединение.

JakeJ
источник
3

В большинстве систем баз данных, IN (val1, val2, …)а также серии ORоптимизированы для того же плана.

Третий способ - импортировать список значений во временную таблицу и объединить ее, что более эффективно в большинстве систем, если имеется много значений.

Вы можете прочитать эту статью:

Quassnoi
источник
3

Пример 3 был бы худшим из всех, потому что вы попали в базу данных бесчисленное количество раз без видимой причины.

Загрузка данных во временную таблицу и последующее объединение будут самыми быстрыми. После этого IN должен работать немного быстрее, чем группа OR.

judda
источник
2

Я думаю, что вы имеете в виду SqlServer, но в Oracle у вас есть жесткое ограничение на количество элементов IN, которые вы можете указать: 1000.

FLQ
источник
1
Даже SQL Server перестает работать после ~ 40k IN элементов. Согласно MSDN: Включение в предложение IN чрезвычайно большого числа значений (многие тысячи) может потреблять ресурсы и возвращать ошибки 8623 или 8632. Чтобы обойти эту проблему, сохраните элементы списка IN в таблице.
Яхав