SQL Server:如何有效地返回两个略有不同的查询的组合结果集

时间:2015-03-14 23:58:06

标签: sql-server union common-table-expression

我已经玩了一段时间了,虽然有一些"蛮力"在技​​术上工作的方法,我觉得我错过了一些更优雅(和更有效)的东西。

我有一个包含事件历史记录的表。对于每个活动,都有一个"来源"和目的地" (这就是我过滤的内容)。我需要返回按日期(最近的第一个)排序的前n行,首先是来自特定源的给定目标,然后是针对除第一个查询中使用的源之外的任何源的相同目标。例如,如果第一个查询(匹配目标和源)返回n行,我不需要第二个查询中的任何行(匹配相同的目标并匹配任何其他源),但是如果第一个查询返回少于n行,我需要用第二个查询填充其余的(假设有这样的行)。

使用UNION ALL不起作用,因为单个查询不能单独排序,因此我得到的结果集包含" any"的前n个。给定目标的源(如果我可以在每个单独的查询中包含ORDER BY,这可能是一个合理的解决方案):

SELECT TOP (100) * FROM
(
SELECT TOP (100) Destin, Source, OtherData, Timestamp
FROM History
WHERE Destin = @Destin
AND Source = @source

UNION ALL

SELECT TOP (100) Destin, Source, OtherData, Timestamp
FROM History
WHERE Destin = @Destin
AND Source <> @source

ORDER BY Timestamp DESC
) AS SubQuery
ORDER BY TimeStamp DESC

我也尝试过CTE,但在这方面没有找到更好的东西。关于替代方法的任何想法?

PS - 子查询中的TOP(100)是尝试提高性能,但我无法确定SQL Server是否会在满足TOP后自动停止运行第一个(或第二个)子查询( 100)在外部查询(这将是理想的)。

1 个答案:

答案 0 :(得分:2)

以下示例使用CASE表达式提供所需的序列。 destin上的聚簇(或覆盖)索引将有助于优化此查询。这将执行单个搜索。

SELECT TOP(100) Destin
      , Source
      , OtherData
      , Timestamp
      , CASE WHEN Source = @source THEN 1
             ELSE 2
        END AS seq
FROM    History
WHERE   Destin = @Destin
ORDER BY seq
      , Timestamp DESC;

修改

下面是另一种技术,如果典型情况对于同一目的地而言明显多于100行,则可能表现得更好。像Aaron建议的ORDER BY列上的聚簇/覆盖索引将有助于提高性能。它不像CASE表达方法那样优雅,需要2个搜索运算符,但如果性能比可维护性更重要,则可以选择。

WITH    same_source
          AS ( SELECT   Destin
                      , Source
                      , OtherData
                      , Timestamp
                      , 1 AS seq
                      , ROW_NUMBER() OVER ( ORDER BY Destin, Source, Timestamp ) AS row_num
               FROM     dbo.History
               WHERE    Destin = @Destin
                        AND Source = @Source
             ) ,
        different_source
          AS ( SELECT   Destin
                      , Source
                      , OtherData
                      , Timestamp
                      , 2 AS seq
                      , ROW_NUMBER() OVER ( ORDER BY Destin, Source, Timestamp ) AS row_num
               FROM     dbo.History
               WHERE    Destin = @Destin
                        AND Source <> @Source
             )
    SELECT TOP ( 100 )
            Destin
          , Source
          , OtherData
          , Timestamp
    FROM    ( SELECT    Destin
                      , Source
                      , OtherData
                      , Timestamp
                      , seq
              FROM      same_source
              WHERE     row_num <= 100
              UNION ALL
              SELECT    Destin
                      , Source
                      , OtherData
                      , Timestamp
                      , seq
              FROM      different_source
              WHERE     row_num <= 100
            ) AS test
    ORDER BY seq
          , Destin
          , Source
          , Timestamp;