Syntax error in pivoting a SQL Server table using VARCHAR columns - sql-server

I have a table (~50m rows) like below in SQL Server:
PId CustomColumnName CustomColumnValue
1 PropertyA 1
1 PropertyB B
1 PropertyC C
2 PropertyA 1.5
2 PropertyB BB
2 PropertyC CC
3 PropertyD D1
3 PropertyA 2.0
I'd like to PIVOT that table to look something like this:
PId PropertyA PropertyB PropertyC PropertyD
1 1 B C NULL
2 1.5 BB CC NULL
3 2.0 NULL NULL D1
I know that SQL Server has PIVOT function, so I wrote something like this:
SELECT *
FROM
(
SELECT [PId], [CustomColumnName], [CustomColumnValue]
FROM [myTable]
) AS src
PIVOT
(
MAX([CustomColumnValue]) -- Not sure how to aggregate VARCHAR data here
FOR [CustomColumnName] IN ('PropertyA', 'PropertyB', 'PropertyC', 'PropertyD')
) AS pvt
But I got Incorrect syntax near 'PropertyA' error. I'm not sure if what I'm trying to do is even possible with the PIVOT function of SQL Server (because I don't think I can aggregate [CustomColumnValue], which is of VARCHAR type). If using PIVOT is not feasible in my use case, is there an efficient, alternative way to achieve what I'm trying to do?
Thank you in advanced for your suggestions/answers!

--remove the Single quotes you will get desired Result set
;With cte(PId, CustomColumnName,CustomColumnValue)
AS
(
SELECT 1,'PropertyA','1' Union all
SELECT 1,'PropertyB','B' Union all
SELECT 1,'PropertyC','C' Union all
SELECT 2,'PropertyA','1.5' Union all
SELECT 2,'PropertyB','BB' Union all
SELECT 2,'PropertyC','CC' Union all
SELECT 3,'PropertyD','D1' Union all
SELECT 3,'PropertyA','2.0'
)
SELECT *
FROM
(
SELECT [PId], [CustomColumnName], [CustomColumnValue]
FROM cte
) AS src
PIVOT
(
MAX([CustomColumnValue])
FOR [CustomColumnName] IN (PropertyA, PropertyB, PropertyC, PropertyD)
) AS pvt
You can generate the same Result Set using Dynamic Pivot.it is as below
IF OBJECT_ID('tempdb..#temp') IS NOT NULL
Drop table #temp
;With cte(PId, CustomColumnName,CustomColumnValue)
AS
(
SELECT 1,'PropertyA','1' Union all
SELECT 1,'PropertyB','B' Union all
SELECT 1,'PropertyC','C' Union all
SELECT 2,'PropertyA','1.5' Union all
SELECT 2,'PropertyB','BB' Union all
SELECT 2,'PropertyC','CC' Union all
SELECT 3,'PropertyD','D1' Union all
SELECT 3,'PropertyA','2.0'
)
SELECT * INTO #temp FROM cte
Declare
#Sql nvarchar(max),
#dynamicCol nvarchar(max)
--Create columns Dynamically
SELECT #dynamicCol=STUFF((SELECT DISTINCT ', '+ QUOTENAME(CustomColumnName )
From #temp For XML PATH ('')),1,1,'')
--SELECT #dynamicCol
SET #Sql='
SELECT [PId] ,'+ #dynamicCol +' From
(
SELECT [PId], [CustomColumnName], [CustomColumnValue] From
#temp
)AS Src
PIVOT
(
MAX([CustomColumnValue]) For [CustomColumnName] IN ('+#dynamicCol+')
)
AS Pvt'
PRINT #Sql
EXEC(#Sql)

Remove the quotes.
SELECT *
FROM
(
SELECT [PId], [CustomColumnName], [CustomColumnValue]
FROM [myTable]
) AS src
PIVOT
(MAX([CustomColumnValue])
FOR [CustomColumnName] IN ([PropertyA], [PropertyB], [PropertyC], [PropertyD])
) AS pvt
GO
PId | PropertyA | PropertyB | PropertyC | PropertyD
--: | :-------- | :-------- | :-------- | :--------
1 | 1 | B | C | null
2 | 1.5 | BB | CC | null
3 | 2.0 | null | null | D1
dbfiddle here

You require to use quotename of columns
SELECT *
FROM
(
SELECT [PId], [CustomColumnName], [CustomColumnValue]
FROM [myTable]
) AS src
PIVOT
(
MAX([CustomColumnValue]) -- Not sure how to aggregate VARCHAR data here
FOR [CustomColumnName] IN ([PropertyA], [PropertyB], [PropertyC], [PropertyD]) --provide in squarebrackets if generating dynamic then use quotename() function of sql server
) AS pvt

Related

Get the max number in a varchar column SQL Server

I have a varchar column which holds comma separated numbers.
I want to fetch the max number in that column.
+-------------------+--------------+
| reference_no | Name |
+-------------------|--------------+
| 17530, 20327 | John |
| , 14864 | Smith |
| 8509 | Michael |
| 14864, 17530 | Kelly |
+-------------------+--------------+
So, in the above column (reference_no) example the output should be 20327.
Then I have to select the row which contains this number.
Assuming NOT 2016+
If you have no more than 4 values in reference_no in any particular row, then perhaps parsename()
If more than 4, you may need to fix the data or use a split/parse function.
Example
Select MaxValue = max(V)
From YourTable A
Cross Apply ( values (replace([reference_no],',','.')) ) B(S)
Cross Apply ( values (try_convert(int,parsename(S,1)))
,(try_convert(int,parsename(S,2)))
,(try_convert(int,parsename(S,3)))
,(try_convert(int,parsename(S,4)))
) C(V)
Returns
MaxValue
20327
try the following:
declare #tab table (reference_no varchar(max), [Name] varchar(100))
insert into #tab
select '17530, 20327','John' union
select ', 14864 ','Smith' union
select '8509 ','Michael' union
select '14864, 17530','Kelly'
create table #final (val int)
insert into #final
SELECT Split.a.value('.', 'VARCHAR(100)') AS String
FROM (SELECT reference_no reference_no,
CAST ('<S>' + REPLACE(reference_no, ',', '</S><S>') + '</S>' AS XML) AS String
FROM #tab) AS A CROSS APPLY String.nodes ('/S') AS Split(a);
select * from #tab where reference_no like '%'+ (select convert(varchar(100), max(val)) from #final) + '%'
drop table #final

How to retrieve a different attribute from a row with multiple different and repeated values into a new column?

I have a table that looks like this:
att1 att2
| a | 1 |
| a | 2 |
| b | 2 |
| b | 3 |
| c | 1 |
| c | 2 |
| c | 2 |
And I need the different record of att2 for the duplicate value on att1 to be grouped into a new column like this
att1 att2 att3
| a | 1 | 2 |
| b | 2 | 3 |
| c | 1 | 2 |
I tried to pivot, I tried to self join, but I can't seem to find the query to separate the values like this. Can someone please help me? Thanks
you can use a dynamic pivot query like below
see demo link
create table tt (att1 varchar(10), att2 int)
insert into tt values
('a',1)
,('a',2)
,('b',2)
,('b',3)
,('c',1)
,('c',2)
,('c',2)
go
declare #q varchar(max), #cols varchar(max)
set #cols
= STUFF((
SELECT distinct ',' +
QUOTENAME('att '+
cast(1+ row_number() over (partition by att1 order by att2 ) as varchar(max))
)
FROM (select distinct att1,att2 from tt)tt --note this distinct
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #q=
'select
att1,'+ #cols +
' from
(
select
att1,att2,
''att ''+
cast(1+row_number() over (partition by att1 order by att2 ) as varchar(max)) as r
from
(select distinct att1,att2 from tt)tt
)I
pivot
(
max(att2)
for r in ('+#cols+')
)piv'
exec(#q)
Any query like this always smells like report formatting, rather than genuine data requirement, which should probably be done in a reporting tool rather than a database. But as with all things it is possible with enough code.
This should work for you.
create table #t (att1 nvarchar(max) ,att2 int);
insert #t select 'a', 1 union all select 'a', 2;
insert #t select 'b', 2 union all select 'b', 3;
insert #t select 'c', 1 union all select 'c', 2 union all select 'c', 2;
select att1, 1 as att2, 2 as att3 from
(
select att1, att2, row_number() over (partition by att1 order by att1, att2) as r
from (select distinct att1, att2 from #t) as x
) src
pivot ( avg(att2) for r in ([1],[2])) p;
drop table #t;
The first step is to get the distinct values in your table, and then sort and group them by att1. I'm doing this with a row_number() command, which looks like this:
select att1, att2, row_number() over (partition by att1 order by att1, att2) as r
from (select distinct att1, att2 from #t) as x ;
att1 attr2 r
a 1 1
a 2 2
b 2 1
b 3 2
c 1 1
c 2 2
From there the pivot command transforms rows into columns. The catch with the pivot command is that the names of those new columns need to be data driven; during your row_number command you could provide better names, or you can alias them as I have done here.
Finally, this only works when there are only two values to pivot. To add more, modify the for r in ([1], [2]) line to include e.g. 3, 4, etc.

Convert row to column and apply sorting using sql or c#

I have requirement of sorting dynamically made column from row.
I have following structure of data in SQL :
All attributes are treated as column but actually it stored in DB as row and their respective TextValue(If type is Text),DateValue (if type is date time or date)
Id | TextValue | DateValue | Attribute
--------------------------------------------
1 | abc | - | SiteLocation
2 | - | 1-1-2013 | Holiday date
3 | xyz | - | SiteLocation
4 | - | 2-2-2014 | Holiday date
5 | pqr | - | SiteLocation
6 | abc | - | SiteLocation
I want to apply sorting on SiteLocation and I am displaying it as column.
So how can i achieve this
SiteLocation | Holiday date
abc | -
- | 1-1-2013
xyz |-
- |2-2-2014
pqr |-
abc |-
I want to apply sorting on SiteLcoation or Holiday date in UI grid.
Please suggest me some way how can I do it?
Here is your table
CREATE TABLE #TEMP(Id INT,TextValue VARCHAR(100),DateValue DATE,Attribute VARCHAR(100))
INSERT INTO #TEMP
SELECT 1 Id, 'abc' TextValue ,NULL DateValue ,'SiteLocation' Attribute
UNION ALL
SELECT 2 ,NULL ,'1-1-2013' ,'Holiday date'
UNION ALL
SELECT 3 ,'xyz' ,NULL ,'SiteLocation'
UNION ALL
SELECT 4 , NULL ,'2-2-2014' ,'Holiday date'
UNION ALL
SELECT 5 ,'pqr' ,NULL ,'SiteLocation'
UNION ALL
SELECT 6 ,'abc' ,NULL ,'SiteLocation'
QUERY
SELECT [SiteLocation],[Holiday DATE]
FROM
(
SELECT ID,ISNULL(TextValue,DateValue) VALUE,Attribute
FROM #TEMP
)P
PIVOT
(
min(VALUE) FOR
Attribute IN ([SiteLocation],[Holiday DATE])
)
AS i
SQL FIDDLE
UPDATE
I am updating the query as you suggested.
Here you will select the columns for converting rows to columns
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + [Attribute] + ']',
'[' + [Attribute] + ']')
FROM (SELECT DISTINCT [Attribute] FROM #TEMP) PV
ORDER BY [Attribute]
Now you can pivot dynamically here.
DECLARE #query NVARCHAR(MAX)
SET #query = '
-- Your pivoted columns will be displayed
SELECT ' + #cols + ' FROM
(
-- Combine into single column
SELECT ID,ISNULL(TextValue,DateValue) VALUE,Attribute
FROM #TEMP
) x
PIVOT
(
MIN(VALUE)
FOR [Attribute] IN (' + #cols + ')
) p
'
EXEC SP_EXECUTESQL #query
SQL FIDDLE

simple SQL Pivot Query

I apologize for my ignorance. I just am not familiar with pivot queries AT ALL and all the examples I find seem about as clear as mud. I have table that returns GroupName and ID Numbers.
For Example:
SELECT GroupName, IDnumber FROM do.Table_1
Returns
GroupName IDnumber
1 8395
1 A660
1 8396
1 A661
2 8398
2 A662
2 8399
What I want is something more like this:
GroupName ID1 ID2 ID3 ID4
1 8395 A660 8396 A661
2 8398 A662 8399 NULL
How can I do this? Pivot query? Some other method?
I am open to suggestion and appreciate any help you could provide.
Yes, you can do it using PIVOT but not in this shape, you have firstly to generate a row number to use it to format the data in the way you want. Something like this:
WITH Ranked
AS
(
SELECT GroupName, IDnumber,
ROW_NUMBER() OVER(PARTITION BY GroupName ORDER BY GroupName) AS RN
FROM Table1
)
SELECT GroupName,
[1] AS ID1, [2] AS ID2, [3] AS ID3, [4] AS ID4
FROM Ranked AS r
PIVOT
(
MAX(IDnumber)
FOR RN IN([1], [2], [3], [4])
) AS p;
SQL Fiddle Demo
This will give you:
| GROUPNAME | ID1 | ID2 | ID3 | ID4 |
|-----------|------|------|------|--------|
| 1 | 8395 | A660 | 8396 | A661 |
| 2 | 8398 | A662 | 8399 | (null) |
If you want to do it dynamically and not to write the row number by hand in the pivot table operator, you have to do it using dynamic SQL, something like:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #colnames AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT distinct ',' +
QUOTENAME(RN)
FROM
(
SELECT ROW_NUMBER() OVER(PARTITION BY GroupName ORDER BY GroupName) AS RN
FROM Table1
) AS t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #colnames = STUFF((SELECT distinct ',' +
QUOTENAME(RN) + 'AS' +
QUOTENAME('ID' + CAST(RN AS NVARCHAR(5)))
FROM
(
SELECT ROW_NUMBER() OVER(PARTITION BY GroupName ORDER BY GroupName) AS RN
FROM Table1
) AS t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = 'WITH Ranked
AS
(
SELECT GroupName, IDnumber,
ROW_NUMBER() OVER(PARTITION BY GroupName ORDER BY GroupName) AS RN
FROM Table1
)
SELECT GroupName, ' + #colnames +
' FROM Ranked AS r
PIVOT
(
MAX(IDnumber)
FOR RN IN(' + #cols + ')' +
') p';
execute(#query);
SQL Fiddle Demo
This should give you the same result:
| GROUPNAME | ID1 | ID2 | ID3 | ID4 |
|-----------|------|------|------|--------|
| 1 | 8395 | A660 | 8396 | A661 |
| 2 | 8398 | A662 | 8399 | (null) |
You may need to use dynamic pivoting since the Id will be dynamic. Here is your sample table
SELECT * INTO #TEMP
FROM
(
SELECT 1 GroupName, '8395' IDnumber
UNION ALL
SELECT 1, 'A660'
UNION ALL
SELECT 1, '8396'
UNION ALL
SELECT 1, 'A661'
UNION ALL
SELECT 2, '8398'
UNION ALL
SELECT 2, 'A662'
UNION ALL
SELECT 2, '8399'
)TAB
Select row number over each Groupname and insert into a temporary table so that it can be used for both selecting the columns for pivoting and inside the pivot
SELECT *,
'ID' + CAST(ROW_NUMBER() OVER(PARTITION BY GroupName ORDER BY GROUPNAME) AS VARCHAR(10)) IDS
INTO #NEWTABLE
FROM #TEMP
Select columns for pivot
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + IDS + ']',
'[' + IDS + ']')
FROM (SELECT DISTINCT IDS FROM #NEWTABLE) PV
ORDER BY IDS
Now pivot dynamically
DECLARE #query NVARCHAR(MAX)
SET #query = '
SELECT * FROM
(
SELECT * FROM #NEWTABLE
) x
PIVOT
(
MAX(IDnumber)
FOR IDS IN (' + #cols + ')
) p
'
EXEC SP_EXECUTESQL #query
Click here to view the result (incase an error is occured on loading page press RUNSQL, it works)
RESULT

How do I get distinct COUNT in pivot?

I have a following table:
State LAB GROUP DATE CODE ID
UP A I 1-Jan 1 345
UP R S 1-Feb 1 456
UP A S 1-Jan 2 567
DL R S 1-Feb 3 678
DL T S 1-Jan 1 789
DL A S 1-Jan 2 900
MN T S 1-Jan 3 1011
MN R I 1-Feb 1 1122
MN S I 1-Feb 2 1233
I need a pivot table of following type:
STATE A R T TOTAL
UP 2 1 0 3
DL 1 1 1 3
MN 0 1 1 2
DISTINCT COUNT OF ID FOR EACH LAB FOR EACH STATE.
I then need the pivot tables filtered for following columns:
GROUP
DATE
CODE
So 1st table will have the pivot table above counting only those records which have GROUP=S
2nd table will have the pivot table above counting only those records which have CODE=1
and so on, I wish to put multiple conditions. and generate several tables one by one and export them.
If this is possible in SQL please let me know! I ruled out excel vba due to the size of table (source table will have 800,000 records approx).
Try this :-
Select [State],[A],[R],[T],Total = [A] + [R]+ [T]
from
(
Select [State],
[A] = Sum(Case when LAB='A' then 1 else 0 END) ,
[R] = Sum(Case when LAB='R' then 1 else 0 END) ,
[T] = Sum(Case when LAB='T' then 1 else 0 END)
from YourTable
group by [State]
)a
SQL FIDDLE
CREATE TABLE #t(States VARCHAR(10),LAB VARCHAR(5),GROUPs VARCHAR(5),DATEs VARCHAR(10),CODE INT,ID INT)
INSERT INTO #t values('UP','A','I','1-Jan',1,345)
INSERT INTO #t values('UP','R','S','1-Feb',1,456)
INSERT INTO #t values('UP','A','S','1-Jan',2,567)
INSERT INTO #t values('DL','R','S','1-Feb',3,678)
INSERT INTO #t values('DL','T','S','1-Jan',1,789)
INSERT INTO #t values('DL','A','S','1-Jan',2,900)
INSERT INTO #t values('MN','T','S','1-Jan',3,1011)
INSERT INTO #t values('MN','R','I','1-Feb',1,1122)
INSERT INTO #t values('MN','S','I','1-Feb',2,1233)
SELECT States,ISNULL(A,0) A,ISNULL(R,0) R,ISNULL(T,0) T,ISNULL(A,0)+ISNULL(R,0)+ISNULL(T,0) total
FROM
(
SELECT States,LAB,Count(ID) AS cnt FROM #t GROUP BY States,LAB /*apply GROUP DATE CODE condition here*/
) AS PVT
PIVOT(MAX(cnt) FOR LAB IN (A,R,T)) pvt
Another solution using PIVOT :
WITH PivotInUse AS (
SELECT state,lab,COUNT(*) AS cnt
FROM YourTable
GROUP BY state,lab
)
SELECT STATE
,COALESCE([A], 0) AS A
,COALESCE([R], 0) AS R
,COALESCE([T], 0) AS T
,COALESCE([A], 0) + COALESCE([R], 0) + COALESCE([T], 0) AS TOTAL
FROM PivotInUse
PIVOT(SUM(cnt) FOR lab IN ([A],[R],[T])) AS p;
Your sample table
SELECT * INTO #TEMP FROM
(
SELECT 'UP' [State],'A' LAB,'I' [GROUP],'1-Jan' [DATE],1 CODE,345 ID
UNION ALL
SELECT 'UP','R','S','1-Feb',1,456
UNION ALL
SELECT 'UP','A','S','1-Jan',2,567
UNION ALL
SELECT 'DL','R','S','1-Feb',3,678
UNION ALL
SELECT 'DL','T','S','1-Jan',1,789
UNION ALL
SELECT 'DL','A','S','1-Jan',2,900
UNION ALL
SELECT 'MN','T','S','1-Jan',3,1011
UNION ALL
SELECT 'MN','R','I','1-Feb',1,1122
UNION ALL
SELECT 'MN','S','I','1-Feb',2,1233
)TAB
Now you need to get the distinct count of each state and get the sum as the result to show Total
in pivoted result.
SELECT DISTINCT [State],LAB,SUM(CNT) CNT
INTO #NEWTABLE
FROM
(
SELECT DISTINCT
[State],LAB,
CASE WHEN [State] IS NULL THEN NULL ELSE COUNT([State]) OVER(PARTITION BY [State],LAB) END CNT
FROM #TEMP
)TAB
GROUP BY [State],LAB
WITH ROLLUP
Now we need to get the distinct columns for pivot(#cols) and columns to identify and replace null with zero in pivot(#NullToZeroCols)
DECLARE #cols NVARCHAR (MAX)
DECLARE #NullToZeroCols NVARCHAR (MAX)
SET #cols = SUBSTRING((SELECT DISTINCT ',['+LAB+']' FROM #NEWTABLE GROUP BY LAB FOR XML PATH('')),2,8000)
SET #NullToZeroCols = SUBSTRING((SELECT DISTINCT ',ISNULL(['+LAB+'],0) AS ['+LAB+']'
FROM #NEWTABLE GROUP BY LAB FOR XML PATH('')),2,8000)
Join the pivotted query with the #NEWTABLE to get the Total for each State
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT P.State,' + #NullToZeroCols + ',T2.CNT TOTAL FROM
(
SELECT DISTINCT [State],LAB,CNT FROM #NEWTABLE
) x
PIVOT
(
SUM(CNT)
FOR [LAB] IN (' + #cols + ')
) p
JOIN #NEWTABLE T2 ON P.[STATE]=T2.[STATE]
WHERE P.State IS NOT NULL AND T2.LAB IS NULL AND T2.[STATE] IS NOT NULL;'
EXEC SP_EXECUTESQL #query
Here is your result
Here is the SQLFiddle http://sqlfiddle.com/#!3/c2588/1 (If it shows any error while loading the page, just click RUNSQL, it will work)
Now if you want to get the result as you said DISTINCT COUNT OF ID FOR EACH LAB FOR EACH STATE, just change
OVER(PARTITION BY [State],LAB)
to
OVER(PARTITION BY [State],LAB,Id)
which will show the following result after executing the pivot query

Resources