2015-07-30 5 views
1

Скажем, у меня есть таблица платежей. Мне нужно знать, сколько раз разрыв между платежами превышает 90 дней, сгруппированных по идентификатору personID. Частота оплаты меняется. Ожидаемого количества платежей нет. В течение 90 дней можно было выплатить 0 или сотни платежей. Если бы не было никакого платежа в течение года, это считалось бы 1. Если бы каждый месяц был платеж, то счет был бы равен 0. Если бы в первый месяц было 4 платежа, тогда 90-дневный разрыв, затем еще 2 платежа, тогда другой 90 день промежуток, счетчик будет 2.SQL-показатель 90 дневных промежутков между записями

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

структура таблицы? –

+1

Поскольку ваш вопрос не имеет специфики (см. Комментарии выше), я могу предоставить общий подход без SQL. Вам необходимо упорядочить платежи последовательно для каждого клиента или самостоятельно присоединить каждую строку к хронологическому «следующему». Затем для каждой из этих двух строк используйте функции даты, специфичные для СУБД, для определения количества дней между датами, сохраняя только те, у которых разница> = 90 дней. Затем group byIDID и подсчитайте номер. – Turophile

+0

Просто ищите общий подход. Структура таблицы: идентификатор, личный идентификатор, CreateDate – gene

ответ

3

Если у вас есть SQL Server 2014, вы можете использовать LAG или LEAD функция заглянуть в другие строки, что упрощает:

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 также прост; мы выбираем каждую запись, принимая к сведению человека и оцениваем ли этот платеж (точнее, задержку после этого платежа) нечасто.

В заявлении дела указано, что представляет собой нечастую оплату. Здесь мы говорим, что если платеж с датой записи плюс 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.

Я надеюсь, что это имеет смысл/соответствует вашему требованию. Скажите, если что-то неясно, и я попытаюсь улучшить свое объяснение.


EDIT

Для более старых версий SQL, тот же эффект может быть достигнут за счет использования или ROW_NUMBER и LEFT 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 
0

Общий запрос для этой задачи дано 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 
Смежные вопросы