DECLARE @tags
SET @tags = ‘ruby|rails|scruffy|rubyonrails’
select * from Tagswhere 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))
BEGINDECLARE @sItem VARCHAR(8000)WHILE CHARINDEX(@sDelimiter,@sInputList,0) <> 0BEGINSELECT@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) > 0INSERT INTO @List SELECT @sItemEND
IF LEN(@sInputList) > 0INSERT INTO @List SELECT @sInputList -- Put the last item inRETURNEND
ALTER FUNCTION [dbo].[Fn_sqllist_to_table](@list AS VARCHAR(8000),@delim AS VARCHAR(10))RETURNS @listTable TABLE(Position INT,Value VARCHAR(8000))ASBEGINDECLARE @myPos INT
SET @myPos = 1
WHILE Charindex(@delim, @list) > 0BEGININSERT 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) > 0INSERT INTO @listTable(Position,Value)VALUES (@myPos,@list)
RETURNEND
所以:
@Name varchar(8000) = null // parameter for search values
select * from Tagswhere Name in (SELECT value From fn_sqllist_to_table(@Name,',')))order by Count desc
<cfset myvalues = "ruby|rails|scruffy|rubyonrails"><cfquery name="q">select * from sometable where values in <cfqueryparam value="#myvalues#" list="true"></cfquery>
我听到Jeff/Joel今天在播客上谈论这个(# 0,2008-12-16 (MP3, 31 MB), 1小时03分38秒-1小时06分45秒),我想我记得Stack Overflow使用LINQ 用SQL,但可能它被抛弃了。在LINQ to SQL中也是如此。
var inValues = new [] { "ruby","rails","scruffy","rubyonrails" };
var results = from tag in Tagswhere inValues.Contains(tag.Name)select tag;
就是这样。而且,是的,LINQ已经足够向后看了,但是Contains子句对我来说似乎太向后看了。当我在工作中不得不为一个项目做类似的查询时,我自然地试图用错误的方式来做这个,在本地数组和SQL Server表之间做一个连接,认为LINQ to SQL翻译器将足够聪明,以某种方式处理翻译。它没有,但它确实提供了一个描述性的错误消息,并指示我使用包含。
select pivot.n,substring_index(substring_index(list.val, ',', 1 + pivot.n), ',', -1) from (select 1 as nunion allselect 2 as nunion allselect 3 as nunion allselect 4 as nunion allselect 5 as nunion allselect 6 as nunion allselect 7 as nunion allselect 8 as nunion allselect 9 as nunion allselect 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, ',', ''));
CREATE FUNCTION dbo.fnParseArray (@Array VARCHAR(1000),@separator CHAR(1))RETURNS @T Table (col1 varchar(50))ASBEGIN--DECLARE @T Table (col1 varchar(50))-- @Array is the array we wish to parse-- @Separator is the separator charactor such as a commaDECLARE @separator_position INT -- This is used to locate each separator characterDECLARE @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 charactersWHILE PATINDEX('%' + @separator + '%', @array) <> 0BEGIN-- patindex matches the a pattern against a stringSELECT @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 stringSELECT @array = STUFF(@array, 1, @separator_position, '')ENDRETURNEND
使用:
SELECT * FROM dbo.fnParseArray('a,b,c,d,e,f', ',')
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 valuereturn ExecuteQuery<PublishDate>(getValuesInListSQL,origCount,idList[0], idList[1], idList[2], idList[3], idList[4]);
string[] names = new string[] {"ruby","rails","scruffy","rubyonrails"};var tags = dataContext.Query<Tags>(@"select * from Tagswhere Name in @namesorder by Count desc", new {names});
string[] names = new string[] {"ruby","rails","scruffy","rubyonrails"};var tags = from tag in dataContext.Tagswhere names.Contains(tag.Name)orderby tag.Count descendingselect tag;
declare @x xmlset @x='<items><item myvalue="29790" /><item myvalue="31250" /></items>';With CTE AS (SELECTx.item.value('@myvalue[1]', 'decimal') AS myvalueFROM @x.nodes('//items/item') AS x(item) )
select * from YourTable where tableColumnName in (select myvalue from cte)
-- 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)
create stored procedure GetSearchMachingTagNames@PipeDelimitedTagNames varchar(max),@delimiter char(1)asbeginselect * from Tagswhere Name in (select data from [dbo].[Split](@PipeDelimitedTagNames,@delimiter)end
[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 Tagswhere Name in [dbo].[Split] (',', @desiredTags)order by Count desc
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 parametersqlCommand.Parameters.Add(new SqlParameter("@0", DBNull.Value));}using (var sqlDataAdapter = new SqlDataAdapter(sqlCommand)){sqlDataAdapter.Fill(ds);}}}return ds;}
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 */}
关于这个话题,我有我自己的问题,最初建议用重复的IN子句填充,但后来更喜欢NHibernate风格语句split:# 0 < / p >
这个问题仍然很有趣,即使在被问了5年多之后……
编辑:我注意到,在给定的情况下,在SQL Server上,有很多值的IN查询(比如250或更多)仍然倾向于很慢。虽然我希望DB在内部创建一种临时表并对其进行连接,但它似乎只重复了n次单个值SELECT表达式。每次查询所需的时间大约为200ms——甚至比将原始id检索SELECT与其他相关表连接还要糟糕。此外,在SQL Server Profiler中有大约10到15个CPU单元,这对于重复执行相同的参数化查询来说是不寻常的,这表明在重复调用中创建了新的查询计划。也许像个别查询这样的特别查询一点也不差。为了得出最终结论,我不得不将这些查询与大小变化的非分割查询进行比较,但目前看来,无论如何都应该避免长IN子句。
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);
DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';
SELECT *FROM TagsWHERE Name IN (SELECT [value] FROM STRING_SPLIT(@names, ','))ORDER BY [Count] DESC;
public static class ParameterExtensions{public static Tuple<string, SqlParameter[]> ToParameterTuple<T>(this IEnumerable<T> values){var createName = new Func<int, string>(index => "@value" + index.ToString());var paramTuples = values.Select((value, index) =>new Tuple<string, SqlParameter>(createName(index), new SqlParameter(createName(index), value))).ToArray();var inClause = string.Join(",", paramTuples.Select(t => t.Item1));var parameters = paramTuples.Select(t => t.Item2).ToArray();return new Tuple<string, SqlParameter[]>(inClause, parameters);}}
用法:
string[] tags = {"ruby", "rails", "scruffy", "rubyonrails"};var paramTuple = tags.ToParameterTuple();var cmdText = $"SELECT * FROM Tags WHERE Name IN ({paramTuple.Item1})";
using (var cmd = new SqlCommand(cmdText)){cmd.Parameters.AddRange(paramTuple.Item2);}