2011-01-20 4 views
3

J'ai une table Payment qui ressemble un peu à ceci:requête SQL Server sans utiliser des boucles

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

Maintenant, je veux écrire une requête qui trouveront les clients qui ont fait trois paiements dans un délai de trois mois. Étant donné les données suivantes:

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 

Je voudrais produire le résultat suivant:

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

LastPaymentDateInPeriod est le PaymentDate avec la valeur la plus élevée dans un délai de trois mois. S'il y a plus d'une période de trois mois pour un client donné, il devra retourner la valeur la plus élevée de la période la plus récente (c'est ce que j'ai essayé d'illustrer pour le client 2 dans l'exemple ci-dessus). Notez que trois paiements sur trois jours consécutifs répondraient également aux critères. Les paiements doivent simplement tomber dans une période de trois mois.

Je sais comment faire cela avec un curseur et beaucoup de petites requêtes mais c'est lent (et, j'ai compris, cela ne devrait être qu'un dernier recours). Alors, est-ce que l'un de vos génies SqlServer sait comment faire cela avec une requête?

Merci d'avance.

+1

Ils ne doivent pas être séparés en 3 mois? 3 paiements sur 3 jours consécutifs répondraient aux critères? –

+0

@Martin, oui 3 jours consécutifs répondraient également aux critères. –

+0

Si les dates étaient «Jan-31, Feb-28, Apr-3», est-ce dans un délai de 3 mois? 'Jan-31 + 3m = Apr-30' – RichardTheKiwi

Répondre

5

Cela devrait faire le travail:

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 

mise à jour: Je l'ai testé ce maintenant et il semble fonctionner pour @ les données de Martin

Update2: Si c'est une exigence que Jan 31 et avril 1 doivent être considérés comme étant à moins de 3 mois d'intervalle, puis l'appel de fonction DATEDIFF peut être remplacé par quelque chose comme ceci:

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: vient d'éditer cela car le datediff était en arrière –

+0

Comment assurez-vous 3 paiements? –

+0

la table qui est alias LastPaymentInPeriod est la première, puis le compte (*)> 1 s'assure qu'il y en a au moins 2 de plus –

1

Un peu d'un travail pressé comme je suis éteint.

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

Merci beaucoup pour votre solution alternative. +1 –

1

Cela vous donne tous les trois paiements dans un délai de 3 mois.

; 
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

Cela semble vraiment complexe et pas très flexible. Que se passe-t-il lorsque la direction (change d'avis) et veut obtenir les 4 derniers paiements? –

+0

Cela semble un peu complexe mais +1 pour l'effort. –

0

Je pensé:

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;