Tsql 从查询生成的EXEC动态SQL
我做了一些调查,发现有点不符合我的需要 我受到编码标准的限制,这些标准阻止我使用NVARCHAR(MAX)或VARCHAR(MAX),这是我的痛点 我需要创建一个最有可能超过8000个字符的动态SQL语句。之所以会发生这种情况,是因为有一个巨大的案例,当要求使用语句时。我的方法是将每个案例作为记录插入表中。然后在动态SQL语句中使用XML路径(“”)创建另一个动态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(
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)这仍然是违反编码标准的。我考虑过这一点,但是,随着语句数量的增加,我的数据来自另一台服务器这一事实会增加运行时间。这就是为什么我试图同时使用所有语句进行查询