Параметризуйте пункт SQL IN


1041

Як параметризувати запит, що містить INпункт із змінною кількістю аргументів, як цей?

SELECT * FROM Tags 
WHERE Name IN ('ruby','rails','scruffy','rubyonrails')
ORDER BY Count DESC

У цьому запиті кількість аргументів може бути від 1 до 5.

Я вважаю за краще не використовувати спеціально збережену процедуру для цього (або XML), але якщо є якийсь елегантний спосіб, характерний для SQL Server 2008 , я відкритий для цього.



Відповіді:


316

Ось швидкий і брудний метод, який я використав:

SELECT * FROM Tags
WHERE '|ruby|rails|scruffy|rubyonrails|'
LIKE '%|' + Name + '|%'

Отже ось код C #:

string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
const string cmdText = "select * from tags where '|' + @tags + '|' like '%|' + Name + '|%'";

using (SqlCommand cmd = new SqlCommand(cmdText)) {
   cmd.Parameters.AddWithValue("@tags", string.Join("|", tags);
}

Два застереження:

  • Виступ жахливий. LIKE "%...%"запити не індексуються.
  • Переконайтесь, що у вас немає жодних |, порожніх чи нульових тегів, інакше це не працюватиме

Є й інші способи досягти цього, які деякі люди можуть вважати чистішими, тому, будь ласка, читайте.


119
Це буде привіт повільно
Метт Рогіш

13
Так, це сканування таблиці. Чудово на 10 рядів, паршиві на 100 000.
Уїлл Хартюнг

17
Переконайтесь, що ви перевіряли теги, які містять труби.
Joel Coehoorn

17
Це навіть не відповідає на питання. Зрозуміло, легко зрозуміти, куди додати параметри, але як можна прийняти це рішення, якщо воно навіть не заважає параметризувати запит? Це виглядає просто простіше, ніж @Mark Brackett, тому що він не параметризований.
tvanfosson

21
Що робити, якщо ваш тег "рубін | рейки". Він буде відповідати, що буде неправильним. Коли ви розгортаєте подібні рішення, вам потрібно переконатися, що теги не містять труб, або явно відфільтрувати їх: виберіть * з Тегів, де '| ruby ​​| рейли | scruffy | rubyonrails |' як "% |" + Ім'я + '|%' І ім’я не подобається '%!%'
АК

729

Ви можете параметризувати кожне значення таким чином:

string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
string cmdText = "SELECT * FROM Tags WHERE Name IN ({0})";

string[] paramNames = tags.Select(
    (s, i) => "@tag" + i.ToString()
).ToArray();

string inClause = string.Join(", ", paramNames);
using (SqlCommand cmd = new SqlCommand(string.Format(cmdText, inClause))) {
    for(int i = 0; i < paramNames.Length; i++) {
       cmd.Parameters.AddWithValue(paramNames[i], tags[i]);
    }
}

Що дасть вам:

cmd.CommandText = "SELECT * FROM Tags WHERE Name IN (@tag0, @tag1, @tag2, @tag3)"
cmd.Parameters["@tag0"] = "ruby"
cmd.Parameters["@tag1"] = "rails"
cmd.Parameters["@tag2"] = "scruffy"
cmd.Parameters["@tag3"] = "rubyonrails"

Ні, це не відкрито для ін'єкції SQL . Єдиний введений текст у CommandText не заснований на введенні користувача. Він базується виключно на твердо кодованому префіксі "@tag" та індексі масиву. Індекс завжди буде цілим числом, не створюється користувачем і є безпечним.

Введені користувачем значення все ще заповнені параметрами, тому вразливості там немає.

Редагувати:

Ін'єкція стосується стороні, зауважте, що побудова тексту команди для розміщення змінної кількості параметрів (як вище) перешкоджає можливості SQL-сервера використовувати переваги кешованих запитів. Результатом цього є те, що ви майже напевно втрачаєте значення використання параметрів в першу чергу (на відміну від просто вставлення рядків предиката в сам SQL).

Не те, що кешовані плани запитів не є цінними, але IMO цей запит не є досить складним, щоб побачити від нього велику користь. Хоча витрати на компіляцію можуть наближатись (або навіть перевищувати) витрати на виконання, ви все ще говорите мілісекунди.

Якщо у вас достатньо оперативної пам’яті, я б очікував, що SQL Server, ймовірно, кеширує план і для загальних підрахунків параметрів. Я припускаю, що ви завжди можете додати п’ять параметрів, і нехай не визначені теги будуть NULL - план запитів повинен бути однаковим, але це здається мені дуже некрасивим, і я не впевнений, що вартує мікрооптимізації (хоча, on Stack Overflow - це може бути дуже вартим).

Крім того, SQL Server 7 і пізніші версії будуть автоматично параметризувати запити , тому використання параметрів насправді не потрібно з точки зору продуктивності - це, однак, критично з точки зору безпеки - особливо з такими введеними користувачем даними.


2
В основному такий же, як моя відповідь на "споріднене" питання і, очевидно, найкраще рішення, оскільки це конструктивне та ефективне, а не інтерпретаційне (набагато складніше).
tvanfosson

49
Ось як це робить LINQ до SQL, BTW
Марк Сідаде

3
@ Чистота: Вся справа в тому, щоб уникнути ін'єкції SQL, до якої ви були б уразливі, якби використовували динамічний SQL.
Рей

4
@God of Data - Так, я думаю, якщо вам потрібно більше 2100 тегів, вам знадобиться інше рішення. Але Басарб міг досягти 2100 лише тоді, коли середня довжина тегів склала <3 знаки (оскільки вам потрібен і роздільник). msdn.microsoft.com/en-us/library/ms143432.aspx
Марк Брекетт

2
@bonCodigo - вибрані значення знаходяться в масиві; ви просто переведіть на масив і додасте параметр (суфікс із індексом) для кожного.
Марк Брокетт

249

Для SQL Server 2008 можна використовувати параметр, що оцінюється в таблиці . Це трохи робота, але вона, певно, чистіша, ніж мій інший метод .

По-перше, ви повинні створити тип

CREATE TYPE dbo.TagNamesTableType AS TABLE ( Name nvarchar(50) )

Тоді ваш код ADO.NET виглядає так:

string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
cmd.CommandText = "SELECT Tags.* FROM Tags JOIN @tagNames as P ON Tags.Name = P.Name";

// value must be IEnumerable<SqlDataRecord>
cmd.Parameters.AddWithValue("@tagNames", tags.AsSqlDataRecord("Name")).SqlDbType = SqlDbType.Structured;
cmd.Parameters["@tagNames"].TypeName = "dbo.TagNamesTableType";

// Extension method for converting IEnumerable<string> to IEnumerable<SqlDataRecord>
public static IEnumerable<SqlDataRecord> AsSqlDataRecord(this IEnumerable<string> values, string columnName) {
    if (values == null || !values.Any()) return null; // Annoying, but SqlClient wants null instead of 0 rows
    var firstRecord = values.First();
    var metadata = SqlMetaData.InferFromValue(firstRecord, columnName);
    return values.Select(v => 
    {
       var r = new SqlDataRecord(metadata);
       r.SetValues(v);
       return r;
    });
}

41
ми перевірили це, і параметри, які оцінюються в таблиці, повільні. Буквально швидше виконати 5 запитів, ніж це зробити один ТВП.
Джефф Етвуд

4
@JeffAtwood - Ви спробували перестановити запит на щось подібне SELECT * FROM tags WHERE tags.name IN (SELECT name from @tvp);? Теоретично це справді має бути найшвидший підхід. Ви можете використовувати відповідні індекси (наприклад, індекс на ім'я тегів, що INCLUDEпідрахунок s був би ідеальним), і SQL Server повинен зробити кілька спроб захопити всі теги та їх кількість. Як виглядає план?
Nick Chammas

9
Я також перевірив це, і це ШВИДКО ЯК Світло (порівняно зі створенням великої струни IN). У мене виникли проблеми з налаштуванням параметра, хоча я постійно отримував "Не вдалося перетворити значення параметра з Int32 [] в IEnumerable`1.". У всякому разі, вирішив це, і ось зразок, який я зробив pastebin.com/qHP05CXc
Фредрік Йоханссон

6
@FredrikJohansson - Із 130 оновлених результатів ви можете бути єдиним запуском, який намагався запустити це! Я помилився, читаючи документи, і вам насправді потрібен IEnumerable <SqlDataRecord>, а не будь-який IEnumerable. Код оновлено.
Марк Брекетт

3
@MarkBrackett Чудово з оновленням! Точно цей код дійсно врятував мені день, оскільки я запитую Lucene-індекс пошуку, і він іноді повертає понад 50 000 або близько хітів, які потрібно подвійно перевірити на сервері SQL. Тому я створюю масив int [] (document / Клавіші SQL), а потім надходить код вище. Весь ОП зараз займає менше 200 мс :)
Фредрік Йоханссон

188

Первісне питання було "Як параметризувати запит ..."

Дозвольте тут констатувати, що це не відповідь на початкове запитання. У цьому вже є деякі демонстрації в інших хороших відповідях.

З урахуванням сказаного, ідіть уперед і позначте цю відповідь, похитніть її, позначте її як не відповідь ... робіть все, що ви вважаєте правильним.

Див. Відповідь від Марка Бранкета за кращою відповіддю, яку я (та 231 інші) підтримали. Підхід, наведений у його відповіді, дозволяє 1) для ефективного використання змінних зв'язувати, і 2) для предикатів, які є спірними.

Вибрана відповідь

Я хочу тут звернутись - це підхід, наведений у відповіді Джоела Спольського, відповідь "обрана" як правильна відповідь.

Підхід Джоела Спольського розумний. І це працює розумно, він буде демонструвати передбачувану поведінку та передбачувану продуктивність, з урахуванням "нормальних" значень, і з нормативними крайовими випадками, такими як NULL та порожній рядок. І це може бути достатньо для конкретного застосування.

Але з точки зору узагальнення цього підходу, давайте також розглянемо більш незрозумілі кутові випадки, як, наприклад, коли Nameстовпець містить символи підстановки (як визнано предикатом LIKE.) Символ підстановки, який я бачу, як правило, використовується %(знак відсотка). Тож давайте зараз розберемося з цим, а пізніше перейдемо до інших справ.

Деякі проблеми з символом%

Розглянемо значення імені 'pe%ter'. (Для прикладів тут я використовую буквальне значення рядка замість імені стовпця.) Рядок із значенням імені `` pe% ter 'буде повернутий запитом форми:

select ...
 where '|peanut|butter|' like '%|' + 'pe%ter' + '|%'

Але той самий рядок не буде повернуто, якщо порядок пошукових термінів буде змінено:

select ...
 where '|butter|peanut|' like '%|' + 'pe%ter' + '|%'

Поведінка, яку ми спостерігаємо, є якоюсь дивною. Зміна порядку пошуку пошукових термінів у списку змінює набір результатів.

Цілком зрозуміло, що ми можемо не хотіти pe%terвідповідати арахісовому маслу, як би він не любив його.

Неясний кутовий корпус

(Так, я погоджусь, що це неясний випадок. Ймовірно, такий варіант, який, швидше за все, не буде перевірений. Ми не очікуємо, що підказки у значенні стовпця. Ми можемо припустити, що програма не дозволяє зберігати таке значення. Але в моєму досвіді я рідко бачив обмеження в базі даних, яке конкретно забороняло символи чи візерунки, які вважалися б символами з правого боку LIKEоператора порівняння.

Заклеювання отвору

Один із підходів до виправлення цього отвору - це уникнути %символу підстановки. (Для всіх, хто не знайомий з умовою про вихід на оператора, ось посилання на документацію на SQL Server .

select ...
 where '|peanut|butter|'
  like '%|' + 'pe\%ter' + '|%' escape '\'

Тепер ми можемо відповідати буквальному%. Звичайно, коли у нас є назва стовпця, нам потрібно буде динамічно вийти з підстановки. Ми можемо скористатися REPLACEфункцією для пошуку подій %символу та вставлення символу зворотної косої риси перед кожним із них, наприклад:

select ...
 where '|pe%ter|'
  like '%|' + REPLACE( 'pe%ter' ,'%','\%') + '|%' escape '\'

Таким чином, це вирішує проблему із символом підстановки%. Майже.

Уникнути втечі

Ми визнаємо, що наше рішення ввело ще одну проблему. Характер втечі. Ми бачимо, що нам також потрібно буде уникати будь-яких випадків втечі самого персонажа. Цього разу ми використовуємо! як персонаж втечі:

select ...
 where '|pe%t!r|'
  like '%|' + REPLACE(REPLACE( 'pe%t!r' ,'!','!!'),'%','!%') + '|%' escape '!'

Підкреслення теж

Тепер, коли ми перебуваємо в рулоні, ми можемо додати ще одну REPLACEпідстановку підкреслення підкреслення. І просто для розваги, цього разу ми будемо використовувати $ як символ втечі.

select ...
 where '|p_%t!r|'
  like '%|' + REPLACE(REPLACE(REPLACE( 'p_%t!r' ,'$','$$'),'%','$%'),'_','$_') + '|%' escape '$'

Я віддаю перевагу такому підходу, ніж втечу, оскільки він працює в Oracle і MySQL, а також на SQL Server. (Я зазвичай використовую \ backslash як символ втечі, оскільки це символ, який ми використовуємо в регулярних виразах. Але чому обмежуватись умовністю!

Ті досадні дужки

Також SQL Server дозволяє символам підстановки вважатись буквальними, додаючи їх у дужки []. Тож ми ще не завершили виправлення, принаймні для SQL Server. Оскільки пари дужок мають особливе значення, нам також потрібно уникнути цих. Якщо нам вдасться належним чином уникнути дужок, то, принаймні, нам не доведеться турбуватися дефісом -і каратами ^в дужках. І ми можемо залишити будь-які, %і _символи всередині дужок втекли, оскільки ми в основному відключили особливе значення дужок.

Пошук відповідних пар дужок не повинен бути таким складним. Це трохи складніше, ніж обробляти події синглів% та _. (Зверніть увагу, що недостатньо просто уникнути всіх випадків дужок, тому що одинарна дужка вважається буквальною, і її не потрібно уникати. Логіка стає трохи нечіткішою, ніж я можу впоратися, не запускаючи більше тестових випадків .)

Вбудований вираз стає безладним

Це вбудоване вираження в SQL стає все довше і негарніше. Ми, мабуть, можемо змусити його працювати, але небо допомагає бідній душі, яка йде позаду і має розшифрувати її. Оскільки я є прихильником я за вбудовані вирази, я схильний не використовувати його тут, головним чином тому, що мені не хочеться залишати коментар, що пояснює причину безладу, і вибачаюся за це.

Функція де?

Гаразд, тому, якщо ми не розглядаємо це як вбудований вираз у SQL, найближча альтернатива, яку ми маємо, - це визначена користувачем функція. І ми знаємо, що це не прискорить ніяких дій (якщо тільки ми не зможемо визначити індекс на ньому, як ми могли б з Oracle.) Якщо нам доведеться створити функцію, ми можемо краще зробити це в коді, що викликає SQL заява.

І ця функція може мати деякі відмінності в поведінці, залежно від СУБД та версії. (Вигук усім, хто розробляє Java, настільки прагне використовувати взаємозамінно будь-який движок бази даних.)

Знання домену

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

Значення, що зберігаються у стовпці, можуть містити символи% або _, але обмеження може вимагати уникнення цих значень, можливо, з використанням визначеного символу, таким чином, що значення LIKE порівняння "безпечні". Знову ж таки, швидкий коментар щодо дозволеного набору значень, зокрема того, який персонаж використовується як символ втечі, і піти з підходом Джоела Спольського.

Але, відсутня спеціалізовані знання та гарантія, нам важливо хоча б розглянути питання щодо розгляду цих незрозумілих кутових випадків і розглянути, чи є поведінка розумною та "відповідно до специфікації".


Інші питання рекапітульовані

Я вважаю, що інші вже достатньо вказали на деякі з найбільш поширених проблемних питань:

  • Інжекція SQL (прийняття інформації, що представляється інформацією, що надається користувачем, і включення її в текст SQL, а не подання їх через змінні змінні. Використання змінних зв'язків не потрібно, це лише один зручний підхід для запобігання ін'єкції SQL. Є й інші способи боротьби з цим:

  • план оптимізатора, що використовує сканування індексів, а не пошук індексу, можлива потреба у виразі чи функції для уникнення підстановок (можливий індекс за виразом чи функцією)

  • використання буквальних значень замість змінних зв'язків впливає на масштабованість


Висновок

Мені подобається підхід Джоела Спольського. Це розумно. І це працює.

Але як тільки я побачив це, я одразу побачив потенційну проблему з цим, і не моя природа дозволяти йому ковзати. Я не хочу критикувати зусилля інших. Я знаю, що багато розробників сприймають свою роботу дуже особисто, тому що вони так багато вкладають у неї і так сильно піклуються про неї. Тож зрозумійте, будь ласка, це не особиста атака. Що я тут визначаю - це проблема, яка виникає у виробництві, а не на тестуванні.

Так, я пішов далеко від початкового питання. Але де ще залишити цю замітку стосовно того, що я вважаю важливим питанням із "обраною" відповіддю на запитання?


чи можете ви, будь ласка, повідомте нас, чи використовуєте ви, або як параметризовані запити? чи в цьому конкретному випадку правильно переходити через правило "використовувати параметризовані запити" та санітувати з мовою оригіналу?
ДЯКУЄТЬ

2
@Luis: так, я вважаю за краще використовувати змінні змінні в операторах SQL, і уникатимуть змінних змінних лише тоді, коли їх використання спричиняє проблеми з продуктивністю. моєю нормативною схемою для вихідної проблеми було б динамічно створити оператор SQL з необхідною кількістю заповнювачів у списку IN, а потім прив’язати кожне значення до одного із заповнювачів. Дивіться відповідь від Марка Брекетта, що є відповіддю, який я (та 231 інші) проголосили.
spencer7593

133

Ви можете передавати параметр у вигляді рядка

Отже, у вас є рядок

DECLARE @tags

SET @tags = ruby|rails|scruffy|rubyonrails

select * from Tags 
where Name in (SELECT item from fnSplit(@tags, ‘|’))
order by Count desc

Тоді все, що вам потрібно зробити, - це передавати рядок як 1 параметр.

Ось функція розділення, яку я використовую.

CREATE FUNCTION [dbo].[fnSplit](
    @sInputList VARCHAR(8000) -- List of delimited items
  , @sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
) RETURNS @List TABLE (item VARCHAR(8000))

BEGIN
DECLARE @sItem VARCHAR(8000)
WHILE CHARINDEX(@sDelimiter,@sInputList,0) <> 0
 BEGIN
 SELECT
  @sItem=RTRIM(LTRIM(SUBSTRING(@sInputList,1,CHARINDEX(@sDelimiter,@sInputList,0)-1))),
  @sInputList=RTRIM(LTRIM(SUBSTRING(@sInputList,CHARINDEX(@sDelimiter,@sInputList,0)+LEN(@sDelimiter),LEN(@sInputList))))

 IF LEN(@sItem) > 0
  INSERT INTO @List SELECT @sItem
 END

IF LEN(@sInputList) > 0
 INSERT INTO @List SELECT @sInputList -- Put the last item in
RETURN
END

2
При такому підході ви також можете приєднатись до функції таблиці.
Майкл Харен

Я використовую рішення, подібне до цього в Oracle. Його не потрібно переробляти, як це роблять деякі інші рішення.
Лі Ріффель

9
Це чистий підхід до бази даних, інший вимагає роботи над кодом поза базою даних.
Девід Басараб

Це стосується сканування таблиці або може скористатися індексами тощо?
Pure.Krome

краще було б використовувати CROSS APPLY проти функції таблиці SQL (принаймні, у 2005 році далі), яка по суті приєднується до повернутої таблиці
часник adolf час

66

Я чув, як Джефф / Джоел говорили про це сьогодні в подкасті ( епізод 34 , 2008-12-16 (MP3, 31 МБ), 1 год. 03 хв. 38 сек. - 1 год. 06 хв. 45 сек.), І мені здалося, що я згадував стек переповнення використовував LINQ для SQL , але, можливо, він був викреслений. Ось те саме в LINQ до SQL.

var inValues = new [] { "ruby","rails","scruffy","rubyonrails" };

var results = from tag in Tags
              where inValues.Contains(tag.Name)
              select tag;

Це воно. І так, LINQ вже виглядає досить назад, але ця Containsпропозиція здається мені додатково зворотною. Коли мені довелося виконати подібний запит для проекту на роботі, я, природно, намагався зробити це неправильно, зробивши з'єднання між локальним масивом та таблицею SQL Server, вважаючи, що перекладач LINQ до SQL буде досить розумним, щоб обробляти переклад якось. Це не так, але воно надало повідомлення про помилку, яке було описовим і вказувало на використання вмісту .

У будь-якому випадку, якщо ви запускаєте це в настійно рекомендованому LINQPad і запускаєте цей запит, ви можете переглянути фактичний SQL, який генерував постачальник SQL LINQ. Він покаже вам кожне з параметрів, параметризованих у INпункт.


50

Якщо ви телефонуєте з .NET, ви можете використовувати крапкову мережу Dapper :

string[] names = new string[] {"ruby","rails","scruffy","rubyonrails"};
var tags = dataContext.Query<Tags>(@"
select * from Tags 
where Name in @names
order by Count desc", new {names});

Тут Даппер думає, тож вам не доведеться. Щось подібне можливо з LINQ у SQL , звичайно:

string[] names = new string[] {"ruby","rails","scruffy","rubyonrails"};
var tags = from tag in dataContext.Tags
           where names.Contains(tag.Name)
           orderby tag.Count descending
           select tag;

11
що трапляється на тому, що ми використовуємо на цій сторінці, для актуального запитання (dapper) i.stack.imgur.com/RBAjL.png
Сем Сафрон


Це спадає, якщо імена довгі
cs0815

29

Це, можливо, наполовину неприємний спосіб зробити це, я використовував це один раз, був досить ефективним.

Залежно від ваших цілей це може бути корисним.

  1. Створіть темп-таблицю з одним стовпцем.
  2. INSERT кожне значення пошуку в цьому стовпці.
  3. Замість використання an IN, ви можете просто використовувати свої стандартні JOINправила. (Гнучкість ++)

Це має трохи додаткової гнучкості в тому, що ви можете зробити, але це більше підходить для ситуацій, коли у вас є велика таблиця для запиту, з хорошою індексуванням, і ви хочете використовувати параметризований список не один раз. Економить необхідність виконувати це двічі, а санітарію виконувати вручну.

Я ніколи не замислювався над тим, щоб точно проінформувати, як швидко це було, але в моїй ситуації це було потрібно.


Це зовсім не противно! Навіть більше, це ІМХО дуже чистий спосіб. І якщо ви подивитесь на план виконання, ви побачите, що він такий самий, як і пункт IN. Замість таблиці temp ви також можете створити фіксовану таблицю з індексами, де ви зберігаєте параметри разом із SESSIONID.
SQL Police

27

У SQL Server 2016+ви можете використовувати STRING_SPLITфункцію:

DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';

SELECT * 
FROM Tags
WHERE Name IN (SELECT [value] FROM STRING_SPLIT(@names, ','))
ORDER BY [Count] DESC;

або:

DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';

SELECT t.*
FROM Tags t
JOIN STRING_SPLIT(@names,',')
  ON t.Name = [value]
ORDER BY [Count] DESC;

LiveDemo

Загальноприйнятий відповідь буде, звичайно , робота , і це один із способів йти, але це анти-модель.

E. Знайдіть рядки за списком значень

Це заміна на поширений антидіапазон, такий як створення динамічної рядка SQL в шарі програми або Transact-SQL, або за допомогою оператора LIKE:

SELECT ProductId, Name, Tags
FROM Product
WHERE ',1,2,3,' LIKE '%,' + CAST(ProductId AS VARCHAR(20)) + ',%';

Додаток :

Щоб покращити STRING_SPLITоцінку рядків функцій таблиці, рекомендується матеріалізувати розділені значення у вигляді тимчасової змінної таблиці / таблиці:

DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails,sql';

CREATE TABLE #t(val NVARCHAR(120));
INSERT INTO #t(val) SELECT s.[value] FROM STRING_SPLIT(@names, ',') s;

SELECT *
FROM Tags tg
JOIN #t t
  ON t.val = tg.TagName
ORDER BY [Count] DESC;

SEDE - демонстрація в прямому ефірі

Пов'язане: Як передати список цінностей у збережену процедуру


Оригінальне запитання має вимогу SQL Server 2008. Оскільки це запитання часто використовується як дублікат, я додав цю відповідь як посилання.


1
Я не проходив тестування цього, але відчуваю, що це найчистіше рішення 2016 року. Я все одно хотів би просто передати масив int, але до цього часу ...
Даніель,

24

У нас є функція, яка створює змінну таблиці, до якої ви можете приєднатися:

ALTER FUNCTION [dbo].[Fn_sqllist_to_table](@list  AS VARCHAR(8000),
                                           @delim AS VARCHAR(10))
RETURNS @listTable TABLE(
  Position INT,
  Value    VARCHAR(8000))
AS
  BEGIN
      DECLARE @myPos INT

      SET @myPos = 1

      WHILE Charindex(@delim, @list) > 0
        BEGIN
            INSERT INTO @listTable
                        (Position,Value)
            VALUES     (@myPos,LEFT(@list, Charindex(@delim, @list) - 1))

            SET @myPos = @myPos + 1

            IF Charindex(@delim, @list) = Len(@list)
              INSERT INTO @listTable
                          (Position,Value)
              VALUES     (@myPos,'')

            SET @list = RIGHT(@list, Len(@list) - Charindex(@delim, @list))
        END

      IF Len(@list) > 0
        INSERT INTO @listTable
                    (Position,Value)
        VALUES     (@myPos,@list)

      RETURN
  END 

Тому:

@Name varchar(8000) = null // parameter for search values    

select * from Tags 
where Name in (SELECT value From fn_sqllist_to_table(@Name,',')))
order by Count desc

20

Це валово, але якщо ви гарантовано матимете хоча б один, ви можете зробити:

SELECT ...
       ...
 WHERE tag IN( @tag1, ISNULL( @tag2, @tag1 ), ISNULL( @tag3, @tag1 ), etc. )

Наявність IN ('tag1', 'tag2', 'tag1', 'tag1', 'tag1') буде легко оптимізована за допомогою SQL Server. Крім того, ви отримуєте прямі пошукові показники


1
Необов’язкові параметри з Null check псують продуктивність, оскільки оптимізатору потрібна кількість параметрів, що використовуються для створення ефективних запитів. Для запиту для 5 параметрів може знадобитися інший план запитів, ніж один для 500 параметрів.
Ерік Харт

18

На мою думку, найкращим джерелом для вирішення цієї проблеми є те, що було розміщено на цьому веб-сайті:

Системи. Дінакар Неті

CREATE FUNCTION dbo.fnParseArray (@Array VARCHAR(1000),@separator CHAR(1))
RETURNS @T Table (col1 varchar(50))
AS 
BEGIN
 --DECLARE @T Table (col1 varchar(50))  
 -- @Array is the array we wish to parse
 -- @Separator is the separator charactor such as a comma
 DECLARE @separator_position INT -- This is used to locate each separator character
 DECLARE @array_value VARCHAR(1000) -- this holds each array value as it is returned
 -- For my loop to work I need an extra separator at the end. I always look to the
 -- left of the separator character for each array value

 SET @array = @array + @separator

 -- Loop through the string searching for separtor characters
 WHILE PATINDEX('%' + @separator + '%', @array) <> 0 
 BEGIN
    -- patindex matches the a pattern against a string
    SELECT @separator_position = PATINDEX('%' + @separator + '%',@array)
    SELECT @array_value = LEFT(@array, @separator_position - 1)
    -- This is where you process the values passed.
    INSERT into @T VALUES (@array_value)    
    -- Replace this select statement with your processing
    -- @array_value holds the value of this element of the array
    -- This replaces what we just processed with and empty string
    SELECT @array = STUFF(@array, 1, @separator_position, '')
 END
 RETURN 
END

Використання:

SELECT * FROM dbo.fnParseArray('a,b,c,d,e,f', ',')

КРЕДИТИ ДІНАКАРУ Неті


Чудова відповідь, чисте та модульне, дуже швидке виконання, за винятком початкового розбору CSV в таблиці (одноразово, невелика кількість елементів). Хоча міг би використовувати простіший / швидший charindex () замість patindex ()? Charindex () також дозволяє аргумент 'start_location', який може уникнути подрібнення вхідного рядка кожного ітера? Щоб відповісти на початкове запитання, можна просто поєднатись з результатом функції.
crokusek

18

Я передав би параметр типу таблиці (оскільки це SQL Server 2008 ), і зробив where existsабо внутрішній приєднання. Ви також можете використовувати XML, використовуючи sp_xml_preparedocument, а потім навіть індексувати цю тимчасову таблицю.


Відповідь Ph.E має приклад таблиці тимчасової побудови (від csv).
crokusek

12

Правильний спосіб IMHO полягає в зберіганні списку в рядку символів (обмежений по довжині тим, що підтримує СУБД); Єдина хитрість полягає в тому, що (щоб спростити обробку) у мене на початку та в кінці рядка є роздільник (кома в моєму прикладі). Ідея полягає в тому, щоб "нормалізуватись на ходу", перетворивши список у таблицю з одним стовпцем, що містить один рядок за значенням. Це дозволяє повернути

в (ct1, ct2, ct3 ... ctn)

в

в (виберіть ...)

або (рішення, яке я, мабуть, вважаю за краще) регулярне приєднання, якщо ви просто додаєте "чітке", щоб уникнути проблем із дублюючими значеннями у списку.

На жаль, методи нарізати рядок досить специфічні для продукту. Ось версія SQL Server:

 with qry(n, names) as
       (select len(list.names) - len(replace(list.names, ',', '')) - 1 as n,
               substring(list.names, 2, len(list.names)) as names
        from (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' names) as list
        union all
        select (n - 1) as n,
               substring(names, 1 + charindex(',', names), len(names)) as names
        from qry
        where n > 1)
 select n, substring(names, 1, charindex(',', names) - 1) dwarf
 from qry;

Версія Oracle:

 select n, substr(name, 1, instr(name, ',') - 1) dwarf
 from (select n,
             substr(val, 1 + instr(val, ',', 1, n)) name
      from (select rownum as n,
                   list.val
            from  (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' val
                   from dual) list
            connect by level < length(list.val) -
                               length(replace(list.val, ',', ''))));

і версія MySQL:

select pivot.n,
      substring_index(substring_index(list.val, ',', 1 + pivot.n), ',', -1) from (select 1 as n
     union all
     select 2 as n
     union all
     select 3 as n
     union all
     select 4 as n
     union all
     select 5 as n
     union all
     select 6 as n
     union all
     select 7 as n
     union all
     select 8 as n
     union all
     select 9 as n
     union all
     select 10 as n) pivot,    (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' val) as list where pivot.n <  length(list.val) -
                   length(replace(list.val, ',', ''));

(Зрозуміло, "стрижневий" повинен повертати стільки рядків, скільки максимальна кількість елементів, які ми можемо знайти у списку)


11

Якщо у вас є SQL Server 2008 або пізнішої версії, я використовував би параметр «Цінність» .

Якщо вам не пощастило застрягти на SQL Server 2005, ви можете додати функцію CLR на зразок цієї,

[SqlFunction(
    DataAccessKind.None,
    IsDeterministic = true,
    SystemDataAccess = SystemDataAccessKind.None,
    IsPrecise = true,
    FillRowMethodName = "SplitFillRow",
    TableDefinintion = "s NVARCHAR(MAX)"]
public static IEnumerable Split(SqlChars seperator, SqlString s)
{
    if (s.IsNull)
        return new string[0];

    return s.ToString().Split(seperator.Buffer);
}

public static void SplitFillRow(object row, out SqlString s)
{
    s = new SqlString(row.ToString());
}

Що ви могли б використовувати так,

declare @desiredTags nvarchar(MAX);
set @desiredTags = 'ruby,rails,scruffy,rubyonrails';

select * from Tags
where Name in [dbo].[Split] (',', @desiredTags)
order by Count desc

10

Я думаю, що це випадок, коли статичний запит - це просто не шлях. Динамічно будуйте список для свого пункту, уникайте своїх єдиних лапок і динамічно створюйте SQL. У цьому випадку ви, мабуть, не побачите великої різниці в будь-якому методі через невеликий список, але найефективнішим методом насправді є надіслати SQL саме так, як це написано у вашому пості. Я думаю, що це гарна звичка писати це найефективнішим способом, а не робити те, що робить найкрасивіший код, або вважати поганою практикою динамічне створення SQL.

Я бачив, як виконувати розбиті функції потрібно більше часу, ніж сам запит у багатьох випадках, коли параметри отримують великі розміри. Збережена процедура з параметрами, що оцінюються в таблиці, у SQL 2008 - це єдиний інший варіант, який я б вважав, хоча це, мабуть, буде повільніше у вашому випадку. TVP, ймовірно, буде швидшим лише для великих списків, якщо ви шукаєте на первинному ключі TVP, оскільки SQL все одно побудує тимчасову таблицю для списку (якщо список великий). Ви точно не дізнаєтесь, якщо не перевірите.

Я також бачив збережені процедури, які мали 500 параметрів із значеннями за замовчуванням null, і які мають WHONE Column1 IN (@ Param1, @ Param2, @ Param3, ..., @ Param500). Це призвело до того, що SQL створив таблицю темпів, зробив сортування / відмінність, а потім здійснив сканування таблиці замість пошуку індексу. Це, по суті, те, що ви робили б, параметризуючи цей запит, хоча в досить малій шкалі, щоб він не змінився помітно. Я настійно рекомендую не мати NULL у своїх списках IN, так як якщо це буде змінено на NOT IN, воно не діятиме за призначенням. Ви можете динамічно складати список параметрів, але єдине очевидне, що ви отримали б - це те, що об'єкти уникають єдиних для вас лапок. Цей підхід також трохи повільніше на кінці програми, оскільки об'єкти повинні проаналізувати запит, щоб знайти параметри.

Повторне використання планів виконання збережених процедур або параметризованих запитів може призвести до збільшення продуктивності, але це заблокує вас до одного плану виконання, визначеного першим запитом, який виконується. Це може бути менше, ніж ідеально для наступних запитів у багатьох випадках. У вашому випадку повторне використання планів виконання буде, мабуть, плюсом, але це може не мати ніяких змін, оскільки приклад - це дійсно простий запит.

Скали примітки:

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

Ваш випадок / прості запити з кількома параметрами:

Динамічний SQL, можливо, з параметрами, якщо тестування показує кращі показники.

Запити із планами виконання багаторазового використання, що викликаються кілька разів простою зміною параметрів або запитом складним:

SQL з динамічними параметрами.

Запити з великими списками:

Збережена процедура з таблицевими параметрами. Якщо список може змінюватись на велику кількість, використовуйте ЗНОМИЛЬНІ РЕКОМПЛІЮ на збереженій процедурі або просто використовуйте динамічний SQL без параметрів для створення нового плану виконання для кожного запиту.


Що ви маєте на увазі під "збереженою процедурою" тут? Чи можете ви розмістити приклад?
струхтанов

9

Можливо, ми можемо використовувати XML тут:

    declare @x xml
    set @x='<items>
    <item myvalue="29790" />
    <item myvalue="31250" />
    </items>
    ';
    With CTE AS (
         SELECT 
            x.item.value('@myvalue[1]', 'decimal') AS myvalue
        FROM @x.nodes('//items/item') AS x(item) )

    select * from YourTable where tableColumnName in (select myvalue from cte)

1
CTEі @xможуть бути усунені / вбудовані в підбірку, якщо це зроблено дуже обережно, як показано в цій статті .
robert4

9

Я підходив би до цього за замовчуванням, передаючи функцію значення таблиці (яка повертає таблицю з рядка) у стан IN.

Ось код для UDF (я отримав його десь із Stack Overflow, я не можу зараз знайти джерело)

CREATE FUNCTION [dbo].[Split] (@sep char(1), @s varchar(8000))
RETURNS table
AS
RETURN (
    WITH Pieces(pn, start, stop) AS (
      SELECT 1, 1, CHARINDEX(@sep, @s)
      UNION ALL
      SELECT pn + 1, stop + 1, CHARINDEX(@sep, @s, stop + 1)
      FROM Pieces
      WHERE stop > 0
    )
    SELECT 
      SUBSTRING(@s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
    FROM Pieces
  )

Як тільки ви отримаєте це, ваш код стане таким же простим:

select * from Tags 
where Name in (select s from dbo.split(';','ruby;rails;scruffy;rubyonrails'))
order by Count desc

Якщо у вас смішно довгий рядок, це повинно добре працювати з індексом таблиці.

Якщо потрібно, ви можете вставити його в темп-таблицю, проіндексувати її, а потім запустити з'єднання ...


8

Іншим можливим рішенням є замість передачі змінної кількості аргументів у збережену процедуру, передайте єдину рядок, що містить імена, які ви шукаєте, але зробіть їх унікальними, оточуючи їх "<>". Потім використовуйте PATINDEX для пошуку назв:

SELECT * 
FROM Tags 
WHERE PATINDEX('%<' + Name + '>%','<jo>,<john>,<scruffy>,<rubyonrails>') > 0

8

Використовуйте наступну збережену процедуру. Тут використовується спеціальна функція розбиття, яку можна знайти тут .

 create stored procedure GetSearchMachingTagNames 
    @PipeDelimitedTagNames varchar(max), 
    @delimiter char(1) 
    as  
    begin
         select * from Tags 
         where Name in (select data from [dbo].[Split](@PipeDelimitedTagNames,@delimiter) 
    end

8

Якщо у нас є рядки, що зберігаються у пункті IN із обмеженим комою (,), ми можемо використовувати функцію charindex для отримання значень. Якщо ви використовуєте .NET, то ви можете зробити карту за допомогою SqlParameters.

Сценарій DDL:

CREATE TABLE Tags
    ([ID] int, [Name] varchar(20))
;

INSERT INTO Tags
    ([ID], [Name])
VALUES
    (1, 'ruby'),
    (2, 'rails'),
    (3, 'scruffy'),
    (4, 'rubyonrails')
;

T-SQL:

DECLARE @Param nvarchar(max)

SET @Param = 'ruby,rails,scruffy,rubyonrails'

SELECT * FROM Tags
WHERE CharIndex(Name,@Param)>0

Ви можете використовувати наведене вище твердження у своєму .NET-коді та зіставити параметр за допомогою SqlParameter.

Демонстрація Fiddler

EDIT: Створіть таблицю під назвою SelectedTags за допомогою наступного сценарію.

Сценарій DDL:

Create table SelectedTags
(Name nvarchar(20));

INSERT INTO SelectedTags values ('ruby'),('rails')

T-SQL:

DECLARE @list nvarchar(max)
SELECT @list=coalesce(@list+',','')+st.Name FROM SelectedTags st

SELECT * FROM Tags
WHERE CharIndex(Name,@Param)>0

Чи можете ви показати приклад роботи, коли немає жорсткого коду списку можливих значень?
Джон Сондерс

@JohnSaunders, я редагував сценарій, не використовуючи жодного жорсткого коду. Підтвердьте.
Gowdhaman008

3
Одне обмеження при цій опції. CharIndex повертає 1, якщо рядок знайдено. IN повертає відповідність на точні терміни. CharIndex для "Stack" поверне 1 за терміном "StackOverflow" IN не буде. На цю відповідь є незначний твір, використовуючи PatIndex вище, що додає імена з '<'% name% '>', що долає це обмеження. Хоча творче рішення цієї проблеми.
Річард Вівіан

7

Для змінної кількості таких аргументів я знаю єдиний спосіб явно генерувати SQL або робити щось, що передбачає заповнення тимчасової таблиці потрібними елементами та приєднання до таблиці temp.


7

У ColdFusion ми просто робимо:

<cfset myvalues = "ruby|rails|scruffy|rubyonrails">
    <cfquery name="q">
        select * from sometable where values in <cfqueryparam value="#myvalues#" list="true">
    </cfquery>

7

Ось техніка, яка відтворює локальну таблицю, яка буде використана в рядку запиту. Це робиться таким чином, усуває всі проблеми розбору.

Рядок може бути побудований на будь-якій мові. У цьому прикладі я використовував SQL, оскільки це була первісна проблема, яку я намагався вирішити. Мені потрібен був чистий спосіб передати дані таблиці на льоту в рядку, який буде виконано пізніше.

Використання визначеного користувачем типу необов’язкове. Створення типу створюється лише один раз і може бути здійснено достроково. В іншому випадку просто додайте повний тип таблиці до декларації в рядку.

Загальну схему легко розширити і її можна використовувати для передачі складніших таблиць.

-- Create a user defined type for the list.
CREATE TYPE [dbo].[StringList] AS TABLE(
    [StringValue] [nvarchar](max) NOT NULL
)

-- Create a sample list using the list table type.
DECLARE @list [dbo].[StringList]; 
INSERT INTO @list VALUES ('one'), ('two'), ('three'), ('four')

-- Build a string in which we recreate the list so we can pass it to exec
-- This can be done in any language since we're just building a string.
DECLARE @str nvarchar(max);
SET @str = 'DECLARE @list [dbo].[StringList]; INSERT INTO @list VALUES '

-- Add all the values we want to the string. This would be a loop in C++.
SELECT @str = @str + '(''' + StringValue + '''),' FROM @list

-- Remove the trailing comma so the query is valid sql.
SET @str = substring(@str, 1, len(@str)-1)

-- Add a select to test the string.
SET @str = @str + '; SELECT * FROM @list;'

-- Execute the string and see we've pass the table correctly.
EXEC(@str)

7

У SQL Server 2016+ інша можливість використовувати OPENJSONфункцію.

Такий підхід обговорюється в OPENJSON - одному з найкращих способів вибору рядків за списком ідентифікаторів .

Повний відпрацьований приклад нижче

CREATE TABLE dbo.Tags
  (
     Name  VARCHAR(50),
     Count INT
  )

INSERT INTO dbo.Tags
VALUES      ('VB',982), ('ruby',1306), ('rails',1478), ('scruffy',1), ('C#',1784)

GO

CREATE PROC dbo.SomeProc
@Tags VARCHAR(MAX)
AS
SELECT T.*
FROM   dbo.Tags T
WHERE  T.Name IN (SELECT J.Value COLLATE Latin1_General_CI_AS
                  FROM   OPENJSON(CONCAT('[', @Tags, ']')) J)
ORDER  BY T.Count DESC

GO

EXEC dbo.SomeProc @Tags = '"ruby","rails","scruffy","rubyonrails"'

DROP TABLE dbo.Tags 

7

Ось ще одна альтернатива. Просто передайте розділений комами список як параметр рядка до збереженої процедури та:

CREATE PROCEDURE [dbo].[sp_myproc]
    @UnitList varchar(MAX) = '1,2,3'
AS
select column from table
where ph.UnitID in (select * from CsvToInt(@UnitList))

І функція:

CREATE Function [dbo].[CsvToInt] ( @Array varchar(MAX))
returns @IntTable table
(IntValue int)
AS
begin
    declare @separator char(1)
    set @separator = ','
    declare @separator_position int
    declare @array_value varchar(MAX)

    set @array = @array + ','

    while patindex('%,%' , @array) <> 0
    begin

        select @separator_position = patindex('%,%' , @array)
        select @array_value = left(@array, @separator_position - 1)

        Insert @IntTable
        Values (Cast(@array_value as int))
        select @array = stuff(@array, 1, @separator_position, '')
    end
    return
end

6

У мене є відповідь, яка не вимагає UDF, XML Тому що IN приймає оператор select, наприклад SELECT * FROM Test, де Data IN (SELECT Value OF TABLE)

Вам дійсно потрібен лише спосіб перетворення рядка в таблицю.

Це можна зробити за допомогою рекурсивного CTE або запиту з таблицею номерів (або Master..spt_value)

Ось версія CTE

DECLARE @InputString varchar(8000) = 'ruby,rails,scruffy,rubyonrails'

SELECT @InputString = @InputString + ','

;WITH RecursiveCSV(x,y) 
AS 
(
    SELECT 
        x = SUBSTRING(@InputString,0,CHARINDEX(',',@InputString,0)),
        y = SUBSTRING(@InputString,CHARINDEX(',',@InputString,0)+1,LEN(@InputString))
    UNION ALL
    SELECT 
        x = SUBSTRING(y,0,CHARINDEX(',',y,0)),
        y = SUBSTRING(y,CHARINDEX(',',y,0)+1,LEN(y))
    FROM 
        RecursiveCSV 
    WHERE
        SUBSTRING(y,CHARINDEX(',',y,0)+1,LEN(y)) <> '' OR 
        SUBSTRING(y,0,CHARINDEX(',',y,0)) <> ''
)
SELECT
    * 
FROM 
    Tags
WHERE 
    Name IN (select x FROM RecursiveCSV)
OPTION (MAXRECURSION 32767);

6

Я використовую більш стисну версію голосової відповіді :

List<SqlParameter> parameters = tags.Select((s, i) => new SqlParameter("@tag" + i.ToString(), SqlDbType.NVarChar(50)) { Value = s}).ToList();

var whereCondition = string.Format("tags in ({0})", String.Join(",",parameters.Select(s => s.ParameterName)));

Він перебирає параметри тегу двічі; але це не має значення протягом більшої частини часу (це не буде вашим вузьким місцем; якщо він є, розгорніть цикл).

Якщо ви дійсно зацікавлені в продуктивності і не хочете повторювати цикл двічі, ось менш красива версія:

var parameters = new List<SqlParameter>();
var paramNames = new List<string>();
for (var i = 0; i < tags.Length; i++)  
{
    var paramName = "@tag" + i;

    //Include size and set value explicitly (not AddWithValue)
    //Because SQL Server may use an implicit conversion if it doesn't know
    //the actual size.
    var p = new SqlParameter(paramName, SqlDbType.NVarChar(50) { Value = tags[i]; } 
    paramNames.Add(paramName);
    parameters.Add(p);
}

var inClause = string.Join(",", paramNames);

5

Ось ще одна відповідь на цю проблему.

(нова версія опублікована 4/4/13).

    private static DataSet GetDataSet(SqlConnectionStringBuilder scsb, string strSql, params object[] pars)
    {
        var ds = new DataSet();
        using (var sqlConn = new SqlConnection(scsb.ConnectionString))
        {
            var sqlParameters = new List<SqlParameter>();
            var replacementStrings = new Dictionary<string, string>();
            if (pars != null)
            {
                for (int i = 0; i < pars.Length; i++)
                {
                    if (pars[i] is IEnumerable<object>)
                    {
                        List<object> enumerable = (pars[i] as IEnumerable<object>).ToList();
                        replacementStrings.Add("@" + i, String.Join(",", enumerable.Select((value, pos) => String.Format("@_{0}_{1}", i, pos))));
                        sqlParameters.AddRange(enumerable.Select((value, pos) => new SqlParameter(String.Format("@_{0}_{1}", i, pos), value ?? DBNull.Value)).ToArray());
                    }
                    else
                    {
                        sqlParameters.Add(new SqlParameter(String.Format("@{0}", i), pars[i] ?? DBNull.Value));
                    }
                }
            }
            strSql = replacementStrings.Aggregate(strSql, (current, replacementString) => current.Replace(replacementString.Key, replacementString.Value));
            using (var sqlCommand = new SqlCommand(strSql, sqlConn))
            {
                if (pars != null)
                {
                    sqlCommand.Parameters.AddRange(sqlParameters.ToArray());
                }
                else
                {
                    //Fail-safe, just in case a user intends to pass a single null parameter
                    sqlCommand.Parameters.Add(new SqlParameter("@0", DBNull.Value));
                }
                using (var sqlDataAdapter = new SqlDataAdapter(sqlCommand))
                {
                    sqlDataAdapter.Fill(ds);
                }
            }
        }
        return ds;
    }

Ура.


4

Єдиний виграшний хід - це не грати.

Немає для вас нескінченної мінливості. Тільки кінцева мінливість.

У SQL у вас є такий пункт:

and ( {1}==0 or b.CompanyId in ({2},{3},{4},{5},{6}) )

У коді C # ви робите щось подібне:

  int origCount = idList.Count;
  if (origCount > 5) {
    throw new Exception("You may only specify up to five originators to filter on.");
  }
  while (idList.Count < 5) { idList.Add(-1); }  // -1 is an impossible value
  return ExecuteQuery<PublishDate>(getValuesInListSQL, 
               origCount,   
               idList[0], idList[1], idList[2], idList[3], idList[4]);

Так що в основному, якщо кількість дорівнює 0, то немає фільтра і все проходить. Якщо підрахунок перевищує 0, то значення має бути у списку, але список занижений до п'яти з неможливими значеннями (так що SQL все-таки має сенс)

Іноді кульгаве рішення є єдиним, яке насправді працює.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.