Нужна справка по SQL-запросу, сопоставляющая параметр списка хранимых процедур с отдельными столбцами - PullRequest
1 голос
/ 02 мая 2009

У меня есть хранимая процедура ..

Proc:
spMyStoredProcedure

Принимает параметр:

@List varchar(255)

@List будет списком значений, разделенных запятыми, т.е. ... @List = '1,2,3'
(ясность ... одно значение 1 будет означать все записи с col1 = true)

У меня есть таблица с этими столбцами: ID int, col1 bit, col2 bit, col3 bit, col4 bit.

ID | col1 | col2 | col3 | col4
------------------------------
12 |   0  |   1  |   0  |  0
13 |   1  |   0  |   0  |  0
14 |   0  |   0  |   1  |  0
15 |   0  |   0  |   0  |  1

Мне бы хотелось, чтобы мой результат включал только идентификаторы для этих строк в списке. то есть 12,13,14.

Моя первая мысль - перебрать список и сделать выбор. то есть. для первого значения, равного 1, я беру все записи с 1 (true) в col1 (в результате получается запись 12). Затем перейдите ко второму значению, равному 2, и возьмите все записи с 1 (true) в col2 (что приводит к записи 14) и т. Д. Мне интересно, есть ли более эффективный / лучший / более чистый способ сделать это?

Ответы [ 6 ]

2 голосов
/ 02 мая 2009

Я думаю, что это решает проблему:

declare @sql as nvarchar(max)

set @sql = 'select * from table where col' + 
           replace(@list, ',', '=1 or col') + '=1'
sp_executesql @sql

Предполагается, что список не генерируется пользователем, а генерируется кодом, поэтому я не защищаюсь от атак с использованием SQL-инъекций. Подобный список, как правило, не является чем-то, что генерируется пользователем, поэтому я так и считаю.

Это то, что вы ищете?

0 голосов
/ 02 мая 2009

Мы используем UDF, который анализирует список запятых и возвращает табличное значение:

CREATE FUNCTION [dbo].[ListToTable] (
  @list VARCHAR(MAX),
  @separator VARCHAR(MAX) = ','
)
RETURNS @table TABLE (Value VARCHAR(MAX))
AS BEGIN
   DECLARE @position INT, @previous INT
   SET @list = @list + @separator
   SET @previous = 1
   SET @position = CHARINDEX(@separator, @list)
   WHILE @position > 0 BEGIN
      IF @position - @previous > 0
         INSERT INTO @table VALUES (LTRIM(RTRIM(SUBSTRING(@list, @previous, @position - @previous))))
      IF @position >= LEN(@list) BREAK
      SET @previous = @position + 1
      SET @position = CHARINDEX(@separator, @list, @previous)
   END
   RETURN
END

Тогда вы можете сделать что-то вроде:

SELECT *
FROM MyTable m
JOIN dbo.ListToTable(@List) l
  ON m.ID = l.Value

Приведенная выше функция также принимает дополнительный разделитель, например:

SELECT *
FROM MyTable m
JOIN dbo.ListToTable('1:2:3', ':') l
  ON m.ID = l.Value
0 голосов
/ 02 мая 2009

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

SELECT ID FROM SampleTable WHERE (col1*1 + col2*2 + col3*4 + col4*8) IN (1, 2, 3)

Поскольку 1,2,3 все объединены в переменную, для этого я обычно использую пользовательскую функцию, определенную в http://www.sqlteam.com/article/using-a-csv-with-an-in-sub-select.

Итак, это было бы что-то вроде

SELECT ID FROM SampleTable WHERE (col1*1 + col2*2 + col3*4 + col4*8) IN dbo.CSVToInt(@List)
0 голосов
/ 02 мая 2009

Рассмотрим следующее, используя динамический SQL. Вероятно, это не самый лучший метод, но это единственный способ, который я могу придумать, чтобы не выдавать несколько операторов выбора для вашей таблицы. Предполагается, что вы очистили свой список значений и сначала сделали проверку ошибок ...

CREATE TABLE #tmp
(
    ID INT,
    col1 BIT,
    col2 BIT,
    col3 BIT,
    col4 BIT
)
INSERT INTO #tmp (ID, col1, col2, col3, col4) VALUES (12,0,1,0,0)
INSERT INTO #tmp (ID, col1, col2, col3, col4) VALUES (13,1,0,0,0)
INSERT INTO #tmp (ID, col1, col2, col3, col4) VALUES (14,0,0,1,0)
INSERT INTO #tmp (ID, col1, col2, col3, col4) VALUES (15,0,0,0,1)

DECLARE @List VARCHAR(255)
SELECT @List = '1,2,3'

-- create dynamic sql statement
DECLARE @sql VARCHAR(MAX)
SET @sql = 'SELECT ID FROM #tmp WHERE '

-- append where statement
IF CHARINDEX('1',@List) > 0 BEGIN
    SET @sql = @sql + 'col1 = 1 OR '
END
IF CHARINDEX('2',@List) > 0 BEGIN
    SET @sql = @sql + 'col2 = 1 OR '
END
IF CHARINDEX('3',@List) > 0 BEGIN
    SET @sql = @sql + 'col3 = 1 OR '
END
IF CHARINDEX('4',@List) > 0 BEGIN
    SET @sql = @sql + 'col4 = 1 OR '
END

-- remove the trailing 'OR' and execute
SET @sql = SUBSTRING(@sql,0,LEN(@sql)-2)
EXEC (@sql)
0 голосов
/ 02 мая 2009

Если вы используете Sql Server 2005, вы сможете использовать функцию PIVOT для транспонирования ваших данных.

0 голосов
/ 02 мая 2009

** неправильный ответ, проверьте ответ Эрика для лучшего ответа!

К счастью, ваш список соответствует формату IN:

 select * from yourtable where id in (1,2,3,4)

Чтобы вы могли легко построить динамический запрос:

declare @query varchar(4000)
set @query = 'select * from yourtable where id in (' +
    @list + ')'
 exec sp_executesql @query

Имейте в виду, что ваш клиент должен убедиться, что параметр @list является чистым для инъекций. Вы можете проверить в хранимой процедуре, что она не содержит ни одной кавычки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...