69


LikeおよびInを使用した動的SQLカンマ区切り値クエリのパラメータ化されたクエリの複製

varcharカンマ区切りの値でいっぱいの値を関数に渡したいSQLServerストアドプロシージャがあり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の構築に頼らずに、これ(または比較的類似したもの)をどのように達成できますか?

4

26 に答える 26

62

もちろん、あなたが私のように怠け者なら、これを行うことができます:

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
于 2009-05-19T03:16:20.303 に答える
45

文字列を分割するためにループする関数を使用しないでください! 、以下の私の関数は、ループなしで非常に高速に文字列を分割します!

私の関数を使用する前に、「ヘルパー」テーブルを設定する必要があります。これを行う必要があるのは、データベースごとに 1 回だけです。

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)
于 2009-05-18T17:49:11.430 に答える
24

テーブルなし 関数なし ループなし

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の回答と同じパフォーマンスを持っているようですが、はるかに単純だと思います。

于 2013-06-05T09:48:08.750 に答える
11

テーブルを返す関数を作成できます。

だからあなたの声明は次のようになります

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
于 2009-05-18T17:29:30.800 に答える
9

これは非常に一般的な質問です。缶詰の回答、いくつかの優れたテクニック:

http://www.sommarskog.se/arrays-in-sql-2005.html

于 2009-05-18T19:52:01.370 に答える
8

これは完璧に機能します!以下の回答は複雑すぎます。これを動的なものと見なさないでください。ストア プロシージャを次のように設定します。

(@id as varchar(50))
as

Declare @query as nvarchar(max)
set @query ='
select * from table
where id in('+@id+')'
EXECUTE sp_executesql @query
于 2009-05-18T17:32:49.607 に答える
6

動的 SQL を使用しない場合は、入力変数を取得し、分割関数を使用してデータを一時テーブルに入れ、そこに結合する必要があります。

于 2009-05-18T17:26:13.817 に答える
6

次のように使用することをお勧めし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;
于 2015-09-26T05:18:49.360 に答える
3

あなたの機能をありがとう、私はそれを使用しました.................................これは私の例です

**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
于 2010-03-05T06:37:15.320 に答える
3

しばらく経ちましたが、暫定的に 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)
于 2016-02-23T11:17:33.920 に答える
2

私はユーザー 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
于 2016-11-29T02:14:38.913 に答える
2

以前にこれを行う方法を示すストアド プロシージャを作成しました。基本的に文字列を処理する必要があります。ここにコードを投稿しようとしましたが、書式設定がすべておかしくなりました。

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
于 2009-05-18T17:29:35.260 に答える
1
-- 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
于 2012-07-31T11:20:14.517 に答える
1

次のようにできます。

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 句は、他の状況でも regexp_substr で使用できます。試してみてください。

于 2014-12-22T12:58:08.593 に答える
1

これを試して:

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

このリンクの最後の例で述べたように

于 2017-08-20T03:31:36.447 に答える
0

ここにはたくさんの答えがありますが、私の2セントを追加すると、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, ','))
于 2021-06-10T04:22:41.123 に答える
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)
于 2020-11-28T22:05:06.153 に答える