我如何参数化一个包含有可变数量参数的IN子句的查询,就像这样?
SELECT * FROM Tags
WHERE Name IN ('ruby','rails','scruffy','rubyonrails')
ORDER BY Count DESC
在这个查询中,参数的数量可以是1到5之间的任意值。
我不喜欢使用专门的存储过程(或XML),但如果有一些特定于SQL Server 2008的优雅方式,我愿意接受。
我如何参数化一个包含有可变数量参数的IN子句的查询,就像这样?
SELECT * FROM Tags
WHERE Name IN ('ruby','rails','scruffy','rubyonrails')
ORDER BY Count DESC
在这个查询中,参数的数量可以是1到5之间的任意值。
我不喜欢使用专门的存储过程(或XML),但如果有一些特定于SQL Server 2008的优雅方式,我愿意接受。
当前回答
这很恶心,但如果你保证至少有一个,你可以这样做:
SELECT ...
...
WHERE tag IN( @tag1, ISNULL( @tag2, @tag1 ), ISNULL( @tag3, @tag1 ), etc. )
有IN('tag1', 'tag2', 'tag1', 'tag1', 'tag1')将很容易被SQL Server优化掉。另外,你可以直接搜索索引
其他回答
(编辑:如果表值参数不可用) 最好的方法似乎是将大量的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(processed). take (size[…])而不是list/array 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 */
}
我观察过NHibernate对象关系映射器生成的SQL(当查询数据并从中创建对象时),它在多个查询下看起来最好。在NHibernate中,可以指定批处理大小;如果需要获取许多对象数据行,它将尝试检索与批处理大小相等的行数
SELECT * FROM MyTable WHERE Id IN (@p1, @p2, @p3, ... , @p[batch-size])
,而不是发送数百或数千
SELECT * FROM MyTable WHERE Id=@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中的参数不会!)并重用查询计划(如果可用的话)。不需要单独的prepare语句,也不需要在代码中维护查询句柄!ADO。NET是这样工作的,但是Java似乎仍然使用prepare/execute by句柄(不确定)。
关于这个主题,我有自己的问题,最初建议用重复的IN子句填充,但后来更喜欢NHibernate样式语句split: 参数化SQL -在/不在与固定数量的参数,查询计划缓存优化?
这个问题仍然很有趣,即使在被问了5年多之后……
EDIT: I noted that IN queries with many values (like 250 or more) still tend to be slow, in the given case, on SQL Server. While I expected the DB to create a kind of temporary table internally and join against it, it seemed like it only repeated the single value SELECT expression n-times. Time was up to about 200ms per query - even worse than joining the original IDs retrieval SELECT against the other, related tables.. Also, there were some 10 to 15 CPU units in SQL Server Profiler, something unusual for repeated execution of the same parameterized queries, suggesting that new query plans were created on repeated calls. Maybe ad-hoc like individual queries are not worse at all. I had to compare these queries to non-split queries with changing sizes for a final conclusion, but for now, it seems like long IN clauses should be avoided anyway.
创建一个存储名称的临时表,然后使用以下查询:
select * from Tags
where Name in (select distinct name from temp)
order by Count desc
在SQL SERVER 2016或更高版本中,您可以使用STRING_SPLIT。
DECLARE @InParaSeprated VARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails'
DECLARE @Delimeter VARCHAR(10) = ','
SELECT
*
FROM
Tags T
INNER JOIN STRING_SPLIT(@InputParameters,@Delimeter) SS ON T.Name = SS.value
ORDER BY
Count DESC
我使用这个是因为在我的查询中,有时连接速度比Like操作符快。 此外,您还可以按您喜欢的任何单独格式输入无限数量的数据。 我喜欢这个。
在我看来,正确的方法是将列表存储在字符串中(长度受DBMS支持的限制);唯一的技巧是(为了简化处理)我在字符串的开头和结尾都有一个分隔符(在我的例子中是一个逗号)。其思想是“动态规范化”,将列表转换为单列表,每个值包含一行。这让你可以转弯
In (ct1,ct2, ct3…卡通)
成一个
在(选择…)
或者(我可能更喜欢的解决方案)常规连接,如果你只是添加一个“distinct”来避免列表中重复值的问题。
不幸的是,分割字符串的技术是相当特定于产品的。 下面是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, ',', ''));
(当然,“pivot”必须返回与的最大行数相同的行数 我们可以在列表中找到的项目)
我将传递一个表类型参数(因为它是SQL Server 2008),并做一个where exists,或内部连接。您也可以使用XML,使用sp_xml_preparedocument,然后甚至可以索引临时表。