T:SQL: select values from rows as columns - sql

I have a table for Profiles stores profile properties values in row style, ex:
[ProfileID] [PropertyDefinitionID] [PropertyValue]
1 6 Jone
1 7 Smith
1 8 Mr
1 3 50000
and another table for property definitions :
[PropertyDefinitionID] [PropertyName]
6 FirstName
7 LastName
8 Prefix
3 Salary
How to use PIVOT or any other way to show it in this way:
[ProfileID] [FirstName] [LastName] [Salary]
1 Jone Smith 5000

It's easy to do this without PIVOT keyword, just by grouping
select
P.ProfileID,
min(case when PD.PropertyName = 'FirstName' then P.PropertyValue else null end) as FirstName,
min(case when PD.PropertyName = 'LastName' then P.PropertyValue else null end) as LastName,
min(case when PD.PropertyName = 'Salary' then P.PropertyValue else null end) as Salary
from Profiles as P
left outer join PropertyDefinitions as PD on PD.PropertyDefinitionID = P.PropertyDefinitionID
group by P.ProfileID
you can also do this with PIVOT keyword
select
*
from
(
select P.ProfileID, P.PropertyValue, PD.PropertyName
from Profiles as P
left outer join PropertyDefinitions as PD on PD.PropertyDefinitionID = P.PropertyDefinitionID
) as P
pivot
(
min(P.PropertyValue)
for P.PropertyName in ([FirstName], [LastName], [Salary])
) as PIV
UPDATE: For dynamic number of properties - take a look at Increment value in SQL SELECT statement

It looks like you might have an unknown number of PropertyName's that you need to turn into columns. If that is the case, then you can use dynamic sql to generate the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(PropertyName)
from propertydefinitions
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT profileid, ' + #cols + ' from
(
select p.profileid,
p.propertyvalue,
d.propertyname
from profiles p
left join propertydefinitions d
on p.PropertyDefinitionID = d.PropertyDefinitionID
) x
pivot
(
max(propertyvalue)
for propertyname in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.

Related

Create a pivot of same columns in to 1 row

I'm using a SQL Server, I've a query which return the data of all the fields, The main thing is that 1 field can belongs to multiple records, the record ID differentiate them.
I've a data set like this.
This is my current data set
My current query:
Select fd.FieldName ,FV.FieldID, Data , R.RecordID from FieldValues FV
Inner Join Records R on R.RecordID = FV.RecordID
Inner Join Forms F On f.FormID = R.FormID
Inner join Fields fd on fd.FieldID = fv.FieldID
Where R.RecordID IN (45,46)
I need to create 1 row of each columns that belongs to the same RecordID like this.
Service Name Location city VendorCode RecordID
Raj ABC LOCATION ABC CITY 32 45
BEN ABC LOCATION ABC CITY -- 46
The above is my desired output.
I've tried with pivot but have not succeeded.
If you don't like to deal with dynamic pivot and you do know the key of the rows you want to convert into columns, you can use standard sql with max and case when
select
max(case fd.FieldName when 'SelectService' then Data else null end) as ServiceName,
max(case fd.FieldName when 'EnterYourLocation' then Data else null end) as Location,
max(case fd.FieldName when 'City' then Data else null end) as city,
max(case fd.FieldName when 'VendorCodeOption' then Data else null end) as VendorCode,
R.RecordId
from FieldValues FV
Inner Join Records R on R.RecordID = FV.RecordID
Inner Join Forms F On f.FormID = R.FormID
Inner join Fields fd on fd.FieldID = fv.FieldID
where R.RecordID IN (45,46)
group by R.RecordId
This is the solution with pivot but it is missing to include adjust joins
declare #columns varchar(max) set #columns = ''
select #columns = coalesce(#columns + '[' + cast(col as varchar(MAX)) + '],', '')
FROM ( select FieldName as col from FieldValues group by FieldName ) m
set #columns = left(#columns,LEN(#columns)-1)
DECLARE #SQLString nvarchar(max);
set #SQLString = '
select * from
( select RecordId, FieldName, Data from FieldValues) m
PIVOT
( MAX(Data)
FOR FieldName in (' + #columns + ')
) AS PVT'
EXECUTE sp_executesql #SQLString

Show value of particular column as Header in SQL Server

I am creating a web app in which I have a requirement where I want to display a column value as a header
Example
SELECT Name, Leave
FROM tblUser
INNER JOIN tblLeaveMaster ON tblUser.EmployeeID = tblLeaveMaster.EmployeeID
From that query, I get these results:
Name Leave
---------------
Test1 5
Test2 10
test3 2
Now I want to get these values as
Test1 Test2 Test3
-----------------
5 10 2
How can I achieve this?
You can try using pivot
select pv.* from
(SELECT Name,Leave
FROM tblUser INNER JOIN tblLeaveMaster ON tblUser.EmployeeID=tblLeaveMaster.EmployeeID
)X
pivot
(max(leave) for name in ([Test1],[Test2],[Test3])) as pv
For Dynamic PIVOT
declare #sql varchar(max)='',#col_list varchar(8000)=''
set #col_list = (select distinct quotename([Name])+',' from (SELECT Name,Leave
FROM tblUser INNER JOIN tblLeaveMaster ON tblUser.EmployeeID=tblLeaveMaster.EmployeeID
)X
for xml path(''))
set #col_list = left (#col_list,len(#col_list)-1)
set #sql = 'select '+#col_list+' from
(SELECT Name,Leave
FROM tblUser INNER JOIN tblLeaveMaster ON tblUser.EmployeeID=tblLeaveMaster.EmployeeID
)X
pivot (max([Leave]) for [Name] in ('+#col_list+'))pv'
exec(#sql)
try by using case when
select max( case when name='Test1' then Leave end) as test1,
max( case when name='Test2' then Leave end) as test2,
max( case when name='Test3' then Leave end) as test3 from
tblUser INNER JOIN tblLeaveMaster
ON tblUser.EmployeeID=tblLeaveMaster.EmployeeID
You can try to use condition aggregate function. CASE WHEN with MAX or MIN
SELECT
MAX(CASE WHEN Name = 'Test1' THEN Leave END) Test1,
MAX(CASE WHEN Name = 'Test2' THEN Leave END) Test2,
MAX(CASE WHEN Name = 'Test3' THEN Leave END) Test3
FROM tblUser
INNER JOIN tblLeaveMaster ON tblUser.EmployeeID=tblLeaveMaster.EmployeeID
EDIT
If your column want to create dynamic you can try to use Dynamic PIVOT
create your SQL statement and make condition aggregate function by connect SQL string. then use execute it Dynamically.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ', MAX(CASE WHEN Name = ''' + Name+''' THEN Leave END) ' + QUOTENAME(Name)
FROM tblUser
INNER JOIN tblLeaveMaster ON tblUser.EmployeeID=tblLeaveMaster.EmployeeID
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query= 'SELECT '+ #cols+'
FROM tblUser
INNER JOIN tblLeaveMaster ON tblUser.EmployeeID=tblLeaveMaster.EmployeeID'
execute(#query)
sqlfiddle
You can find your result from the query as shown below. Here I have taken your query output in a temporary table.
Create table #finalData(ColName Varchar(30), Leave INT)
INSERT INTO #finalData Values('Test1', 5),('Test2', 10),('Test3', 2)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.ColName)
FROM #finalData c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + ' from
(
select ColName
, Leave
from #finalData
) x
pivot
(
max(Leave)
for ColName in (' + #cols + ')
) p '
execute(#query)
DROP TABLE #finalData
Hope this will help you.
The output is as shown below
Test1 Test2 Test3
5 10 2
select pv.* from
(SELECT Name,Leave
FROM tblUser INNER JOIN tblLeaveMaster ON tblUser.EmployeeID=tblLeaveMaster.EmployeeID
)X
pivot
(max(leave) for name in ([Test1],[Test2],[Test3])) as pv
Note:- this work for me but how to use where condition base on dropdown list selected value.
eg: if wants to show only 2020 and not 2019.

Restructure table by removing NULL values

I have a table in SQL that looks like this:
Customer Product 1999 2000 2001 2002 2003
Smith 51 NULL NULL 15 14 NULL
Jones 14 11 7 NULL NULL NULL
Jackson 13 NULL NULL NULL 3 9
The figures under each year column are amounts, in dollars. Each customer has two consecutive years of amounts, and the rest of the years are zero. I would like to re-structure this table so that instead of wide list of years, it just has two columns Amount-Year1 and Amount-Year2. So it selects the two non-zero years and puts them in those columns, in the correct order. This would greatly reduce the size of my table.
So far I've been able to re-structure it so that there is one amount column and one year column, but I then get multiple rows per customer, which unfortunately I can't have (due to downstream analysis). Can anyone think of a way to get the two Amount-Year columns?
I would like the final table to look like this:
Customer Product Amount_Y1 Amount_Y2
Smith 51 15 14
Jones 14 11 7
Jackson 13 3 9
I don't mind that I lose the information about the specific years, as I can get that from another source. The actual table has data for all years between 1999 and 2018, and there will be further years in the future.
Thanks
Thankfully UNPIVOT removes NULLs anyway, so we can do this with UNPIVOT/ROWNUMBER(),PIVOT:
declare #t table (Customer varchar(15),Product int,[1999] int,
[2000] int,[2001] int,[2002] int,[2003] int)
insert into #T(CUstomer,Product,[1999],[2000],[2001],[2002],[2003]) values
('Smith' ,51,NULL,NULL, 15, 14,NULL),
('Jones' ,14, 11, 7,NULL,NULL,NULL),
('Jackson',13,NULL,NULL,NULL, 3, 9)
;With Numbered as (
select
Customer,Product,Value,
ROW_NUMBER() OVER (PARTITION BY Customer,Product
ORDER BY Year) rn
from
#t t
unpivot
(Value for Year in ([1999],[2000],[2001],[2002],[2003])) u
)
select
*
from
Numbered n
pivot
(SUM(Value) for rn in ([1],[2])) w
Results:
Customer Product 1 2
--------------- ----------- ----------- -----------
Jackson 13 3 9
Jones 14 11 7
Smith 51 15 14
Use COALESCE that will do the job for you. The query is dynamics so that if tomorrow year columns are changed, i.e. removed or added you do not have to change anything.
Sample query: (Assuming table to be table1 and column names to be same as year).
DECLARE #columnsdesc nvarchar(max), #columnsasc nvarchar(max)
SET #columnsdesc = ''
SELECT #columnsdesc = (select + '[' + ltrim(c.Name) + ']' + ','
FROM sys.columns c
JOIN sys.objects o ON o.object_id = c.object_id
WHERE o.type = 'U' and o.Name = 'table1' and c.Name not in ('Customer', 'Product')
ORDER BY c.Name desc for xml path ( '' ))
SET #columnsasc = ''
SELECT #columnsasc = (select + '[' + ltrim(c.Name) + ']' + ','
FROM sys.columns c
JOIN sys.objects o ON o.object_id = c.object_id
WHERE o.type = 'U' and o.Name = 'table1' and c.Name not in ('Customer', 'Product')
ORDER BY c.Name asc for xml path ( '' ))
SELECT #columnsasc = LEFT( #columnsasc,LEN(#columnsasc)-1)
SELECT #columnsdesc = LEFT( #columnsdesc,LEN(#columnsdesc)-1)
DECLARE #sql nvarchar(max)
SET #sql = 'SELECT Customer, Product, COALESCE('+ #columnsasc +') as Amount_Y1,
COALESCE(' + #columnsdesc +' ) as Amount_Y2
FROM Table1'
EXEC(#sql)
If you're dealing with a temporary table, then the code will change slightly:
Test it here: http://rextester.com/MRVR48808
DECLARE #columnsdesc nvarchar(max), #columnsasc nvarchar(max)
SET #columnsdesc = ''
SELECT #columnsdesc = (select + '[' + ltrim(c.Name) + ']' + ','
FROM tempdb.sys.columns c --Changes here
JOIN tempdb.sys.objects o ON o.object_id = c.object_id --Changes here
WHERE o.type = 'U' and o.Name like '#table1%' and c.Name not in ('Customer', 'Product') --Changes here
ORDER BY c.Name desc for xml path ( '' ))
SET #columnsasc = ''
SELECT #columnsasc = (select + '[' + ltrim(c.Name) + ']' + ','
FROM tempdb.sys.columns c --Changes here
JOIN tempdb.sys.objects o ON o.object_id = c.object_id --Changes here
WHERE o.type = 'U' and o.Name like '#table1%' and c.Name not in ('Customer', 'Product') --Changes here
ORDER BY c.Name asc for xml path ( '' ))
SELECT #columnsasc = LEFT( #columnsasc,LEN(#columnsasc)-1)
SELECT #columnsdesc = LEFT( #columnsdesc,LEN(#columnsdesc)-1)
DECLARE #sql nvarchar(max)
SET #sql = 'SELECT Customer, Product, COALESCE('+ #columnsasc +') as Amount_Y1,
COALESCE(' + #columnsdesc +' ) as Amount_Y2
FROM #Table1' --Changes here
EXEC(#sql)
Try using COALESCE as follows : For one field from beginning to end and for the second in the reverse manner.
SELECT Customer,Product, COALESCE([1999],[2000],[2001],[2002],[2003]) as Y1,
COALESCE([2003],[2002],[2001],[2000],[1999]) as Y2
FROM #TEMPDATA
I would do this using cross apply:
select t.customer, t.product, v.Amount_Y1, v.Amount_Y2
from t cross apply
(select max(case when which = 1 then val end) as Amount_Y1,
max(case when which = 2 then val end) as Amount_Y2
from (select val, yr, row_number() over (order by yr) as which
from (values (t.[1999], 1999), (t.[2000], 2000), (t.[2001], 2001),
(t.[2002], 2002), (t.[2003], 2003)
) v(val, yr)
where val is not null
) v

SQL Server 2012 Dynamic Pivot with a Join [duplicate]

I have a table of Customers
Customer ID Name
1 John
2 Lewis
3 Mary
I have another table CustomerRewards
TypeID Description
1 Bronze
2 Silver
3 Gold
4 Platinum
5 AnotherOne
And the final table
RewardID TypeID CustomerID
1 1 1
2 1 1
3 2 1
4 2 2
The customerTypes table is dynamic, many of these types can be added and removed. Basically all I want is the columns to be generated dynamically and a count in each, something like
CustomerName Bronze Silver Gold Platinum AnotherOne total
John 2 1 0 0 0 3
Lewis 0 1 0 0 0 1
Grand TOTAL 2 2 0 0 0 4
The problem like I said it that the types are dynamic and the customers are dynamic so I need the columns to be dynamic depending on the types in the system
I have tagged c# as I need this in a DataGridView
Thanks in advance
You will want to use a PIVOT function for this. If you have a known number of columns, then you can hard-code the values:
select name, [Bronze], [Silver], [Gold], [Platinum], [AnotherOne]
from
(
select c.name,
cr.description,
r.typeid
from customers c
left join rewards r
on c.id = r.customerid
left join customerrewards cr
on r.typeid = cr.typeid
) x
pivot
(
count(typeid)
for description in ([Bronze], [Silver], [Gold], [Platinum], [AnotherOne])
) p;
See SQL Fiddle with Demo.
Now if you have an unknown number of columns, then you can use dynamic SQL to PIVOT:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(description)
from customerrewards
group by description, typeid
order by typeid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT name,' + #cols + ' from
(
select c.name,
cr.description,
r.typeid
from customers c
left join rewards r
on c.id = r.customerid
left join customerrewards cr
on r.typeid = cr.typeid
) x
pivot
(
count(typeid)
for description in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle With Demo
If you need to include the Total column, then you can use ROLLUP (Static Version Demo):
select name, sum([Bronze]) Bronze, sum([Silver]) Silver,
sum([Gold]) Gold, sum([Platinum]) Platinum, sum([AnotherOne]) AnotherOne
from
(
select name, [Bronze], [Silver], [Gold], [Platinum], [AnotherOne]
from
(
select c.name,
cr.description,
r.typeid
from customers c
left join rewards r
on c.id = r.customerid
left join customerrewards cr
on r.typeid = cr.typeid
) x
pivot
(
count(typeid)
for description in ([Bronze], [Silver], [Gold], [Platinum], [AnotherOne])
) p
) x
group by name with rollup
Dynamic version (Demo):
DECLARE #cols AS NVARCHAR(MAX),
#colsRollup AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(description)
from customerrewards
group by description, typeid
order by typeid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsRollup
= STUFF((SELECT ', Sum(' + QUOTENAME(description) + ') as '+ QUOTENAME(description)
from customerrewards
group by description, typeid
order by typeid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'SELECT name, '+ #colsRollup + '
FROM
(
SELECT name,' + #cols + ' from
(
select c.name,
cr.description,
r.typeid
from customers c
left join rewards r
on c.id = r.customerid
left join customerrewards cr
on r.typeid = cr.typeid
) x
pivot
(
count(typeid)
for description in (' + #cols + ')
) p
) x1
GROUP BY name with ROLLUP'
execute(#query)

creating a pivot table using sql

I am trying to create a pivot table in sql but am having difficulties. Here is my problem: I have a column in my database called 'statusreason', and I need to provide a sum of each statusreason for the past week. My set is as follows:
I need to pivot this table so that it appears like the following:
There are a number of statusreasons that are not represented in the above table, since they did not occur in the past week.
The query used to generate the result set is:
select inv.statusreason
, count(inv.statusreason) as 'StatusCount'
from invoicetbl inv (nolock)
inner join trucktbl tru (nolock) on inv.tru_key = tru.tru_key
where inv.client_key = 123
and inv.createdate > getdate() - 7
group by inv.statusreason
If this isn't enough information, please advise what I could add to improve the question.
Thank you for any assistance you can provide.
Since you want to convert your rows of data into columns, you need to PIVOT the data. This can be done a number of ways.
If you have a limited number of values that you are going to be returning, then you can use an aggregate function with a CASE expression:
select
count(case when statusreason = 181 then 1 end) [181],
count(case when statusreason = 20 then 1 end) [20],
count(case when statusreason = 212 then 1 end) [212],
count(case when statusreason = 232 then 1 end) [232]
from
(
select inv.statusreason
from invoicetbl inv (nolock)
inner join trucktbl tru (nolock)
on inv.tru_key = tru.tru_key
where inv.client_key = 123
and inv.createdate > getdate() - 7
) d;
Or you can use the PIVOT function:
select [181], [20], [212], [232]
from
(
select inv.statusreason
from invoicetbl inv (nolock)
inner join trucktbl tru (nolock)
on inv.tru_key = tru.tru_key
where inv.client_key = 123
and inv.createdate > getdate() - 7
) d
pivot
(
count(statusreason)
for statusreason in ([181], [20], [212], [232])
) p;
If you have an unknown number of values that will be returned, then you will want to look at using dynamic SQL. This creates a sql string that will then be executed.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(statusreasons )
from statusreasontbl
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + '
from
(
select inv.statusreason
from invoicetbl inv (nolock)
inner join trucktbl tru (nolock)
on inv.tru_key = tru.tru_key
where inv.client_key = 123
and inv.createdate > getdate() - 7
) x
pivot
(
count(statusreason)
for statusreason in (' + #cols + ')
) p '
execute sp_executesql #query;