如何从未快照的 table 中查询余额指标

How to query a balance metric from a table that is not snapshotted

问题:
查询优化 - 当前解决方案需要 10 多分钟才能完成,这对于报告目的来说太长了。

目标:
按天报告活跃客户。活跃客户是指去年购买过商品的人。

示例数据集:
Customer_Id | Order_Date
1 | 2014 年 1 月 1 日
1 | 2014 年 1 月 2 日
2 | 2014 年 1 月 1 日
3 | 2014 年 1 月 1 日
3 | 2014 年 1 月 3 日

需要的结果集:
Snapshot_Date | Active_Customers
2014 年 1 月 1 日 | 3
2014 年 1 月 2 日 | 3
2014 年 1 月 3 日 | 3
...
2015 年 1 月 1 日 | 3
2015 年 1 月 2 日 | 2
2015 年 1 月 3 日 | 1

当前查询:

--Date Dimension table
CREATE TABLE #Snapshot ( Snapshot DATE )
INSERT  INTO #Snapshot
        ( Snapshot )
VALUES  ( '1/1/2014' ) ,
        ( '1/2/2014' ) ,
        ( '1/3/2014' ) ,
             ...
        ( '1/1/2015' ) ,
        ( '1/2/2015' ) ,
        ( '1/3/2015' ) 

--Orders Table
CREATE TABLE #Orders ( Customer_Id INT , Order_Date DATE )
INSERT INTO #Orders ( Customer_Id , Order_Date )
VALUES ( 1, '1/1/2014' ),
       ( 1, '1/2/2014' ),
       ( 2, '1/1/2014' ),
       ( 3, '1/1/2014' ),
       ( 3, '1/3/2014' )

--Query
SELECT  a.Snapshot ,
        SUM(CASE WHEN DATEDIFF(DAY, a.Last_Order, a.Snapshot) <= 365 THEN 1 ELSE 0 END) AS Active_Customers
FROM    ( SELECT    a.Snapshot ,
                    b.Customer_Id ,
                    MAX(b.Order_Date) AS Last_Order
          FROM      #Snapshot a
                    JOIN #Orders b ON b.Order_Date <= a.Snapshot
          GROUP BY  a.Snapshot ,
                    b.Customer_Id
        ) a
GROUP BY a.Snapshot

问题:
由于 sub-select 中的连接,查询陷入困境。如果订单 table 有 100 万行,并且日期维度 table 有 1,000 行,则连接创建大约 10 亿行。

有没有办法创建相同的结果集,同时消除子select内的内部连接以防止多对多关系?

我会推荐一种不同的方法,一种使用累计总和而不是每天单独计数的方法——这样要快得多。

想法是每次出现新客户或一年未见订单时添加新客户。同样,在 365 天后减去一个客户——除非出现新订单。剩下的就是累计金额。 SQL Server 2012 支持所有这些。

以下将给出每天有订单的报告:

WITH o as (
      SELECT o.*, 
             LAG(Order_Date) OVER (PARTITION BY Customer_Id ORDER BY Order_Date) as prev_OrderDate,
             LEAD(Order_Date) OVER (PARTITION BY Customer_Id ORDER BY Order_Date) as next_OrderDate
      FROM Orders o 
     )
SELECT thedate, SUM(inc), SUM(SUM(inc)) OVER (ORDER BY thedate)
FROM ((SELECT o.OrderDate as thedate, 1 as inc
       FROM o
       WHERE prev_OrderDate IS NULL OR DATEADD(year, 1, prev_OrderDate) < Order_Date
      ) UNION ALL
      (SELECT o.OrderDate + 365, -1 as inc
       FROM o
       WHERE next_OrderDate IS NULL OR DATEADD(year, -1, next_OrderDate > Order_Date
      )
     ) d
GROUP BY thedate