Get the Maximum Value from a related table based on date

  • Hi Folks,

    Can someone help with the following on SQL Server 2008...

    I have a table Customer and an Orders Table...

    I want to return back against each customer the Order number from the last order placed (highest OrderDate)...

    For example, if Customer X has 3 orders and Customer Y has 3 orders, I want X and Y customer names returned along with the the order number form each customer's last placed order.

    Setup script for the 2 tables plus data as follows...

    SET ANSI_NULLS ON

    GO

    SET QUOTED_IDENTIFIER ON

    GO

    SET ANSI_PADDING ON

    GO

    CREATE TABLE [dbo].[Customer](

    [CustomerID] [int] NOT NULL,

    [CustomerName] [varchar](50) NULL,

    CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED

    (

    [CustomerID] ASC

    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

    ) ON [PRIMARY]

    GO

    SET ANSI_PADDING OFF

    GO

    SET ANSI_NULLS ON

    GO

    SET QUOTED_IDENTIFIER ON

    GO

    SET ANSI_PADDING ON

    GO

    CREATE TABLE [dbo].[Orders](

    [OrderID] [int] NOT NULL,

    [OrderDate] [datetime] NULL,

    [OrderNumber] [varchar](50) NULL,

    [CustomerID] [int] NULL,

    CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED

    (

    [OrderID] ASC

    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

    ) ON [PRIMARY]

    GO

    SET ANSI_PADDING OFF

    GO

    INSERT INTO [Customer] ([CustomerID],[CustomerName])

    VALUES

    (1,'David'),

    (2,'John'),

    (3,'Sue')

    GO

    INSERT INTO [Orders] ([OrderID],[OrderDate],[OrderNumber],[CustomerID])

    VALUES

    (1, '2013-01-01', '0001', 1),

    (2, '2013-01-03', '0002', 1),

    (3, '2013-02-12', '0003', 1),

    (4, '2013-01-17', '0004', 2),

    (5, '2013-01-18', '0005', 2),

    (6, '2013-02-01', '0006', 2),

    (7, '2013-01-14', '0007', 3),

    (8, '2013-01-21', '0008', 3)

    GO

    What I'd Like back is...

    'David', '0003'

    'John', '0006'

    'Sue', '0008'

    So far I've got...

    SELECT A.[CustomerName],

    ISNULL(X.[MaxOrderNo], '') AS [LastOrderNumber]

    FROM [Customer] A

    LEFT OUTER JOIN (SELECT A.[CustomerID],

    ISNULL(MAX(A.[OrderNumber]), '') AS [MaxOrderNo]

    FROM [Orders] A

    GROUP BY A.[CustomerID]) X ON X.[CustomerID] = A.[CustomerID]

    However, although this gives the correct result with the current data, I need to check that the value from the order returned = the last order date as I can't guarentee that the highest order number for a customer has the highest date - this can be demonstrated by swapping the dates round on the last 2 orders (my query would give the wrong result)...

    Many thanks for any help 🙂

  • This should give you what you're after.

    with cte as (

    select c.customername, o.ordernumber, o.orderdate, row_number() over (partition by c.customername order by orderdate desc) rownum

    from customer c

    inner join Orders o on o.customerid = c.customerid)

    select customername, ordernumber

    from cte

    where rownum = 1;

  • Thanks Ed, that's given me what I needed. Many thanks.

    😀

  • Suspecting the CTE with an inner join and row numbering would be somewhat costly, I compared it against this:

    SELECT c.customername, o.ordernumber, o.orderdate

    FROM customer c

    CROSS APPLY (SELECT TOP(1) ox.ordernumber, ox.orderdate FROM orders ox ORDER BY ox.orderdate DESC) o

    On a test with 100,000 rows from a "customer" table that has a one-to-many relationship with an "order" table and an index on the "customername" in the "orders" table, the I/O was about the same, but the CROSS APPLY outperformed the INNER JOIN speedwise - it ran about three times faster.

    This is probably because the CROSS APPLY generated a much better execution plan. The CROSS APPLY execution plan was just an index seek on the "orders" table followed by a TOP operator on that result and all inner joined to the "customer" table. The INNER JOIN performed a parallelized index seek on the "orders" table followed by an inner join to the customers table, then gathered the streams for segment and sequence operations (to accomplish the row number) then performed a parallelized filter operation to get the final result. All that bouncing back and forth between parallel and non-parallel processing gets costly.

    Jason Wolfkill

  • Hi Wolfkillj,

    Many thanks to you also for taking the time to respond to my question. Unfortunately it doesn't seem to give me the correct result - all 3 customers have order 0003 returned against them. Any suggestions?

    Regards Charlotte CB 😀

  • Does this work for your case?

    WITH Orders AS

    (

    SELECT *

    ,rn=ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY OrderID DESC)

    FROM Orders

    )

    SELECT CustomerName, OrderNumber

    FROM Orders a

    JOIN Customer b ON a.CustomerID = b.CustomerID

    WHERE rn=1;


    My mantra: No loops! No CURSORs! No RBAR! Hoo-uh![/I]

    My thought question: Have you ever been told that your query runs too fast?

    My advice:
    INDEXing a poor-performing query is like putting sugar on cat food. Yeah, it probably tastes better but are you sure you want to eat it?
    The path of least resistance can be a slippery slope. Take care that fixing your fixes of fixes doesn't snowball and end up costing you more than fixing the root cause would have in the first place.

    Need to UNPIVOT? Why not CROSS APPLY VALUES instead?[/url]
    Since random numbers are too important to be left to chance, let's generate some![/url]
    Learn to understand recursive CTEs by example.[/url]
    [url url=http://www.sqlservercentral.com/articles/St

  • Hi dwain.c,

    Yep that works for me - I'll scale up my data to see how it performs against the other solutions.

    Many thanks for responding.:-D

  • Charlottecb (9/23/2013)


    Hi dwain.c,

    Yep that works for me - I'll scale up my data to see how it performs against the other solutions.

    Many thanks for responding.:-D

    You are welcome and thanks for letting me know.


    My mantra: No loops! No CURSORs! No RBAR! Hoo-uh![/I]

    My thought question: Have you ever been told that your query runs too fast?

    My advice:
    INDEXing a poor-performing query is like putting sugar on cat food. Yeah, it probably tastes better but are you sure you want to eat it?
    The path of least resistance can be a slippery slope. Take care that fixing your fixes of fixes doesn't snowball and end up costing you more than fixing the root cause would have in the first place.

    Need to UNPIVOT? Why not CROSS APPLY VALUES instead?[/url]
    Since random numbers are too important to be left to chance, let's generate some![/url]
    Learn to understand recursive CTEs by example.[/url]
    [url url=http://www.sqlservercentral.com/articles/St

  • Charlottecb (9/20/2013)


    Hi Wolfkillj,

    Many thanks to you also for taking the time to respond to my question. Unfortunately it doesn't seem to give me the correct result - all 3 customers have order 0003 returned against them. Any suggestions?

    Regards Charlotte CB 😀

    Gah! In cutting and pasting the code, I left out a key piece - the WHERE clause in the correlated subquery (without which, it's just a subquery that's not correlated to anything!). Try this:

    SELECT c.customername, o.ordernumber, o.orderdate

    FROM customer c

    CROSS APPLY (SELECT TOP(1) ox.ordernumber, ox.orderdate FROM orders ox WHERE c.customerID = ox.customerID ORDER BY ox.orderdate DESC) o

    Sorry about that mistake!

    Jason Wolfkill

  • wolfkillj (9/23/2013)


    Charlottecb (9/20/2013)


    Hi Wolfkillj,

    Many thanks to you also for taking the time to respond to my question. Unfortunately it doesn't seem to give me the correct result - all 3 customers have order 0003 returned against them. Any suggestions?

    Regards Charlotte CB 😀

    Gah! In cutting and pasting the code, I left out a key piece - the WHERE clause in the correlated subquery (without which, it's just a subquery that's not correlated to anything!). Try this:

    SELECT c.customername, o.ordernumber, o.orderdate

    FROM customer c

    CROSS APPLY (SELECT TOP(1) ox.ordernumber, ox.orderdate FROM orders ox WHERE c.customerID = ox.customerID ORDER BY ox.orderdate DESC) o

    Sorry about that mistake!

    Thanks wolfkillj:-D

Viewing 10 posts - 1 through 9 (of 9 total)

You must be logged in to reply to this topic. Login to reply