【问题标题】:How to find 12 consecutive transactions如何查找连续 12 笔交易
【发布时间】:2021-08-16 08:28:22
【问题描述】:

我在下面有一张桌子。

我正在寻找连续 12 个月的交易(以黄色、橙色和绿色突出显示)。对于绿色,有 06/2013 月份的 2 笔交易,但它补偿了 07/2013 的交易,因为它丢失了。也有一些交易在一个月内有超过 2 笔交易,但可以对其他缺失的交易进行补偿。

下面是构造表的查询。

CREATE TABLE #Temp 
( 
  ProductProvider  VARCHAR(200),
  ProductType  VARCHAR(200),
  PolicyID BIGINT, 
  TransactionDate DATE, 
  TotalRevenue DECIMAL(10,2)
 )
 --DROP TABLE #temp
insert into #Temp
values
('XYZ',     'ONG',  2789753,    '2009-04-30',   0.19)
,('XYZ',    'ONG',  2789753,    '2009-05-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2009-07-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2009-08-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2009-10-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2009-11-30',   0.19)
,('XYZ',    'ONG',  2789753,    '2009-12-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2010-01-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2010-02-28',   0.19)
,('XYZ',    'ONG',  2789753,    '2010-03-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2010-04-30',   0.19)
,('XYZ',    'ONG',  2789753,    '2010-05-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2010-06-30',   0.19)
,('XYZ',    'ONG',  2789753,    '2010-07-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2010-08-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2010-09-30',   0.19)
,('XYZ',    'ONG',  2789753,    '2010-10-30',   0.19)
,('XYZ',    'ONG',  2789753,    '2011-01-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2011-02-28',   0.19)
,('XYZ',    'ONG',  2789753,    '2011-04-30',   0.19)
,('XYZ',    'ONG',  2789753,    '2011-05-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2011-06-30',   0.19)
,('XYZ',    'ONG',  2789753,    '2011-07-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2011-08-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2011-10-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2011-12-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2012-05-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2012-06-30',   0.19)
,('XYZ',    'ONG',  2789753,    '2012-07-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2012-08-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2012-09-30',   0.19)
,('XYZ',    'ONG',  2789753,    '2012-10-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2012-11-30',   0.19)
,('XYZ',    'ONG',  2789753,    '2012-12-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2013-01-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2013-02-28',   0.19)
,('XYZ',    'ONG',  2789753,    '2013-03-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2013-04-30',   0.19)
,('XYZ',    'ONG',  2789753,    '2013-05-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2013-06-25',   0.19)
,('XYZ',    'ONG',  2789753,    '2013-06-30',   0.19)
,('XYZ',    'ONG',  2789753,    '2013-08-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2013-09-30',   0.19)
,('XYZ',    'ONG',  2789753,    '2013-10-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2013-11-30',   0.19)
,('XYZ',    'ONG',  2789753,    '2013-12-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2014-01-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2014-02-28',   0.19)
,('XYZ',    'ONG',  2789753,    '2014-03-31',   0.19)
,('XYZ',    'ONG',  2789753,    '2014-04-30',   0.19)
,('XYZ',    'ONG',  2789753,    '2014-08-30',   0.19)

下面是我的第一次尝试。

select *
from (select t.*, 
             count(*) over (partition by ProductProvider, ProductType, TotalRevenue, DATEADD(m,seqnum, convert(date, DATEADD(month, DATEDIFF(month, 0, TransactionDate), 0)))) as cnt_group
      from (select *,
                   row_number() over (partition by ProductProvider, ProductType, TotalRevenue order by convert(date, DATEADD(month, DATEDIFF(month, 0, TransactionDate), 0))) as seqnum
            from #Temp
           ) t
     ) t
where cnt_group = 12
order by transactiondate

我从上述查询中一无所获(我想我无法对 ProductProvider/ProductType/PolicyId 进行行编号,因为交易日期都不同,因此给了我第 1 到 51 行没有分组)。

下面是我的第二次尝试。

SELECT DISTINCT *,
       month1+month2+month3+month4+month5+month6+month7+month8+month9+month10+month11+month12 AS Total
FROM 
    (
    SELECT a.*, 
           1 AS Month1, 
           CASE WHEN b.ProductProvider IS NULL THEN 0 ELSE 1 END AS Month2,
           CASE WHEN c.ProductProvider IS NULL THEN 0 ELSE 1 END AS Month3,
           CASE WHEN d.ProductProvider IS NULL THEN 0 ELSE 1 END AS Month4,
           CASE WHEN e.ProductProvider IS NULL THEN 0 ELSE 1 END AS Month5,
           CASE WHEN f.ProductProvider IS NULL THEN 0 ELSE 1 END AS Month6,
           CASE WHEN g.ProductProvider IS NULL THEN 0 ELSE 1 END AS Month7,
           CASE WHEN h.ProductProvider IS NULL THEN 0 ELSE 1 END AS Month8,
           CASE WHEN i.ProductProvider IS NULL THEN 0 ELSE 1 END AS Month9,
           CASE WHEN j.ProductProvider IS NULL THEN 0 ELSE 1 END AS Month10,
           CASE WHEN k.ProductProvider IS NULL THEN 0 ELSE 1 END AS Month11,
           CASE WHEN l.ProductProvider IS NULL THEN 0 ELSE 1 END AS Month12
    FROM #Temp a
    LEFT JOIN #temp b ON a.ProductProvider = b.ProductProvider
                     AND a.ProductType = b.ProductType
                     AND a.policyid = b.policyid
                     AND convert(date, DATEADD(month, DATEDIFF(month, 0, b.TransactionDate), 0)) = DATEADD(m, 1, convert(date, DATEADD(month, DATEDIFF(month, 0, a.TransactionDate), 0)))
    LEFT JOIN #temp c ON a.ProductProvider = c.ProductProvider
                     AND a.ProductType = c.ProductType
                     AND a.policyid = c.policyid
                     AND convert(date, DATEADD(month, DATEDIFF(month, 0, c.TransactionDate), 0)) = DATEADD(m, 2, convert(date, DATEADD(month, DATEDIFF(month, 0, a.TransactionDate), 0)))
    LEFT JOIN #temp d ON a.ProductProvider = d.ProductProvider
                     AND a.ProductType = d.ProductType
                     AND a.policyid = d.policyid
                     AND convert(date, DATEADD(month, DATEDIFF(month, 0, d.TransactionDate), 0)) = DATEADD(m, 3, convert(date, DATEADD(month, DATEDIFF(month, 0, a.TransactionDate), 0)))
    LEFT JOIN #temp e ON a.ProductProvider = e.ProductProvider
                     AND a.ProductType = e.ProductType
                     AND a.policyid = e.policyid
                     AND convert(date, DATEADD(month, DATEDIFF(month, 0, e.TransactionDate), 0)) = DATEADD(m, 4, convert(date, DATEADD(month, DATEDIFF(month, 0, a.TransactionDate), 0)))
    LEFT JOIN #temp f ON a.ProductProvider = f.ProductProvider
                     AND a.ProductType = f.ProductType
                     AND a.policyid = f.policyid
                     AND convert(date, DATEADD(month, DATEDIFF(month, 0, f.TransactionDate), 0)) = DATEADD(m, 5, convert(date, DATEADD(month, DATEDIFF(month, 0, a.TransactionDate), 0)))
    LEFT JOIN #temp g ON a.ProductProvider = g.ProductProvider
                     AND a.ProductType = g.ProductType
                     AND a.policyid = g.policyid
                     AND convert(date, DATEADD(month, DATEDIFF(month, 0, g.TransactionDate), 0)) = DATEADD(m, 6, convert(date, DATEADD(month, DATEDIFF(month, 0, a.TransactionDate), 0)))
    LEFT JOIN #temp h ON a.ProductProvider = h.ProductProvider
                     AND a.ProductType = h.ProductType
                     AND a.policyid = h.policyid
                     AND convert(date, DATEADD(month, DATEDIFF(month, 0, h.TransactionDate), 0)) = DATEADD(m, 7, convert(date, DATEADD(month, DATEDIFF(month, 0, a.TransactionDate), 0)))
    LEFT JOIN #temp i ON a.ProductProvider = i.ProductProvider
                     AND a.ProductType = i.ProductType
                     AND a.policyid = i.policyid
                     AND convert(date, DATEADD(month, DATEDIFF(month, 0, i.TransactionDate), 0)) = DATEADD(m, 8, convert(date, DATEADD(month, DATEDIFF(month, 0, a.TransactionDate), 0)))
    LEFT JOIN #temp j ON a.ProductProvider = j.ProductProvider
                     AND a.ProductType = j.ProductType
                     AND a.policyid = j.policyid
                     AND convert(date, DATEADD(month, DATEDIFF(month, 0, j.TransactionDate), 0)) = DATEADD(m, 9, convert(date, DATEADD(month, DATEDIFF(month, 0, a.TransactionDate), 0)))
    LEFT JOIN #temp k ON a.ProductProvider = k.ProductProvider
                     AND a.ProductType = k.ProductType
                     AND a.policyid = k.policyid
                     AND convert(date, DATEADD(month, DATEDIFF(month, 0, k.TransactionDate), 0)) = DATEADD(m, 10, convert(date, DATEADD(month, DATEDIFF(month, 0, a.TransactionDate), 0)))
    LEFT JOIN #temp l ON a.ProductProvider = l.ProductProvider
                     AND a.ProductType = l.ProductType
                     AND a.policyid = l.policyid
                     AND convert(date, DATEADD(month, DATEDIFF(month, 0, l.TransactionDate), 0)) = DATEADD(m, 11, convert(date, DATEADD(month, DATEDIFF(month, 0, a.TransactionDate), 0)))
    ) x

我的逻辑是,如果我可以得到 12 列的总数,这就是我将保留的结果。然而,使用这种方法,它似乎每行计算 12 个连续事务(我猜该术语连续运行 12 个月)。因此,连续 12 个月检查第 1 行,连续 12 个月检查第 2 行,连续 12 个月检查第 3 行,等等。最后没有明确的指示符,我可以从 #temp 表中选择和删除哪些连续 12 个月所以他们不会被重复计算。此外,如果同一年有超过 1 笔交易可以补偿其他缺失的交易,则此方法也不起作用,因为它总共只计算 11 笔。

最后一种方法是使用循环。所以选择第1行,检查是否有12个连续交易,如果没有选择第2行,如果没有选择第3行,如果是,则选择第3行的日期到第3行的最后12个日期并将其放在某处并删除它们来自表#temp。然后再次开始循环。但是,由于除了 DO-WHILE-PRINT 之类的简单循环之外,我在构造循环方面存在局限性,因此我无法为此编写任何查询。

我使用的是 Microsoft SQL 2012。

17/08/2021 - 我在下面还有另一种方法。

;WITH CTEDATES
AS
(
    SELECT ROW_NUMBER() OVER (ORDER BY TransactionDate asc ) AS ROWNUMBER,* FROM #Temp  

),
 CTEDATES1
AS
(
   SELECT *, 1 as groupid FROM CTEDATES WHERE ROWNUMBER=1
   UNION ALL
   SELECT a.*,case datediff(m, b.transactiondate,a.transactiondate) when 1 then b.groupid else b.groupid+1 end as gap FROM CTEDATES A INNER JOIN CTEDATES1 B ON A.ROWNUMBER-1 = B.ROWNUMBER
)
, cte AS
(
select productprovider, producttype, policyid, totalrevenue, MIN(transactiondate) as startdate, max(transactiondate) as enddate from CTEDATES1 group by productprovider, producttype, policyid, totalrevenue,groupid
)
SELECT b.productprovider, 
     b.producttype, 
     b.policyid, 
     b.totalrevenue,a.startdate, a.enddate, COUNT(b.totalrevenue) AS TotalTransaction
FROM cte a
INNER JOIN #temp b ON a.productprovider = b.ProductProvider
                  AND a.producttype = b.ProductType
                  AND a.policyid = b.PolicyID
                  AND a.totalrevenue = b.totalrevenue
WHERE b.TransactionDate BETWEEN a.startdate AND a.enddate
GROUP BY b.productprovider, 
     b.producttype, 
     b.policyid, 
     b.totalrevenue, 
     a.startdate, 
     a.enddate

它可以检测间隙和这些间隙之间的总交易量,最后我可以选择 TotalTransactions >= 12 并应用另一个 row_numbering 以获得恰好 12 个连续交易。然而,使用这种方法,不幸的是,它无法检测到更可能用于补偿缺失的月份年份的同一月份年份内的多个交易。下面是结果。

第 8 列的结果应该是 StartDate = 2012/05/31 和 EndDate = 2014/04/30 且 TotalTransaction = 24,因为在 06/2013 年有 2 笔交易,其中一笔是为了补偿缺少 07/2013 月份的交易。

18/08/2021 - 下面的另一种方法仅适用于这种情况。

IF OBJECT_ID('tempdb..#AssigningNewTransactionDate') IS NOT NULL                                                
DROP TABLE #AssigningNewTransactionDate

;WITH cte AS
(
SELECT *, 
       CONVERT(date, DATEADD(month, DATEDIFF(month, 0, TransactionDate), 0)) AS FirstoftheMonth
FROM #temp
)
,cte2 AS
(
SELECT productprovider, producttype, policyid, totalrevenue, firstofthemonth, COUNT(firstofthemonth) AS NumberofFirstOfTheMonth
FROM cte           
GROUP BY productprovider, producttype, policyid, totalrevenue, firstofthemonth
)
, cte3 AS
(
SELECT a.*, 
       b.NumberofFirstOfTheMonth, 
       ROW_NUMBER() OVER(PARTITION BY a.productprovider, a.producttype, a.policyid, a.totalrevenue, a.firstofthemonth ORDER by a.firstofthemonth) AS Row_num
FROM cte a
LEFT JOIN cte2 b ON a.productprovider = b.ProductProvider
              AND a.producttype = b.ProductType
              AND a.policyid = b.PolicyID
              AND a.totalrevenue = b.TotalRevenue
              AND a.firstofthemonth = b.firstofthemonth
)
SELECT *, 
       CASE WHEN NumberofFirstOfTheMonth = 2 AND row_num = 1 AND LAG(firstofthemonth, 1) OVER (PARTITION BY productprovider, producttype, policyid, totalrevenue ORDER BY firstofthemonth) = DATEADD(m, -1, firstofthemonth) THEN cte3.TransactionDate
            WHEN NumberofFirstOfTheMonth = 2 AND row_num = 2 AND LAG(firstofthemonth, 1) OVER (PARTITION BY productprovider, producttype, policyid, totalrevenue ORDER BY firstofthemonth) = firstofthemonth THEN DATEADD(m, 1, transactiondate)
            ELSE cte3.TransactionDate
       END AS NewTransactionDate
INTO #AssigningNewTransactionDate
FROM cte3

;WITH CTEDATES
AS
    (
    SELECT ROW_NUMBER() OVER (ORDER BY NewTransactionDate asc ) AS ROWNUMBER,* 
    FROM #AssigningNewTransactionDate  
    )
, CTEDATES1
AS
    (
       SELECT *, 1 as groupid 
       FROM CTEDATES WHERE ROWNUMBER=1
       UNION ALL
       SELECT a.*,case datediff(m, b.newtransactiondate,a.newtransactiondate) when 1 then b.groupid else b.groupid+1 end as gap FROM CTEDATES A INNER JOIN CTEDATES1 B ON A.ROWNUMBER-1 = B.ROWNUMBER
    )
, cte AS
        (
        select productprovider, producttype, policyid, totalrevenue, MIN(newtransactiondate) as startdate, max(newtransactiondate) as enddate 
        FROM CTEDATES1 
        GROUP by productprovider, producttype, policyid, totalrevenue,groupid
        )
SELECT b.productprovider, 
     b.producttype, 
     b.policyid, 
     b.totalrevenue,a.startdate, a.enddate, COUNT(b.totalrevenue) AS TotalTransaction
FROM cte a
INNER JOIN #AssigningNewTransactionDate b ON a.productprovider = b.ProductProvider
                  AND a.producttype = b.ProductType
                  AND a.policyid = b.PolicyID
                  AND a.totalrevenue = b.totalrevenue
WHERE b.NewTransactionDate BETWEEN a.startdate AND a.enddate
GROUP BY b.productprovider, 
     b.producttype, 
     b.policyid, 
     b.totalrevenue, 
     a.startdate, 
     a.enddate

使用此方法,07/13 ...05/13, 06/13, 06/13, 08/13 之间的缺失交易可以通过 06/13 的交易进行补偿。但是,如果发生其他有多个事务的情况,例如 ...04、06、06、07、08... 或 ... 04、07、07、07、08...,则此方法不是万无一失的。

【问题讨论】:

  • 补偿的逻辑不是很清楚。例如月份是..05, 07,07, 09.. 期望的结果是什么,..05,06,07 即补偿 06 或 07,08,09.. 即补偿 08?
  • 嗨,Serg,对于补偿逻辑,它必须补偿最早的月份(因为所有收入都相同),例如如果月份是 ...05、07、07、09.. . 那么07月份的其中一个需要补偿缺少的06。所以它将是...... 05、06、07、09......谢谢。
  • ...05, 07, 07, 07, 09... 是否同时补偿 06 和 08? .. 04, 06, 07, 07, 08.. 呢,05 有补偿吗?补偿规则应在问题中详细说明。
  • 嗨,Serg,对于 ..05、07、07、07、09.. 是的,它补偿 06 和 08。对于 .. 04、06、07、07、08.. 是的,它补偿05. 所以同年同月的多次交易必须补缺月的第一个月。如果是.. 05, 07, 07, 09.. 它只会补偿 06 和 08 将继续拖欠付款。我已经更新了逻辑并将其发布在上面。使用 LAG,我可以创建 CASE 逻辑,但它仅适用于 ..05,06,06,08 等场景。是否可以使用 CASE 创建“场景分组”(如场景 ..05、06、06 , 08.. 这样做,场景 ..04,06, 07,07,08.. 这样做等等)?

标签: sql sql-server transactions row-number


【解决方案1】:

据我所知,您想知道在不到一年的时间内是否有 12 笔交易。

为此,您可以使用lag()lead()

select t.*
from (select t.*,
             lag(transactionDate, 11) over (partition by ProductProvider, ProductType, PolicyId order by TransactionDate) as transactionDate_11
      from #temp t
     ) t
where transactionDate_11 > datedadd(year, -1, TransactionDate)

【讨论】:

  • 感谢戈登的帮助。不幸的是,使用这种方法会重复计算行数。例如,应排除结果 TransactionDate = 2010/10/30 和 TransactionDate_11 = 2009/11/30,因为 2009/11/30 是第一个结果的 12 个月范围的一部分(TransactionDate 2010/09/30 和 TransactionDate_11 2009/ 10/31)。谢谢。
【解决方案2】:

查找开始日期,后跟至少 11 行在接下来的 11 个月内。

select t.ProductProvider, t.ProductType, t.PolicyID, t.TransactionDate first_date, max(t2.TransactionDate) last_date
from #Temp t
join #Temp t2 on t.ProductProvider = t2.ProductProvider and  t.ProductType = t2.ProductType and t.PolicyID = t2.PolicyID
   and t2.TransactionDate > t.TransactionDate and datediff(month, t.TransactionDate, t2.TransactionDate) < 12
group by t.ProductProvider, t.ProductType, t.PolicyID, t.TransactionDate
having count(*) >= 11

然后您可以从找到的开始日期列出以下行。

编辑 排除重叠区间递归遍历上述结果,即

with allIntv as (
  select t.ProductProvider, t.ProductType, t.PolicyID, t.TransactionDate first_date, max(t2.TransactionDate) last_date
  from #Temp t
  join #Temp t2 on t.ProductProvider = t2.ProductProvider and  t.ProductType = t2.ProductType and t.PolicyID = t2.PolicyID
     and t2.TransactionDate > t.TransactionDate and datediff(month, t.TransactionDate, t2.TransactionDate) < 12
  group by t.ProductProvider, t.ProductType, t.PolicyID, t.TransactionDate
  having count(*) >= 11 
), rcte as (
  -- the first interval
  select top(1) with ties t.*
  from allIntv t
  order by row_number() over(partition by t.ProductProvider, t.ProductType, t.PolicyID order by t.first_date)

  union all

  -- first non-overlapping interval
  -- TOP() is forbidden in the recursive part, using NOT EXISTS
  select t.*
  from allIntv t
  join rcte on t.first_date > rcte.last_date
     and not exists (select 1 
                     from allIntv t2
                     where t.ProductProvider = t2.ProductProvider and t.ProductType = t2.ProductType and t.PolicyID = t2.PolicyID
                        and t2.first_date > rcte.last_date and  t2.first_date < t.first_date)
)
select * 
from rcte;

对于它返回的 OP 样本数据

ProductProvider ProductType PolicyID    first_date  last_date
XYZ ONG 2789753 2009-10-31  2010-09-30
XYZ ONG 2789753 2012-05-31  2013-04-30
XYZ ONG 2789753 2013-05-31  2014-04-30

db<>fiddle

【讨论】:

  • 感谢您的帮助。这种方法的缺点是它会查找 first_date 之后 12 个月的任何行。例如,第一个结果是 2009/10/31 到 2010/09/30 - 这是正确的。然而,第二个结果是 2009/11/30 - 2010/30。由于日期 2009/11/30 已经在第一个结果的范围内,因此应排除此结果。所以最后,我只需要获取 2009/10/31 到 2010/09/30、2012/05/31 到 2013/04/30 和 201305/31 到 2014/04/30 之间的所有日期。
猜你喜欢
  • 2013-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-23
  • 1970-01-01
  • 2016-03-17
相关资源
最近更新 更多