Передача varchar, полного значений, разделенных запятыми, в функцию SQL Server IN - PullRequest
49 голосов
/ 18 мая 2009

Дубликат
Динамический SQL-запрос с разделителями-запятыми
Параметризованные Запросы с Like и 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?

Ответы [ 22 ]

53 голосов
/ 19 мая 2009

Конечно, если вы ленивы, как я, вы можете просто сделать это:

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
42 голосов
/ 18 мая 2009

Не используйте функцию, которая зацикливается для разделения строки! , моя функция ниже будет разбивать строку очень быстро, без зацикливания!

Прежде чем использовать мою функцию, вам нужно настроить вспомогательную таблицу, вам нужно сделать это только один раз для каждой базы данных:

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)
17 голосов
/ 05 июня 2013

Нет таблицы Нет функции Нет цикла

Основываясь на идее разбора вашего списка в таблицу, наш администратор базы данных предложил использовать 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, но, думаю, намного проще.

9 голосов
/ 18 мая 2009

Вы можете создать функцию, которая возвращает таблицу.

поэтому ваше утверждение будет примерно таким:

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
9 голосов
/ 18 мая 2009

Это очень распространенный вопрос. Консервированный ответ, несколько приятных приемов:

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

7 голосов
/ 18 мая 2009

Это работает отлично! Приведенные ниже ответы слишком сложны. Не смотрите на это как на динамику. Настройте процедуру вашего магазина следующим образом:

(@id as varchar(50))
as

Declare @query as nvarchar(max)
set @query ='
select * from table
where id in('+@id+')'
EXECUTE sp_executesql @query
5 голосов
/ 18 мая 2009

Без использования динамического SQL вы должны взять входную переменную и использовать функцию разбиения, чтобы поместить данные во временную таблицу и затем присоединиться к ней.

4 голосов
/ 26 сентября 2015

Я могу предложить использовать 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;
2 голосов
/ 05 марта 2010

Спасибо, за вашу функцию я использовал ЭТО ........................ Это мой ПРИМЕР

**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
2 голосов
/ 06 октября 2014

Если вы используете 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
...