How to Convert (null) values with 0 output in PIVOT SQL - sql

I tried to Replace the (null) values with 0 (zeros) output by Using PIVOT.
Code below:
declare #col as nvarchar(max),
#query AS NVARCHAR(MAX)
select #col = STUFF((SELECT ',' + QUOTENAME(Week)
from pivote_created group by Week order by Week FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'),1,1,'')
set #query = 'select * from ( select store, week, xCount from pivote_created ) src pivot (sum(xcount)for week in (' + #col + ')) piv';
EXEC sp_executesql #query
output
store | 1 | 2 | 3 | 5
____________________________
101 | 138| 282| 220 | NULL
102 | 96 | 212| 123 | NULL
105 | 37 | 78 | NULL| 60
109 | 59 | 97 | 87 | NULL
I would like this
store | 1 | 2 | 3 | 5
___________________________
10 | 138 | 282 | 220 | 0
102 | 96 | 212 | 123 | 0
105 | 37 | 78 | 0 | 60
109 | 59 | 97 | 87 | 0

The most natural function to use is COALESCE() because it is the ANSI-standard function for this purpose. Under some circumstances in SQL Server, ISNULL() has better performance, but this is not one of those circumstances.
In the code, this looks like:
declare #cols nvarchar(max),
#colnames nvarchar(MAX),
#query nvarchar(MAX);
select #cols = stuff((select ',' + QUOTENAME(Week)
from pivote_created
group by Week
order by Week FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, ''
);
select #colnames = stuff((select ', COALESCE(' + QUOTENAME(Week) + ', ''0'') as ' + QUOTENAME(Week)
from pivote_created
group by Week
order by Week FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, ''
);
set #query = 'select ' + #colnames + '
from (select store, week, xCount from pivote_created
) src
pivot (sum(xcount)for week in (' + #cols + ')
) piv';
exec sp_executesql #query;

If you are using Oracle, you can use NVL Function.
select NVL(xCount, 0) from dual
If you are using SQL Server, you can use IsNull function:
select ISNULL(xCount, 0)

I think Its work
#Ezlo code work properly but add the missing second paramater given below:
COALESCE([1],NULL,'0') as [1]

Related

Dynamic Statement to Get Top "n" for Each Individual Column from Ms SQL Table

Help me with this Dynamic statement perform faster, the statement will fetch top n values for each column from a table.
The table will have an "n" number of columns but will have a primary key. NULLs couldn't have been avoided as any other value is considered as VALID and should go to the database.
Table
+-------+------+------+------+
| Depth | RPMA | ROP | WOB |
+-------+------+------+------+
| 6111 | 72 | 14.6 | 0 |
| 6110 | 72 | 14.1 | 1 |
| 6109 | 66 | 15.2 | NULL |
| 6108 | 68 | 14 | NULL |
| 6107 | 69 | 14 | NULL |
| 6106 | 61 | 14.8 | NULL |
| 6105 | 70 | NULL | NULL |
| 6104 | 64 | NULL | NULL |
| 6103 | 59 | NULL | NULL |
| 6102 | 49 | NULL | NULL |
+-------+------+------+------+
Result set,
+-------+------+------+------+
| Depth | RPMA | ROP | WOB |
+-------+------+------+------+
| 6111 | 72 | NULL | 0 |
| 6110 | 72 | NULL | 1 |
| 6109 | NULL | 15.2 | NULL |
| 6106 | NULL | 14.8 | NULL |
+-------+------+------+------+
Dynamic SQL used to get current result set,
DECLARE #Columns VARCHAR(MAX); -- Param1
DECLARE #IdxColumn VARCHAR(250); --Param2
DECLARE #Limit VARCHAR(11); --Param3
DECLARE #SQL NVARCHAR(MAX)=''; --Param4
DECLARE #query NVARCHAR(MAX) = ' SELECT TOP (' + #pLimit + ') ' + #IdxColumn + ', ' + #Columns + ' FROM [Table] WHERE '
SET #SQL = #query + REPLACE(#Columns,',', ' IS NOT NULL ORDER BY '+ #IdxColumn + ' ASC ' + N' UNION' + #query) + ' IS NOT NULL ORDER BY ' + #IdxColumn
SET #SQL = 'SELECT * FROM ('+#SQL+') T ORDER BY ' + #IdxColumn + ' ASC'
EXEC (#SQL)
The following query should work for the sample data:
WITH cte AS (
SELECT *
, DENSE_RANK() OVER (ORDER BY RPMA DESC) AS RPMA_RANK
, DENSE_RANK() OVER (ORDER BY ROP DESC) AS ROP_RANK
, DENSE_RANK() OVER (ORDER BY WOB DESC) AS WOB_RANK
FROM t
)
SELECT Depth
, CASE WHEN RPMA_RANK <= 2 THEN RPMA END
, CASE WHEN ROP_RANK <= 2 THEN ROP END
, CASE WHEN WOB_RANK <= 2 THEN WOB END
FROM cte
WHERE RPMA_RANK <= 2
OR ROP_RANK <= 2
OR WOB_RANK <= 2
Note that it returns three rows for RPMA column (there are two 72 and one 70). For n number of columns you need to use dynamic SQL.
This doesn't answer the question, but does fix the terrifying security flaws in the above.
There are multiple problems with the above, so please note that this is a significant but needed change to the SQL you have. Right now you are injecting unsantised parameters into your code, and also using datatypes that are vastly too large. #Columns is varchar(MAX), meaning that someone has 2GB worth of characters to inject into your system. #IdxColumn is a varchar(250) and references a single column; an object can at most be 128 characters long so there's no need for the other 122 characters. Also #Limit is a varchar, despite being an int and should be a parameter.
Firstly, rather than using a varchar(MAX) for #Columns I suggest a table type object:
CREATE TYPE dbo.ObjectList (ObjectName sysname);
sysname is a synonym of nvarchar(128) NOT NULL; and is the data type used for object names in SQL Server. You'll then need to INSERT the names of the columns into a declared table type parameter; one row for each Column Name
Then we can safely inject and parametrise your query:
--Parameters
DECLARE #Columns dbo.ObjectList,
#IdxColumn sysname, --sysname as well
#Limit int; --not varchar
--Variables needed in the SQL:
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = N'SELECT TOP (#Limit)' + #CRLF +
N' ' + QUOTENAME(#IdxColumn) + N',' + #CRLF +
STUFF((SELECT N',' + #CRLF +
N' ' + QUOTENAME(C.ObjectName)
FROM #Columns C
FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,3,N'') + #CRLF +
N'FROM dbo.[Table]' + #CRLF + --Should dbo.[Table] also not be safely injected?
N'WHERE ' +
STUFF((SELECT #CRLF +
N' OR ' + QUOTENAME(C.ObjectName) + N' IS NOT NULL'
FROM #Columns C
FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,8,N'') + #CRLF +
N'ORDER BY ' + QUOTENAME(#IdxColumn) + N' ASC;'
EXEC sp_executesql #SQL, N'#Limit int', #Limit;

Dynamic sql pivot values coming in seperate columns

HI I am trying to write dynamic pivot as I have over 100 columns
ID Question Answer
1 Name peter
1 DOB 11/12/2003
1 address …..
1 Issue1 d
1 Issue2 a
2 Name sam
2 DOB 10/01/1998
2 address …..
2 Issue1 p
2 Issue2 f
I want the output like this:
ID Name DOB address Issue1 Issue2
1 peter 11/12/2003 …. d a
2 sam 10/01/1998 …. p f
Here is the code that I have used:-
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(Question)
from #temp
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT ID + #cols + N'
from #temp
pivot
(
max([answer])
for Question in (' + #cols + N')
) p '
exec sp_executesql #query;
But I am getting each answer in a separate row. I want all the answers of an ID to come in one row. Can somebody help me. Appreciate your time on this. Thank you.
I am getting the output like this, which is not I want. I want the output as above.
ID Name DOB address Issue1 Issue2
1 Peter
1 11/12/2003
1 …
1 d
1 a
It looks like you are looking for a way to dynamically create a pivot based on your table without aggregation. You can try the following:
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(Question)
FROM #TEMP
GROUP BY QUESTION
ORDER BY QUESTION
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ID,' + #cols + ' FROM
(
SELECT ID, Question, Answer
FROM #TEMP
) x
pivot
(
MAX(Answer)
for Question in (' + #cols + ')
) p '
execute(#query);
Expected Output:
+----+---------+------------+--------+--------+-------+
| ID | address | DOB | Issue1 | Issue2 | Name |
+----+---------+------------+--------+--------+-------+
| 1 | ….. | 11/12/2003 | d | a | peter |
| 2 | ….. | 10/1/1998 | p | f | sam |
+----+---------+------------+--------+--------+-------+

Dynamically append columns based on another table in SQL

Suppose I want the following behavior in SQL
QTY Table
SERIAL_NO QTY CODE
1111111 1 AA
1111112 1 AA
1111111 2 BB
1111111 4 BB
1111113 7 CC
Code Table
CODE CODE_NAME
AA NameA
BB NameB
CC NameC
Query Result
SERIAL_NO NameA NameB NameC
1111111 1 6 0
1111112 1 0 0
1111113 0 0 7
NameA,B,C column in query result are basically the sums grouped by their respective code.
How do I achieve this behavior? The hardest part I'm trying to grasp is to dynamically add the columns to the query result based on the code table. For example, if the user adds another code called DD, the query result should automatically append NameD to the right side, and get the corresponding sum for that new code.
If you really need it in SQL you can leverage PIVOT and dynamic SQL in the following way
DECLARE #cols NVARCHAR(MAX), #colp NVARCHAR(MAX), #sql NVARCHAR(MAX)
SET #cols = STUFF(
(
SELECT DISTINCT ',COALESCE(' + QUOTENAME(code_name) + ',0) AS ' + QUOTENAME(code_name)
FROM code
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SET #colp = STUFF(
(
SELECT DISTINCT ',' + QUOTENAME(code_name)
FROM code
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SET #sql = 'SELECT serial_no, ' + #cols +
' FROM
(
SELECT serial_no, code_name, qty
FROM qty q JOIN code c
ON q.code = c.code
) x
PIVOT
(
SUM(qty) FOR code_name IN (' + #colp + ')
) p
ORDER BY serial_no'
EXECUTE(#sql)
Output:
| SERIAL_NO | NAMEA | NAMEB | NAMEC |
|-----------|-------|-------|-------|
| 1111111 | 1 | 6 | 0 |
| 1111112 | 1 | 0 | 0 |
| 1111113 | 0 | 0 | 7 |
Here is SQLFiddle demo

Subtotals and grand totals SQL Pivot

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.

Dynamically create columns in SQL select query and join tables

I have 2 tables. They are as follows
Table : Grade
GradeID | Grade
-----------------
1 | Chopsaw
2 | Classic
3 | Chieve
Table : Moulded Quantity
Batch ID | Grade | Moulded | Date
-------------------------------------
1 | 1 | 150 | 21st May
2 | 1 | 150 | 22nd May
3 | 2 | 150 | 21st May
4 | 2 | 150 | 21st May
5 | 2 | 150 | 22nd May
I should get the Output like the following
Date | Moulded | Chopsaw | Classic | Cieve
--------------------------------------------------
21st May | 450 | 150 | 300 | 0
22nd May | 300 | 150 | 150 | 0
I am using MSSQL 2008 and i use Crystal report to display the same.
If the number of grades is known beforehand then you can do it with a static query.
SELECT date,
SUM(moulded) moulded,
SUM(CASE WHEN grade = 1 THEN moulded ELSE 0 END) Chopsaw,
SUM(CASE WHEN grade = 2 THEN moulded ELSE 0 END) Classic,
SUM(CASE WHEN grade = 3 THEN moulded ELSE 0 END) Chieve
FROM moulded_quantity
GROUP BY date
This query is not vendor specific so it should work on any major RDBMS.
Now, if the number of grades is unknown or you want it to work even if you make changes to grade table (without changing the query itself) you can resort to dynamic query. But dynamic SQL is vendor specific. Here is an example of how you can do that in MySql
SELECT CONCAT (
'SELECT date, SUM(moulded) moulded,',
GROUP_CONCAT(DISTINCT
CONCAT('SUM(CASE WHEN grade = ',gradeid,
' THEN moulded ELSE 0 END) ', grade)),
' FROM moulded_quantity GROUP BY date') INTO #sql
FROM grade;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Output (in both cases):
| DATE | MOULDED | CHOPSAW | CLASSIC | CHIEVE |
---------------------------------------------------
| 21st May | 450 | 150 | 300 | 0 |
| 22nd May | 300 | 150 | 150 | 0 |
Here is SQLFiddle demo (for both approaches).
UPDATE In Sql Server you can use STUFF and PIVOT to produce expected result with dynamic sql
DECLARE #colx NVARCHAR(MAX), #colp NVARCHAR(MAX), #sql NVARCHAR(MAX)
SET #colx = STUFF((SELECT ', ISNULL(' + QUOTENAME(Grade) + ',0) ' + QUOTENAME(Grade)
FROM grade
ORDER BY GradeID
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #colp = STUFF((SELECT DISTINCT ',' + QUOTENAME(Grade)
FROM grade
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #sql = 'SELECT date, total moulded, ' + #colx +
' FROM
(
SELECT date, g.grade gradename, moulded,
SUM(moulded) OVER (PARTITION BY date) total
FROM moulded_quantity q JOIN grade g
ON q.grade = g.gradeid
) x
PIVOT
(
SUM(moulded) FOR gradename IN (' + #colp + ')
) p
ORDER BY date'
EXECUTE(#sql)
Output is the same as in MySql case.
Here is SQLFiddle demo.
I would suggest you before asking any question research first as it is very common question.
Updated
DECLARE #COLUMNS varchar(max)
SELECT #COLUMNS = COALESCE(#COLUMNS+'],[' ,'') + CAST(Grade as varchar)
FROM Grade
GROUP BY Grade
SET #COLUMNS = '[' + #COLUMNS + ']'
DECLARE #COLUMNS_WITH_NULL varchar(max)
SELECT #COLUMNS_WITH_NULL = COALESCE(#COLUMNS_WITH_NULL+',ISNULL([' ,'ISNULL([') + CAST(Grade as varchar) + '], 0) AS ' + CAST(Grade as varchar)
FROM Grade
GROUP BY Grade
DECLARE #COLUMNS_SUMS varchar(max)
SELECT #COLUMNS_SUMS = COALESCE(#COLUMNS_SUMS+' + ISNULL([' ,'ISNULL([') + CAST(Grade as varchar) + '], 0) '
FROM Grade
GROUP BY Grade
SET #COLUMNS_SUMS = '(' + #COLUMNS_SUMS + ') as Moulded'
PRINT #COLUMNS_SUMS
EXECUTE (
'
SELECT
Date, ' + #COLUMNS_SUMS + ', ' + #COLUMNS_WITH_NULL + '
FROM (
SELECT
m.Moulded,
m.date AS Date,
g.Grade
FROM Grade g
INNER JOIN [Moulded Quantity] m
ON m.GRADE = g.GradeID
) up
PIVOT (SUM(Moulded) FOR Grade IN ('+ #COLUMNS +')) AS pvt')