I have the following dataset
Account Contact
1 324324324
1 674323234
2 833343432
2 433243443
3 787655455
4 754327545
4 455435435
5 543544355
5 432455553
5 432433242
5 432432432
I'd like output as follows:
Account Contact1 Contact2 Contact3 Contact4
1 324324324 674323234
2 833343432 433243443
3 787655455
4 754327545 455435435
5 543544355 432455553 432433242 432432432
The problem is also that I have an unfixed amount of Accounts & unfixed amount of Contacts
If you are going to apply the PIVOT function, you will need to use an aggregate function to get the result but you will also want to use a windowing function like row_number() to generate a unique sequence for each contact in the account.
First, you will query your data similar to:
select account, contact,
'contact'
+ cast(row_number() over(partition by account
order by contact) as varchar(10)) seq
from yourtable
See SQL Fiddle with Demo. This will create a new column with the unique sequence:
| ACCOUNT | CONTACT | SEQ |
|---------|-----------|----------|
| 1 | 324324324 | contact1 |
| 1 | 674323234 | contact2 |
If you have a limited number of columns, then you could hard-code your query:
select account,
contact1, contact2, contact3, contact4
from
(
select account, contact,
'contact'
+ cast(row_number() over(partition by account
order by contact) as varchar(10)) seq
from yourtable
) d
pivot
(
max(contact)
for seq in (contact1, contact2, contact3, contact4)
) piv;
See SQL Fiddle with Demo
If you have an unknown number of columns, then you will have to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(seq)
from
(
select 'contact'
+ cast(row_number() over(partition by account
order by contact) as varchar(10)) seq
from yourtable
) d
group by seq
order by seq
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT account, ' + #cols + '
from
(
select account, contact,
''contact''
+ cast(row_number() over(partition by account
order by contact) as varchar(10)) seq
from yourtable
) x
pivot
(
max(contact)
for seq in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. Both will give you a result of:
| ACCOUNT | CONTACT1 | CONTACT2 | CONTACT3 | CONTACT4 |
|---------|-----------|-----------|-----------|-----------|
| 1 | 324324324 | 674323234 | (null) | (null) |
| 2 | 433243443 | 833343432 | (null) | (null) |
| 3 | 787655455 | (null) | (null) | (null) |
| 4 | 455435435 | 754327545 | (null) | (null) |
| 5 | 432432432 | 432433242 | 432455553 | 543544355 |
Just a slightly different way to generate the dynamic PIVOT:
DECLARE #c INT;
SELECT TOP 1 #c = COUNT(*)
FROM dbo.YourTable
GROUP BY Account
ORDER BY COUNT(*) DESC;
DECLARE #dc1 NVARCHAR(MAX) = N'', #dc2 NVARCHAR(MAX) = N'', #sql NVARCHAR(MAX);
SELECT #dc1 += ',Contact' + RTRIM(i), #dc2 += ',[Contact' + RTRIM(i) + ']'
FROM (SELECT TOP (#c) i = number + 1
FROM master.dbo.spt_values WHERE type = N'P' ORDER BY number) AS x;
SET #sql = N'SELECT Account ' + #dc1 +
' FROM (SELECT Account, Contact, rn = ''Contact''
+ RTRIM(ROW_NUMBER() OVER (PARTITION BY Account ORDER BY Contact))
FROM dbo.YourTable) AS src PIVOT (MAX(Contact) FOR rn IN ('
+ STUFF(#dc2, 1, 1, '') + ')) AS p;';
EXEC sp_executesql #sql;
SQLiddle demo
Related
I have the following code fragment which gives me the current results below. I'm attempting to add an additional column for each of my pivoted values in order to include the lastview data for each of my siteuserid / tagname combo (see expected results). Since this column isn't an aggregation, I don't believe an additional pivot would help. I've tried multiple ways of adding lastview, but it always results in additional rows rather than the desired output.
create table #taghits (userid int, email varchar(20), tagname varchar(20), hits int, lastview date)
insert into #taghits select 1, 'email1#here.com', 'tag1', 3, '2020-03-24';
insert into #taghits select 2, 'email2#here.com', 'tag1', 1, '2020-03-17';
insert into #taghits select 2, 'email2#here.com', 'tag2', 1, '2020-03-18';
insert into #taghits select 3, 'email3#here.com', 'tag1', 2, '2020-03-25';
insert into #taghits select 3, 'email3#here.com', 'tag2', 5, '2020-03-28';
select * from #taghits;
DECLARE #Columns3 as NVARCHAR(MAX)
SELECT #Columns3 = ISNULL(#Columns3 + ', ','') + QUOTENAME(TagName)
FROM (
select distinct TagName
from #taghits
) AS TagNames
ORDER BY TagNames.TagName
DECLARE #scolumns as NVARCHAR(MAX)
SELECT #scolumns = ISNULL(#Scolumns + ', ','')+ 'ISNULL(' + QUOTENAME(TagName) + ', 0) AS '+ QUOTENAME(TagName)
FROM (select distinct TagName
from #taghits) AS TagNames
ORDER BY TagNames.TagName
DECLARE #SQL as NVARCHAR(MAX)
SET #SQL = '
select userid, email, ' + #scolumns + '
from
(
select userid, email, tagname, hits
from #taghits
) as TagHits
PIVOT (
SUM(hits)
FOR TagName IN (' + #Columns3 + ')
) AS PivotTable
order by userId
'
exec sp_executesql #SQL;
Current Result
| userid | email | tag1 | tag2 |
|--------|-----------------|------|------|
| 1 | email1#here.com | 3 | 0 |
| 2 | email2#here.com | 1 | 1 |
| 3 | email3#here.com | 2 | 5 |
Desired Result
| userid | email | tag1_hits | tag1_lastview | tag2_hits | tag2_lastview |
|--------|-----------------|-----------|---------------|-----------|---------------|
| 1 | email1#here.com | 3 | 2020-03-24 | 0 | null |
| 2 | email2#here.com | 1 | 2020-03-17 | 1 | 2020-03-18 |
| 3 | email3#here.com | 2 | 2020-03-25 | 5 | 2020-03-28 |
try the following:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((select distinct ',
SUM(CASE WHEN tagname=''' + CAST(tagname as varchar(10)) + ''' THEN [hits] ELSE 0 END) AS [' + CAST(tagname as varchar(10)) + '_hits],
MAX(CASE WHEN tagname=''' + CAST(tagname as varchar(10)) + ''' THEN [lastview] ELSE NULL END) AS [' + CAST(tagname as varchar(10)) + '_lastview]'
/*---------------You can add other columns here similar to above--------------*/
FROM #taghits
FOR XML PATH(''),type).value('.','varchar(max)'),1,2,'')
SET #query = 'SELECT userid, email, ' + #Cols + ' FROM #taghits group by userid, email'
print (#query)
exec(#query)
Please see db<>fiddle here.
I have a dynamic SQL query that gets me result sets after execution. However, the UI model that I am rendering results back from SQL server engine doesn't provide a way to render query column names.
Due to the dynamic nature of the query, I can't hard code the column names at design time. So my question is how do I get column names along with the data set returned by the query?
This Query:
DECLARE #SQLSTATMENT nvarchar(1000)
SELECT #SQLSTATEMENT = '
SELECT
convert(date, DATEADDED) DATEADDED
,COUNT(1) as NUMBEROFRECORDS
FROM
dbo.CONSTITUENT
GROUP BY
convert(date, DATEADDED)
ORDER BY
convert(date, DATEADDED) DESC
'
Exec (#SQLSTATEMENT);
Gives me this table (Original Image):
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
+ ---------- + --------------- +
| 2017-03-14 | 1 |
| 2017-03-10 | 1 |
| 2016-07-07 | 5 |
| 2016-06-29 | 3 |
| 2016-06-15 | 1 |
| 2014-11-11 | 465 |
| 2005-06-09 | 11 |
| 2005-04-13 | 1 |
| 2005-02-28 | 2 |
+ ---------- + --------------- +
But I want this (Original Image):
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
| 2017-03-14 | 1 |
| 2017-03-10 | 1 |
| 2016-07-07 | 5 |
| 2016-06-29 | 3 |
| 2016-06-15 | 1 |
| 2014-11-11 | 465 |
| 2005-06-09 | 11 |
| 2005-04-13 | 1 |
| 2005-02-28 | 2 |
+ ---------- + --------------- +
Thanks
It's doable, but not very pretty. A Stored Procedure where you pass the dynamic SQL would be much cleaner
We're essentially doing Dynamic SQL within Dynamic SQL
One caveat: I reserved the field RN
Example (Using my FRED Series Data)
-- This is Your Base/Initial Query, or the only portion you need to supply
Declare #SQL varchar(max) = 'Select Updated as Updated,Count(*) as NumberOfRecords From [dbo].[FRED-Series] Group By Updated'
Select #SQL = '
;with cte0 as ('+#SQL+')
, cte1 as (Select *,RN = Row_Number() over (Order By (Select null)) From cte0 )
, cte2 as (
Select A.RN,C.*
From cte1 A
Cross Apply (Select XMLData=cast((Select A.* for XML Raw) as xml)) B
Cross Apply (
Select Item = attr.value(''local-name(.)'',''varchar(100)'')
,Value = attr.value(''.'',''varchar(max)'')
,ColNr = Row_Number() over (Order By (Select Null))
From B.XMLData.nodes(''/row'') as A(r)
Cross Apply A.r.nodes(''./#*'') AS B(attr)
Where attr.value(''local-name(.)'',''varchar(100)'') not in (''RN'')
) C
)
Select Distinct RN=0,Item,Value=Item,ColNr Into #Temp From cte2 Union All Select * from cte2
Declare #SQL varchar(max) = Stuff((Select '','' + QuoteName(Item) From #Temp Where RN=0 Order by ColNr For XML Path('''')),1,1,'''')
Select #SQL = ''Select '' + #SQL + '' From (Select RN,Item,Value From #Temp ) A Pivot (max(Value) For [Item] in ('' + #SQL + '') ) p''
Exec(#SQL);
'
Exec(#SQL)
Returns
Updated NumberOfRecords
Updated NumberOfRecords
2017-03-22 597
2017-03-23 40
2017-03-20 228
2017-03-21 1404
Just some Commentary
cte0 is your primary query
cte1 will take the results of your initial query and add a Row Number
cte2 will dynamically unpivot your data
The results of cte2 are dropped into a #temp table for convenience (assuming this is allowed)
Then we perform a dynamic pivot
Union a static query with the column names. You have to cast the results of the second query to varchar or nvarchar so they are the same data type as your column names.
DECLARE #SQLSTATMENT nvarchar(1000)
SELECT #SQLSTATEMENT = '
SELECT
''DATEADDED'' AS [DATEADDED]
,''NUMBEROFRECORDS'' AS [NUMBEROFRECORDS]
SELECT
CAST(convert(date, DATEADDED) AS NVARCHAR(MAX)
,CAST(COUNT(1) AS NVARCHAR(MAX))
FROM
dbo.CONSTITUENT
GROUP BY
convert(date, DATEADDED)
ORDER BY
convert(date, DATEADDED) DESC
'
Exec (#SQLSTATEMENT);
With this said, you should be able to reference the column names via code and not have to add them to the query. This way you could keep the data types of the result set.
example Code
Declare
#table1 VARCHAR(MAX)
Set #table1 = 'Select * from #tempTbl'
Declare
#List VARCHAR(MAX),
#Pivot VARCHAR(MAX)
Select #List = ISNULL(#List + ',', '') + TrxCd From TransacMaster Where Module = 'CB'
Set #Pivot = '
SELECT ROW_NUMBER() OVER (ORDER BY DebtorCd ASC) As RowIndex, *
FROM (
Select Distinct
c.UserId,
d.Name,
b.TrxCd
SUM(b.Amount) As Amount
From tbl_InvH a
Inner Join tbl_InvD b on a.subCd = b.subCd and a.InvNo = b.InvNo
Left Join tbl_User c On a.UserId = c.UserId
Left Join tbl_Personnel d on c.PersonnelId = d.PersonnelId
Group By c.UserId, d.Name, b.TrxCd
) as s
PIVOT
(
SUM(Amount)
FOR TrxCd IN (' + #List + ')
)AS pvt'
Declare #Result nVarchar(MAX)
Set #Result = #table1 + '
Union All
' + #Pivot
Exec sp_executesql #Result
I want to Convert Field Amount from decimal to String, because after this I want to UNION with another tables, but field amount and field from another table is different type.
I have tried CAST(SUM(Amount) as Varchar) But Error :
'CAST' is not a recognized aggregate function.
I can't Convert on Main Select because Field of TrxCd is Dynamic
Result Of Pivot
RowIndex | UserId | Name | IT01 | IT02 | IT03 | IT04
--------------------------------------------------------------------------------
1 | John | John Ivy | 2,000 | 2,000 | 1,000 | 5,000
2 | Merry | Merry Ish | 1,000 | 1,000 | 1,000 | 6,000
other Table
RowIndex | UserId | Name | Transac1 | Transac2 | Transac3 | Transac4
-------------------------------------------------------------------------------------------------
1 | John | John Ivy | Trx Bank A | Trx Bank B | Trx Bank C | Trx Bank D
What should I do to Convert Field Amount from Pivot.
Because of the dynamic nature, you could take the same #List expression Select #List = ISNULL(#List + ',', '') + TrxCd From TransacMaster Where Module = 'CB' and create a second for the dynamic casting in the main select;
Select #List2 = ISNULL(#List2 + ',', '') + 'cast(' + TrxCd + 'as varchar)' From TransacMaster Where Module = 'CB'
Then use this and additional columns required to replace the asterix in the main select;
'SELECT ROW_NUMBER() OVER (ORDER BY DebtorCd ASC) As RowIndex, UserId, Name,' + #List2 +'....'
I am working on a double dynamic pivot based on 2 columns (HardwarePhase & HardwarePhase_Result).
Using the first result set in the image below, is the raw data that I have. Each set of 5 items (highlighted in images) are grouped based on HardwareTestCaseID.
The second result set in the image, is the current results that I'm getting from how I've constructed this query. Ideally, the result of the second column would be the same results, but instead it would be grouping the results.
The grouping is supposed to be based on the HardwareTestCaseID, but however, this is not happening.
The results I actually want are shown here. (There should be multiple rows, but this is just how it should be grouped per 5 entries).
This is the query I am currently using:
NOTE: The #col variables are built up based on the list of HardwarePhases (P0, M1, M2, M3).
select #query = 'SELECT ' + #colsNames + ',' + #colsResultNames + ', HardwareTestCaseID FROM
(
SELECT HardwarePhase_Result, HardwarePhase, ResultValue, HardwareTestCaseID, HardwareStatus
FROM #temp4
) as x
pivot
(
MAX(ResultValue)
FOR HardwarePhase_Result IN (' + #colsResult + ')
) as p
pivot
(
MAX(HardwareStatus)
FOR HardwarePhase IN (' + #cols + ')
) as p2 ';
using this table:
create table #temp4
(
HardwarePhase nvarchar(max),
HardwarePhase_Result nvarchar(max),
ResultValue bigint,
HardwareTestCaseID bigint,
HardwareStatus nvarchar(max),
Block nvarchar(max)
);
I personally would do it slightly different since you want to PIVOT on two columns. I would look at unpivoting the data in the multiple columns first, then apply the PIVOT function. I also would suggest that you start with writing a hard-coded version of the query first then convert it to dynamic SQL - this allows you to get the correct logic.
To unpivot the data, I would use CROSS APPLY so you can convert the pairs of columns into rows at the same time, the syntax would be similar to the following:
select col, value, HardwareTestCaseID
from temp4
cross apply
(
select HardwarePhase, HardwareStatus union all
select HardwarePhase_Result, cast(ResultValue as varchar(10))
) c (col, value)
See SQL Fiddle with Demo. Your data is then in the format:
| COL | VALUE | HARDWARETESTCASEID |
|-----------|-------------|--------------------|
| P0 | Not Started | 365 |
| P0_Result | 1 | 365 |
| M1 | Pass | 365 |
| M1_Result | 1 | 365 |
| M4 | Pass | 365 |
| M4_Result | 1 | 365 |
| M2 | Blocked | 365 |
| M2_Result | 1 | 365 |
Then you just apply the pivot function to the data:
select M1, M2, M3, M4, P0,
M1_Result, M2_Result, M3_Result,
M4_Result, P0_Result,
HardwareTestCaseID
from
(
select col, value, HardwareTestCaseID
from temp4
cross apply
(
select HardwarePhase, HardwareStatus union all
select HardwarePhase_Result, cast(ResultValue as varchar(10))
) c (col, value)
) d
pivot
(
max(value)
for col IN (M1, M2, M3, M4, P0,
M1_Result, M2_Result, M3_Result,
M4_Result, P0_Result)
) piv;
See SQL Fiddle with Demo.
Once you have the logic down, then convert it to dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col)
from temp4
cross apply
(
select HardwarePhase, 1 union all
select HardwarePhase_Result, 2
) c (col, so)
group by col, so
order by so, col
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + ' , HardwareTestCaseID
from
(
select col, value, HardwareTestCaseID
from temp4
cross apply
(
select HardwarePhase, HardwareStatus union all
select HardwarePhase_Result, cast(ResultValue as varchar(10))
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. This process gets a result:
| M1 | M2 | M3 | M4 | P0 | M1_RESULT | M2_RESULT | M3_RESULT | M4_RESULT | P0_RESULT | HARDWARETESTCASEID |
|---------|---------|---------|---------|-------------|-----------|-----------|-----------|-----------|-----------|--------------------|
| Pass | Blocked | Pass | Pass | Not Started | 1 | 1 | 1 | 1 | 1 | 365 |
| Blocked | Blocked | Blocked | Blocked | Pass | 1 | (null) | 1 | 1 | 1 | 366 |
--This is Just AWESOME. Simplified it for just one table as it's a much more common case (and could not find anything even close to this elegant after trying for hours)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(your_key_column)
from YOUR_ORIGINAL_KEY_AND_VALUE_TABLE
group by your_key_column
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT your_row_heading_columns,' + #cols + '
INTO YOUR_NEW_PIVOTED_TABLE
from
(
select your_row_heading_columns,your_key_column,your_value_column
from YOUR_ORIGINAL_KEY_AND_VALUE_TABLE
) x
pivot
(
max(your_value_column)
for your_key_column in (' + #cols + ')
) p '
execute sp_executesql #query;
Currently have a script that creates a pivot table with current year values subtraction prior year values.
use devmreports
-- Creates dynamic values for pivot table
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(month)
from ABR
group by ',' + QUOTENAME(month)
order by datalength(',' + QUOTENAME(month))
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
-- Pivot table for YOY change in booked passengers
set #query
=
'SELECT Region,
CityPair,
Year,
' + #cols + '
FROM
(
SELECT ABR.Region,
ABR.CityPair,
ABR.Year,
ABR.Month,
ABR.Adv_B - ABRP.Adv_B as Total
FROM ABR LEFT OUTER JOIN
ABRP ON
ABR.Month = ABRP.Month AND
ABR.CityPair = ABRP.CityPair) P
PIVOT
(
SUM(Total)
FOR MONTH IN
(
'+#cols+'))as pvt'
execute (#Query)
Current Pivot looks like this:
+------------+----------+----+-----+-----+----+
| Region | CityPair | 8 | 9 | 10 | 11 |
+------------+----------+----+-----+-----+----+
| A | 1 | 16 | 17 | 18 | 7 |
| A | 2 | 17 | -20 | -10 | 1 |
| B | 3 | 5 | 8 | 4 | -3 |
| B | 4 | 21 | 10 | 3 | 2 |
| C | 5 | 15 | -14 | -12 | 1 |
+------------+----------+----+-----+-----+----+
What I would like to have is this:
+-----------------+----------+----+-----+-----+----+
| Region | CityPair | 8 | 9 | 10 | 11 |
+-----------------+----------+----+-----+-----+----+
| A | 1 | 16 | 17 | 18 | 7 |
| A | 2 | 17 | -20 | -10 | 1 |
| A Total | | 33 | -3 | 8 | 8 |
| B | 3 | 5 | 8 | 4 | -3 |
| B | 4 | 21 | 10 | 3 | 2 |
| B Total | | 26 | 18 | 7 | -1 |
| C | 5 | 15 | -14 | -12 | 1 |
| C Total | | 15 | -14 | -12 | 1 |
| Grand Total | | 74 | 1 | 3 | 8 |
+-----------------+----------+----+-----+-----+----+
Any assistance would be greatly appreciated.
My suggestion would be to look at using GROUP BY ROLLUP to get the total rows.
The basic syntax if you were hard-coding the query would be:
select
case
when region is null then 'Grand Total'
when citypair is null then region +' Total'
else region end region,
coalesce(cast(citypair as varchar(10)), '') citypair,
sum([8]) [8],
sum([9]) [9]
from
(
select region, citypair, month, total
from yourtable
) d
pivot
(
sum(total)
for month in ([8], [9])
) piv
GROUP BY rollup(region, citypair);
See SQL Fiddle with Demo. Then to use your dynamic SQL version you could alter the code to use:
-- Creates dynamic values for pivot table
DECLARE #cols AS NVARCHAR(MAX),
#colsRollup AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(month)
from ABR
group by ',' + QUOTENAME(month)
order by datalength(',' + QUOTENAME(month))
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsRollup = STUFF((SELECT ', sum(' + QUOTENAME(month)+ ') as '+ QUOTENAME(month)
from ABR
group by ',' + QUOTENAME(month)
order by datalength(',' + QUOTENAME(month))
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
-- Pivot table for YOY change in booked passengers
set #query
=
'SELECT
case
when region is null then ''Grand Total''
when citypair is null then region +'' Total' '
else region end region,
coalesce(cast(citypair as varchar(10)), '''') citypair,
' + #colsRollup + '
FROM
(
SELECT ABR.Region,
ABR.CityPair,
ABR.Year,
ABR.Month,
ABR.Adv_B - ABRP.Adv_B as Total
FROM ABR LEFT OUTER JOIN
ABRP ON
ABR.Month = ABRP.Month AND
ABR.CityPair = ABRP.CityPair
) P
PIVOT
(
SUM(Total)
FOR MONTH IN ('+#cols+')
)as pvt
GROUP BY rollup(region, citypair);'
execute sp_executesql #Query
That's why I prefer to pivot by hand with aggregates and not by pivot. Here's a query which will show you all sums with totals and grand totals:
select
case
when grouping(Region) = 1 then 'Grand Total'
when grouping(CityPair) = 1 then Region + ' Total'
else Region
end as Region,
isnull(cast(CityPair as nvarchar(max)), '') as CityPair,
sum(case when Month = 8 then Value end) as [8],
sum(case when Month = 9 then Value end) as [9],
sum(case when Month = 10 then Value end) as [10],
sum(case when Month = 11 then Value end) as [11]
from test
group by rollup(Region, CityPair)
sql fiddle demo
Here's dynamic one:
declare #stmt nvarchar(max)
select
#stmt = isnull(#stmt + ',', '') +
'sum(case when Month = ' + cast(Month as nvarchar(max)) +
' then Value end) as [' + cast(Month as nvarchar(max)) + ']'
from (select distinct Month from test) as a
select #stmt = '
select
case
when grouping(Region) = 1 then ''Grand Total''
when grouping(CityPair) = 1 then Region + '' Total''
else Region
end as Region,
isnull(cast(CityPair as nvarchar(max)), ''''), ' + #stmt + '
from test
group by rollup(Region, CityPair)'
exec sp_executesql #stmt = #stmt
sql fiddle demo
As always, tried to make it as readable as possible.
I'm suggesting to use group by rollup instead of with rollup, because last one will be deprecated:
WITH ROLLUP This feature will be removed in a future version of
Microsoft SQL Server. Avoid using this feature in new development
work, and plan to modify applications that currently use this feature.