sp_executesql 分组依据 "must contain at least one column that is not an outer reference"

sp_executesql Group by "must contain at least one column that is not an outer reference"

在简化版本中,我正在尝试做类似的事情:

Set @datepart = 'DATEPART(year, myDate)' 
Set @SQLQuery = 'SELECT @datepart AS TIME 
                 FROM someTable 
                 GROUP BY @datepart'
Execute sp_executesql @SQLQuery, N'@datepart nvarchar(1000)', @datepart

但我得到:

Each GROUP BY expression must contain at least one column that is not an outer reference.

如果我不对其进行参数化,它就可以工作,意思是

Set @SQLQuery = 'SELECT ' + @datepart + ' AS TIME 
                 FROM someTable 
                 GROUP BY ' + @datepart
Execute sp_executesql @SQLQuery

我认为简单 select 应该可以完成这项工作。 查询是

SELECT DISTINCT DATEPART(year,mydate) AS TIME 
FROM someTable 

对于动态查询:

Set @SQLQuery = 'SELECT DISTINCT' + @datepart + ' AS TIME 
                 FROM someTable'
Execute sp_executesql @SQLQuery

通过使用参数化查询,您无法实现您想要做的事情,唯一的方法是简单的动态查询

Set @SQLQuery = 'SELECT ' + @datepart + ' AS TIME 
                 FROM someTable 
                 GROUP BY ' + @datepart
Execute sp_executesql @SQLQuery

因为变量只是作为动态查询中的值。考虑以下示例。

DECLARE @datepart NVARCHAR(1000),
        @SQLQuery NVARCHAR(max)

SET @datepart = 'DATEPART(year, getdate())'
SET @SQLQuery = 'SELECT @datepart AS TIME'

EXECUTE Sp_executesql
  @SQLQuery,
  N'@datepart nvarchar(1000)',
  @datepart 

根据您的说法,答案应该是 2015 但结果是

DATEPART(year, getdate())

没有人完全意识到您提出了一个问题并自己解决了。 SQL 服务器非常正确地注意到您正在尝试 分组,但报告没有输出。

我接受了你的查询并制作了一个几乎我们所有人都应该能够在 SQL 2005+:

上使用的版本
DECLARE  @datepart NVARCHAR(MAX), @SQLQUERY NVARCHAR(MAX)
SEt @datepart  = 'DATEPART(year, myDate)' 
Set @SQLQuery = 'SELECT @datepart AS TIME 
                 FROM sys.objects 
                 GROUP BY @datepart'
Execute sp_executesql @SQLQuery, N'@datepart nvarchar(1000)', @datepart

现在,这将按预期爆炸。原因是 @datepart 是一个 string 值。它实际上是在尝试:

SELECT 'String' 来自 'Table' 分组 'String'

实际上根本不需要 table 来回答这个问题。 SQL 困惑地举起手臂。要实现您似乎想要做的事情,您需要做的只是简单地将字符串连接在一起,但是这可能是不可取的:

DECLARE  @datepart NVARCHAR(MAX), @SQLQUERY NVARCHAR(MAX)
SEt @datepart  = 'DATEPART(year, create_date)' 
Set @SQLQuery = 'SELECT ' + @datepart +' AS TIME 
                 FROM sys.objects 
                 GROUP BY '+ @datepart
Execute sp_executesql @SQLQuery 

或者在您的具体情况下:

DECLARE  @datepart NVARCHAR(MAX), @SQLQUERY NVARCHAR(MAX)
SEt @datepart  = 'DATEPART(year, myDate)' 
Set @SQLQuery = 'SELECT ' + @datepart +' AS TIME 
                 FROM someTable 
                 GROUP BY '+ @datepart
Execute sp_executesql @SQLQuery 

如果您提交这样的代码,我建议您输入一份简历。这是不好的做法。

如果您 REPLACE 在 运行 时间使用变量进行查询,那么您可以实现 2015 动态

DECLARE @datepart NVARCHAR(100),
        @SQLQuery NVARCHAR(max)

SET @datepart = 'DATEPART(year, getdate())'
SET @SQLQuery = 'SELECT @datepart AS TIME'

SET @SQLQuery    =   REPLACE(@SQLQuery, '@datepart', @datepart)
exec (@SQLQuery);

@tinka,这很好用。但我刚刚用另一个例子进行了测试,它似乎对对抗 SQLinjection 没有帮助。也许您的解决方案与使用普通的串联查询相同?

DECLARE @SQLQueryInnen nvarchar(max)
DECLARE @ParameterDefinition nvarchar(max)
DECLARE @aufnr nvarchar(200)
SET @aufnr = '1234; SELECT * FROM sensitiveTable'-- here the attacker inserted an SQL Statement in aufnr


Set @SQLQueryInnen = 'SELECT * FROM myTable WHERE aufnr = @aufnr'
Set @SQLQueryInnen = REPLACE(@SQLQueryInnen, '@aufnr', @aufnr)
Set @ParameterDefinition =  '@aufnr nvarchar(200)'
Execute sp_Executesql @SQLQueryInnen, @ParameterDefinition, @aufnr
-- Result: The illegal SELECT statement gets executed!!!

如果我在没有 REPLACE 的情况下执行这个例子,攻击将不会起作用,但你会得到:Conversion failed when converting the nvarchar value '1234; SELECT * FROM qdb_tab_imp_me_q_fehler' to data type int.