【问题标题】:Returning top group of rows by aggregate rank按聚合排名返回顶部行组
【发布时间】:2014-10-05 15:16:46
【问题描述】:

我想根据其中一列的聚合排名来选择一组顶部的行。例如,假设我有以下查询:

SELECT CustomerID, ItemID, Price * Qty as Revenue
FROM InvoiceTable
WHERE InvoiceDate between @StartDate, @EndDate

这里很简单,一个返回一组日期之间每张发票的每个行项目的查询。现在,我想做的是限制这一点,以便仅返回基于收入总和的属于前 10 名客户的项目。有没有直接的方法可以做到这一点?

我总是可以将上述查询的结果转储到临时表中,运行第二遍以计算每个客户的总和并将其存储在临时表的另一列中,然后使用 RANK OVER 子句进行选择,但是这似乎有点令人费解,我希望有更直接的方法来实现我的目标。

为了澄清,上面的查询非常简单,在实际生成列表之前预先计算前 10 个客户端的列表是不切实际的(实际查询由许多联合和其他条件),这就是为什么我希望在查询本身中限制它,或者作为后计算步骤。

【问题讨论】:

    标签: sql sql-server tsql


    【解决方案1】:

    您的问题(很遗憾)确实依赖于 db-engine。既然你标记了 Transact-SQL,那我就先在那里回答:

    T-SQL 具有 SELECT TOP N 功能,允许获取前 N 行,如果它与 ORDER BY 子句耦合,它将首先排序并给你TOP N。如果您给它们起别名,Transact 还允许按聚合字段排序。 您的问题涉及两件事:物品和“顶级买家”;所以我们应该首先接触买家,因为他们是过滤器:

    SELECT TOP 10 CustomerID, SUM(Price * Qty) as SumRevenue
    FROM InvoiceTable
    WHERE InvoiceDate between @StartDate, @EndDate
    GROUP BY CustomerID
    ORDER BY SumRevenue DESC
    

    问题是,您想要此客户的项目列表。我会冒昧地猜测您想要这里每个客户每件商品的总收入,所以您现在需要使用它作为子查询来获取您的商品。我不会参与in vs inner join 的辩论,所以我只是举个例子:

    SELECT r.CustomerID, r.ItemID, SUM(r.Price * r.Qty) ItemRevenue
    FROM InvoiceTable
         INNER JOIN (SELECT TOP 10 i.CustomerID, SUM(i.Price * i.Qty) as SumRevenue
             FROM InvoiceTable i
             WHERE i.InvoiceDate between @StartDate, @EndDate
             GROUP BY i.CustomerID
             ORDER BY SumRevenue DESC) s
         ON r.CustomerID = s.CustomerID
    WHERE r.InvoiceDate between @StartDate, @EndDate
    GROUP BY r.CustomerID, r.ItemID
    

    如果您使用 MYSQL,则 TOP N 习语将替换为 LIMIT N 习语,因此会导致:

    SELECT r.CustomerID, r.ItemID, SUM(r.Price * r.Qty) ItemRevenue
    FROM InvoiceTable
         INNER JOIN (SELECT i.CustomerID, SUM(i.Price * i.Qty) as SumRevenue
             FROM InvoiceTable i
             WHERE i.InvoiceDate between @StartDate, @EndDate
             GROUP BY i.CustomerID
             ORDER BY SumRevenue DESC
             LIMIT 10) s
         ON r.CustomerID = s.CustomerID
    WHERE r.InvoiceDate between @StartDate, @EndDate
    GROUP BY r.CustomerID, r.ItemID
    

    如果您使用 Oracle,查询会有点困难,因为您没有 TOP N 惯用语,而且您必须使用 ROWNUM 和子查询,所以我推荐一种类似的方法这个:

    SELECT m.CustomerID, m.ItemID, m.ItemRevenue
        (SELECT ROWNUM, r.CustomerID, r.ItemID, SUM(r.Price * r.Qty) ItemRevenue
        FROM InvoiceTable
             INNER JOIN (SELECT i.CustomerID, SUM(i.Price * i.Qty) as SumRevenue
                 FROM InvoiceTable i
                 WHERE i.InvoiceDate between @StartDate, @EndDate
                 GROUP BY i.CustomerID) s
             ON r.CustomerID = s.CustomerID
        WHERE r.InvoiceDate between @StartDate, @EndDate
        GROUP BY r.CustomerID, r.ItemID
        ORDER BY s.SumRevenue DESC) m
    WHERE ROWNUM < 11
    

    【讨论】:

      【解决方案2】:

      基于您有一个复杂的查询这一事实,cte 是否足够简单?:

      WITH    cte
                AS ( SELECT TOP 10
                              CustomerID ,
                              SUM(Price * Qty) AS TotalRevenue
                     FROM     InvoiceTable
                     WHERE    InvoiceDate BETWEEN @startdate AND @EndDate
                     GROUP BY CustomerID
                     ORDER BY Price * Qty DESC
                   )
          SELECT  it.CustomerID ,
                  it.ItemID ,
                  it.Price * it.Qty AS Revenue
          FROM    InvoiceTable it
                  INNER JOIN cte ON it.CustomerID = cte.CustomerID
          WHERE   it.InvoiceDate BETWEEN @StartDate AND @EndDate
      

      【讨论】:

        【解决方案3】:

        您可以使用窗口函数来做到这一点:

        select it.*
        from (select it.*, dense_rank() over (order by TotalRevenue) as seqnum
              from (select it.*, sum(Price*Qty) over (order by customerid) as TotalRevenue
                    from InvoiceTable it
                    where InvoiceDate between @StartDate, @EndDate
                   ) it
             ) it
        where seqnum <= 10;
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-10-12
          • 2014-11-17
          • 2022-01-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-09-21
          相关资源
          最近更新 更多