dynamic pivot issue in sql server 2012 - sql

I'm trying to implement a dynamic pivoting in SQL to represent my results given below
ID Charge Message Amt Of Billing
4563 WEB FEE 9.75
4563 MONTHLY FEE 6
4563 CLUB FEE 9.95
4648 MONTHLY FEE 6
4648 ACCOUNT FEE 5
4648 CLUB FEE 9.95
4648 WEB FEE 9.75
4650 MONTHLY FEE 6
4650 WEB FEE 9.75
4650 CLUB FEE 9.95
into a desired representation like this.
ID ACCOUNT FEE MONTHLY FEE CLUB FEE WEB FEE
4563 6 9.95 9.75
4648 5 6 9.95 9.75
4650 6 9.95 9.75
Your help is highly appreciated.

For a dynamic pivot, you could use something like this:
declare #cols nvarchar(max);
declare #sql nvarchar(max);
select #cols = stuff((
select distinct
', ' + quotename(isnull(nullif(ChargeMessage,''),'unknown'))
from t
order by 1
for xml path (''), type).value('.','nvarchar(max)')
,1,1,'')
select #sql ='
select Id, ' + #cols +'
from (
select Id, ChargeMessage= isnull(nullif(ChargeMessage,''''),''unknown''), AmtOfBilling
from t
) as t
pivot (sum([AmtOfBilling]) for [ChargeMessage] in (' + #cols +')) p'
select #sql
exec(#sql);
rextester demo: http://rextester.com/NRRGA52425
returns: (included an empty string for test data)
+------+-------------+----------+-------------+---------+---------+
| Id | ACCOUNT FEE | CLUB FEE | MONTHLY FEE | unknown | WEB FEE |
+------+-------------+----------+-------------+---------+---------+
| 4563 | NULL | 9,95 | 6,00 | NULL | 9,75 |
| 4648 | 5,00 | 9,95 | 6,00 | NULL | 9,75 |
| 4650 | NULL | 9,95 | 6,00 | 9,95 | 9,75 |
+------+-------------+----------+-------------+---------+---------+
Query that is generated:
select Id, [ACCOUNT FEE], [CLUB FEE], [MONTHLY FEE], [unknown], [WEB FEE]
from (
select Id, ChargeMessage= isnull(nullif(ChargeMessage,''),'unknown'), AmtOfBilling
from t
) as t
pivot (sum([AmtOfBilling]) for [ChargeMessage]
in ( [ACCOUNT FEE], [CLUB FEE], [MONTHLY FEE], [unknown], [WEB FEE])) p
dynamic conditional aggregation:
declare #cols nvarchar(max);
declare #sql nvarchar(max);
select #cols = stuff((
select distinct
char(10)+' , '
+ quotename(isnull(nullif(ChargeMessage,''),'unknown'))
+' = sum(case when ChargeMessage = '''+ChargeMessage+''' then AmtOfBilling end)'
from t
order by 1
for xml path (''), type).value('.','nvarchar(max)')
,1,0,'')
select #sql ='
select Id'+#cols+'
from t
group by Id'
select #sql
exec(#sql);
Query Generated:
select Id
, [ACCOUNT FEE] = sum(case when ChargeMessage = 'ACCOUNT FEE' then AmtOfBilling end)
, [CLUB FEE] = sum(case when ChargeMessage = 'CLUB FEE' then AmtOfBilling end)
, [MONTHLY FEE] = sum(case when ChargeMessage = 'MONTHLY FEE' then AmtOfBilling end)
, [unknown] = sum(case when ChargeMessage = '' then AmtOfBilling end)
, [WEB FEE] = sum(case when ChargeMessage = 'WEB FEE' then AmtOfBilling end)
from t
group by Id

Related

How to convert columns to rows in SQL Server without pivot and value is dynamic

I have some values in rows like :
Month | Product | SalesQty
-------+---------+---------
Jan-17 | ABC | 3
Feb-17 | ABC | 6
Apr-17 | ABC | 19
But i want to show the some values in columns like:
Model| Apr-17 | May-17 | Jun-17 | Jul-17
ABC 1 2 12 0
BCS 212 12 12 112
Months must be generated dynamically. Static month will not help me in this situation.
Why not Use pivot? it is simpler than other solutions like case expression:
SELECT *
FROM table
PIVOT
(
SUM(SalesQty)
FOR Month IN([Apr-17] ,[May-17], [Jun-17], [Jul-17])
) AS p;
To do it dynamically you can use the same query with dynamic sql like this:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' +QUOTENAME(CONCAT(LEFT(datename(month, Month), 3),
CAST(DATEPART(day, month) AS NVARCHAR(2))))
FROM table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = ' SELECT *
FROM
(
SELECT product, SalesQty,
CONCAT(LEFT(datename(month, Month), 3),
CAST(DATEPART(day, month) AS NVARCHAR(2))) AS Month
FROM table1
) AS t
PIVOT
(
SUM(SalesQty)
FOR Month IN( ' + #cols + ' )
) AS p';
execute(#query);
dynamic demo
If you don't want to use PIVOT then you can use CASE expression like this:
SELECT product,
SUM(CASE WHEN month = 'Jan17' THEN SalesQty ELSE 0 END) AS Jan17,
SUM(CASE WHEN month = 'Jan17' THEN SalesQty ELSE 0 END) AS Jun17,
SUM(CASE WHEN month = 'Jan17' THEN SalesQty ELSE 0 END) AS Jul17
FROM
(
SELECT product, SalesQty,
CONCAT(LEFT(datename(month, Month), 3),
CAST(DATEPART(day, month) AS NVARCHAR(2))) AS Month
FROM table1
) AS t
GROUP BY Product;
Then to do this dynamically, you just need to replace the case expression part to by dynamic in the cols names variable.

SQL - Combine multiple rows into single row with dynamic number of columns

I'm trying to setup a query that selects multiple rows from a table and combines similar rows into a single row with multiple columns. I believe I can do this with pivot however each row won't have the same number of columns and that's where I'm running into problems. I gave an example below of what I mean.
This:
Account Period Amount
01 0001 1111
01 0002 2222
01 0003 3333
02 0001 1111
03 0001 1111
04 0001 1111
04 0002 2222
Should be come this:
Account 0001 0002 0003
01 1111 2222 3333
02 1111
03 1111
04 1111 2222
Here is my initial query that's pulling all the data together:
WITH CTE AS(
SELECT
a.Period, a.Account, SUM(a.Amount) Amount
FROM
LedgerAP a
WHERE
a.Period >= 201500
GROUP BY a.Period, a.Account
UNION
SELECT
b.Period, b.Account, SUM(b.Amount) Amount
FROM
LedgerAR b
WHERE
b.Period >= 201500
GROUP BY b.Period, b.Account
UNION
SELECT
c.Period, c.Account, SUM(c.Amount)
FROM
LedgerEx c
WHERE
c.Period >= 201500
GROUP BY c.Period, c.Account
UNION
SELECT
d.Period, d.Account, SUM(d.Amount)
FROM
LedgerMisc d
WHERE
d.Period >= 201500
GROUP BY d.Period, d.Account
)
SELECT account,
max(case when period = #Budgetyear + '01' then SUM(amount) end) Amount1,
max(case when period = #Budgetyear + '02' then SUM(amount) end) Amount2,
max(case when period = #Budgetyear + '03' then SUM(amount) end) Amount3,
max(case when period = #Budgetyear + '04' then SUM(amount) end) Amount4,
max(case when period = #Budgetyear + '05' then SUM(amount) end) Amount5,
max(case when period = #Budgetyear + '06' then SUM(amount) end) Amount6,
max(case when period = #Budgetyear + '07' then SUM(amount) end) Amount7,
max(case when period = #Budgetyear + '08' then SUM(amount) end) Amount8,
max(case when period = #Budgetyear + '09' then SUM(amount) end) Amount9,
max(case when period = #Budgetyear + '10' then SUM(amount) end) Amount10,
max(case when period = #Budgetyear + '11' then SUM(amount) end) Amount11,
max(case when period = #Budgetyear + '12' then SUM(amount) end) Amount12
FROM CTE
GROUP BY account
ORDER BY account ASC
Now how can I go about organizing this like I have shown above? Any help would be amazing!
Credit to #Bluefeet's solution here, you can build up dynamic pivot something like this:
create table table1 (Account varchar(2), Period varchar(4), Amount int)
insert into table1 values
('01', '0001', 1111),
('01', '0002', 2222),
('01', '0003', 3333),
('02', '0001', 1111),
('03', '0001', 1111),
('04', '0001', 1111),
('04', '0002', 2222);
Dynamic Query:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(t.period)
FROM table1 t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT Account, ' + #cols + ' from
(
select Account
, Period
, Amount
from table1
) x
pivot
(
max(amount)
for period in (' + #cols + ')
) p '
execute(#query)
GO
Result:
+---------+------+--------+--------+
| Account | 0001 | 0002 | 0003 |
+---------+------+--------+--------+
| 01 | 1111 | 2222 | 3333 |
| 02 | 1111 | (null) | (null) |
| 03 | 1111 | (null) | (null) |
| 04 | 1111 | 2222 | (null) |
+---------+------+--------+--------+
SQL Fiddle Demo
Your basic pivot:
SELECT
SUM(case Period when '0001' then Amount end) as '0001',
SUM(case Period when '0002' then Amount end) as '0002',
SUM(case Period when '0003' then Amount end) as '0003'
FROM LedgerAP
GROUP BY Account
To create that query dynamically, which is helpful if you have many values for Period:
DECLARE #SQL varchar(max) = 'SELECT '
;WITH Periods AS
(
SELECT DISTINCT Period
FROM LedgerAP
)
SELECT #SQL = #SQL +
'SUM(case Period when ''' + Period + ''' then Amount end) as ''' + Period + ''','
SET #SQL = LEFT(#SQL,LEN(#SQL) - 1) + ' FROM LedgerAP GROUP BY Account'
EXEC(#SQL)

Why name column in table repeats itself?

hello i have been working on this query since many days and finally almost done with it except 1 problem.
It gives me this output
N ConductorName Denomination totaltransactions totalamount
1 NULL NULL 1882 41610.00
1 Imran 30.00 199 5970.00
2 NULL Imran total 199 5970.00
1 Shoaib 30.00 99 2970.00
2 NULL Shoaib total 99 2970.00
1 Umair 10.00 792 7920.00
2 Umair 15.00 396 5940.00
3 Umair 30.00 99 2970.00
4 Umair 40.00 99 3960.00
5 Umair 60.00 198 11880.00
6 NULL Umair total 1584 32670.00
it fine but i want to eliminate name repetition in ConductorName column i.e.
Expected:
N ConductorName Denomination totaltransactions totalamount
1 NULL NULL 1882 41610.00
1 Imran 30.00 199 5970.00
2 NULL Imran total 199 5970.00
1 Shoaib 30.00 99 2970.00
2 NULL Shoaib total 99 2970.00
1 Umair 10.00 792 7920.00
2 NULL 15.00 396 5940.00
3 NULL 30.00 99 2970.00
4 NULL 40.00 99 3960.00
5 NULL 60.00 198 11880.00
6 NULL Umair total 1584 32670.00
so every name 1 time but data intact.
SP:
ALTER PROCEDURE [dbo].[ReportConductorPerformance]
#FromDate DATE,
#ToDate DATE
AS
BEGIN
SELECT ROW_NUMBER() OVER (PARTITION BY c.name order by c.name) 'N',CASE WHEN isnull(CAST(T .amount AS varchar(30)), c.name + ' total') LIKE '%total%' THEN NULL ELSE c.name END AS ConductorName,
ISNULL(CAST(T.Amount AS varchar(30)), c.Name + ' total') AS Denomination, COUNT(*) AS totaltransactions, SUM(T.Amount) AS totalamount
FROM dbo.Tickets AS T INNER JOIN
Transport.Conductors AS c ON c.ConductorID = T.Conductor_ID
WHERE CONVERT(DATE,ServerDateTime) BETWEEN #FromDate and #ToDate
GROUP BY c.Name, T.Amount WITH ROLLUP
END
While I wouldn't recommend handling this in the database, you could utilize the case statement to achieve your results since you're already using row_number:
SELECT N,
case when N = 1 then ConductorName else NULL end ConductorName,
Denomination,
totaltransactions,
totalamount
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY c.name order by c.name) 'N',
CASE WHEN isnull(CAST(T.amount AS varchar(30)), c.name + ' total') LIKE '%total%'
THEN NULL
ELSE c.name
END AS ConductorName,
ISNULL(CAST(T.Amount AS varchar(30)), c.Name + ' total') AS Denomination,
COUNT(*) AS totaltransactions,
SUM(T.Amount) AS totalamount
FROM dbo.Tickets AS T INNER JOIN
Transport.Conductors AS c ON c.ConductorID = T.Conductor_ID
WHERE CONVERT(DATE,ServerDateTime) BETWEEN #FromDate and #ToDate
GROUP BY c.Name, T.Amount WITH ROLLUP
) T
In this scenario you can use the existing CASE statement with a simplified condition only keeping the name for the first row within your partition. Also, if you leave out the 'ELSE' the value will be NULL by default, a slight simplification.
Another slight simplification is to use SQL Server's grouping method to detect your extra rows instead of watching for values that are NULL.
SELECT
ROW_NUMBER() OVER (PARTITION BY c.name order by c.name) 'N',
CASE
WHEN T.amount IS NOT NULL AND ROW_NUMBER() OVER (PARTITION BY c.name order by c.name) = 1 THEN c.name
END AS ConductorName,
CASE
WHEN grouping(T.Amount) = 0 THEN CONVERT(VARCHAR(30), T.Amount)
ELSE c.Name + ' total'
END AS totaltransactions,
SUM(T.Amount) AS totalamount
FROM dbo.Tickets AS T
INNER JOIN Transport.Conductors AS c ON c.ConductorID = T.Conductor_ID
WHERE CONVERT(DATE,ServerDateTime) BETWEEN #FromDate and #ToDate
GROUP BY c.Name, T.Amount WITH ROLLUP;

How to loop/pivot between start and end date to display monthly cost

I have a table with a PROJECTNAME, ACTUALCOST, PROJECTSTART and PROJECTEND columns like so:
PROJECTNAME ACTUAL COST PROJECTSTART PROJECTEND
abc 1500 2011-12-01 2012-07-31
prj1 1170 2012-01-09 2012-06-30
xyz 5350 2012-01-30 2012-03-30
I am trying to get an output like below:
PRJNAME DEC11 JAN12 FEB12 MAR12 APR12 MAY12 JUN12 JUL12 ...
abc 187.5 187.5 187.5 187.5 187.5 187.5 187.5 187.5
prj1 117 195 195 195 195 195
I need the monthly cost breakdowns of each project name, depending on how many days left are in each month.
You can use the PIVOT function to get the result.
The way I would recommend doing this would be to write a hard-coded version of this first, then convert it to dynamic sql.
Note: I determined the cost per month by dividing the Actual Cost by the number of days per between the projectstart/projectend. This should at least get you started for your actual report.
The static version would be similar to this:
;with cte as
(
select projectname, [ACTUAL COST], [PROJECTSTART], [PROJECTEND]
from yourtable
union all
select projectname, [ACTUAL COST],
dateadd(d, 1, PROJECTSTART),
PROJECTEND
from cte
where dateadd(d, 1, PROJECTSTART) <= ProjectEnd
)
select *
from
(
select
my.projectname,
my.monthyear,
my.totaldayspermonth * a.perdaycost AmountPerMonth
from
(
select projectname,
left(datename(m, projectstart), 3) + cast(year(projectstart) as varchar(4)) monthyear,
count(*) TotalDaysPerMonth
from cte
group by projectname,
[actual cost],
left(datename(m, projectstart), 3) + cast(year(projectstart) as varchar(4))
) my
cross apply
(
select projectname,
round([actual cost] / (datediff(d, projectstart, projectend) *1.0), 2) PerDayCost
from yourtable a
where my.projectname = a.projectname
) a
) src
pivot
(
max(AMOUNTPERMONTH)
for monthyear in (Dec2011, Jan2012, Feb2012, Mar2012,
Apr2012, May2012, Jun2012, Jul2012, Aug2012)
) piv
OPTION(MAXRECURSION 0);
See SQL Fiddle with Demo.
Once you have a static version, it makes it much easier to convert it to dynamic SQL. The dynamic SQL would be:
;with cte as
(
select projectname, [ACTUAL COST], [PROJECTSTART], [PROJECTEND]
from yourtable
union all
select projectname, [ACTUAL COST],
dateadd(d, 1, PROJECTSTART),
PROJECTEND
from cte
where dateadd(d, 1, PROJECTSTART) <= ProjectEnd
)
select *
into #dates
from cte
OPTION(MAXRECURSION 0)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(left(datename(m, projectstart), 3) + cast(year(projectstart) as varchar(4)))
from #dates
group by datename(m, projectstart), year(projectstart), month(projectstart)
order by year(projectstart), month(projectstart)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = ';with cte as
(
select projectname, [ACTUAL COST], [PROJECTSTART], [PROJECTEND]
from yourtable
union all
select projectname, [ACTUAL COST],
dateadd(d, 1, PROJECTSTART),
PROJECTEND
from cte
where dateadd(d, 1, PROJECTSTART) <= ProjectEnd
)
select projectname, '+#cols+'
from
(
select
my.projectname,
my.monthyear,
my.totaldayspermonth * a.perdaycost AmountPerMonth
from
(
select projectname,
left(datename(m, projectstart), 3) + cast(year(projectstart) as varchar(4)) monthyear,
count(*) TotalDaysPerMonth
from cte
group by projectname,
[actual cost],
left(datename(m, projectstart), 3) + cast(year(projectstart) as varchar(4))
) my
cross apply
(
select projectname,
round([actual cost] / (datediff(d, projectstart, projectend) *1.0), 2) PerDayCost
from yourtable a
where my.projectname = a.projectname
) a
) src
pivot
(
max(AMOUNTPERMONTH)
for monthyear in ('+#cols+')
)piv
OPTION(MAXRECURSION 0)'
execute(#query)
See SQL Fiddle with Demo
The result of both queries is:
| PROJECTNAME | DEC2011 | JAN2012 | FEB2012 | MAR2012 | APR2012 | MAY2012 | JUN2012 | JUL2012 |
-----------------------------------------------------------------------------------------------
| abc | 191.27 | 191.27 | 178.93 | 191.27 | 185.1 | 191.27 | 185.1 | 191.27 |
| prj1 | (null) | 155.48 | 196.04 | 209.56 | 202.8 | 209.56 | 202.8 | (null) |
| xyz | (null) | 178.34 | 2585.93 | 2675.1 | (null) | (null) | (null) | (null) |

Finding grand total of day wise total monthly sale

in rows i have months. In columns i have 1 to 31 days.
I want to add a column at the end Sum of all sale sale in the 31 days of a month.
SELECT * FROM (
SELECT DATENAME(month, date) AS SaleMonth,
DATEPART(dd,date) AS SaleDay FROM EnquiryMaster
) p PIVOT
(COUNT (SaleDay) FOR
SaleDay IN ( [1],[2],[3],[4],[5],[6],[7],[8],[9],[10],
[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],
[21],[22],[23],[24],[25],[26],[27],[28],[29],[30],[31] )) AS pvt
This code displays day wise data of 31 days but doesnt display sum.
If you want this as a column to display the total by month, there unfortunately is no easy way. You can use something like this:
SELECT SaleMonth,
[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],
[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],
[21],[22],[23],[24],[25],[26],[27],[28],[29],[30],[31],
([1]+[2]+[3]+[4]+[5]+[6]+[7]+[8]+[9]+[10]+
[11]+[12]+[13]+[14]+[15]+[16]+[17]+[18]+[19]+[20]+
[21]+[22]+[23]+[24]+[25]+[26]+[27]+[28]+[29]+[30]+[31]) TotalMonth
FROM
(
SELECT DATENAME(month, date) AS SaleMonth,
DATEPART(dd,date) AS SaleDay
FROM EnquiryMaster
) p
PIVOT
(
COUNT (SaleDay)
FOR SaleDay IN ( [1],[2],[3],[4],[5],[6],[7],[8],[9],[10],
[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],
[21],[22],[23],[24],[25],[26],[27],[28],[29],[30],[31] )
) AS pvt
See SQL Fiddle with Demo
This might be a case when dynamic sql would be easier to implement to pivot the data. By using dynamic SQL, you would not have to hard-code the values.
If you were to use dynamic sql your query would be similar to this:
DECLARE #cols AS NVARCHAR(MAX),
#colsTotal AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(DATEPART(dd,date))
from EnquiryMaster
group by DATEPART(dd,date)
order by DATEPART(dd,date)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsTotal = STUFF((SELECT distinct '+' + QUOTENAME(DATEPART(dd,date))
from EnquiryMaster
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT SaleMonth,' + #cols + ', '+ #colsTotal+' as GrandTotal from
(
SELECT DATENAME(month, date) AS SaleMonth,
DATEPART(dd,date) AS SaleDay
FROM EnquiryMaster
) x
pivot
(
count(SaleDay)
for SaleDay in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
Or another suggestion, I might have would be to alternate your columns headers to be the Month and then you can implement a GROUP BY with ROLLUP on each month to get a totals row.
So your new result set would look like this:
Day | Jan | Feb | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec
-------------------------------------------------------------------------------
1 | 12 | 34 | 5 | 3 | 3 | 2 | 32 | 0 | 9 | 87 | 23 | 54
Total | ....
try BETWEEN Condition
SELECT * FROM (SELECT DATENAME(month, date) AS SaleMonth,DATEPART(dd,date) AS SaleDay FROM EnquiryMaster) p PIVOT (COUNT (SaleDay) FOR SaleDay BETWEEN 1 and 31) AS pvt
Try adding just one more column to your subselect:
SELECT * FROM (
SELECT DATENAME(month, date) AS SaleMonth,
DATEPART(dd,date) AS SaleDay,
COUNT(*) OVER (PARTITION BY DATENAME(month, date)) AS MonthlyTotal
FROM EnquiryMaster
) p PIVOT
(COUNT (SaleDay) FOR
SaleDay IN ( [1],[2],[3],[4],[5],[6],[7],[8],[9],[10],
[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],
[21],[22],[23],[24],[25],[26],[27],[28],[29],[30],[31] )) AS pvt