选择范围内每个月的前5个SUM(每个客户一个)

时间:2012-09-14 02:18:16

标签: sql-server aggregate ranking

我有一个查询可以为客户提取月/年总数,并添加ntile排名。如果我能够得出ntile 1,2,3,4和5的最大小计,我几乎会得到我所追求的,但我不知道如何继续。

例如,我想要的结果如下:

Month   Year   CustomerCode   SubTotal   ntile
1       2012   CCC            131.45     1
1       2012   CCC            342.95     2
1       2012   ELITE          643.92     3
1       2012   CCC            1454.05    4
1       2012   CCC            12971.78   5
2       2012   CCC            135.99     1
2       2012   CCI            370.47     2
2       2012   NOC            766.84     3
2       2012   ELITE          1428.26    4
2       2012   VBC            5073.20    5
3       2012   CCC            119.02     1
3       2012   CCC            323.78     2
3       2012   HUCC           759.66     3
3       2012   ELITE          1402.95    4
3       2012   CCC            7964.20    5 

除了 - 我希望排名与第2个月的客户不同,但是我的基本查询没有给我那个结果 - 我显然不知道如何在SQL SERVER 2005上的T-SQL中获取它 - 事实上,我不确定我得到了什么。

我的下一个选择是在C#中使用DataTable并做一些体操来实现目标,但必须有一种更简单的方法:)

我的基本查询是

SELECT 
i.DateOrdered
,LTRIM(STR(DATEPART(MONTH,i.DateOrdered))) AS [Month]   
,LTRIM(STR(YEAR(i.Dateordered))) AS [Year]   
,c.CustomerCode 
,SUM(i.Jobprice) AS Subtotal  
,NTILE(5) OVER(ORDER BY SUM(i.JobPrice)) AS [ntile]
FROM Invoices i 
JOIN 
Customers c 
ON i.CustomerID = c.ID 
WHERE i.DateOrdered >= '1/1/2012'
AND i.DateOrdered <= '9/30/2012' 
GROUP BY YEAR(i.DateOrdered),  MONTH(i.DateOrdered), i.DateOrdered, c.CustomerCode
ORDER BY LTRIM(STR(DATEPART(MONTH,i.DateOrdered))),   
TRIM(STR(YEAR(i.Dateordered))),     
SUM(i.JobPrice), c.CustomerCode ASC

我真的很感谢帮助做到这一点。

提前致谢

4 个答案:

答案 0 :(得分:3)

如果我正确地读了你,你所追求的是

对于范围内的每个月,
显示当月具有最大SUM的5位客户
并针对每个客户,显示相应的SUM。

在这种情况下,此SQL Fiddle创建一个示例表并运行查询,为您提供上述输出。如果您想查看创建的表中的内容,只需在右侧面板上执行简单的SELECT。

查询是:

;     WITH G as -- grouped by month and customer
(
    SELECT DATEADD(D,1-DAY(i.DateOrdered),i.DateOrdered) [Month],
           c.CustomerCode,
           SUM(i.Jobprice) Subtotal
      FROM Invoices i
      JOIN Customers c ON i.CustomerID = c.ID
     WHERE i.DateOrdered >= '1/1/2012' AND i.DateOrdered <= '9/30/2012'
  GROUP BY DATEADD(D,1-DAY(i.DateOrdered),i.DateOrdered), c.CustomerCode
)
    SELECT MONTH([Month]) [Month],
           YEAR([Month]) [Year],
           CustomerCode,
           SubTotal,
           Rnk [Rank]
      FROM
(
    SELECT *, RANK() OVER (partition by [Month] order by Subtotal desc) Rnk
      FROM G
) X
     WHERE Rnk <= 5
  ORDER BY Month, Rnk

为了解释,第一部分(WITH块)只是编写子查询的一种奇特方式,它按月和客户对数据进行GROUP。表达式DATEADD(D,1-DAY(i.DateOrdered),i.DateOrdered)将每个日期转换为该月的第一天,以便可以按月轻松地对数据进行分组。以传统形式编写的下一个子查询在每个月内通过小计添加RANK列,最后选择该列以获得前5 *。

请注意,RANK允许相同的排名,如果其中3个在第4位排名相同,最终可能会显示6个客户一个月。如果这不是您想要的,那么您可以更改单词{{1到RANK,它会在相等的小计之间随机打破。

答案 1 :(得分:0)

试试这个:

declare @tab table
(
[month] int,
[year] int,
CustomerCode varchar(20),
SubTotal float 
)
insert into @tab
select
1,2012,'ccc',131.45 union all 
select
1,2012,'ccc',343.45 union all
select 
1,2012,'ELITE',643.92 union all
select 
2,2012,'ccc',131.45 union all 
select
2,2012,'ccc',343.45 union all
select 
2,2012,'ELITE',643.92 union all
select 
3,2012,'ccc',131.45 union all 
select
3,2012,'ccc',343.45 union all
select 
3,2012,'ELITE',643.92

;with cte as 
(
 select NTILE(3) OVER(partition by [month] ORDER BY [month]) AS [ntile],* from @tab
)
select * from cte

即使在您的基本查询中,您也需要添加partition by,以便获得正确的输出。

答案 2 :(得分:0)

需要修改查询以仅获取月份和年份日期部分。您在同一个月内多次出现同一客户的问题是由于在select和group by子句中包含了i.DateOrdered。

以下查询应该可以满足您的需求。此外,我怀疑它是查询的最后一行的拼写错误,但tsql没有TRIM()函数只有LTRIM和RTRIM。

SELECT 

LTRIM(STR(DATEPART(MONTH,i.DateOrdered))) AS [Month]   
,LTRIM(STR(YEAR(i.Dateordered))) AS [Year]   
,c.CustomerCode 
,SUM(i.Jobprice) AS Subtotal  
,NTILE(5) OVER(ORDER BY SUM(i.JobPrice)) AS [ntile]
FROM Invoices i 
JOIN 
Customers c 
ON i.CustomerID = c.ID 
WHERE i.DateOrdered >= '1/1/2012'
AND i.DateOrdered <= '9/30/2012' 
GROUP BY YEAR(i.DateOrdered),  MONTH(i.DateOrdered),  c.CustomerCode
ORDER BY LTRIM(STR(DATEPART(MONTH,i.DateOrdered))),   
LTRIM(STR(YEAR(i.Dateordered))),     
SUM(i.JobPrice), c.CustomerCode ASC

这给出了这些结果

Month   Year    CustomerCode    Subtotal    ntile
 1      2012    ELITE            643.92      2
 1      2012    CCC            14900.23      5
 2      2012    CCC              135.99      1
 2      2012    CCI              370.47      1
 2      2012    NOC              766.84      3
 2      2012    ELITE           1428.26      4
 2      2012    VBC             5073.20      4
 3      2012    HUCC             759.66      2
 3      2012    ELITE           1402.95      3
 3      2012    CCC             8407.00      5

答案 3 :(得分:0)

如果没有 double 排名,我无法看到如何解决这个问题:

  1. 您需要获得每位客户的最大金额&amp;一个月。

  2. 然后,您需要每个月检索找到的前五笔款项。

  3. 以下是我如何做到这一点:

    ;
    WITH MaxSubtotals AS (
      SELECT DISTINCT
        CustomerID,
        MonthDate = DATEADD(MONTH, DATEDIFF(MONTH, 0, DateOrdered), 0),
        Subtotal  = MAX(SUM(JobPrice)) OVER (
          PARTITION BY Customer, DATEADD(MONTH, DATEDIFF(MONTH, 0, DateOrdered), 0)
          ORDER BY SUM(JobPrice)
        )
      FROM Invoices
      GROUP BY
        CustomerID,
        DateOrdered
    ),
    TotalsRanked AS (
      SELECT
        CustomerID,
        MonthDate,
        Subtotal,
        Ranking = ROW_NUMBER() OVER (PARTITION BY MonthDate ORDER BY Subtotal DESC)
      FROM MaxDailyTotals
    )
    SELECT
      Month = MONTH(i.MonthDate),
      Year  = YEAR(i.MonthDate),
      c.CustomerCode,
      i.Subtotal,
      i.Ranking
    FROM TotalsRanked i
      INNER JOIN Customers ON i.CustomerID = c.ID
    WHERE i.Ranking <= 5
    ;
    

    第一个CTE MaxSubtotals确定每个客户的最大小计数&amp;月。涉及DISTINCT和窗口聚合函数,它实际上是以下两步查询的“快捷方式”:

    SELECT
      CustomerID,
      MonthDate,
      Subtotal = MAX(Subtotal)
    FROM (
      SELECT
        CustomerID,
        MonthDate = DATEADD(MONTH, DATEDIFF(MONTH, 0, DateOrdered), 0),
        Subtotal = SUM(JobPrice)
      FROM Invoices
      GROUP BY
        CustomerID,
        DateOrdered
    ) s
    GROUP BY
      CustomerID,
      MonthDate
    

    另一个CTE,TotalsRanked,只是添加了找到的susbtotals的排名数,按客户和月分区。作为最后一步,您只需要将行限制为排名不超过5的行(或者您可能选择的其他时间)。

    请注意,在这种情况下使用ROW_NUMBER()对行进行排名可确保您使用Ranking <= 5过滤器获得的行数不会超过5行。如果有两个或更多行具有相同的小计,则会获得不同的排名,最后您可能会得到如下输出:

       Month  Year  CustomerCode  Subtotal  Ranking
       -----  ----  ------------  --------  -------
       1      2012  CCC           1500.00   1
       1      2012  ELITE         1400.00   2
       1      2012  NOC           900.00    3
       1      2012  VBC           700.00    4
       1      2012  HUCC          700.00    5
    
    -- 1      2012  ABC           690.00    6   -- not returned
    -- 1      2012  ...           ...       ...
    

    即使可能有其他客户在同一个月内使用700.00的小计,他们也不会被退回,因为他们将在5之后被分配排名。

    您可以使用RANK()代替ROW_NUMBER()来解释该问题。但请注意,每月最多可能有超过5行,输出如下:

       Month  Year  CustomerCode  Subtotal  Ranking
       -----  ----  ------------  --------  -------
       1      2012  CCC           1500.00   1
       1      2012  ELITE         1400.00   2
       1      2012  NOC           900.00    3
       1      2012  VBC           700.00    4
       1      2012  HUCC          700.00    4
       1      2012  ABC           700.00    4
    
    -- 1      2012  DEF           690.00    7   -- not returned
    -- 1      2012  ...           ...       ...
    

    小计小于700.00的客户不会进入输出,因为他们的排名将从7开始,如果按ROW_NUMBER()排名,则对应于第一次700.00以下的排名。

    还有另一种选择,DENSE_RANK()。如果您希望输出中每月最多有5个不同的总和,则可能需要使用它。对于DENSE_RANK(),您的输出每月可能包含的行数比RANK()更多,但不同的小计数量恰好为5(或者如果原始数据集无法为您提供5 )。也就是说,您的输出可能如下所示:

       Month  Year  CustomerCode  Subtotal  Ranking
       -----  ----  ------------  --------  -------
       1      2012  CCC           1500.00   1
       1      2012  ELITE         1400.00   2
       1      2012  NOC           900.00    3
       1      2012  VBC           700.00    4
       1      2012  HUCC          700.00    4
       1      2012  ABC           700.00    4
       1      2012  DEF           650.00    5
       1      2012  GHI           650.00    5
       1      2012  JKL           650.00    5
    
    -- 1      2012  MNO           600.00    5   -- not returned
    -- 1      2012  ...           ...       ...
    

    RANK()一样,DENSE_RANK()函数为相同的值分配相同的排名,但 RANK()不同,它不会在排名顺序中产生差距


    参考文献: