【问题标题】:SQL Server query for new and repeat orders per monthSQL Server 每月查询新订单和重复订单
【发布时间】:2018-10-01 12:24:59
【问题描述】:

我在 SQL Server 2008 R2 中工作,并且很难收集新客户订单和重复客户订单。

我有这种格式的数据:

OrderID     OrderDate   Customer    OrderAmount
-----------------------------------------------
1           1/1/2017    A           $10
2           1/2/2017    B           $20
3           1/3/2017    C           $30
4           4/1/2017    C           $40
5           4/2/2017    D           $50
6           4/3/2017    D           $60
7           1/6/2018    B           $70

这就是我们想要的:

  • 新定义为:客户在前几个月没有下过任何订单。
  • 重复定义为:客户在前一个月(即使是多年前)下过订单。

这意味着,如果新客户在第一个月下达了多个订单,则这些订单都将被视为“新”客户订单。随后几个月的订单都将被视为“重复”客户订单。

我们希望每年每月获得新订单(计数和总和)和重复订单(计数和总和):

Year    Month   NewCount        NewSum          RepeatCount         RepeatSum
-----------------------------------------------------------------------------
2017    1       3 (A,B,C)       $60 (10+20+30)  0                   $0
2017    4       2 (D,D)         $110 (50+60)    1 (C)               $40 (40)
2018    1       0               $0              1 (B)               $70 (70)

(() 括号中的信息不是结果的一部分;只是为了清楚起见将其放在这里)

对于任何一个给定的月份来说,SQL 都很容易编写,但是当一次收集数年的月份时,我不知道该怎么做......

如果有一个月没有任何类型的订单,那么 year:month 的 NULL 或 0 值将是首选。

【问题讨论】:

  • 看看窗口函数和LEAD()LAG()
  • 据我所知,Lead 和 Lag 在 2008R2 上不可用。
  • @BillyTurtleneck 你说得对,它们不可用。但是您是否知道 Sql Server 2008 R2 的最终生命周期只有一年左右?在 2019 年 7 月 9 日之后,将不会为该版本的 Sql Server 创建新的补丁......甚至没有关键的安全补丁。是时候为更新的版本启动迁移项目了。
  • 这很好。但是,不使用 Lead/Lag 尝试这样做是徒劳的,还是有办法做到这一点?目前我在我的网络应用程序代码中使用循环,它很慢。如果我能以我需要的方式从 SQL 中获取所有信息,那将是非常棒的一次查询。

标签: sql join sql-server-2008-r2 datepart


【解决方案1】:

您可以使用dense_rank寻找新老客户。此查询返回您提供的输出

declare @t table (OrderID int, OrderDate date, Customer char(1), OrderAmount int)
insert into @t
values  (1, '20170101', 'A', 10)
    , (2, '20170102', 'B', 20), (3, '20170103', 'C', 30)
    , (4, '20170401', 'C', 40), (5, '20170402', 'D', 50)
    , (6, '20170403', 'D', 60), (7, '20180106', 'B', 70)

select
    [year], [month], NewCount = isnull(sum(case when dr = 1 then 1 end), 0)
    , NewSum = isnull(sum(case when dr = 1 then OrderAmount end), 0)
    , RepeatCount = isnull(sum(case when dr > 1 then 1 end), 0)
    , RepeatSum = isnull(sum(case when dr > 1 then OrderAmount end), 0)
from (
    select 
        *, [year] = year(OrderDate), [month] = month(OrderDate)
        , dr = dense_rank() over (partition by Customer order by dateadd(month, datediff(month, 0, OrderDate), 0))
    from 
        @t
) t
group by [year], [month]

输出

year    month  NewCount   NewSum   RepeatCount   RepeatSum
----------------------------------------------------------
2017    1      3          60       0             0
2018    1      0          0        1             70
2017    4      2          110      1             40

如果您想显示没有订单的月份,您必须首先将表格中的每一年与所有月份组合起来。然后加入上层查询

select
    *
from
    (select distinct y = year(OrderDate) from @t) t
    cross join (values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12)) q(m)

【讨论】:

  • 是的!太棒了。哇。这样一个优雅而简单的解决方案。这很好用,并且提供了我需要的东西。我认为 Over 和 Partition 是其中的一部分,但没有考虑 Dense_Rank。谢谢!!
【解决方案2】:

首先,首先用每个客户每月一条记录汇总数据。

然后,您可以使用自联接或类似的构造来获取您需要的信息:

with cm as (
      select customer, dateadd(day, 1 - day(orderdate), orderdate) as yyyymm
             sum(orderamount) as monthamount, count(*) as numorders
      from orders
      group by customer
     )
select year(cm.yyyymm) as yr, month(cm.yyyymm) as mon,
       sum(case when cm.num_orders > 0 and cm_prev.customer is null then 1 else 0 end) as new_count,
       sum(case when cm.num_orders > 0 and cm_prev.customer is null then monthamount else 0 end) as new_amount,
       sum(case when cm.num_orders > 0 and cm_prev.customer > 0 then 1 else 0 end) as repeat_count,
       sum(case when cm.num_orders > 0 and cm_prev.customer > 0 then monthamount else 0 end) as repeat_amount
from cm left join
     cm cm_prev
     on cm.customer = cm_prev.customer and
        cm.yyyymm = dateadd(month, 1, cm_prev.yyyymm)
group by year(cm.yyyymm), month(cm.yyyymm)
order by year(cm.yyyymm), month(cm.yyyymm);

这在 SQL Server 2012 中会更容易一些,您可以在其中使用lag()

【讨论】:

  • 您的意思是在第 2 行使用月份吗?尝试处理该单独的选择语句时出现错误。
  • 我看不懂“_prev”...你能解释一下吗?
  • @BillyTurtleneck 。 . .查询中没有调用_prev,所以我不明白这个问题。第 2 行是获取当月第一个日期的一种标准方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
  • 2017-01-06
相关资源
最近更新 更多