重复
Dynamic SQL Comma Delimited Value Query
Parameterized Queries with Like and In
我有一个SQL Server存储过程,我希望将varchar
个逗号分隔的值传递给IN
函数。例如:
DECLARE @Ids varchar(50);
SET @Ids = '1,2,3,5,4,6,7,98,234';
SELECT *
FROM sometable
WHERE tableid IN (@Ids);
这当然不起作用。我收到错误:
将varchar值'1,2,3,5,4,6,7,98,234'转换为数据类型int时转换失败。
如何在不诉诸构建动态SQL的情况下完成此(或相对类似的事情)?
答案 0 :(得分:53)
当然,如果你像我一样懒,你就可以这样做:
Declare @Ids varchar(50) Set @Ids = ',1,2,3,5,4,6,7,98,234,'
Select * from sometable
where Charindex(','+cast(tableid as varchar(8000))+',', @Ids) > 0
答案 1 :(得分:42)
不要使用循环分割字符串的函数!,我的函数下面会快速分割字符串,没有循环!
在使用我的功能之前,您需要设置一个“帮助程序”表,每个数据库只需执行一次这样的操作:
CREATE TABLE Numbers
(Number int NOT NULL,
CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
DECLARE @x int
SET @x=0
WHILE @x<8000
BEGIN
SET @x=@x+1
INSERT INTO Numbers VALUES (@x)
END
使用此函数来分割您的字符串,该字符串不会循环且非常快:
CREATE FUNCTION [dbo].[FN_ListToTable]
(
@SplitOn char(1) --REQUIRED, the character to split the @List string on
,@List varchar(8000) --REQUIRED, the list to split apart
)
RETURNS
@ParsedList table
(
ListValue varchar(500)
)
AS
BEGIN
/**
Takes the given @List string and splits it apart based on the given @SplitOn character.
A table is returned, one row per split item, with a column name "ListValue".
This function workes for fixed or variable lenght items.
Empty and null items will not be included in the results set.
Returns a table, one row per item in the list, with a column name "ListValue"
EXAMPLE:
----------
SELECT * FROM dbo.FN_ListToTable(',','1,12,123,1234,54321,6,A,*,|||,,,,B')
returns:
ListValue
-----------
1
12
123
1234
54321
6
A
*
|||
B
(10 row(s) affected)
**/
----------------
--SINGLE QUERY-- --this will not return empty rows
----------------
INSERT INTO @ParsedList
(ListValue)
SELECT
ListValue
FROM (SELECT
LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(@SplitOn, List2, number+1)-number - 1))) AS ListValue
FROM (
SELECT @SplitOn + @List + @SplitOn AS List2
) AS dt
INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
WHERE SUBSTRING(List2, number, 1) = @SplitOn
) dt2
WHERE ListValue IS NOT NULL AND ListValue!=''
RETURN
END --Function FN_ListToTable
您可以将此功能用作连接中的表格:
SELECT
Col1, COl2, Col3...
FROM YourTable
INNER JOIN FN_ListToTable(',',@YourString) s ON YourTable.ID = s.ListValue
以下是您的示例:
Select * from sometable where tableid in(SELECT ListValue FROM dbo.FN_ListToTable(',',@Ids) s)
答案 2 :(得分:17)
无表无功能无循环
基于将列表解析为表的想法,我们的DBA建议使用XML。
Declare @Ids varchar(50)
Set @Ids = ‘1,2,3,5,4,6,7,98,234’
DECLARE @XML XML
SET @XML = CAST('<i>' + REPLACE(@Ids, ',', '</i><i>') + '</i>' AS XML)
SELECT *
FROM
SomeTable
INNER JOIN @XML.nodes('i') x(i)
ON SomeTable .Id = x.i.value('.', 'VARCHAR(MAX)')
这些似乎与@ KM的答案具有相同的性能,但我认为,更简单。
答案 3 :(得分:9)
您可以创建一个返回表格的函数。
所以你的陈述就像是
select * from someable
join Splitfunction(@ids) as splits on sometable.id = splits.id
这是一个类似的函数。
CREATE FUNCTION [dbo].[FUNC_SplitOrderIDs]
(
@OrderList varchar(500)
)
RETURNS
@ParsedList table
(
OrderID int
)
AS
BEGIN
DECLARE @OrderID varchar(10), @Pos int
SET @OrderList = LTRIM(RTRIM(@OrderList))+ ','
SET @Pos = CHARINDEX(',', @OrderList, 1)
IF REPLACE(@OrderList, ',', '') <> ''
BEGIN
WHILE @Pos > 0
BEGIN
SET @OrderID = LTRIM(RTRIM(LEFT(@OrderList, @Pos - 1)))
IF @OrderID <> ''
BEGIN
INSERT INTO @ParsedList (OrderID)
VALUES (CAST(@OrderID AS int)) --Use Appropriate conversion
END
SET @OrderList = RIGHT(@OrderList, LEN(@OrderList) - @Pos)
SET @Pos = CHARINDEX(',', @OrderList, 1)
END
END
RETURN
END
答案 4 :(得分:9)
这是一个非常常见的问题。罐头答案,几种不错的技巧:
答案 5 :(得分:7)
这完美无缺!以下答案太复杂了。不要把它视为动态。按如下方式设置您的商店程序:
(@id as varchar(50))
as
Declare @query as nvarchar(max)
set @query ='
select * from table
where id in('+@id+')'
EXECUTE sp_executesql @query
答案 6 :(得分:5)
不使用动态SQL,您必须获取输入变量并使用split函数将数据放入临时表,然后加入到该表中。
答案 7 :(得分:4)
我建议像这样使用WITH
:
DECLARE @Delim char(1) = ',';
SET @Ids = @Ids + @Delim;
WITH CTE(i, ls, id) AS (
SELECT 1, CHARINDEX(@Delim, @Ids, 1), SUBSTRING(@Ids, 1, CHARINDEX(@Delim, @Ids, 1) - 1)
UNION ALL
SELECT i + 1, CHARINDEX(@Delim, @Ids, ls + 1), SUBSTRING(@Ids, ls + 1, CHARINDEX(@Delim, @Ids, ls + 1) - CHARINDEX(@Delim, @Ids, ls) - 1)
FROM CTE
WHERE CHARINDEX(@Delim, @Ids, ls + 1) > 1
)
SELECT t.*
FROM yourTable t
INNER JOIN
CTE c
ON t.id = c.id;
答案 8 :(得分:2)
谢谢,因为你的功能我用它........................ 这是我的例子
**UPDATE [RD].[PurchaseOrderHeader]
SET [DispatchCycleNumber] ='10'
WHERE OrderNumber in(select * FROM XA.fn_SplitOrderIDs(@InvoiceNumberList))**
CREATE FUNCTION [XA].[fn_SplitOrderIDs]
(
@OrderList varchar(500)
)
RETURNS
@ParsedList table
(
OrderID int
)
AS
BEGIN
DECLARE @OrderID varchar(10), @Pos int
SET @OrderList = LTRIM(RTRIM(@OrderList))+ ','
SET @Pos = CHARINDEX(',', @OrderList, 1)
IF REPLACE(@OrderList, ',', '') <> ''
BEGIN
WHILE @Pos > 0
BEGIN
SET @OrderID = LTRIM(RTRIM(LEFT(@OrderList, @Pos - 1)))
IF @OrderID <> ''
BEGIN
INSERT INTO @ParsedList (OrderID)
VALUES (CAST(@OrderID AS int)) --Use Appropriate conversion
END
SET @OrderList = RIGHT(@OrderList, LEN(@OrderList) - @Pos)
SET @Pos = CHARINDEX(',', @OrderList, 1)
END
END
RETURN
END
答案 9 :(得分:2)
如果使用SQL Server 2008或更高版本,请使用表值参数;例如:
CREATE PROCEDURE [dbo].[GetAccounts](@accountIds nvarchar)
AS
BEGIN
SELECT *
FROM accountsTable
WHERE accountId IN (select * from @accountIds)
END
CREATE TYPE intListTableType AS TABLE (n int NOT NULL)
DECLARE @tvp intListTableType
-- inserts each id to one row in the tvp table
INSERT @tvp(n) VALUES (16509),(16685),(46173),(42925),(46167),(5511)
EXEC GetAccounts @tvp
答案 10 :(得分:2)
创建一个像下面这样的表函数,它解析逗号分隔的varchar并返回一个可以与其他表内连接的表。
CREATE FUNCTION [dbo].[fn_SplitList]
(
@inString varchar(MAX) = '',
@inDelimiter char(1) = ',' -- Keep the delimiter to 100 chars or less. Generally a delimiter will be 1-2 chars only.
)
RETURNS @tbl_Return table
(
Unit varchar(1000) COLLATE Latin1_General_BIN
)
AS
BEGIN
INSERT INTO @tbl_Return
SELECT DISTINCT
LTRIM(RTRIM(piece.value('./text()[1]', 'varchar(1000)'))) COLLATE DATABASE_DEFAULT AS Unit
FROM
(
--
-- Replace any delimiters in the string with the "X" tag.
--
SELECT
CAST(('<X>' + REPLACE(s0.prsString, s0.prsSplitDelimit, '</X><X>') + '</X>') AS xml).query('.') AS units
FROM
(
--
-- Convert the string and delimiter into XML.
--
SELECT
(SELECT @inString FOR XML PATH('')) AS prsString,
(SELECT @inDelimiter FOR XML PATH('')) AS prsSplitDelimit
) AS s0
) AS s1
CROSS APPLY units.nodes('X') x(piece)
RETURN
END
=============================================== == 现在在代码中使用上面创建的表函数,函数的创建是数据库中的一次性活动,可以在数据库中以及同一服务器上使用。
DECLARE @Ids varchar(50);
SET @Ids = '1,2,3,5,4,6,7,98,234';
SELECT
*
FROM sometable AS st
INNER JOIN fn_SplitList(@ids, ',') AS sl
ON sl.unit = st.tableid
答案 11 :(得分:2)
我认为一个非常简单的解决方案可能是:
DECLARE @Ids varchar(50);
SET @Ids = '1,2,3,5,4,6,7,98,234';
SELECT *
FROM sometable
WHERE ','+@Ids+',' LIKE '%,'+CONVERT(VARCHAR(50),tableid)+',%';
答案 12 :(得分:1)
我之前编写了一个存储过程来说明如何执行此操作。 你基本上必须处理字符串。 我试图在这里发布代码,但格式化得很复杂。
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[uspSplitTextList]') AND OBJECTPROPERTY(id, N'IsProcedure') = 1)
DROP PROCEDURE [dbo].[uspSplitTextList]
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
-- uspSplitTextList
--
-- Description:
-- splits a separated list of text items and returns the text items
--
-- Arguments:
-- @list_text - list of text items
-- @Delimiter - delimiter
--
-- Notes:
-- 02/22/2006 - WSR : use DATALENGTH instead of LEN throughout because LEN doesn't count trailing blanks
--
-- History:
-- 02/22/2006 - WSR : revised algorithm to account for items crossing 8000 character boundary
-- 09/18/2006 - WSR : added to this project
--
CREATE PROCEDURE uspSplitTextList
@list_text text,
@Delimiter varchar(3)
AS
SET NOCOUNT ON
DECLARE @InputLen integer -- input text length
DECLARE @TextPos integer -- current position within input text
DECLARE @Chunk varchar(8000) -- chunk within input text
DECLARE @ChunkPos integer -- current position within chunk
DECLARE @DelimPos integer -- position of delimiter
DECLARE @ChunkLen integer -- chunk length
DECLARE @DelimLen integer -- delimiter length
DECLARE @ItemBegPos integer -- item starting position in text
DECLARE @ItemOrder integer -- item order in list
DECLARE @DelimChar varchar(1) -- first character of delimiter (simple delimiter)
-- create table to hold list items
-- actually their positions because we may want to scrub this list eliminating bad entries before substring is applied
CREATE TABLE #list_items ( item_order integer, item_begpos integer, item_endpos integer )
-- process list
IF @list_text IS NOT NULL
BEGIN
-- initialize
SET @InputLen = DATALENGTH(@list_text)
SET @TextPos = 1
SET @DelimChar = SUBSTRING(@Delimiter, 1, 1)
SET @DelimLen = DATALENGTH(@Delimiter)
SET @ItemBegPos = 1
SET @ItemOrder = 1
SET @ChunkLen = 1
-- cycle through input processing chunks
WHILE @TextPos <= @InputLen AND @ChunkLen <> 0
BEGIN
-- get current chunk
SET @Chunk = SUBSTRING(@list_text, @TextPos, 8000)
-- setup initial variable values
SET @ChunkPos = 1
SET @ChunkLen = DATALENGTH(@Chunk)
SET @DelimPos = CHARINDEX(@DelimChar, @Chunk, @ChunkPos)
-- loop over the chunk, until the last delimiter
WHILE @ChunkPos <= @ChunkLen AND @DelimPos <> 0
BEGIN
-- see if this is a full delimiter
IF SUBSTRING(@list_text, (@TextPos + @DelimPos - 1), @DelimLen) = @Delimiter
BEGIN
-- insert position
INSERT INTO #list_items (item_order, item_begpos, item_endpos)
VALUES (@ItemOrder, @ItemBegPos, (@TextPos + @DelimPos - 1) - 1)
-- adjust positions
SET @ItemOrder = @ItemOrder + 1
SET @ItemBegPos = (@TextPos + @DelimPos - 1) + @DelimLen
SET @ChunkPos = @DelimPos + @DelimLen
END
ELSE
BEGIN
-- adjust positions
SET @ChunkPos = @DelimPos + 1
END
-- find next delimiter
SET @DelimPos = CHARINDEX(@DelimChar, @Chunk, @ChunkPos)
END
-- adjust positions
SET @TextPos = @TextPos + @ChunkLen
END
-- handle last item
IF @ItemBegPos <= @InputLen
BEGIN
-- insert position
INSERT INTO #list_items (item_order, item_begpos, item_endpos)
VALUES (@ItemOrder, @ItemBegPos, @InputLen)
END
-- delete the bad items
DELETE FROM #list_items
WHERE item_endpos < item_begpos
-- return list items
SELECT SUBSTRING(@list_text, item_begpos, (item_endpos - item_begpos + 1)) AS item_text, item_order, item_begpos, item_endpos
FROM #list_items
ORDER BY item_order
END
DROP TABLE #list_items
RETURN
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
答案 13 :(得分:1)
过去已经有一段时间了,但过去我使用XML作为过渡时间。
我不能相信这一点,但我担心我不知道从哪里得到这个想法:
-- declare the variables needed
DECLARE @xml as xml,@str as varchar(100),@delimiter as varchar(10)
-- The string you want to split
SET @str='A,B,C,D,E,Bert,Ernie,1,2,3,4,5'
-- What you want to split on. Can be a single character or a string
SET @delimiter =','
-- Convert it to an XML document
SET @xml = cast(('<X>'+replace(@str,@delimiter ,'</X><X>')+'</X>') as xml)
-- Select back from the XML
SELECT N.value('.', 'varchar(10)') as value FROM @xml.nodes('X') as T(N)
答案 14 :(得分:1)
我和用户KM有同样的想法。但不需要额外的表号。只是这个功能。
CREATE FUNCTION [dbo].[FN_ListToTable]
(
@SplitOn char(1) --REQUIRED, the character to split the @List string on
,@List varchar(8000) --REQUIRED, the list to split apart
)
RETURNS
@ParsedList table
(
ListValue varchar(500)
)
AS
BEGIN
DECLARE @number int = 0
DECLARE @childString varchar(502) = ''
DECLARE @lengthChildString int = 0
DECLARE @processString varchar(502) = @SplitOn + @List + @SplitOn
WHILE @number < LEN(@processString)
BEGIN
SET @number = @number + 1
SET @lengthChildString = CHARINDEX(@SplitOn, @processString, @number + 1) - @number - 1
IF @lengthChildString > 0
BEGIN
SET @childString = LTRIM(RTRIM(SUBSTRING(@processString, @number + 1, @lengthChildString)))
IF @childString IS NOT NULL AND @childString != ''
BEGIN
INSERT INTO @ParsedList(ListValue) VALUES (@childString)
SET @number = @number + @lengthChildString - 1
END
END
END
RETURN
END
这是测试:
SELECT ListValue FROM dbo.FN_ListToTable('/','a/////bb/c')
结果:
ListValue
______________________
a
bb
c
答案 15 :(得分:1)
试试这个:
SELECT ProductId, Name, Tags
FROM Product
WHERE '1,2,3,' LIKE '%' + CAST(ProductId AS VARCHAR(20)) + ',%';
的最后一个例子所述
答案 16 :(得分:0)
这里有很多答案,但加上我的两分钱,我认为 STRING_SPLIT
是解决此类问题的一种非常简单的方法:
DECLARE @Ids varchar(50);
SET @Ids = '1,2,3,5,4,6,7,98,234';
SELECT *
FROM sometable
WHERE tableid IN;
(SELECT value FROM STRING_SPLIT(@Ids, ','))
答案 17 :(得分:0)
-- select * from dbo.Split_ID('77,106')
ALTER FUNCTION dbo.Split_ID(@String varchar(8000))
returns @temptable TABLE (ID varchar(8000))
as
begin
declare @idx int
declare @slice varchar(8000)
declare @Delimiter char(1)
set @Delimiter =','
select @idx = 1
if len(@String)<1 or @String is null return
while @idx!= 0
begin
set @idx = charindex(@Delimiter,@String)
if @idx!=0
set @slice = left(@String,@idx - 1)
else
set @slice = @String
if(len(@slice)>0)
insert into @temptable(ID) values(@slice)
set @String = right(@String,len(@String) - @idx)
if len(@String) = 0 break
end
return
end
答案 18 :(得分:0)
@RBarryYoung(以上)的回答对我有用。 但是当逗号分隔的字符串值之间有空格时,它将省略带空格的 ID。所以我删除了空格。
看看下面的代码片段。
Declare @Ids varchar(50) Set @Ids = '1 , 2,3'
set @Ids=','+Replace(@Ids,' ', '')+',';
Select * from [tblEmployee]
where Charindex(','+cast(ID as varchar(8000))+',', @Ids) > 0
答案 19 :(得分:0)
我遇到了同样的问题,并且我不想在源数据库上有任何占用空间-即,没有存储过程或函数。我这样处理:
declare @IDs table (Value int)
insert into @IDs values(1)
insert into @IDs values(2)
insert into @IDs values(3)
insert into @IDs values(5)
insert into @IDs values(4)
insert into @IDs values(6)
insert into @IDs values(7)
insert into @IDs values(98)
insert into @IDs values(234)
SELECT *
FROM sometable
WHERE tableid IN (select Value from @IDs)
答案 20 :(得分:0)
Error 493: The column 'i' that was returned from the nodes() method cannot be
used directly. It can only be used with one of the four XML data type
methods, exist(), nodes(), query(), and value(), or in IS NULL and IS NOT
NULL checks.
使用以下代码段
在SQL Server 2014中修复了上述错误Declare @Ids varchar(50)
Set @Ids = '1,2,3,5,4,6,7,98,234'
DECLARE @XML XML
SET @XML = CAST('<i>' + REPLACE(@Ids, ',', '</i><i>') + '</i>' AS XML)
SELECT SomeTable.*
FROM
SomeTable
cross apply @XML.nodes('i') x(i)
where SomeTable .Id = x.i.value('.', 'VARCHAR(MAX)')
答案 21 :(得分:0)
你可以这样做:
create or replace
PROCEDURE UDP_SETBOOKMARK
(
P_USERID IN VARCHAR2
, P_BOOKMARK IN VARCHAR2
) AS
BEGIN
UPDATE T_ER_Bewertung
SET LESEZEICHEN = P_BOOKMARK
WHERE STAMM_ID in( select regexp_substr(P_USERID,'[^,]+', 1, level) from dual
connect by regexp_substr(P_USERID, '[^,]+', 1, level) is not null )
and ER_ID = (select max(ER_ID) from T_ER_Bewertung_Kopie);
commit;
END UDP_SETBOOKMARK;
然后尝试
Begin
UDP_SETBOOKMARK ('1,2,3,4,5', 'Test');
End;
你也可以在其他情况下使用这个IN-Clause和regexp_substr,试试吧。
答案 22 :(得分:-1)
CREATE TABLE t
(
id INT,
col1 VARCHAR(50)
)
INSERT INTO t
VALUES (1,
'param1')
INSERT INTO t
VALUES (2,
'param2')
INSERT INTO t
VALUES (3,
'param3')
INSERT INTO t
VALUES (4,
'param4')
INSERT INTO t
VALUES (5,
'param5')
DECLARE @params VARCHAR(100)
SET @params = ',param1,param2,param3,'
SELECT *
FROM t
WHERE Charindex(',' + Cast(col1 AS VARCHAR(8000)) + ',', @params) > 0
工作小提琴找到Fiddle
答案 23 :(得分:-1)
这对于我不想使用CTE并且也不想使用内部联接的要求之一很方便。
DECLARE @Ids varchar(50);
SET @Ids = '1,2,3,5,4,6,7,98,234';
SELECT
cn1,cn2,cn3
FROM tableName
WHERE columnName in (select Value from fn_SplitList(@ids, ','))
答案 24 :(得分:-1)
我发现最简单的方法是使用FIND_IN_SET
FIND_IN_SET(column_name, values)
values=(1,2,3)
SELECT name WHERE FIND_IN_SET(id, values)
答案 25 :(得分:-1)
最简单的方法。
DECLARE @AccumulateKeywordCopy NVARCHAR(2000),@IDDupCopy NVARCHAR(50);
SET @AccumulateKeywordCopy ='';
SET @IDDupCopy ='';
SET @IDDup = (SELECT CONVERT(VARCHAR(MAX), <columnName>) FROM <tableName> WHERE <clause>)
SET @AccumulateKeywordCopy = ','+@AccumulateKeyword+',';
SET @IDDupCopy = ','+@IDDup +',';
SET @IDDupCheck = CHARINDEX(@IDDupCopy,@AccumulateKeywordCopy)