对于以下查询,我需要替代游标/while 循环:
我们有一个表格,如下面的屏幕截图所示,包含以下列:ID
, Module
, Query
。该表包含近 100,000 条记录。目前,我们正在使用游标和 while 循环将选择查询的输出插入到临时表中,我们将根据ID
.
我需要帮助编写一个查询,该查询将Query
列作为输入并像函数一样将结果作为值提供。
我试图创建其执行动态SQL功能,但它失败,因为EXEC
并sp_executesql
没有一个函数中允许的。尝试使用存储过程也不起作用,因为我们只能将一个查询作为参数从该表中传递以获取作为值的输出。
每个查询返回一个值(一列,一行)。不同的查询可能返回不同数据类型的值;所有都应转换为nvarchar
.
任何解决方案将不胜感激。
我已经测试了以下内容(在 SQL Server 2014 中,但它应该至少可以追溯到 2008 年),我相信它可以满足您的需求:
CREATE TABLE QueryList (Id int, Name nvarchar(128), Query nvarchar(4000));
INSERT INTO QueryList
VALUES (1, 'Date', 'SELECT GETDATE()')
,(2, 'String', 'SELECT ''What''''s this? Just a varchar string...'' as StrVal')
,(3, 'Server', 'SELECT @@SERVERNAME')
;
CREATE TABLE results (Id int, Name nvarchar(128), Result nvarchar(4000));
-----
DECLARE @stmt nvarchar(max);
SELECT @stmt = stuff( (SELECT N'UNION ALL SELECT TOP (1) ' + CAST(Id as nvarchar(10))
+N' as Id, ''' + Name + N''', CAST(qr.[Result] as nvarchar(4000)) as [Result] '
+N'FROM (' + Query + N') AS qr ([Result])
'
FROM QueryList
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)')
,1,9,'INSERT INTO results') + ';'
;
SELECT @stmt; -- FOR TESTING - displays the resulting query
EXECUTE sp_executesql @stmt;
SELECT * FROM results;
Run Code Online (Sandbox Code Playgroud)
中央查询从 中获取所有记录#QueryList
,并构建一个INSERT
语句,将每行的 id、名称和查询结果放入#results
临时表中。每行的值都在它们自己的SELECT
语句中,所有语句都用 组合成一个结果集UNION ALL
。我已经获取了包含数千行的文本文件,将每一行转换为类似的SELECT ... UNION ALL
. 我不希望合并在一起的语句数量成为问题(但我不能保证在某些时候没有限制)。
如果你还没有看到它之前,它使用FOR XML PATH
以连接UNION ALL SELECT ...
从每一行的值成一个字符串,将STUFF
替换第一个UNION ALL
符合实际INSERT INTO #results
。有关跨多行聚合字符串数据的更多信息,请参阅此文章(或移至 SQL 2017 并使用STRING_AGG()
)。
您以前可能没有见过的其他东西是FROM (<subquery>) AS qr ([Result])
. 正如AS qr
品牌qr
和别名子查询,([Result])
提供别名列在子查询列表(在我们的例子中,列)。
这是根据上述数据构建的查询:
INSERT INTO #results SELECT TOP (1) 1 as Id, 'Date', CAST(qr.[Result] as nvarchar(4000)) as [Result] FROM (SELECT GETDATE()) AS qr ([Result])
UNION ALL SELECT TOP (1) 2 as Id, 'String', CAST(qr.[Result] as nvarchar(4000)) as [Result] FROM (SELECT 'This is just a varchar string' as StrVal) AS qr ([Result])
UNION ALL SELECT TOP (1) 3 as Id, 'Server', CAST(qr.[Result] as nvarchar(4000)) as [Result] FROM (SELECT @@SERVERNAME) AS qr ([Result])
;
Run Code Online (Sandbox Code Playgroud)
并且,#results
查询执行后的内容:
Id Name Result
---- -------- --------------------------------------
1 Date Jul 14 2017 12:19PM
2 String What's this? Just a varchar string...
3 Server MYSERVERNAME\INSTANCE
Run Code Online (Sandbox Code Playgroud)
笔记:
#QueryList
使用 CTE的查询),这将失败。;
在 # 中的查询末尾包含一个结束QueryList
符,它将失败(特殊情况下不会被简单地用作子查询;#QueryList
由于习惯,我几乎将其包含在我的查询中)。如果您确实遇到了通过 组合的语句数量的问题UNION ALL
,您可以将其分解为更小的块。假设Id
和Name
放在一起,在 # 中是唯一的QueryList
,那么:
SELECT @stmt = stuff( (SELECT TOP (10000) N'UNION ALL SELECT TOP (1) ' + CAST(ql.Id as nvarchar(10))
+N' as Id, ''' +ql. Name + N''', CAST(qr.[Result] as nvarchar(4000)) as [Result] '
+N'FROM (' + ql.Query + N') AS qr ([Result])
'
FROM #QueryList ql
LEFT JOIN #results r ON (ql.Id = r.Id AND ql.Name = r.Name)
WHERE r.Id IS NULL
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)')
,1,9,'INSERT INTO results') + ';'
;
Run Code Online (Sandbox Code Playgroud)
将让你抢1000#QueryList
行的时间,并且只抢的人在Id
和Name
不已经存在#results
。你必须把它放在一个循环中,并继续下去,直到#QueryList
. @stmt
是NULL
如果没有更多行要处理,那么您可以使用它来跳出循环。
同样,我不希望这成为一个问题。而且,即使是这样,当它带您回到WHILE
循环时,您一次处理 10,000(或更多)行,而不是一个。它应该比您当前的游标实现更快。
并且 - 如果您发现有一些查询会导致问题,这可能对测试很有用;遍历数据子集,直到找到一组不起作用的数据,然后检查这些查询以查看其中是否存在问题。