sqlsql-serverpivotsapb1

Pivoting a table by month and year


Let's start with the query I already have, just for reference:

/*Parameter Area*/
/*SELECT FROM [dbo].[OSRT] P0*/
declare @StartDate as datetime
/* WHERE */
set @StartDate = /* P0.FromDate */ '[%0]'

/*SELECT FROM [dbo].[OSRT] P1*/
declare @EndDate as datetime
/* WHERE */
set @EndDate = /* P1.ToDate */ '[%1]'

/* SELECT FROM [dbo].[OCRD] P2 */
declare @Vendor as varchar(30)
set @Vendor =  /* P2.CardCode */ '[%2]'

/* SELECT FROM [dbo].[OSRN] P4 */
declare @Serial as varchar(30)
set @Serial = /* P4.DistNumber */ '[%4]'

 
 declare @months as int
 declare @i as int
 declare @cols as NVARCHAR(MAX)
 set @months=MONTH(@EndDate)-Month(@StartDate)
 declare @query as NVARCHAR(MAX)
 
  SET @cols = STUFF((SELECT distinct ',' + (CONVERT(CHAR(4), c.DocDate, 100) + CONVERT(CHAR(4), c.DocDate, 120))
            FROM OPCH c
            WHERE (c.DocDate BETWEEN @StartDate and @EndDate)
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')
 
 
 
SET @query = 'Select Q1.CardCode, Q1.ItemCode, Q1.Dscription,'+  @cols +'

FROM (SELECT P1.CardCode, P1.DocDate, P2.ItemCode, P2.Dscription, P2.U_Unit, P2.U_UnitSer, P2.LineTotal FROM OPCH P1 INNER JOIN PCH1 P2 ON P1.DocEntry=P2.DocEntry) Q1 

WHERE (Q1.ItemCode Like ''PROM%'' AND Q1.CardCode=@Vendor AND Q1.DocDate BETWEEN @StartDate and @EndDate) OR (Q1.ItemCode Like ''PROM%'' AND Q1.U_UnitSer=@Serial AND Q1.DocDate BETWEEN @StartDate and @EndDate)

GROUP BY Q1.CardCode, Q1.ItemCode, Q1.Dscription

Pivot
(sum(Q1.LineTotal) FOR ((CONVERT(CHAR(4), Q1.DocDate, 100) + CONVERT(CHAR(4), Q1.DocDate, 120))) IN ('+@cols+')) P 

Order By Q1.CardCode ASC

 
FOR BROWSE'

Execute(@query)

Now, most of this query is user prompted variables and dynamically generated columns, both of which I already know work. The essential idea is to generate an expense report queried against the employee and the vehicle number we use, then pivoted (broken down by month and further categorized by adding the year in case my end user decides to do something crazy like select multiple years.) It groups by the expense code and 'hopefully' breaks down those expenses by the month they were incurred in. I know there are probably simpler ways to obtain this data, but none of them provide that 'professional' report look I was asked to deliver.

That all being said, I've already managed to track my main problem down to the pivot table I'm using. All of my parameters and dynamic stuff generates just fine, so we can leave it aside for testing purposes and make the whole query a little more clean for you.

Let's start with a couple of tables (I want to help out with testing as much as possible):

Create table temp
(
DocEntry int,
DocDate DateTime,
CardCode NVARCHAR(50)
)

Create table templines
(
DocEntry int,
ItemCode NVARCHAR(50),
Dscription NVARCHAR(100),
U_Unit NVARCHAR(50),
U_UnitSer NVARCHAR(50),
LineTotal decimal(18,2)
)

We'll insert a few values too, just for simplicity sake:

Insert into temp (DocEntry,DocDate,CardCode)
VALUES
(1,'2013-08-08','Mike Ronnie'),
(2,'2013-09-09','Mike Ronnie'),
(3,'2013-08-10','GMC'),
(4,'2013-10-10','Mike Ronnie');

Insert into templines (DocEntry, ItemCode, Dscription, U_Unit, U_UnitSer, LineTotal)
Values
(1, 'PROM45', 'Misc Expenses', null, null, 175.89),
(1, 'PROM49', 'Hotel Expenses', null, null, 180.96),
(2, 'PROM45', 'Misc Expenses', null, null, 458.89),
(2, 'PROM38', 'Food Expenses', null, null, 222.58),
(2, 'PRO456', 'Random name', null, null, 1580.98),
(3, 'PROP589', 'Motor', 'PROE49', '400', 2897.36),
(3, 'PROM11', 'Service Labour', 'PROE49', '400', 333.89)
(4, 'PROM45', 'Misc Expenses', null, null, 150.48)

That should give a decent starting off point I hope and should emulate what I hope to accomplish in a small way. Now, we'll go with a stripped down version of the query that removes all user input variables and the dynamic generation based on that and replaces it with static information for the purpose of testing:

Select Q1.CardCode, Q1.ItemCode, Q1.Dscription,[Aug 2013],[Feb 2013],[Jul 2013],[Jan 2013],[May 2013],[Sep 2013],[Dec 2013],[Oct 2013],[Mar 2013],[Jun 2013],[Apr 2013],
 Q1.LineTotal    
FROM (SELECT P1.CardCode, P1.DocDate, P2.ItemCode, P2.Dscription, P2.U_Unit, P2.U_UnitSer, P2.LineTotal FROM temp P1 INNER JOIN templines P2 ON P1.DocEntry=P2.DocEntry) Q1     
WHERE (Q1.ItemCode Like 'PROM%' AND Q1.CardCode='Mike Ronnie' AND Q1.DocDate BETWEEN DATEADD(year,-1,GETDATE()) and GETDATE())
 OR (Q1.ItemCode Like 'PROM%' AND Q1.U_UnitSer='400' AND Q1.DocDate BETWEEN DATEADD(year,-1,GETDATE()) and GETDATE())    
GROUP BY Q1.CardCode, Q1.ItemCode, Q1.Dscription    
Pivot
(
SUM(Q1.LineTotal)
 FOR 
 (
 CONVERT(CHAR(4), Q1.DocDate, 100)
  + CONVERT(CHAR(4), Q1.DocDate, 120)
  ) IN 
  ([Aug 2013],[Feb 2013],[Jul 2013],[Jan 2013],[May 2013],[Sep 2013],[Dec 2013],[Oct 2013],[Mar 2013],[Jun 2013],[Apr 2013])
  ) as P     
  
  Order By Q1.CardCode ASC       
  
  FOR BROWSE

Now is where I run into errors:

Msg 156, Level 15, State 1, Line 4 Incorrect syntax near the keyword 'Pivot'.

I admit, I don't know enough about pivot tables to troubleshoot what I'm doing wrong here.

I'm sorry for the massive explanation, but any help you can give me would be greatly appreciated.


Solution

  • I believe your issue has to do with the PIVOT syntax. Give this a shot:

    SELECT CardCode, ItemCode, Dscription,
        [Aug 2013], [Feb 2013], [Jul 2013], [Jan 2013], [May 2013],
        [Sep 2013], [Dec 2013], [Oct 2013], [Mar 2013], [Jun 2013], [Apr 2013]
    FROM (
        SELECT CONVERT(CHAR(4), P1.DocDate, 100) + CONVERT(CHAR(4), P1.DocDate, 120) AS DocDate,
            P1.CardCode, P2.ItemCode, P2.Dscription, P2.U_Unit, P2.U_UnitSer, P2.LineTotal
        FROM temp AS P1
            INNER JOIN templines AS P2 ON
                P1.DocEntry = P2.DocEntry
        WHERE (P2.ItemCode LIKE 'PROM%' AND P1.CardCode='Mike Ronnie' AND
            P1.DocDate BETWEEN DATEADD(YEAR, -1, GETDATE()) AND GETDATE()) OR
            (P2.ItemCode LIKE 'PROM%' AND P2.U_UnitSer='400' AND P1.DocDate BETWEEN
                DATEADD(YEAR, -1, GETDATE()) AND GETDATE())) AS src
    PIVOT (
        SUM(LineTotal) FOR src.DocDate IN ([Aug 2013], [Feb 2013], [Jul 2013], [Jan 2013],
            [May 2013], [Sep 2013], [Dec 2013], [Oct 2013], [Mar 2013], [Jun 2013], [Apr 2013])
        ) AS pvt
    

    The major change here is that all of the "source data" are pushed into a single inline FROM clause (the "source table"), which is required for PIVOT to work properly. I did remove a GROUP BY statement, so double-check to ensure that the data are coming out as expected (and re-add it in the inline source table if needed). If you want a row total, you'll need to add up each individual month in the top SELECT statement, taking NULL into account (e.g. ISNULL([Aug 2013], 0) + ISNULL([Feb 2013], 0) +...).