Запрос SQL Server без использования циклов - PullRequest
3 голосов
/ 20 января 2011

У меня есть Payment таблица, которая выглядит примерно так:

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, знает, как сделать это с помощью запроса?

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

Ответы [ 4 ]

5 голосов
/ 20 января 2011

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

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

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

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
1 голос
/ 20 января 2011

Это дает вам все три платежа в течение 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
1 голос
/ 20 января 2011

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

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 голосов
/ 20 января 2011

Я думал о:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...