Tsql 从查询生成的EXEC动态SQL

Tsql 从查询生成的EXEC动态SQL,tsql,sql-server-2012,Tsql,Sql Server 2012,我做了一些调查,发现有点不符合我的需要 我受到编码标准的限制,这些标准阻止我使用NVARCHAR(MAX)或VARCHAR(MAX),这是我的痛点 我需要创建一个最有可能超过8000个字符的动态SQL语句。之所以会发生这种情况,是因为有一个巨大的案例,当要求使用语句时。我的方法是将每个案例作为记录插入表中。然后在动态SQL语句中使用XML路径(“”)创建另一个动态SQL语句 下面是我正在使用的代码示例: CREATE TABLE #TempTest ( ID INT ,Data VARCHAR(

我做了一些调查,发现有点不符合我的需要

我受到编码标准的限制,这些标准阻止我使用NVARCHAR(MAX)或VARCHAR(MAX),这是我的痛点

我需要创建一个最有可能超过8000个字符的动态SQL语句。之所以会发生这种情况,是因为有一个巨大的案例,当要求使用语句时。我的方法是将每个案例作为记录插入表中。然后在动态SQL语句中使用XML路径(“”)创建另一个动态SQL语句

下面是我正在使用的代码示例:

CREATE TABLE #TempTest (
ID INT
,Data VARCHAR(50)
,Data2 VARCHAR(50)
,Flag INT
)

CREATE TABLE #TempCase (
ID INT
,CaseS VARCHAR(50)
)

INSERT INTO #TempCase (
ID
,CaseS
)
VALUES
(1,'CASE WHEN Flag = 1 THEN Data ELSE Data2 END')
,(2,'CASE WHEN Flag = 2 THEN ID ELSE 0 END')

INSERT INTO #TempTest (
ID
,Data
,Data2
,Flag
)
VALUES
(1,'Hobo','Jim',1)
,(2,'Hobo Again','Jane',2)

EXEC('SELECT TOP 1 ''SELECT '' + STUFF((SELECT N'', '' + CaseS
    FROM #TempCase TC
    ORDER BY TC.ID
    FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''nvarchar(max)''), 1, 2, N'''') + '' FROM #TempCase''
FROM #TempCase TC
GROUP BY TC.ID') 
本EXEC声明的结果为-

SELECT CASE WHEN Flag = 1 THEN Data ELSE Data2 END, CASE WHEN Flag = 2 THEN ID ELSE 0 END FROM #TempCase
查询的结果是我必须运行的动态SQL。我试图将当前EXEC语句嵌套到另一个EXEC语句中,但这只创建了两个具有相同结果的数据集。大多数人处理这一问题的方法是将初始EXEC结果设置为VARCHAR(MAX),然后将其分配给第二个EXEC语句,但由于公司的编码标准,我无法这样做


任何建议都将不胜感激。

假设您确实有权创建临时表,并且您已经有了将各种案例语句放入表中的逻辑(以及一些其他假设,这些假设可能有效,也可能无效,但仍然是隐含的,但缺乏明确的描述),那么以下方法如何:

CREATE TABLE #TempTest
  (
       ID    INT,
       Data  VARCHAR(50),
       Data2 VARCHAR(50),
       Flag  INT
  )

CREATE TABLE #TempCase
  (
       ID    INT,
       CaseS VARCHAR(50),
       CaseSDataType VARCHAR(20),
       HasProcessed BIT DEFAULT(0)
  )

INSERT INTO #TempCase
            (ID,
             CaseS,
             CaseSDataType)
     VALUES (1,
             'CASE WHEN Flag = 1 THEN Data ELSE Data2 END',
             'VARCHAR(50)'),
            (2,
             'CASE WHEN Flag = 2 THEN ID ELSE 0 END',
             'VARCHAR(50)')

INSERT INTO #TempTest
            (ID,
             Data,
             Data2,
             Flag)
     VALUES (1,
             'Hobo',
             'Jim',
             1),
            (2,
             'Hobo Again',
             'Jane',
             2) 

CREATE TABLE #Computed ( ID INT )
INSERT INTO #Computed ( ID ) SELECT ID FROM #TempTest

DECLARE @caseID INT
DECLARE @caseSQL VARCHAR(100)
DECLARE @colSQL VARCHAR(100)
DECLARE @updateSQL VARCHAR(255)
WHILE EXISTS( SELECT * FROM #TempCase WHERE HasProcessed = 0 )
BEGIN
    SELECT TOP 1 
        @caseID = ID, 
        @caseSQL = REPLACE(REPLACE('ALTER TABLE #TempTest ADD CaseStmt{id} AS ({stmt})', '{id}', CAST(ID AS VARCHAR)), '{stmt}', CaseS),
        @colSQL = REPLACE(REPLACE('ALTER TABLE #Computed ADD CaseStmt{id} {datatype}', '{id}', CAST(ID AS VARCHAR)), '{datatype}', CaseSDataType),
        @updateSQL = REPLACE('UPDATE t2 SET t2.CaseStmt{id} = t1.CaseStmt{id} FROM #TempTest t1 INNER JOIN #Computed t2 ON t1.ID = t2.ID', '{id}', ID)
    FROM #TempCase 
    WHERE HasProcessed = 0

    -- Add the columns
    EXEC(@caseSQL)
    EXEC(@colSQL)

    -- Update the column
    EXEC(@updateSQL)

    -- Next
    UPDATE #TempCase
       SET HasProcessed = 1
    WHERE ID = @caseID
END

-- Return your results (all of #Computed)
SELECT * FROM #Computed
实际上,#Computed的结构变成了结果,就好像各种CASE语句都链接在一个SELECT语句中一样


我们可以通过直接将CASE语句插入UPDATE sql来简化计算列的创建,但这可能需要,对CASE语句进行一些操作,以确保表前缀正确。

假设您确实有权创建临时表,并且您已经有了将各种CASE语句放入表中的逻辑(以及一些其他假设,这些假设可能有效,也可能无效,但仍然隐含,但此处缺乏明确的描述),以下方法如何:

CREATE TABLE #TempTest
  (
       ID    INT,
       Data  VARCHAR(50),
       Data2 VARCHAR(50),
       Flag  INT
  )

CREATE TABLE #TempCase
  (
       ID    INT,
       CaseS VARCHAR(50),
       CaseSDataType VARCHAR(20),
       HasProcessed BIT DEFAULT(0)
  )

INSERT INTO #TempCase
            (ID,
             CaseS,
             CaseSDataType)
     VALUES (1,
             'CASE WHEN Flag = 1 THEN Data ELSE Data2 END',
             'VARCHAR(50)'),
            (2,
             'CASE WHEN Flag = 2 THEN ID ELSE 0 END',
             'VARCHAR(50)')

INSERT INTO #TempTest
            (ID,
             Data,
             Data2,
             Flag)
     VALUES (1,
             'Hobo',
             'Jim',
             1),
            (2,
             'Hobo Again',
             'Jane',
             2) 

CREATE TABLE #Computed ( ID INT )
INSERT INTO #Computed ( ID ) SELECT ID FROM #TempTest

DECLARE @caseID INT
DECLARE @caseSQL VARCHAR(100)
DECLARE @colSQL VARCHAR(100)
DECLARE @updateSQL VARCHAR(255)
WHILE EXISTS( SELECT * FROM #TempCase WHERE HasProcessed = 0 )
BEGIN
    SELECT TOP 1 
        @caseID = ID, 
        @caseSQL = REPLACE(REPLACE('ALTER TABLE #TempTest ADD CaseStmt{id} AS ({stmt})', '{id}', CAST(ID AS VARCHAR)), '{stmt}', CaseS),
        @colSQL = REPLACE(REPLACE('ALTER TABLE #Computed ADD CaseStmt{id} {datatype}', '{id}', CAST(ID AS VARCHAR)), '{datatype}', CaseSDataType),
        @updateSQL = REPLACE('UPDATE t2 SET t2.CaseStmt{id} = t1.CaseStmt{id} FROM #TempTest t1 INNER JOIN #Computed t2 ON t1.ID = t2.ID', '{id}', ID)
    FROM #TempCase 
    WHERE HasProcessed = 0

    -- Add the columns
    EXEC(@caseSQL)
    EXEC(@colSQL)

    -- Update the column
    EXEC(@updateSQL)

    -- Next
    UPDATE #TempCase
       SET HasProcessed = 1
    WHERE ID = @caseID
END

-- Return your results (all of #Computed)
SELECT * FROM #Computed
实际上,#Computed的结构变成了结果,就好像各种CASE语句都链接在一个SELECT语句中一样


我们可以通过直接将CASE语句插入UPDATE sql来简化计算列的创建,但这可能需要对CASE语句进行一些操作,以确保表前缀是正确的。

以这种方式构建动态sql,您将暴露于sql注入中

无论如何,您可以将第一次执行的结果插入临时表并执行:

CREATE TABLE #final(result VARCHAR(8000));

INSERT INTO #final(result)
EXEC(
'SELECT TOP 1 ''SELECT '' + STUFF((SELECT N'', '' + CaseS
    FROM #TempCase TC
    ORDER BY TC.ID
    FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''nvarchar(max)''), 1, 2, N'''') 
        + '' FROM #TempTest''   
FROM #TempCase TC
GROUP BY TC.ID');

DECLARE @sql VARCHAR(8000);

SELECT @sql = result
FROM #final;

EXEC(@sql);

输出:

╔══════╦══════╗
║ col1 ║ col2 ║
╠══════╬══════╣
║ Hobo ║    0 ║
║ Jane ║    2 ║
╚══════╩══════╝

以SQL注入的方式构建动态SQL

无论如何,您可以将第一次执行的结果插入临时表并执行:

CREATE TABLE #final(result VARCHAR(8000));

INSERT INTO #final(result)
EXEC(
'SELECT TOP 1 ''SELECT '' + STUFF((SELECT N'', '' + CaseS
    FROM #TempCase TC
    ORDER BY TC.ID
    FOR XML PATH(N''''), TYPE).value(N''.[1]'', N''nvarchar(max)''), 1, 2, N'''') 
        + '' FROM #TempTest''   
FROM #TempCase TC
GROUP BY TC.ID');

DECLARE @sql VARCHAR(8000);

SELECT @sql = result
FROM #final;

EXEC(@sql);

输出:

╔══════╦══════╗
║ col1 ║ col2 ║
╠══════╬══════╣
║ Hobo ║    0 ║
║ Jane ║    2 ║
╚══════╩══════╝

为什么不使用
EXEC sp_executesql@sql
,在这种情况下
@sql
可以是
NVARCHAR(MAX)
?存在限制是有原因的-使用动态sql是不好的,因为它会使您暴露于sql注入中,如果您的单个字符出错,则很难维护和调试。超过8000个字符的动态SQL语句非常糟糕,因为维护它几乎是不可能的。性能可能也会非常糟糕,因为优化器很难为如此复杂的查询制定计划。为什么不使用CASE语句创建一个或多个视图,并且只选择所需的字段?或者将大查询分解为多个查询,并与UNION ALL结合使用?因为我不能使用NVARCHAR(MAX)——@sql仍必须定义为NVARCHAR(MAX)。我理解sql注入的问题,但该过程没有用户前端,因此不是真正的问题。我已经只选择了我需要的字段,但是基于记录中返回的内容决定了输出,因此需要CASE-WHEN语句。信息来自不同的服务器,因此我只想查询一次数据。但是在通过FOR XML连接之后,在转换从XML输出提取的值时,您使用nvarchar(max)作为sql类型,因此即使您可以执行exec(exec)如果不使用
EXEC sp_executesql@sql
,它仍然会违反编码标准,在这种情况下
@sql
可能是
NVARCHAR(MAX)
?限制存在是有原因的-使用动态sql是不好的,因为它会让您暴露在sql注入中,如果您得到一个字符错误,则很难维护和调试。超过8000个字符的动态SQL语句非常糟糕,因为维护它几乎是不可能的。性能可能也会非常糟糕,因为优化器很难为如此复杂的查询制定计划。为什么不使用CASE语句创建一个或多个视图,并且只选择所需的字段?或者将大查询分解为多个查询,并与UNION ALL结合使用?因为我不能使用NVARCHAR(MAX)——@sql仍必须定义为NVARCHAR(MAX)。我理解sql注入的问题,但该过程没有用户前端,因此不是真正的问题。我已经只选择了我需要的字段,但是基于记录中返回的内容决定了输出,因此需要CASE-WHEN语句。信息来自不同的服务器,因此我只想查询一次数据。但是在通过FOR XML连接之后,在转换从XML输出提取的值时,您使用nvarchar(max)作为sql类型,因此即使您可以执行exec(exec)这仍然是违反编码标准的。我考虑过这一点,但是,随着语句数量的增加,我的数据来自另一台服务器这一事实会增加运行时间。这就是为什么我试图同时使用所有语句进行查询