Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sql-server/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql server 具有动态SQL和ORDER BY的存储过程_Sql Server_Stored Procedures_Sql Order By_Dynamic Sql - Fatal编程技术网

Sql server 具有动态SQL和ORDER BY的存储过程

Sql server 具有动态SQL和ORDER BY的存储过程,sql-server,stored-procedures,sql-order-by,dynamic-sql,Sql Server,Stored Procedures,Sql Order By,Dynamic Sql,我构建了一个存储过程,旨在识别表中的重复项,并以有意义的顺序显示重复的行。看起来是这样的: CREATE PROCEDURE [dbo].[spFindDuplicates] @tableName nvarchar(255), @field1 nvarchar(255), @field2 nvarchar(255) = '1', @field3 nvarchar(255) = '2', @field4 nvarchar(255) = '3',

我构建了一个存储过程,旨在识别表中的重复项,并以有意义的顺序显示重复的行。看起来是这样的:

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、dates等的字段

当我使用5个不同的字段执行上述存储过程时,它工作正常。但我希望能够使用从1到5的可变字段数运行它,这就是为什么我为@field2到@field5提供了默认值

但是,当我使用上面提供的示例3字段执行时,会收到以下错误消息:

在“排序依据”列表中多次指定了列。排序依据列表中的列必须是唯一的

问题:如何在不出错的情况下对结果表进行排序


附加问题:如果您找到一种动态方式,将该存储过程与任意数量的字段4、17或其他字段一起使用,这对我来说会更有用。

编辑代码,通过确保只获得适当的列来检查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

就像我在评论中所说的,注射是一个很大的问题,你需要考虑。让我们考虑一下,我不介意注射是天真的,你需要改变这种态度。始终确保SQL安全;这样,您的应用程序就不会有任何借口和机会遭到破坏

正如你所追求的,我怀疑这达到了目标。子查询不需要用IN-here扫描表,您可以在CTE中使用COUNT和OVER子句

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];

我相信这会给你带来你想要的行为。

你可以动态地做到这一点,通过使用单列参数,你将在其中传递逗号分隔的列,或者使用表类型,你有一个巨大的安全漏洞;它对SQL注入非常开放。您应该使用sp_executesql参数化查询,并使用QUOTENAME引用对象。现在你有一个漏洞在等待开发。拉努让我考虑一下SQL注入是不可能的。是的。如果没有,请重新思考,改变主意,然后解决问题。如果不是,我不知道是什么。因此,您希望在许多表中允许重复行,但还需要定期识别这些重复行?你确定你解决了正确的问题吗?因为更正常的情况下,它表明您首先需要一些独特的约束来阻止重复项的存在。这远远不能解决注入问题。事实上,按'+@columnlist'排序会使情况更糟;因为这是不可能保证安全的。@Dherendra您的建议几乎不错,但“SELECT*”对我来说很重要,这就是为什么我需要使用子查询并连接字段的原因。在这种情况下,当涉及到concatenating@Guillaume-我已经编辑了代码。我认为,为了消除SQL注入,我们可以从列列表中分割列,并与SysStudio的列匹配,只考虑正在匹配的一个列,并将逗号分隔列表更新为对过程的增强。它是完全开放的。使用while循环来分割字符串太糟糕了。我知道你复制并粘贴了这个。有几种更好的方法来处理字符串拆分。更好的方法是使用表值参数,这样您就不必解析它了。干得好。我花了一秒钟的时间才弄明白你是如何处理空值的,但这很聪明。CONCAT和STUFF对空值参数和类似的参数非常有用,@SeanLange。为了向其他人解释,也许我应该给出答案,与普通字符串串联不同的是,当其中一个表达式的值为NULL时,CONCAT不会返回NULL。所以'a'+NULL将是NULL,然而,CONCAT'a',NULL,NULL,'b'将是'ab'。我使用它对我有利,这意味着在构建PARTITION BY和ORDER BY子句时,空值参数被有效地丢弃。是的,我从来没有考虑过这样使用。很酷。