【问题标题】:SQL count of 90 day gaps between records记录之间 90 天间隔的 SQL 计数
【发布时间】:2015-07-30 22:46:35
【问题描述】:

假设我有一张付款表。我需要知道按 personID 分组的付款间隔大于 90 天的次数。付款频率不同。没有预期的付款次数。在 90 天内可能有 0 或数百笔付款。如果一年没有付款,则计为 1。如果每个月都有付款,则计数为 0。如果第一个月有 4 笔付款,则间隔 90 天,然后再付款 2 次,然后再间隔 90 天,计数将是 2。

CREATE TABLE Payments
(
    ID int PRIMARY KEY,
    PersonID int FOREIGN KEY REFERENCES Persons(ID),
    CreateDate datetime
)

【问题讨论】:

  • 由于您的问题没有具体细节(参见上面的 cmets),我只能提供没有 SQL 的通用方法。您需要为每个客户按顺序安排付款,或者将每一行自行加入到按时间顺序排列的“下一个”行。然后对于每一对行,使用 DBMS 特定的日期函数来确定它们的日期之间的天数,只保留那些 >= 90 天的差异。然后按 personID 分组并计数。
  • 只是寻找一个通用的方法。表结构:ID、PersonID、CreateDate

标签: sql


【解决方案1】:

如果您有 SQL Server 2014,则可以使用 LAGLEAD 函数来查看其他行,这很容易:

Select PersonId, Sum(InfrequentPayment) InfrequentPayments
from
(
    select PersonId
    , case 
        when dateadd(day,@period,paymentdate) < coalesce(lead(PaymentDate) over (partition by personid order by PaymentDate),getutcdate()) 
        then 1 
        else 0 
      end InfrequentPayment
    from @Payment
) x
Group by PersonId

演示:http://sqlfiddle.com/#!6/9eecb7d/491

说明:

外部 SQL 相当简单;我们获取内部 SQL 的结果,按 PersonId 分组,并计算/总结他们支付的次数,判断为不频繁。

内部SQL也很简单;我们正在选择每条记录,记下该人以及该笔付款(或更确切地说是付款后的延迟)是否被判定为不频繁。

案例陈述决定了不经常付款的构成。 这里我们说,如果记录的 paymentdate 加上 90 天仍然早于下一次付款(如果是最后一次付款,则为当前日期,因此没有下一次付款),那么它是罕见的 (1);否则不是 (0)。

coalesce 只是用来处理一个人的最后一条记录;即,如果没有下一次付款,则使用当前日期(从而捕获上次付款时间超过今天 90 天的任何人)。

现在是“聪明”位:lead(PaymentDate) over (partition by personid order by PaymentDate)LEAD 是一个新的 SQL 函数,可以让您查看当前记录之后的记录(LAG 是查看上一条记录)。
如果您熟悉row_number()rank(),您可能已经了解这里发生了什么。 但是,要确定当前记录之后的记录,我们不查看当前查询;相反,我们只为这个函数指定一个order by 子句;这就是over 关键字后面括号中的内容。 我们还只想将每个人的付款日期与他们进行的其他付款进行比较;不是任何客户。为此,我们使用partition by 子句。

我希望这有意义/符合您的要求。如果有什么不清楚的地方请说出来,我会尽力改进我的解释。


编辑

对于老版本的SQL,使用或ROW_NUMBERLEFT OUTER JOIN可以达到同样的效果;即

;with cte (PersonId, PaymentDate, SequenceNo) as
(
    select PersonId
    , PaymentDate
    , ROW_NUMBER() over (partition by PersonId order by PaymentDate)
    from @Payment
)
select a.PersonId
, sum(case when dateadd(day,@period,a.paymentdate) < coalesce(b.paymentdate,getutcdate()) then 1 else 0 end) InfrequentPayments
from cte a
left outer join cte b 
on b.PersonId = a.PersonId 
and b.SequenceNo = a.SequenceNo + 1
Group by a.PersonId

应该适用于大多数数据库的另一种方法(虽然效率较低)

select PersonId
, sum(InfrequentPayment) InfrequentPayments
from 
(
    select PersonId
    , case when dateadd(day,@period,paymentdate) < coalesce((
        select min(PaymentDate) 
        from @Payment b 
        where b.personid = a.personid
        and b.paymentdate > a.paymentdate
    ),getutcdate()) then 1 else 0 end InfrequentPayment
    from @Payment a
) x
Group by PersonId

【讨论】:

    【解决方案2】:

    给定timestamp 字段的此问题的通用查询将是这样的:

    SELECT p1.personID, COUNT(*)
    FROM payments p1
    JOIN payments p2 ON
      p1.timestamp < p2.timestamp
      AND p1.personID = p2.personID
      AND NOT EXISTS (-- exclude combinations of p1 and p2 where p exists between them
       SELECT * FROM payments p 
       WHERE p.personID = p1.personID
       AND p.timestamp > p1.timestamp 
       AND p.timestamp < p2.timestamp)
    WHERE
      DATEDIFF(p2.timestamp, p1.timestamp) >= 90
    GROUP BY p1.personID
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-17
      • 1970-01-01
      • 1970-01-01
      • 2012-10-28
      相关资源
      最近更新 更多