【问题标题】:Union and order by联合并订购
【发布时间】:2010-12-25 08:45:24
【问题描述】:

考虑像这样的表

tbl_ranks
--------------------------------
family_id | item_id | view_count 
--------------------------------
1           10        101
1           11        112
1           13        109

2           21        101
2           22        112
2           23        109

3           30        101
3           31        112
3           33        109

4           40        101
4           51        112
4           63        109

5           80        101
5           81        112
5           88        109

我需要生成一个结果集,其中包含按观看次数排序的家庭 ID 子集(例如 1、2、3 和 4)的前两 (2) 行。 我想做类似的事情

select top 2 * from tbl_ranks where family_id = 1 order by view_count
union all
select top 2 * from tbl_ranks where family_id = 2 order by view_count
union all
select top 2 * from tbl_ranks where family_id = 3 order by view_count
union all
select top 2 * from tbl_ranks where family_id = 4 order by view_count

但是,当然,order byunion all 上下文中以这种方式无效。有什么建议?我知道我可以运行一组 4 个查询,将结果存储到临时表中,然后选择该临时表的内容作为最终结果,但如果可能的话,我宁愿避免使用临时表。

注意:在实际应用中,每个家庭 id 的记录数是不确定的,并且 view_counts 也不是固定的,如上例所示。

【问题讨论】:

    标签: sql sql-server tsql


    【解决方案1】:

    你可以试试这样的

    DECLARE @tbl_ranks TABLE(
            family_id INT,
            item_id INT,
            view_count INT
    )
    
    INSERT INTO @tbl_ranks SELECT 1,10,101
    INSERT INTO @tbl_ranks SELECT 1,11,112
    INSERT INTO @tbl_ranks SELECT 1,13,109
    
    INSERT INTO @tbl_ranks SELECT 2,21,101
    INSERT INTO @tbl_ranks SELECT 2,22,112
    INSERT INTO @tbl_ranks SELECT 2,23,109
    
    INSERT INTO @tbl_ranks SELECT 3,30,101
    INSERT INTO @tbl_ranks SELECT 3,31,112
    INSERT INTO @tbl_ranks SELECT 3,33,109
    
    INSERT INTO @tbl_ranks SELECT 4,40,101
    INSERT INTO @tbl_ranks SELECT 4,51,112
    INSERT INTO @tbl_ranks SELECT 4,63,109
    
    INSERT INTO @tbl_ranks SELECT 5,80,101
    INSERT INTO @tbl_ranks SELECT 5,81,112
    INSERT INTO @tbl_ranks SELECT 5,88,109
    
    SELECT  *
    FROm    (
                SELECT  *,
                        ROW_NUMBER() OVER(PARTITION BY family_id ORDER BY view_count DESC) MyOrder
                FROM    @tbl_ranks
            ) MyOrders
    WHERE   MyOrder <= 2
    

    【讨论】:

    • 我将其修改为使用 CTE 而不是嵌套选择,但除此之外它是完美的。谢谢!
    【解决方案2】:

    如果您使用的是 SQL Server 2005 或更高版本,则可以利用分析功能:

    SELECT * FROM (
       SELECT rank() OVER (PARTITION BY family_id ORDER BY view_count) AS RNK, * FROM ...
         )
    WHERE RNK <= 2
    ORDER BY ...
    

    【讨论】:

      【解决方案3】:
      SELECT  tro.*
      FROM    family
      CROSS APPLY
              (
              SELECT  TOP 2 *
              FROM    tbl_ranks tr
              WHERE   tr.family_id = family.id
              ORDER BY
                      view_count DESC
              ) tro
      WHERE   family.id IN (1, 2, 3, 4)
      

      如果您没有实际的 family 表,您可以使用一组联合或递归 CTE 来构造它:

      WITH   family AS
             (
             SELECT  1 AS id
             UNION ALL
             SELECT  2 AS id
             UNION ALL
             SELECT  3 AS id
             UNION ALL
             SELECT  4 AS id
             )
      SELECT  tro.*
      FROM    family
      CROSS APPLY
              (
              SELECT  TOP 2 *
              FROM    tbl_ranks tr
              WHERE   tr.family_id = family.id
              ORDER BY
                      view_count DESC
              ) tro
      WHERE   family.id IN (1, 2, 3, 4)
      

      确保您在tbl_ranks (family_id, viewcount) 上有一个索引。

      如果每个家庭有很多等级,这将是有效的,因为如果与 PARTITION BY 一起使用,像 ROW_NUMBER 这样的分析函数将不会使用 TOP 方法。

      【讨论】:

        【解决方案4】:

        用途:

        SELECT *
          FROM (select *,
                       ROW_NUMBER() OVER (PARTITION BY family_id ORDER BY view_count DESC) 'rank'
                  from tbl_ranks) x
          WHERE x.rank <= 2
        ORDER BY ...
        

        基本原理是分配一个排名,然后根据它进行过滤。

        【讨论】:

        • 应该可能使用子选择来访问排名列。
        • 我不相信排名列在 WHERE 谓词中可用。分析是在 谓词之后评估的,因此您需要一个子选择才能使其工作。
        【解决方案5】:

        您只需稍微调整建议的 SQL 命令即可使其按您想要的方式工作。要绑定 TOP 和 ORDER BY,您可以将语句放在您从中选择的括号内并给出名称(此处未使用但必需)。

        使用来自Adriaan Stander's answer 的 DECLARE 和 INSERT 语句如下

        SELECT * FROM (SELECT TOP 2 * FROM @tbl_ranks WHERE family_id = 1 ORDER BY view_count) AS dummy1 UNION ALL
        SELECT * FROM (SELECT TOP 2 * FROM @tbl_ranks WHERE family_id = 2 ORDER BY view_count) AS dummy2 UNION ALL
        SELECT * FROM (SELECT TOP 2 * FROM @tbl_ranks WHERE family_id = 3 ORDER BY view_count) AS dummy3 UNION ALL
        SELECT * FROM (SELECT TOP 2 * FROM @tbl_ranks WHERE family_id = 4 ORDER BY view_count) AS dummy4
        

        给予

        family_id   item_id view_count
        1   10  101
        1   13  109
        2   21  101
        2   23  109
        3   30  101
        3   33  109
        4   40  101
        4   63  109
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-07-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多