执行多个动态T-SQL语句并获得有限数量的唯一值,同时保留顺序

时间:2013-01-10 05:10:11

标签: sql-server sql-server-2005 dynamic-sql

我有一个SourceTable和一个表变量@TQueries,其中包含针对SourceTable的各种T-SQL谓词。

预期的结果是动态生成SELECT语句,这些语句返回由@TQueries中的谓词指定的Id列表。每个动态生成的SELECT语句也需要按特定顺序执行,最后一组值必须是唯一的,并且必须保留顺序。

幸运的是,需要检索多少个值以及需要生成多少个动态查询是有限制的。 Id列表最多应包含10个ID,我们预计不会超过7个查询。

以下是此设置的示例,而不是实际的数据/数据库:

-- Set up some test data, this is quick and dirty just to provide some data to test against
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[SourceTable]') AND type in (N'U'))
BEGIN
    -- Create a numbers table, sorta
    SELECT TOP 20 
        IDENTITY(INT,1,1) AS Id,
        ABS(CHECKSUM(NewId())) % 100 AS [SomeValue]
    INTO [SourceTable]
    FROM sysobjects a
END


DECLARE @TQueries TABLE (
    [Ordinal] INT,
    [WherePredicate] NVARCHAR(MAX),
    [OrderByPredicate] NVARCHAR(MAX)
);

-- Simulate SELECTs with different order by that get different data due to varying WHERE clauses and ORDER conditions
INSERT INTO @TQueries VALUES ( 1, N'[Id] IN (6,11,13,7,10,3,15)',  '[SomeValue] ASC' ) -- Sort Asc
INSERT INTO @TQueries VALUES ( 2, N'[Id] IN (9,15,14,20,17)', '[SomeValue] DESC' ) -- Sort Desc
INSERT INTO @TQueries VALUES ( 3, N'[Id] IN (20,10,1,16,11,19,9,15,17,6,2,3,13)', 'NEWID()' ) -- Sort Random

我的主要问题是避免使用CURSOR或逐行遍历行。我最接近符合此条件的设置操作是使用表变量来存储每个查询的结果或大量CTE。

欢迎提出建议和意见。

2 个答案:

答案 0 :(得分:1)

这是我从最初的回应中拼凑而成的,并通过@AndriyM的评论进行了改进

DECLARE @sql_prefix NVARCHAR(MAX);
SET @sql_prefix = 
N'DECLARE @TResults TABLE (
    [Ordinal] INT IDENTITY(1,1),
    [ContentItemId] INT
);

DECLARE @max INT, @top INT;
SELECT @max = 10;';


DECLARE @sql_insert_template NVARCHAR(MAX), @sql_body NVARCHAR(MAX);
SET @sql_insert_template = 
N'SELECT @top = @max - COUNT(*) FROM @TResults; 
INSERT INTO @TResults
SELECT TOP (@top) [Id] 
FROM [dbo].[SourceTable] 
WHERE 
    {WherePredicate} 
    AND NOT EXISTS (
        SELECT 1 
        FROM @TResults AS [tr] 
        WHERE [tr].[ContentItemId] = [SourceTable].[Id]
    )
ORDER BY {OrderByPredicate};';

    WITH Query ([Ordinal],[SqlCommand]) AS (
        SELECT 
            [Ordinal],
            REPLACE(REPLACE(@sql_insert_template, '{WherePredicate}', [WherePredicate]), '{OrderByPredicate}', [OrderByPredicate])
        FROM @TQueries
    )
    SELECT 
        @sql_body = @sql_prefix + (
            SELECT [SqlCommand]
            FROM Query
            ORDER BY [Ordinal] ASC
            FOR XML PATH(''),TYPE).value('.', 'varchar(max)') + CHAR(13)+CHAR(10)
            +N' SELECT * FROM @TResults ORDER BY [Ordinal]';

    EXEC(@sql_body);

基本思想是使用表变量来保存每个查询的结果。我为SQL创建了一个模板,并根据@TQueries中存储的内容替换模板中的值。

完成整个脚本后,我用EXEC运行它。

答案 1 :(得分:1)

这是一个构建单个语句的解决方案,既可以运行所有查询,也可以返回结果。

在迭代@TQueries表时,它使用与您的答案类似的方法,即它还使用{...}来自@TQuery的列值的REPLACE()标记,并将值放入那里有嵌套的DECLARE @QueryTemplate nvarchar(max), @FinalSQL nvarchar(max); SET @QueryTemplate = N'SELECT [Id], QueryRank = {Ordinal}, RowRank = ROW_NUMBER() OVER (ORDER BY {OrderByPredicate}) FROM [dbo].[SourceTable] WHERE {WherePredicate} '; SET @FinalSQL = N'WITH AllData AS ( ' + SUBSTRING( ( SELECT 'UNION ALL ' + REPLACE(REPLACE(REPLACE(@QueryTemplate, '{Ordinal}' , [Ordinal] ), '{OrderByPredicate}', [OrderByPredicate]), '{WherePredicate}' , [WherePredicate] ) FROM @TQueries ORDER BY [Ordinal] FOR XML PATH (''), TYPE ).value('.', 'nvarchar(max)'), 11, -- starting just after the first 'UNION ALL ' CAST(0x7FFFFFFF AS int) -- max int; no need to specify the exact length ) + '), RankedData AS ( SELECT [Id], QueryRank, RowRank, ValueRank = ROW_NUMBER() OVER (PARTITION BY [Id] ORDER BY QueryRank) FROM AllData )SELECT TOP (@top) [Id] FROM RankedData WHERE ValueRank = 1 ORDER BY QueryRank, RowRank '; PRINT @FinalSQL; EXECUTE sp_executesql @FinalSQL, N'@top int', 10; 电话。

除此之外,它在很大程度上取决于ranking functions,我不确定是否真的滥用它们。你需要先测试一下这个方法,然后再决定它是否比你到目前为止更好或更差。

QueryRank

基本上,每个子查询都会获得这些辅助列:

  • [Ordinal] - 从RowRank派生的常量值(在子查询的结果集中);

  • [OrderByPredicate] - 根据ValueRank分配给某一行的排名。

结果集是UNIONed,然后根据查询排名再次对每个唯一值的每个条目进行排名(ValueRank = 1)。

在提取最终结果集时,会删除重复项(条件为QueryRank),并在RowRank子句中使用ORDER BYEXECUTE sp_executesql @query来保留原始文档行顺序。

我使用EXECUTE (@query)代替TOP,因为前者允许您向查询添加参数。特别是,我参数化了要返回的结果数(EXECUTE ()的参数)。但是你可以将这个值直接连接到动态脚本中,就像其他事情一样,如果你更喜欢EXECUTE sq_executesql而不是@TQueries

如果您愿意,可以尝试此查询at SQL Fiddle。 (注意:SQL Fiddle版本用TQueries表替换{{1}}表变量。)