1

我有一个SourceTable和一个表变量@TQueries,其中包含以 SourceTable.

预期结果是动态生成SELECT语句,这些语句返回由 in 中的谓词指定的 Id 列表@TQueries。每个动态生成的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 的结果。

欢迎提出建议和意见。

4

2 回答 2

2

这是一个构建单个语句来运行所有查询并返回结果的解决方案。

它在迭代表时使用与您的答案类似的方法@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;

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

  • QueryRank– 一个常量值(在子查询的结果集中),源自[Ordinal]

  • RowRank– 分配给基于 的行的排名[OrderByPredicate]

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

拉取最终结果集时,重复项被抑制(通过条件ValueRank = 1),并QueryRankRowRank子句中使用andORDER BY以保留原始行顺序。

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

如果您愿意,可以在 SQL Fiddle尝试此查询。(注意:SQL Fiddle 版本将@TQueriestable 变量替换为TQueriestable。)

于 2013-01-12T18:58:49.440 回答
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 运行它。

于 2013-01-10T18:30:34.697 回答