How do I remove Null Values from a SQL pivot table [duplicate] - sql-server

This question already has answers here:
How to replace (null) values with 0 output in PIVOT
(7 answers)
Closed 3 years ago.
I've been trying to remove NULL values from a pivot table. I've tried several suggested solutions to without luck.
Declare #toName as nvarchar(max)
SELECT *
FROM (SELECT
isnull(protocol,0) as Protocol,
isnull(callCategory,0) as DCRCategory,
isnull(DATEPART(Year, eCreationTime),0) AS Year,
isnull(DATENAME(MONTH, eCreationTime),0) [Month],
isnull(COUNT(1),0) callCategory
FROM DCR_DATA
where ProjectManager = ''' + #toName + '''
GROUP BY protocol, callCategory, YEAR(eCreationTime), DATENAME(MONTH, eCreationTime))
AS MontlyDCRData
PIVOT(SUM(callCategory)
FOR Month IN ([January],[February],[March],[April],[May],
[June],[July],[August],[September],[October],[November],
[December])) AS MNamePivot
Here is an example of what I am returning:
Protocol DCRCategory Year January February March April May June July August September October November December
123 Cat 1 2017 NULL NULL NULL NULL NULL NULL NULL NULL 4 NULL NULL NULL

DECLARE #cols AS NVARCHAR(MAX),#cols1 AS NVARCHAR(MAX),#query AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(+'ISNULL('+CAST(ColName AS VARCHAR) +' ,0)' ) + ' ' + QUOTENAME(ColName)
FROM TblName
GROUP BY ColName
ORDER BY ColName
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SELECT #cols1 = STUFF((SELECT ',' + QUOTENAME(ColName)
from TblName
group by ColName
order by ColName
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = 'SELECT ColName1,' + #cols + ' from
(
SELECT ColName1, ColName2, ColName3
FROM TblName
) x
PIVOT
(
SUM(ColName3)
FOR ColName2 IN (' + #cols1 + ')
) p '
EXECUTE(#query);

Not the most elegant solution, but here goes -> Select into a temp table
Declare #toName as nvarchar(max)
SELECT *
INTO #tmpTable
FROM (SELECT isnull(protocol,0) as Protocol
, isnull(callCategory,0) as DCRCategory
, isnull(DATEPART(Year, eCreationTime),0) AS Year
, isnull(DATENAME(MONTH, eCreationTime),0) [Month]
, isnull(COUNT(1),0) callCategory
FROM DCR_DATA
where ProjectManager = ''' + #toName + '''
GROUP BY protocol
, callCategory
, YEAR(eCreationTime)
, DATENAME(MONTH, eCreationTime))
AS MontlyDCRData
PIVOT(SUM( callCategory)
FOR Month IN ([January],[February],[March],[April],[May],
[June],[July],[August],[September],[October],[November],
[December])) AS MNamePivot
and then isnull from there.
SELECT tmp.Protocol, tmp.DCRCategory, tmp.[Year]
, isnull(tmp.January,0)
, ...
, isnull(tmp.December,0)
FROM #tmpTable as tmp

Related

Get the column's data in procedure? [duplicate]

I have the following data output from my database
Observation 1 aug -2015
Improvement suggestion 1 dec -2015
Observation 1 dec -2015
Accident 2 jan -2016
Non Conformity 5 jan -2016
Observation 5 jan -2016
I've tried to figure out how to use PIVOT-table to make this work but cannot make it work. The date is dynamic depending on a date in the database. The output I am looking for is like below. Can someone please point me into right direction?
Look at the fiddle what I've tried so far I know it is using SUM right now, and that is not correct, but I cannot figure out what to use. http://sqlfiddle.com/#!3/0bd0c/4
PS. The data and the image are not related, so don't be fooled by the image. It is just an example
Is this what you were looking for:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT DISTINCT ',' + QUOTENAME(ColumnName)
from tempData
group by ColumnName, name
FOR XML PATH(''), Type
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT Name, ' + #cols + N' from
(
select Name, value, ColumnName
from tempData
) x
pivot
(
SUM(value)
for ColumnName in (' + #cols + N')
) p '
exec sp_executesql #query;
Changing this in your fiddle return the rows as you need it.
Try it like this:
DECLARE #cols AS NVARCHAR(MAX)=
STUFF(
(
SELECT DISTINCT ',[' + ColumnName + ']'
FROM tempData
FOR XML PATH('')
)
,1,1,'');
DECLARE #SqlCmd VARCHAR(MAX)=
'SELECT p.*
FROM
(
SELECT *
FROM tempData
) AS tbl
PIVOT
(
SUM(Value) FOR ColumnName IN(' + #cols +')
) AS p';
EXEC(#SqlCmd);

Remove Null Value in Multiple Dynamic Pivot Result

I want to use dynamic multiple pivot for my table. I successfully to pivot the table but the problem is there are a lot of NULL values.
This is my current result
And this is what I have tried:
CREATE table #tempdata(
Name Varchar(100),
date date,
MeterSN Varchar(100),
Reading float,
Usage VARCHAR(20),
UsageValue float,
RecordedBy Varchar(100),
Remark Varchar(100)
)
INSERT INTO #tempdata
SELECT c.Name, a.date, b.MeterSN, a.Reading, c.Name + ' Usage' AS Usage,
Reading - LAG(reading) over(partition By currentMeterSNID order by a.date) as UsageValue,
a.RecordedBy, a.Remark
FROM INF_Facility_ElectricalRecord a
INNER JOIN INF_Facility_ElectricalMeter b ON b.id = a.currentMeterSNID
INNER JOIN INF_Facility_Location c ON c.id = b.LocationID
WHERE a.date BETWEEN '2019-03-19' AND '2019-03-20'
ORDER BY c.Name, a.date, b.MeterSN
DECLARE #cols AS NVARCHAR(MAX),
#cols2 AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(Name)
from #tempdata
group by Name
order by Name
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SELECT #cols2 = STUFF((SELECT ',' + QUOTENAME(Usage)
from #tempdata
group by Usage
order by Usage
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT CONVERT(VARCHAR(10),date,120) AS Date,' + #cols + ','+ #cols2 + ', recordedby AS [Recorded By] , remark AS Remark from
(
select name,date, Reading, Usage, UsageValue, remark, RecordedBy
from #tempdata
) x
pivot
(
MAX(Reading)
for Name in (' + #cols + ')
) p
pivot
(
MAX(UsageValue)
for Usage in (' + #cols2 + ')
) p
ORDER by date '
execute(#query);
drop table #tempdata
Is there any solution for my problem?

how to make the query pick distinct values from a column as pivot values?

Query:
select employee_sk,
[201701],
[201702]
from
(select Employee_SK, Period_NK, CalcClinical_FTE from cmgr.FACT_Payroll) as sourcetable
pivot
(
sum(CalcClinical_FTE)
for period_nk
in ([201701],[201702])
) as a
I have multiple periods in the period_nk column ranging from 201401 to 201801.
So, how can i assign the pivot values without having to write each column individually like 201701,201702, 201703...?
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.period_nk)
FROM temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT category, ' + #cols + ' from
(
select category,
period_nk,
amount
from temp
) x
pivot
(
max(amount)
for period_nk in (' + #cols + ')
) p '
execute(#query)
drop table temp
Output :
category 201701 201702 201703 201704 201705
1 ABC 1000,0000 NULL NULL NULL 1100,0000
2 DEF NULL 500,0000 NULL 700,0000 NULL
3 GHI NULL NULL 800,0000 NULL NULL
online demo

Sqlserver table Row to column data - pivot table

I have an Excel-sheet as below, I already dump the data into database (as the sample data below) from this excel in normalized form.
Now I want to get the similar view of excel from database's data. I tried this, but given in wrong format. Good if somebody given the same result view as excel with column name and inner join.
I do not want to hardcore as the year expand.
declare #tblyear table(id int, year int)
insert into #tblyear values (1,2012), (2,2013),(3,2014) ,(4,2015),(5,2016)
declare #ChargeableYearDetails table ( id int, year int, CumulativeHrs numeric(18,2), CumulativeChargeableHrs numeric(18,2))
--take only 2 row year wise for the sample
insert into #ChargeableYearDetails values
(1, 1, 1657.75, 1243.50),
(2, 1, 3925.50, 3044.75),
(3, 2, 870.25, 568.25),
(4, 2, 2517.75, 1808.00),
(5, 3, 189.50, 99.00),
(6, 3, 1982.75, 1295.25),
(7, 4, 539.00, 351.00),
(8, 4, 2542.75, 1924.75),
(9, 5, 874.50, 596.50),
(9, 5, 2721.50, 2175.50)
select * from #tblyear
select * from #ChargeableYearDetails
/*I tried this , but given wrong result*/
select * from #ChargeableYearDetails
pivot
(
max(CumulativeHrs)
FOR year in ([1],[2],[3],[4],[5])
) as p
My answer is a little bit complicated, but I should post it. I use dynamic sql and pivoting.
DECLARE #columnsH nvarchar(500),
#columnsCH nvarchar(500),
#columns nvarchar(1000),
#sql nvarchar(4000)
CREATE TABLE #tblyear (id int, [year] int)
INSERT INTO #tblyear VALUES (1,2012), (2,2013),(3,2014) ,(4,2015),(5,2016)
CREATE TABLE #ChargeableYearDetails (id int, [year] int, CumulativeHrs numeric(18,2), CumulativeChargeableHrs numeric(18,2))
INSERT INTO #ChargeableYearDetails VALUES
(1, 1, 1657.75, 1243.50),(2, 1, 3925.50, 3044.75),(3, 2, 870.25, 568.25),
(4, 2, 2517.75, 1808.00),(5, 3, 189.50, 99.00),(6, 3, 1982.75, 1295.25),
(7, 4, 539.00, 351.00),(8, 4, 2542.75, 1924.75),(9, 5, 874.50, 596.50),
(9, 5, 2721.50, 2175.50)
SELECT #columnsH = STUFF((SELECT DISTINCT ',' + QUOTENAME('CumulativeHrsYY'+ CAST([Year] AS NVARCHAR(4))) FROM #tblyear FOR XML PATH('')),1,1,'')
SELECT #columnsCH = STUFF((SELECT DISTINCT ',' + QUOTENAME('CumulativeChargeableHrs'+ CAST([Year] AS NVARCHAR(4))) FROM #tblyear FOR XML PATH('')),1,1,'')
SELECT #columns = STUFF((SELECT DISTINCT ',' + QUOTENAME('CumulativeHrsYY'+ CAST([Year] AS NVARCHAR(4))) +',' + QUOTENAME('CumulativeChargeableHrs'+ CAST([Year] AS NVARCHAR(4))) FROM #tblyear FOR XML PATH('')),1,1,'')
SELECT #sql = '
SELECT '+ #columns+'
FROM (
SELECT *
FROM (
SELECT ''CumulativeHrsYY''+ CAST(t.[Year] AS NVARCHAR(4)) as [Year],
c.CumulativeHrs,
ROW_NUMBER() OVER (PARTITION BY c.[year] ORDER BY c.[year]) as rn
FROM #ChargeableYearDetails c
LEFT JOIN #tblyear t
ON t.ID = c.[year]
) as t
pivot
(
max(CumulativeHrs)
FOR [year] in ('+#columnsH+')
) as p
) as part1
LEFT JOIN (
SELECT *
FROM (
SELECT ''CumulativeChargeableHrs''+ CAST(t.[Year] AS NVARCHAR(4)) as [Year],
c.CumulativeChargeableHrs,
ROW_NUMBER() OVER (PARTITION BY c.[year] ORDER BY c.[year]) as rn
FROM #ChargeableYearDetails c
LEFT JOIN #tblyear t
ON t.ID = c.[year]
) as t
pivot
(
max(CumulativeChargeableHrs)
FOR [year] in ('+#columnsCH+')
) as p
) as part2
ON part1.rn = part2.rn'
EXEC(#sql)
DROP TABLE #ChargeableYearDetails
DROP TABLE #tblyear
Output:
CumulativeHrsYY2012 CumulativeChargeableHrs2012 CumulativeHrsYY2013 CumulativeChargeableHrs2013 CumulativeHrsYY2014 CumulativeChargeableHrs2014 CumulativeHrsYY2015 CumulativeChargeableHrs2015 CumulativeHrsYY2016 CumulativeChargeableHrs2016
1657.75 1243.50 870.25 568.25 189.50 99.00 539.00 351.00 874.50 596.50
3925.50 3044.75 2517.75 1808.00 1982.75 1295.25 2542.75 1924.75 2721.50 2175.50
Using dynamic sql, something like this will work. Have changed your table var to temp tables. You will have to change it to accommodate for multiple rows. Just add a group by to the pivot. Pls refer this msdn blog post on how to pivot on multiple fields
DECLARE #cols AS NVARCHAR(MAX), #cols2 AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME('Cumulative Hours ' + cast(year as varchar(4)))
from #tblyear
group by year, id
order by id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #cols2 = STUFF((SELECT ',' + QUOTENAME('Cumulative Chargable Hours ' + cast(year as varchar(4)))
from #tblyear
group by year, id
order by id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
DECLARE #colsMax AS NVARCHAR(MAX), #cols2Max AS NVARCHAR(MAX)
select #colsMax = STUFF((SELECT ', MAX(' + QUOTENAME('Cumulative Hours ' + cast(year as varchar(4))) + ') ' + QUOTENAME('Cumulative Hours ' + cast(year as varchar(4)))
from #tblyear
group by year, id
order by id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #cols2Max = STUFF((SELECT ', MAX(' + QUOTENAME('Cumulative Chargable Hours ' + cast(year as varchar(4))) + ') ' + QUOTENAME('Cumulative Chargable Hours ' + cast(year as varchar(4)))
from #tblyear
group by year, id
order by id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT ' + #colsMax + ',' + #cols2Max + N' from
(
select CumulativeHrs, CumulativeChargeableHrs
, ''Cumulative Hours '' + cast(b.year as varchar(4)) as [CumHours]
, ''Cumulative Chargable Hours '' + cast(b.year as varchar(4)) as [CumChargableHours]
from #ChargeableYearDetails a join #tblyear b on a.year = b.id
) query
pivot
(
max(CumulativeHrs)
for CumHours in (' + #cols + N')
) p
pivot
(
max(CumulativeChargeableHrs)
for CumChargableHours in (' + #cols2 + N')
) p2
'
print #query
exec sp_executesql #query;
You can do this using dynamic sql.. this will create some MAX(CASE WHEN) statements for each year/hrs combo.. then execute the sql by using EXEC sp_executesql #sql or just EXEC(#sql)
DECLARE #Sql NVARCHAR(MAX),
#Cols NVARCHAR(MAX)
SELECT #Cols = COALESCE(#Cols + ', ', '') +
'MAX(CASE WHEN Year = ' + CAST(id AS VARCHAR) + ' THEN CumulativeHrs END) AS [Cumulative Hrs YTD - ' + CAST([year] AS VARCHAR) + '], ' +
'MAX(CASE WHEN Year = ' + CAST(id AS VARCHAR) + ' THEN CumulativeChargeableHrs END) AS [Cumulative Chargeable Hrs ' + CAST([year] AS VARCHAR) + '] '
FROM tblyear
ORDER BY [year] DESC
SET #Sql = 'SELECT ' + #Cols
+ ' FROM (SELECT *, Row_number() Over (Partition by [year] order by id) Rn FROM ChargeableYearDetails) t '
+ ' GROUP BY Rn'
EXEC sp_executesql #Sql
you won't be able to use table variables in this type of query without defining them as types first, but i'm guessing you're using actual tables.
Row_number was also added to break each record per year into separate rows
It is possible to use the PIVOT clause, but this has two disadvantages. Firstly, it can only pivot a single column. Secondly it can't handle dynamic columns; you have to pre-specify them.
In this example, I'm using two CTEs to pivot each column, then joining the result sets.
You might do better to implement this in a mid-tier layer programmed in C#.
WITH cteHrs AS
(
SELECT id, [1], [2], [3], [4], [5]
FROM
(SELECT id, [year], CumulativeHrs
FROM #ChargeableYearDetails) AS H
PIVOT
(
SUM(CumulativeHrs)
FOR [year] IN ([1], [2], [3], [4], [5])
) as pvtH
),
cteChgHrs AS
(
SELECT id, [1], [2], [3], [4], [5]
FROM
(SELECT id, [year], CumulativeChargeableHrs
FROM #ChargeableYearDetails) AS C
PIVOT
(
SUM(CumulativeChargeableHrs)
FOR [year] IN ([1], [2], [3], [4], [5])
) as pvtC
)
SELECT COALESCE(C.id, H.id) AS 'id',
C.[1] AS 'Cum Hrs 2012', H.[1] AS 'Cum Chg Hrs 2012',
C.[2] AS 'Cum Hrs 2013', H.[2] AS 'Cum Chg Hrs 2013',
C.[3] AS 'Cum Hrs 2014', H.[3] AS 'Cum Chg Hrs 2014',
C.[4] AS 'Cum Hrs 2015', H.[4] AS 'Cum Chg Hrs 2015',
C.[5] AS 'Cum Hrs 2016', H.[5] AS 'Cum Chg Hrs 2016'
FROM cteHrs AS H
FULL OUTER JOIN cteChgHrs AS C
ON C.id = H.id;
The result set is this.

Issue adding order by to PIVOT group by SQL

The SQL Below works gr8, but the order, how to set that to put Jan, Feb, Mar order. I realize I need to do this with ordinal month but when I add in the order by I get error 'The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified.'
The results look like :
DECLARE #cols AS NVARCHAR(MAX)
DECLARE #query AS NVARCHAR(MAX)
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(year(TransactionDateTime))
FROM Quotations
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query =
'SELECT *
FROM (
SELECT
left(datename(month,TransactionDateTime),3) as [month], year(TransactionDateTime) as [year],
isnull(count(*),0) as Total
FROM quotations
group by left(datename(month,TransactionDateTime),3), year(TransactionDateTime)
) as s
PIVOT
(
SUM(Total)
FOR [year] IN (' + #cols + ')
) AS QuotationResults'
EXECUTE(#query)
As the error says, you can't order a subquery unless there is a reason for the order (TOP, FOR XML etc). The reason for this is that just because you have ordered the subquery there is no reason that this order would be maintained in your outer query. SQL Server is essentially telling you that your ORDER BY is pointless, therefore not valid.
The solution is to simply add a column with month number to your subuquery s, then you can order by it. You would also need to explicitly state your select list to ensure that this new column does not appear in it:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(year(TransactionDateTime))
FROM Quotations
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
SET #query =
'SELECT [month], ' + #Cols + '
FROM (
SELECT
left(datename(month,TransactionDateTime),3) as [month],
datepart(month,TransactionDateTime) as [monthNum],
year(TransactionDateTime) as [year],
isnull(count(*),0) as Total
FROM quotations
group by left(datename(month,TransactionDateTime),3), datepart(month,TransactionDateTime), year(TransactionDateTime)
) as s
PIVOT
(
SUM(Total)
FOR [year] IN (' + #cols + ')
) AS QuotationResults
ORDER BY QuotationResults.MonthNum;';
EXECUTE(#query);
ADDENDUM
The ISNULL() does not trap the null values because at the point of using ISNULL() they don't exist. COUNT(*) will never return null, so your ISNULL() is actually redundant.
In a very simple example if you have:
TransactionDateTime
----------------------
2015-01-01
2015-02-01
2015-02-01
2014-03-01
To skip ahead one step, after your pivot you will end up with:
Month 2014 2015
------------------------
Jan NULL 1
Feb NULL 2
Mar 1 NULL
So you end up with NULL values, now to go back a step, if you look at the results after your aggregation you have:
Month MonthNum Year Total
-----------------------------------
Jan 1 2015 1
Feb 2 2015 2
Mar 3 2014 1
So there are no rows for Jan or Feb in 2014, therefore SUM(NULL) will yield NULL. I would suggest leaving all the aggregation to the pivot function. So your non dynamic query would look something like:
SELECT pvt.[Month], pvt.[2014], pvt.[2015]
FROM ( SELECT [Month] = LEFT(DATENAME(MONTH, TransactionDateTime), 3),
[MonthNum] = DATEPART(MONTH, TransactionDateTime),
[Year] = DATEPART(YEAR, TransactionDateTime),
Value = 1
FROM Quotations
) AS t
PIVOT
(
COUNT(Value)
FOR [year] IN ([2014], [2015])
) AS pvt
ORDER BY pvt.MonthNum;
And put into dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT DISTINCT ',' + QUOTENAME(DATEPART(YEAR, TransactionDateTime))
FROM Quotations
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
SET #query =
'SELECT pvt.[Month], ' + #cols + '
FROM ( SELECT [Month] = LEFT(DATENAME(MONTH, TransactionDateTime), 3),
[MonthNum] = DATEPART(MONTH, TransactionDateTime),
[Year] = DATEPART(YEAR, TransactionDateTime),
Value = 1
FROM Quotations
) AS t
PIVOT
(
COUNT(Value)
FOR [year] IN (' + #cols + ')
) AS pvt
ORDER BY pvt.MonthNum;
(
SUM(Total)
FOR [year] IN (' + #cols + ')
) AS QuotationResults
ORDER BY QuotationResults.MonthNum;';
EXECUTE sp_executesql #query;

Resources