(Изменить: если параметры с табличными значениями недоступны)
Лучше всего разделить большое количество параметров IN на несколько запросов с фиксированной длиной, поэтому у вас есть ряд известных операторов SQL с фиксированным числом параметров и без фиктивных / дублированных значений, а также без разбора строк, XML и т. П. .
Вот код на C #, который я написал по этой теме:
public static T[][] SplitSqlValues<T>(IEnumerable<T> values)
{
var sizes = new int[] { 1000, 500, 250, 125, 63, 32, 16, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
int processed = 0;
int currSizeIdx = sizes.Length - 1; /* start with last (smallest) */
var splitLists = new List<T[]>();
var valuesDistSort = values.Distinct().ToList(); /* remove redundant */
valuesDistSort.Sort();
int totalValues = valuesDistSort.Count;
while (totalValues > sizes[currSizeIdx] && currSizeIdx > 0)
currSizeIdx--; /* bigger size, by array pos. */
while (processed < totalValues)
{
while (totalValues - processed < sizes[currSizeIdx])
currSizeIdx++; /* smaller size, by array pos. */
var partList = new T[sizes[currSizeIdx]];
valuesDistSort.CopyTo(processed, partList, 0, sizes[currSizeIdx]);
splitLists.Add(partList);
processed += sizes[currSizeIdx];
}
return splitLists.ToArray();
}
(у вас могут быть другие идеи, пропустите сортировку, используйте valuesDistSort.Skip (обработано). Взять (размер [...]) вместо списка / массива CopyTo).
Вставляя переменные параметра, вы создаете что-то вроде:
foreach(int[] partList in splitLists)
{
/* here: question mark for param variable, use named/numbered params if required */
string sql = "select * from Items where Id in("
+ string.Join(",", partList.Select(p => "?"))
+ ")"; /* comma separated ?, one for each partList entry */
/* create command with sql string, set parameters, execute, merge results */
}
Я наблюдал за SQL, генерируемым объектно-реляционным картографом NHibernate (при запросе данных для создания объектов), и это лучше всего выглядит при множественных запросах. В NHibernate можно указать размер пакета; если нужно извлечь много строк данных объекта, он пытается получить количество строк, эквивалентное размеру пакета
SELECT * FROM MyTable WHERE Id IN (@p1, @p2, @p3, ... , @p[batch-size])
вместо отправки сотен или тысяч
SELECT * FROM MyTable WHERE Id=@id
Когда оставшиеся идентификаторы меньше, чем размер пакета, но все же больше, чем один, он разбивается на меньшие операторы, но все же с определенной длиной.
Если у вас размер пакета 100 и запрос со 118 параметрами, он создаст 3 запроса:
- один со 100 параметрами (размер партии),
- затем один с 12
- и еще один с 6,
, но ни один с 118 или 18. Таким образом, он ограничивает возможные операторы SQL до вероятных известных операторов, предотвращая слишком много различных, таким образом, слишком много планов запросов, которые заполняют кэш и в больших частях никогда не используются повторно. Приведенный выше код делает то же самое, но с длинами 1000, 500, 250, 125, 63, 32, 16, 10-к-1. Списки параметров, содержащие более 1000 элементов, также разделяются, что предотвращает ошибку базы данных из-за ограничения размера.
В любом случае, лучше иметь интерфейс базы данных, который отправляет параметризованный SQL напрямую, без отдельного оператора Prepare и дескриптора вызова. Базы данных, такие как SQL Server и Oracle, запоминают SQL по равенству строк (значения меняются, параметры привязки в SQL нет!) И повторно используют планы запросов, если они доступны. Нет необходимости в отдельных операторах подготовки и утомительном обслуживании дескрипторов запросов в коде! ADO.NET работает следующим образом, но кажется, что Java все еще использует подготовку / выполнение по дескриптору (не уверен).
У меня был свой вопрос на эту тему, первоначально предлагалось заполнить предложение IN дубликатами, но затем я предпочел разделить оператор в стиле NHibernate:
Параметризованный SQL - вход / выход с фиксированным числом параметров, для оптимизации кэша плана запроса?
Этот вопрос по-прежнему интересен, даже спустя 5 лет после того, как его спросили ...
РЕДАКТИРОВАТЬ: я заметил, что запросы IN со многими значениями (например, 250 или более) все еще имеют тенденцию быть медленными, в данном случае, на SQL Server. Хотя я ожидал, что БД создаст внутреннюю временную таблицу и соединится с ней, казалось, что она только один раз повторяла выражение SELECT с одним значением n раз. Время составляло примерно 200 мс на запрос - даже хуже, чем объединение извлечения исходных идентификаторов SELECT с другими связанными таблицами. Кроме того, в SQL Server Profiler было около 10–15 модулей ЦП, что является необычным для повторного выполнения одного и того же параметризованного запросы, предполагая, что новые планы запросов были созданы при повторных вызовах. Может быть, отдельные запросы, как отдельные запросы, ничуть не хуже. Мне пришлось сравнивать эти запросы с неразделенными запросами с изменяющимися размерами для окончательного вывода, но сейчас кажется, что в любом случае следует избегать длинных предложений IN.