Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQL Pivot with varying number of rows and compound key to form columns

SQL pivot question with a twist, Take the following table (note year and month are seperate):

CREATE TABLE [dbo].[tbl_BranchTargets]
    (
      [BranchID] [varchar](4) NOT NULL ,
      [Year] [smallint] NOT NULL ,
      [Month] [smallint] NOT NULL ,
      [Target] [int] NULL ,
      CONSTRAINT [PK_tbl_BranchTargets] PRIMARY KEY CLUSTERED ( [BranchID] ASC, [Year] ASC, [Month] ASC ) WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]
    )
ON  [PRIMARY]

and the following dummy data:

INSERT [dbo].[tbl_BranchTargets] ([BranchID], [Year], [Month], [Target]) VALUES (N'001', 2012, 4, 1)
INSERT [dbo].[tbl_BranchTargets] ([BranchID], [Year], [Month], [Target]) VALUES (N'001', 2012, 5, 117)
INSERT [dbo].[tbl_BranchTargets] ([BranchID], [Year], [Month], [Target]) VALUES (N'001', 2012, 6, 233)
INSERT [dbo].[tbl_BranchTargets] ([BranchID], [Year], [Month], [Target]) VALUES (N'001', 2012, 7, 386)
INSERT [dbo].[tbl_BranchTargets] ([BranchID], [Year], [Month], [Target]) VALUES (N'003', 2012, 4, 2)
INSERT [dbo].[tbl_BranchTargets] ([BranchID], [Year], [Month], [Target]) VALUES (N'003', 2012, 6, 234)
INSERT [dbo].[tbl_BranchTargets] ([BranchID], [Year], [Month], [Target]) VALUES (N'003', 2012, 7, 387)

How would I model the given dummy data like this (note the year and month key columns are merged to form YYYYMM):

Raw Data

into this:

Expected Output

Note the missing entry for branch 3 in May, this needs to be handled as null. Eg, our of the 12 months, a branch might only have a target for 1 of them, so all the other months would need to be null.

I've looked into PIVOT() and clunky cursor options, but i am sturggling to find a quick best practice way of doing this, I'm assuming i need to implement some dynamic SQL + PIVOT() - but cant quite get my head round it.

I know that for dynamic pivots you first identify the column names (i think), i can do this as follows:

DECLARE @Columns AS NVARCHAR(MAX);
DECLARE @StrSQL AS NVARCHAR(MAX); 

SET @Columns = STUFF((SELECT DISTINCT
                            ',' + QUOTENAME(CONVERT(VARCHAR, c.YEAR) + RIGHT('00' + CONVERT(VARCHAR, c.MONTH), 2))
                   FROM     tbl_BranchTargets c
    FOR           XML PATH('') ,
                      TYPE 
            ).value('.', 'NVARCHAR(MAX)'), 1, 1, '') 

But how you perform the pivot is a bit beyond me (as i am essentially merging to key columns to create the final columns) - would i need to merge the data before attempting the pivot where YYYY + MM is defined as a value in 1 column?

(I am using SQL Server 2008 R2)

like image 908
HeavenCore Avatar asked Dec 21 '22 18:12

HeavenCore


2 Answers

You were very close to the final answer. You can use a PIVOT similar to the following (See SQL Fiddle with Demo):

DECLARE @Columns AS NVARCHAR(MAX)
DECLARE @StrSQL AS NVARCHAR(MAX) 

SET @Columns = STUFF((SELECT DISTINCT
                            ',' + QUOTENAME(CONVERT(VARCHAR(4), c.YEAR) + RIGHT('00' + CONVERT(VARCHAR(2), c.MONTH), 2))
                   FROM     tbl_BranchTargets c
    FOR           XML PATH('') ,
                      TYPE 
            ).value('.', 'NVARCHAR(MAX)'), 1, 1, '') 

set @StrSQL = 'SELECT branchid, ' + @Columns + ' from 
            (
                select branchid
                    , target
                    , CONVERT(VARCHAR(4), [YEAR]) + RIGHT(''00'' + CONVERT(VARCHAR(2), [MONTH]), 2) dt
                from tbl_BranchTargets
           ) x
            pivot 
            (
                sum(target)
                for dt in (' + @Columns + ')
            ) p '


execute(@StrSQL)

This will create the list of columns that you want at the execution time.

like image 162
Taryn Avatar answered Apr 08 '23 08:04

Taryn


You were close. Try this:

DECLARE @Columns AS NVARCHAR(MAX)
DECLARE @StrSQL AS NVARCHAR(MAX)

SET @Columns = STUFF((SELECT DISTINCT
                            ',' + QUOTENAME(CONVERT(VARCHAR(4), c.YEAR) + RIGHT('00' + CONVERT(VARCHAR(2), c.MONTH), 2))
                   FROM     tbl_BranchTargets c
    FOR           XML PATH('') ,
                      TYPE 
            ).value('.', 'NVARCHAR(MAX)'), 1, 1, '') 

SET @StrSQL = '
SELECT *
FROM (SELECT BranchId, 
             CONVERT(VARCHAR(4), [YEAR]) + RIGHT(''00'' + CONVERT(VARCHAR(2), [MONTH]), 2) YearMonth,
             Target
      FROM [dbo].[tbl_BranchTargets]) T
PIVOT(MIN(Target) FOR YearMonth IN ('+@Columns+')) AS PT'

EXEC(@StrSQL)

Here is a sqlfiddle to see the results.

like image 22
Lamak Avatar answered Apr 08 '23 08:04

Lamak