【问题标题】:How to Select top n Records for Each Category如何为每个类别选择前 n 条记录
【发布时间】:2019-01-17 02:51:29
【问题描述】:

我正在尝试编写一个连接 2 个表的查询,并将给出前 5 个名称以及他们在某个日期范围(即 2016 年 1 月 1 日至 2017 年 12 月 31 日)之间的某个位置销售的商品数量.

根据我的研究,这是我想出的:

SELECT
    EmployeeName, 
    COUNT(ID) AS 'Number of Deals', 
    CompanyNumber
FROM
(
    SELECT
        EmployeeName, 
        DealID, 
        CompanyNumber,
        ROW_NUMBER() OVER (PARTITION BY CompanyNumber ORDER BY DealID) AS rn
    FROM Deal
    JOIN DealEmployee
        ON Deal.DealID. =DealEmployee.DealID AS T
    WHERE
        Deal.Status = 2 AND
        Date BETWEEN '2016-01-01' AND '2017-12-31' AND
        EmployeeName != '' AND T.rn <=5 

我希望得到以下结果:

我是个新手,我知道我的语法不正确。给予任何帮助将不胜感激

【问题讨论】:

标签: sql inner-join ssms row-number derived-table


【解决方案1】:

我想你想要这个:

SELECT EmployeeName, num_deals, CompanyNumber
FROM (SELECT EmployeeName, CompanyNumber, COUNT(*) as num_deals,
             ROW_NUMBER() OVER (PARTITION BY CompanyNumber ORDER BY COUNT(*) DESC) AS seqnum
      FROM Deal d JOIN
           DealEmployee de
           ON Deal.DealID. = de.DealID 
      WHERE d.Status = 2 AND
            Date BETWEEN '2016-01-01' AND '2017-12-31' AND
            EmployeeName <> ''
      GROUP BY employee, CompanyNumber
     ) t
WHERE seqnum <= 5;

注意事项:

  • 您需要在执行row_number() 之前进行聚合,因为您想根据计数进行枚举。 (此版本将聚合和 row_number() 组合到一个子查询中;聚合先进行。)
  • 交易数量按员工和公司汇总。
  • 如果您有多个表,请限定所有列名。
  • 您的查询存在许多其他错误,例如表别名放错位置和缺少某些行。

【讨论】:

    【解决方案2】:

    在计算/实现后,您需要过滤子查询之外的行号。我想这就是你想要的:

    SELECT EmployeeName, CompanyNumber, cnt
    FROM
    (
        SELECT EmployeeName, CompanyNumber, COUNT(*) AS cnt,
            ROW_NUMBER() OVER (PARTITION BY CompanyNumber ORDER BY COUNT(*) DESC) rn
        FROM Deal d
        INNER JOIN DealEmployee de
            ON d.DealID = de.DealID
        WHERE d.Status = 2 AND Date BETWEEN '2016-01-01' AND '2017-12-31' AND
              EmployeeName != ''
        GROUP BY EmployeeName, CompanyNumber
    ) t
    WHERE rn <= 5
    ORDER BY
        CompanyNumber,
        cnt DESC;
    

    请注意,我们在子查询中执行GROUP BY 聚合,我们还计算行号。

    【讨论】:

    • 请注意,这是行不通的;你不能ORDER BY cnt,因为你只会得到Invalid column name 'cnt'的错误。您实际上必须分两步执行此操作,如 Martin 的回答所示。
    • @RichardHansell 嗯...我不知道你在说什么。在外部查询中,SQL Server 应将cnt 视为任何其他列 AFAIK。您可能是正确的,如果我们尝试在子查询中使用 ORDER BY cnt,如果 SQL Server 不允许在 ORDER BY 子句中使用别名,它可能会失败。
    • 确实是这样,在外部查询中很好,在内部查询中你不能使用你刚刚在窗口函数ROW_NUMBER()中定义的别名。现在行为可能因供应商而异,所以尽管这会在 SQL Server 中引发错误,但在 Oracle 中可能还可以吗?只是要注意这确实会在 SQL Server 中引发错误,因为我用一些临时表进行了快速测试,以确保我不是在说垃圾。
    • 老鼠...你是对的...你应该提到问题出在ROW_NUMBER。我没有/不能真正测试这个查询,因为它很复杂,OP 没有给出示例数据或演示,而且已经很晚了,我不能被打扰。不,你肯定不是在说垃圾。
    • 嗯,这是一个非常诚实的答案;P 你的编辑工作正常,老实说我今天学到了一些东西,因为我不知道你可以在 ROWNUMBER() 中使用 COUNT(*) 和老实说,正如我最初所说,这必须分两个阶段完成。所以一切都很好:D
    【解决方案3】:

    未测试,但我会尝试类似:

    with
    basedata as (    
    select EmployeeName
         , CompanyNumber
         , COUNT(ID) as Number_of_Deals
      from Deal
      join DealEmployee
        on Deal.DealID = DealEmployee.DealID    
     where Deal.Status = 2
       and Date between '2016-01-01' and '2017-12-31'
       and EmployeeName !=''
     group by EmployeeName
            , CompanyNumber 
    )
    ,
    basedata_with_rank as (
    select t.*
         , row_number() over (partition by CompanyNumber order by Number_of_Deals desc) rn
      from basedata
    )
    select *
      from basedata_with_rank
     where rn <= 5
     order by CompanyNumber
            , Number_of_Deals desc
    

    使用 CTE 可使查询通常更具可读性。顺便说一句:我会避免给列命名为“日期”(保留字),并且我总是为我的列使用限定名称。也许使用分析函数 rank 或dense rank 会更合适,但 row_number 也应该可以。

    【讨论】:

    • 谢谢!我能够得到我想要的结果,但是我在理解查询的第二部分时遇到了一些困难。但它就像一个魅力。
    • 第一个基于 CTE 的数据包括连接和您的条件并聚合结果。第二个 CTE basedata_with_rank 基于来自 basedata 的聚合结果添加排名。主查询根据第二个 CTE 的排名过滤前 5 个元素。
    猜你喜欢
    • 2010-09-15
    • 1970-01-01
    • 1970-01-01
    • 2012-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-11
    • 1970-01-01
    相关资源
    最近更新 更多