Хранимая процедура с динамическим SQL и ORDER BY - PullRequest
0 голосов
/ 24 января 2019

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

CREATE PROCEDURE [dbo].[spFindDuplicates] 
    @tableName nvarchar(255), 
    @field1 nvarchar(255), 
    @field2 nvarchar(255) = '1', 
    @field3 nvarchar(255) = '2', 
    @field4 nvarchar(255) = '3', 
    @field5 nvarchar(255) = '4'

AS

BEGIN

DECLARE @query AS nvarchar(MAX);

SET @query = '
SELECT *
FROM ' + @tableName + '
WHERE CAST(' + @field1 + ' AS nvarchar(255)) + CAST(' + @field2 + ' AS nvarchar(255)) + CAST(' + @field3 + ' AS nvarchar(255)) + CAST(' + @field4 + ' AS nvarchar(255)) + CAST(' + @field5 + ' AS nvarchar(255)) 
IN 
(
    SELECT CAST(' + @field1 + ' AS nvarchar(255)) + CAST(' + @field2 + ' AS nvarchar(255)) + CAST(' + @field3 + ' AS nvarchar(255)) + CAST(' + @field4 + ' AS nvarchar(255)) + CAST(' + @field5 + ' AS nvarchar(255))
    FROM ' + @tableName + '
    GROUP BY CAST(' + @field1 + ' AS nvarchar(255)) + CAST(' + @field2 + ' AS nvarchar(255)) + CAST(' + @field3 + ' AS nvarchar(255)) + CAST(' + @field4 + ' AS nvarchar(255)) + CAST(' + @field5 + ' AS nvarchar(255))
    HAVING COUNT(*) > 1
)
ORDER BY ' + @field1 + ', ' + @field2 + ', ' + @field3 + ', ' + @field4 + ', ' + @field5

EXECUTE(@query);

END

GO

--Example:

EXEC spFindDuplicates @tableName = 'someRandomTable', @field1 = 'firstField', @field2 = 'secondField', @field3 = 'thirdField'

Как видите, я могу использовать не более 5 различных полей, которые я объединяю, чтобы получить ключ, используемый для определения, есть ли у нас дубликат или нет. Обратите внимание, что я использую функцию CAST, чтобы иметь возможность объединять поля с различными типами данных (varchar, int, даты и т. Д.).

Когда я выполняю вышеуказанную хранимую процедуру с 5 различными полями, она работает нормально. Но я хотел бы иметь возможность запускать его с переменным количеством полей (от 1 до 5), поэтому я предоставил значения по умолчанию от @ field2 до @ field5.

Но когда я выполняю его в приведенном выше примере (предусмотрено 3 поля), я получаю следующее сообщение об ошибке:

Столбец был указан более одного раза в порядке по списку. Столбцы в порядке по списку должны быть уникальными.

ВОПРОС: Как я могу продолжать упорядочивать полученную таблицу, не получая ошибки?

БОНУСНЫЙ ВОПРОС: Если вы найдете динамический способ использовать эту хранимую процедуру с любым количеством полей (4, 17 или что-то еще), это было бы еще более полезным для меня.

Ответы [ 2 ]

0 голосов
/ 24 января 2019

Как я уже говорил в комментариях, инъекция - это огромная проблема, и вы должны рассмотреть ее.Сказать «Давайте рассмотрим, я не против инъекции» наивно, и вам нужно изменить это отношение.Всегда делайте ваш SQL безопасным;тогда нет никаких оправданий и шансов на то, что ваше приложение будет скомпрометировано.

То, что вы ищете, я подозреваю это достигает цели.Нет необходимости для подзапроса сканировать вашу таблицу с помощью IN здесь, вы можете использовать COUNT и предложение OVER в CTE.

CREATE PROCEDURE [dbo].[FindDuplicates] --I've removed te sp prefix, as sp_ is reserved by MS
    @tableName sysname, 
    @field1 sysname, 
    @field2 sysname = NULL, 
    @field3 sysname = NULL, 
    @field4 sysname = NULL, 
    @field5 sysname = NULL

AS BEGIN

    DECLARE @query AS nvarchar(MAX);

    SET @query = N'WITH CTE AS(' + NCHAR(10) +
                 N'    SELECT *' + NCHAR(10) + 
                 N'           COUNT(*) OVER (PARTITION BY ' + STUFF(CONCAT(N',' + QUOTENAME(@field1),N',' + QUOTENAME(@field2),N',' + QUOTENAME(@field3),N',' + QUOTENAME(@field4),N',' + QUOTENAME(@field5)),1,1,N'') + N' AS RowCount' + NCHAR(10) +
                 N'    FROM ' + QUOTENAME(@tableName) + N')' + NCHAR(10) +
                 N'SELECT *' + NCHAR(10) +
                 N'FROM CTE' + NCHAR(10) +
                 N'WHERE RowCount > 1' + NCHAR(10) + 
                 N'ORDER BY ' + STUFF(CONCAT(N',' + QUOTENAME(@field1),N',' + QUOTENAME(@field2),N',' + QUOTENAME(@field3),N',' + QUOTENAME(@field4),N',' + QUOTENAME(@field5)),1,1,N'') + N';';

    PRINT @query;
    --EXEC sys.sp_executesql @query; --Uncomment to rrun the actual query
END
GO

Для команды, которую вы дали намEXEC dbo.FindDuplicates @tableName = 'someRandomTable', @field1 = 'firstField', @field2 = 'secondField', @field3 = 'thirdField';, это возвращает SQL:

WITH CTE AS(
    SELECT *
           COUNT(*) OVER (PARTITION BY [firstField],[secondField],[thirdField] AS RowCount
    FROM [someRandomTable])
SELECT *
FROM CTE
WHERE RowCount > 1
ORDER BY [firstField],[secondField],[thirdField];

Что, я считаю, дает вам поведение, за которым вы следите.

0 голосов
/ 24 января 2019

Отредактировал код, чтобы проверить, существует ли список столбцов в sys.columns там, убедившись, что мы получаем только подходящие столбцы.

CREATE FUNCTION dbo.fn_SplitString
(
   @List       NVARCHAR(MAX),
   @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
   RETURN 
   (  
      SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
      FROM 
      ( 
        SELECT x = CONVERT(XML, '<i>' 
          + REPLACE(@List, @Delimiter, '</i><i>') 
          + '</i>').query('.')
      ) AS a CROSS APPLY x.nodes('i') AS y(i)
   );
GO
ALTER PROCEDURE [dbo].[spFindDuplicates] 
    @tableName nvarchar(255), 
    @columnlist nvarchar(max)  

AS

BEGIN

DECLARE @query AS nvarchar(MAX);

SET @columnlist = (SELECT STUFF((SELECT ','+'['+[name]+']'
FROM SYS.columns
WHERE object_id = object_id(@tableName)
AND [Name] IN
(
   SELECT Item
   FROM dbo.fn_SplitString(@columnlist,',')
)
FOR XML PATH('')
)
,1,1,''))

PRINT @columnlist

SET @query = 'SELECT * FROM (SELECT '+CAST(@columnlist AS NVARCHAR(MAX))+'
FROM '+CAST(@tableName AS nvarchar(MAX))+'
GROUP BY '+CAST(@columnlist AS NVARCHAR(MAX))+'
HAVING COUNT(*) > 1)Res1
ORDER BY '+@columnlist


EXEC SP_EXECUTESQL @query;

END

GO
...