views:

79

answers:

4

The following query returns the total amount of orders, per week, for the past 12 months (for a specific customer):

SELECT DATEPART(year, orderDate) AS [year],
       DATEPART(month, orderDate) AS [month],
       DATEPART(wk, orderDate) AS [week],
       COUNT(1) AS orderCount
FROM dbo.Orders (NOLOCK)
WHERE customerNumber = @custnum
AND orderDate >= DATEADD(month, -12, GETDATE())
GROUP BY DATEPART(year, orderDate),
         DATEPART(wk, orderDate),
         DATEPART(month, orderDate)
ORDER BY DATEPART(year, orderDate),
         DATEPART(wk, orderDate)

This returns results like:

year    month    week    orderCount
2008      1        1         23
2008      3        12        5

...

As you can see, only weeks that have orders for this customer will be returned in the resultset. I need it to return a row for every week in the past 12 months... if no order exists in the week then returning 0 for orderCount would be fine, but I still need the year, week, and month. I can probably do it by creating a separate table storing the weeks of the year, then left outer join against it, but would prefer not to. Perhaps there's something in SQL that can accomplish this? Can I create a query using built in functions to return all the weeks in the past 12 months with built in SQL functions? I'm on SQL Server 2008.

Edit: Using Scott's suggestion I posted the query solving this problem below.

A: 

In the past I've done this using the table approach that you mention, the other way was to create a table function that I could pass arguments to specifying the start and end range that I wanted and it would build results dynamically to save needing to add a table with all the data.

Chris W
+2  A: 

The table method you are already aware is the best way to go. Not only does it give you alot of control, but it is the best performing.

You could write sql code (user function) to do this, but it won't be as flexible. RDBMs are made for handling sets.

mson
wish I could upvote more than once
HLGEM
+1 Valid point, I agree
Chris Klepeis
+3  A: 

You could join to a recursive CTE - something like below should give you a start...

WITH MyCte AS    
    (SELECT MyWeek = 1     
    UNION ALL     
    SELECT MyWeek + 1     
    FROM MyCte     
    WHERE MyWeek < 53)
SELECT  MyWeek, 
     DATEPART(year, DATEADD(wk, -MyWeek, GETDATE())),
     DATEPART(month, DATEADD(wk, -MyWeek, GETDATE())),
     DATEPART(wk, DATEADD(wk, -MyWeek, GETDATE()))
FROM    MyCte
Scott Ivey
+1  A: 

Solution using CTE: (thanks to Scott's suggestion)

;WITH MyCte AS    
    (SELECT     MyWeek = 1     
    UNION ALL     
    SELECT      MyWeek + 1     
    FROM        MyCte     
    WHERE       MyWeek < 53)

SELECT  myc.[year],
     myc.[month],
     myc.[week],
     isnull(t.orderCount,0) AS orderCount,
     isnull(t.orderTotal,0) AS orderTotal
FROM (SELECT  MyWeek, 
     DATEPART(year, DATEADD(wk, -MyWeek, GETDATE())) AS [year],
     DATEPART(month, DATEADD(wk, -MyWeek, GETDATE())) AS [month],
     DATEPART(wk, DATEADD(wk, -MyWeek, GETDATE())) AS [week]
     FROM    MyCte) myc

     LEFT OUTER JOIN 

     (SELECT DATEPART(year, orderDate) AS [year],
         DATEPART(month, orderDate) AS [month],
         DATEPART(wk, orderDate) AS [week],
         COUNT(1) AS orderCount,
         SUM(orderTotal) AS orderTotal
     FROM dbo.Orders (NOLOCK)
     WHERE customerID = @custnum
     AND orderDate >= DATEADD(month, -12, GETDATE())
     GROUP BY DATEPART(year, ODR_DATE),
        DATEPART(wk, orderDate),
        DATEPART(month, orderDate)) t ON t.[year] = myc.[year] AND t.[week] = myc.[week]
ORDER BY myc.[year],
      myc.[week]

Edit: just noticed one week is being duplicated (2 records for the same week)... probably a simple logical error... disregard... ID-10-T... apparently a week can span months... who would have known lol

Chris Klepeis
Creating a table to store weeks is actually a much better method. Doing this sort of calculation on the fly is unecessarily inefficient and the table will come in useful in multiple queries once you have it. Recalculating something every time you run a query when the information is static enough to be stored in table is a poor practice.
HLGEM
+ 1 to HLGEM You're certainly correct. The inefficiency of this method was not in question. I generally like to know all available options to solve a problem before digging in, and didn't think of using CTE this way. This will only be used on development and when I set it live it will use a table. Now that I know a little bit more about CTE I can use it to initially populate the new table to store the dates. Thanks for your input
Chris Klepeis