2011-01-20 2 views
3

у меня есть Payment таблицу, которая выглядит немного как это:SQL Server без использования петель

Id (int identity) 
CustomerId (int) 
PaymentDate (SmallDateTime) 

Теперь я хочу написать запрос, который будет найти тех клиентов, которые сделали три платежа в течение трех месяцы. Учитывая следующие данные:

Id CustomerId PaymentDate (YYYY-MM-DD) 
------------------------------------------ 
1 1   2010-01-01 
2 1   2010-02-01 
3 1   2010-03-01 
4 1   2010-06-01 
5 2   2010-04-01 
6 2   2010-05-01 
7 2   2010-06-01 
8 2   2010-07-01 

Я хотел бы, чтобы произвести следующий результат:

CustomerId LastPaymentDateInPeriod 
------------------------------------- 
1    2010-03-01 
2    2010-07-01 

Где LastPaymentDateInPeriod является PaymentDate с наибольшим значением в течение трех месяцев. Если для данного клиента существует более одного трехмесячного периода, ему нужно будет вернуть наибольшее значение с самого последнего периода (это то, что я попытался проиллюстрировать для клиента 2 в приведенном выше примере). Примечание, что три платежа в течение трех последовательных дней также отвечают критериям. Платежи должны пройти в течение трех месяцев.

Я знаю, как это сделать с помощью курсора и множества небольших запросов, но это медленно (и я понял, это должно быть только крайним средством). Итак, любой из вас, гениев SqlServer, знает, как это сделать с запросом?

Заранее спасибо.

+1

Они не должны быть в 3-х отдельных месяцев? 3 платежа в течение 3 последовательных дней отвечали бы критериям? –

+0

@ Мартин, да 3 последовательных дня также отвечают критериям. –

+0

Если даты были 'Jan-31, Feb-28, Apr-3', то в течение 3 месяцев? 'Jan-31 + 3m = Apr-30' – RichardTheKiwi

ответ

5

Это должно сделать работу:

select 
    CustomerID, 
    max(LastPaymentDateInPeriod) as LastPaymentDateInPeriod 
from 
(
    select 
     LastPaymentInPeriod.CustomerID, 
     LastPaymentInPeriod.PaymentDate as LastPaymentDateInPeriod 
    from Payment LastPaymentInPeriod 
     inner join Payment RelatedPayment on 
     LastPaymentInPeriod.CustomerID = RelatedPayment.CustomerID and 
     LastPaymentInPeriod.PaymentDate > RelatedPayment.PaymentDate and 
     datediff(m, RelatedPayment.PaymentDate, LastPaymentInPeriod.PaymentDate) < 3 
    group by 
     LastPaymentInPeriod.CustomerID, 
     LastPaymentInPeriod.PaymentDate 
    having 
     count(*) > 1 

) as PaymentPeriods 
group by 
    CustomerID 

обновление: Я проверил это сейчас, и это, кажется, работает для @ данных Мартина

Update2: Если это требование, что Ян 31 и 1 апреля следует рассматривать как менее чем на 3 месяца, тогда вызов функции DATEDIFF можно заменить примерно следующим образом:

create function fn_monthspan 
(
    @startdate datetime, 
    @enddate datetime 
) 
returns int 
as 
begin 
    return datediff(m, @startdate, @enddate) - case when datepart(d, @startdate) > datepart(d, @enddate) then 1 else 0 end 
end 
+0

NB: только что отредактировал это, поскольку датифик был назад –

+0

как вы обеспечиваете 3 платежа? –

+0

таблица с псевдонимом, как LastPaymentInPeriod, является первой, тогда счетчик (*)> 1 гарантирует, что есть как минимум еще 2 –

1

Немного поспешной работы, когда я ухожу.

declare @T TABLE 
(
Id int, 
CustomerId int, 
PaymentDate SmallDateTime 
) 
insert into @T 
SELECT 1, 1,'2010-01-01' UNION ALL 
SELECT 2, 1,'2010-02-01' UNION ALL 
SELECT 3, 1,'2010-03-01' UNION ALL 
SELECT 4, 1,'2010-06-01' UNION ALL 
SELECT 5, 2,'2010-04-01' UNION ALL 
SELECT 6, 2,'2010-05-01' UNION ALL 
SELECT 7, 2,'2010-06-01' UNION ALL 
SELECT 8, 2,'2010-07-01' 

;with CTE1 AS 
(
SELECT Id, CustomerId, PaymentDate, ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY PaymentDate) RN 
FROM @T 
), CTE2 AS 
(
SELECT C1.Id, C1.CustomerId, MAX(C2.PaymentDate) AS LastPaymentDateInPeriod 
FROM CTE1 C1 LEFT JOIN CTE1 C2 ON C1.CustomerId = C2.CustomerId AND C2.RN BETWEEN C1.RN AND C1.RN + 2 and C2.PaymentDate <=DATEADD(MONTH,3,C1.PaymentDate) 
GROUP BY C1.Id, C1.CustomerId 
HAVING COUNT(*)=3 
) 
SELECT CustomerId, MAX(LastPaymentDateInPeriod) LastPaymentDateInPeriod 
FROM CTE2 
GROUP BY CustomerId 
+0

Большое спасибо за альтернативное решение. +1 –

1

Это дает вам все три платежа в течение 3-месячного периода.

; 
WITH CustomerPayments AS 
(
      SELECT 1 Id, 1 CustomerId,   Convert (DateTime, '2010-01-01') PaymentDate 
    UNION SELECT 2, 1,   '2010-02-01' 
    UNION SELECT 3, 1,   '2010-03-01' 
    UNION SELECT 4, 1,   '2010-06-01' 
    UNION SELECT 5, 2,   '2010-04-01' 
    UNION SELECT 6, 2,   '2010-05-01' 
    UNION SELECT 7, 2,   '2010-06-01' 
    UNION SELECT 8, 2,   '2010-07-01' 
    UNION SELECT 9, 3,   '2010-07-01' 
    UNION SELECT 10, 3,   '2010-07-01' 
), 
FirstPayment AS 
(
    SELECT Id, CustomerId, PaymentDate 
    FROM CustomerPayments 
    where Id IN 
    (
     SELECT Min (Id) Id 
     FROM CustomerPayments 
     Group by CustomerId 
    ) 
), 
SecondPayment AS 
(
    SELECT Id, CustomerId, PaymentDate 
    FROM CustomerPayments 
    where Id IN 
    (
     SELECT Min (Id) Id 
     FROM CustomerPayments 
     WHERE ID NOT IN 
     (
      SELECT ID 
      from FirstPayment 
     ) 
     Group by CustomerId 
    ) 
), 
ThirdPayment AS 
(
    SELECT Id, CustomerId, PaymentDate 
    FROM CustomerPayments 
    where Id IN 
    (
     SELECT Min (Id) Id 
     FROM CustomerPayments 
     WHERE ID NOT IN 
     (
      SELECT ID 
      from FirstPayment 
      UNION 
      SELECT ID 
      from SecondPayment 
     ) 
     Group by CustomerId 
    ) 
) 
SELECT * 
FROM 
    FirstPayment FP 

    Left JOIN SecondPayment SP 
     ON FP.CustomerId = SP.CustomerId 

    Left JOIN ThirdPayment TP 
     ON SP.CustomerId = TP.CustomerId 

WHERE 1=1 
    AND SP.PaymentDate IS NOT NULL 
    AND TP.PaymentDate IS NOT NULL 
    AND ABS (DATEDIFF (mm, SP.PaymentDate, TP.PaymentDate)) <3 
+0

Это кажется очень сложным и не очень гибким. Что происходит, когда руководство (передумает) и хочет получить последние 4 платежа ?. –

+0

Это выглядит немного сложным, но +1 для усилий. –

0

Я подумал:

select customerId,max(PaymentDate) from payment where customerId in
(select case when count(*)<3 then null else customerId end as customerId from payment where paymentdate>dateadd(month,-3,getdate()) group by customerId)
group by customerId;

Смежные вопросы