DaveBurns
DaveBurns

Reputation: 2096

How to get the max row number per group/partition in SQL Server?

I'm using SQL Server 2005. I have a payments table with payment id's, user id's, and timestamps. I want to find the most recent payment for each user. This is easy to search and find an answer for. What I also want to know though is if the most recent payment is the user's first payment or not.

I have the following which will number each user's payments:

SELECT
    p.payment_id,
    p.user_id,
    ROW_NUMBER() OVER (PARTITION BY p.user_id ORDER BY p.payment_date) AS paymentNumber
FROM
    payment p

I'm not making the mental leap which then lets me then pick the highest paymentNumber per user. If I use the above as a subselect by using MAX(paymentNumber) and then grouping by user_id, I lose the payment_id which I need. But if I also add the payment_id into the group by clause, I'm back to one row per payment. I'm sure I'm overlooking the obvious. Any help?

Upvotes: 32

Views: 148773

Answers (6)

Ben Thul
Ben Thul

Reputation: 32707

The query provided by OP does most of the work. All we need to do is change the ORDER BY clause provided to ROW_NUMBER() to descending at which point the most recent record will have a value of 1. I'm choosing to use a CTE as a matter of personal preference - a subquery would also be fine.

with cte as (
    SELECT
        p.payment_id,
        p.user_id,
        ROW_NUMBER() OVER (
           PARTITION BY p.user_id
           ORDER BY p.payment_date desc
        ) AS paymentNumber
    FROM
        payment p
    )
select * from cte where paymentNumber = 1

Upvotes: 3

Abhishek Mukherjee
Abhishek Mukherjee

Reputation: 19

SELECT * FROM (
        SELECT ROW_NUMBER() OVER(PARTITION BY OS.ContactId ORDER BY OS.Date ASC) AS FirstRow#,
        ROW_NUMBER() OVER(PARTITION BY OS.ContactId ORDER BY OS.Date DESC) AS LastRow#,
        OS.Contactid,CONVERT(VARCHAR,OS.Date,106) 'Purchase Month',
        OS.ProductId 'MyCII Subscription/Directory', OS.Charges 'Amount(INR)',OS.Date 'RAWDate'
        FROM tblOnlineServices OS
        WHERE Date IS NOT NULL AND Contactid IN('C000013112','C000010859') 
    ) FirstPurchase 
    WHERE FirstRow# = 1 OR LastRow# = 1
    ORDER BY Contactid, RAWDate

Upvotes: 1

MarioVW
MarioVW

Reputation: 2534

How about this?

SELECT
    p.user_id,
    MAX(p.payment_date) as lastPayment,
    CASE COUNT(p.payment_id) WHEN 1 THEN 1 ELSE 0 END as isFirstPayment
FROM
    payment p
GROUP BY
    p.user_id

Upvotes: -2

DForck42
DForck42

Reputation: 20357

a less cool way i suppose

; with maxp as
(
    select
        p.user_id,
        max(p.payment_date) as MaxPaymentDate
    from payment p
    group by p.userid
),
nump as
(
    select
        p.payment_id,     
        p.user_id,     
        p.payment_date,
        ROW_NUMBER() OVER (PARTITION BY p.user_id ORDER BY p.payment_date) AS paymentNumber 
    FROM payment p
),
a as
(
select
    nump.payment_id,
    nump.user_id,
    nump.paymentNumber
    case when maxp.MaxPaymentDate is null then 'Old' else 'New' end as NewState
from nump
    left outer join maxp
        on nump.user_id=maxp.user_id
            and nump.payment_date=maxp.MaxPaymentDate
)

select
*
from a
where NewState='New'

Upvotes: 1

btilly
btilly

Reputation: 46408

Do the same thing again.

SELECT
    p.payment_id,
    p.user_id,
    ROW_NUMBER() OVER (PARTITION BY p.user_id ORDER BY p.payment_date) AS paymentNumber,
    ROW_NUMBER() OVER (PARTITION BY p.user_id ORDER BY p.payment_date DESC) AS reversePaymentNumber,
FROM
    payment p

Now the most recent payment has reversePaymentNumber 1, and the number of payments will be paymentNumber.

Upvotes: 19

Chandu
Chandu

Reputation: 82913

Try this:

SELECT a.*, CASE WHEN totalPayments>1 THEN 'NO' ELSE 'YES' END IsFirstPayment
  FROM(
                SELECT  p.payment_id,     
                                p.user_id,     
                                ROW_NUMBER() OVER (PARTITION BY p.user_id ORDER BY p.payment_date DESC) AS paymentNumber,
                                SUM(1) OVER (PARTITION BY p.user_id) AS totalPayments
                    FROM payment p 
            ) a
WHERE   paymentNumber = 1       

Upvotes: 48

Related Questions