use row value as column and columns as rows - sql-server

I'm trying to create a query in which I use pivot to transform row values into columns. That I am able to do fine. But the requirement is to also use the other columns as row values.
The table and query I have tried can be checked here.
The required output is here.
From the query, I need the values of 'C' to be columns then the other columns (A, B, D & E) to be row values for the first column.
Is it possible to use one single pivot query to display the required output? If not, what would be the best approach to the problem?

SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE Table1
(
A varchar(10),
B varchar(10),
C int,
D varchar(10),
E varchar(10)
)
GO
INSERT INTO Table1 VALUES('A1', 'B1', 1, 'D1', 'E1');
INSERT INTO Table1 VALUES('A2', 'B2', 2, 'D2', 'E2');
INSERT INTO Table1 VALUES('A3', 'B3', 3, 'D3', 'E3');
Query 1:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(C)
from Table1
group by C
order by C
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N';WITH CTE AS
(SELECT *
FROM Table1 UNPIVOT (Vals FOR COLUMNNAMES IN (A,B,D,E))up
)
SELECT *
FROM CTE
PIVOT (MAX(Vals)
FOR C
IN(' + #cols + N'))p '
exec sp_executesql #query
Results:
| COLUMNNAMES | 1 | 2 | 3 |
|-------------|----|----|----|
| A | A1 | A2 | A3 |
| B | B1 | B2 | B3 |
| D | D1 | D2 | D3 |
| E | E1 | E2 | E3 |

Related

Join multiple "exec" as one output SQL server

I'm tring to get multiple results from a dynamic exec to be columns of a table in a single output using SQL Server, right now the exec gives me multiple outputs as different temporal tables.
Is there a way to merge them together and get a single output?
This is my code:
Declare #status nvarchar(255)
Declare POINTER cursor global for select distinct status from intermedio
open POINTER
fetch NEXT from POINTER into #status
while (##FETCH_STATUS=0)
begin
exec('select distinct(
Select COUNT(*) from (
SELECT DISTINCT PKN
FROM INTERMEDIO
WHERE PIECE IN ('+'''+891''','''+75'''+') and ESTATUS = '''+#status+'''
group by PKN
having count(*)=2
) ser) as '+#status);
fetch next from POINTER into #status
end;
close POINTER;
DEALLOCATE POINTER;
Edit: Tried to explain myself better, once again excuse my poor english.
My table has three columns
+-----------+-------+---------+
| PKN | PIECE | ESTATUS |
+-----------+-------+---------+
| Set_one | +891 | A1 |
| Set_one | +75 | A1 |
| Set_one | +45 | A1 |
| Set_two | +891 | A3 |
| Set_two | +75 | A3 |
| Set_three | +700 | B1 |
+-----------+-------+---------+
I'm trying to get the count of the PKNs that have both (+891, +75) and count how many exist under ESTATUS
The output I'm expecting is something like:
+----+----+----+
| A1 | A3 | B1 |
+----+----+----+
| 1 | 1 | 0 |
+----+----+----+
But it gives me the rows on different tables.
The reason I'm doing it like this is because the table can have many different ESTATUS at any time, and many different pieces on different PKN, the result will change constantly and the table generated will be different each time I execute the query.
I'm sorry if the question isn't clear, as english is not my first language.
You are looking for a dynamic pivot and a conditional aggregation. Here's an example.
create table #tempTable (PKN varchar(64), PIECE varchar(64), ESTATUS varchar(2))
insert into #tempTable
values
('Set_one','+891','A1'),
('Set_one','+75','A1'),
('Set_one','+45','A1'),
('Set_two','+891','A3'),
('Set_two','+75','A3'),
('Set_three','+700','B1')
select
ESTATUS
--here is the conditional aggregation
,floor(count(case when PIECE = '+891' or PIECE = '+75' then PKN end)/2) as CT
into #staging
from
#tempTable
group by ESTATUS
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #ColumnName= ISNULL(#ColumnName + ',','')
+ QUOTENAME(ESTATUS)
FROM (SELECT DISTINCT ESTATUS FROM #staging) AS ESTATUS
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
N'SELECT ' + #ColumnName + '
FROM #staging
PIVOT(Sum(CT)
FOR ESTATUS IN (' + #ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
drop table #tempTable
drop table #staging
RETURNS
+----+----+----+
| A1 | A3 | B1 |
+----+----+----+
| 1 | 1 | 0 |
+----+----+----+

Need to Dynamically Pivot Table Making Date Column New Table Headers

I've seen just about every kind of pivot example on the Internet, both text based tutorial and video. Not one is solving the issue at hand.
What I want is to pivot the following table:
Sales Table
SalesId | SalesDate | SalesLocation | SalesAmount
-----------------------------------------------------
1 | 2012-03 | New York, NY | 3,000
2 | 2012-04 | Miami, FL | 2,500
3 | 2012-05 | Carmel, CA | 2,850
4 | 2012-06 | Berkeley, CA | 1,900
5 | 2012-07 | Akron, OH | 4,200
6 | 2012-08 | Portland, OR | 2,200
I would like to PIVOT this table to show each row as a column, not distinct SUM of columns. Each row is specifically it's own column, with the date as the header, as if the table has been turned 90 degrees clockwise like an Excel spreadsheet, for date forecasting:
Account Info | 2012-03 | 2012-04 | 2012-05 | 2012-06
---------------------------------------------------------------------
SalesId | 1 | 2 | 3 | 4
SalesLocation | New York, NY | Miami, FL | Carmel, CA | Berkeley, CA
SalesAmount | 3,000 | 2,500 | 2,850 | 1,900
Most of the tutorials on this subject involve creating sums of several rows that have either the same city, the same status or the same type.
For this situation, each row already has a distinct unique date that will never be duplicated, so all records are unique and therefore do not require a function like SUM.
So far, after viewing an incredibly clear solution by JoyceW on the PIVOT Using Date Column thread at the ASP.net forums, I have gotten this far:
declare #cols varchar(max)
select #cols = (select distinct SalesDate from SalesTable for xml path(''))
select #cols = replace(#cols, '', '[')
select #cols = replace(#cols, '', '],')
select #cols = left(#cols, len(#cols) - 1)
declare #query varchar(max)
select #query = 'select * from (select SalesId, SalesLocation, SalesAmount
from SalesTable) src pivot (max(SalesAmount) for SalesDate in ('
+ #cols + ')) piv;'
execute(#query)
While this is not what I was looking for, it's the only tutorial that has allowed me to actually return a result set where the top columns are actually the dates (yay), as seen below:
SalesId | SalesLocation | SalesAmount | 2012-03 | 2012-04 | 2012-05
1 | New York, NY | 3,000 | {null} | {null} | 4
2 | Miami, FL | 2,500 | {null} | 3 | {null}
3 | Carmel, CA | 2,850 | {null} | {null} | {null}
4 | Berkeley, CA | 1,900 | 1 | {null} | {null}
5 | Akron, OH | 4,200 | {null} | {null} | {null}
6 | Portland, OR | 2,200 | {null} | 2 | {null}
Not really sure what's going on here, but it's a lot closer than I was before, I'm actually receiving a result set. Any ideas about how to fine tune this where the rows showing up need to be rotated or pivoted as well?
It seems that I'm only pivoting the headers and not the entire table as I would like. Any suggestions would be greatly appreciated.
As you want multiple aggregation columns in your pivot you have to do the iterations for each aggregation value. I am using Sales as the table name. Please change the table name if it is different for you. Below query should give you the result you want.
DECLARE #cols AS NVARCHAR(MAX),
#Convertcols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#SQL AS NVARCHAR(MAX),
#PivotColumn Varchar(100),
#i Int = 1,
#AggColumn Varchar(100)
DECLARE #Columns TABLE (ID int IDENTITY(1,1), AccountInfo Varchar(max))
SELECT #cols = STUFF((SELECT ', '+ QUOTENAME(SalesDate)
from Sales
group by SalesDate
order by SalesDate
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SELECT #Convertcols = STUFF((SELECT ', CAST( '+ QUOTENAME(SalesDate) + ' AS VARCHAR(max)) AS ' + QUOTENAME(SalesDate)
from Sales
group by SalesDate
order by SalesDate
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
INSERT INTO #Columns
SELECT C.name
FROM SYS.COLUMNS C
INNER JOIN SYS.TABLES T ON C.OBJECT_ID = T.OBJECT_ID
WHERE T.NAME = 'Sales'
AND C.name <> 'SalesDate'
While #i <= (SELECT MAX(ID) FROM #Columns)
BEGIN
SELECT #AggColumn = AccountInfo FROM #Columns Where ID = #i
set #query = 'SELECT AccountInfo,' + #Convertcols +
' FROM
(
SELECT ''' + #AggColumn + ''' AS AccountInfo, SalesDate, '+ #AggColumn +'
FROM Sales
) X
PIVOT
(
Max('+ #AggColumn +')
FOR SalesDate in (' + #cols + ')
) P '
SET #SQL = CONCAT(#SQL, CHAR(10), CASE WHEN #i = 1 THEN '' ELSE 'UNION ' END, CHAR(10) , #query)
SET #i = #i+1
END
EXECUTE (#SQL)
Result:
In order for you to be able to PIVOT, you'll need to have at least one non-pivoted column - something which your model is lacking.
Documentation: https://technet.microsoft.com/en-us/library/ms177410%28v=sql.105%29.aspx?f=255&MSPPError=-2147217396

SQL Server Transpose Rows into Columns using IDs as Column Names

I have a large file that has the following fields:
Table 1:
+---------+--------+-----------+
| User_Id | Key_Id | Value |
+---------+--------+-----------+
| 100 | 74 | 37 |
| 100 | 65 | Male |
| 100 | 279 | G235467 |
+---------+--------+-----------+
and I have another file that tells what each 'Key_Id' is called (they are column names) e.g.
Table 2:
+--------+------------------+
| Key_Id | Key |
+--------+------------------+
| 65 | Gender |
| 66 | Height |
| 74 | Age |
| 279 | ReferenceNo |
I want to create a table using the Key_Id names found in the Key column of table 2, transpose all of the values from table 1 into table 2, but also include the User_Id from table 1 as this relates to an individual.
PS. Table 2 has nearly 300 keys that would need turning into individual fields
So ultimately I would like a table that looks like this:
+---------+---------+--------+-------+--------------+--------+
| User_Id | Gender | Height | Age | ReferenceNo | etc |
+---------+---------+--------+-------+--------------+--------+
| 100 | Male | | 37 | G235467 | |
So that each User_Id is a row and that all the Keys are columns with their respective values
You can use a dynamic sql query as below.
Query
declare #sql as varchar(max);
select #sql = 'select t1.[User_Id], ' + stuff((select +
', max(case t2.[Key_Id] when ' + cast([Key_Id] as varchar(100)) +
' then t1.[Value] end) as [' + [Key] + '] '
from Table2
for xml path('')
), 1, 2, '') +
'from Table1 t1 left join Table2 t2 on t1.[Key_Id] = t2.[Key_Id] group by t1.[User_Id];'
exec(#sql);
Find a demo here
You need to get a coma-separated list of those 300 key names to be used in PIVOT/UNPIVOT operators in T-SQL like described here
https://learn.microsoft.com/en-us/sql/t-sql/queries/from-using-pivot-and-unpivot
you can use pivot as below:
Select * from (
Select u.UserId, k.[key], u.[Value] from table1 u
join table2 k on u.keyid = k.keyid ) a
pivot ( max([Value]) for [key] in ([Gender], [Height], [Age], [ReferenceNo]) ) p
For dynamic list of keys you can use dynamic sql as below:
Declare #cols1 varchar(max)
Declare #query nvarchar(max)
Select #cols1 = stuff((select ','+QuoteName([Key]) from table2 group by [Key] for xml path('')),1,1,'')
Set #Query = 'Select * from (
Select u.UserId, k.[key], u.[Value] from table1 u
join table2 k on u.keyid = k.keyid ) a
pivot ( max([Value]) for [key] in (' + #cols1 + ') ) p '
Select #Query --Check the generated query and execute by uncommenting below query
--exec sp_executesql #Query

SQL Server 2008 Vertical data to Horizontal

I apologize for submitting another question on this topic, but I've read through many of the answers on this and I can't seem to get it to work for me.
I have three tables I need to join and pull info on. One of the tables is only 3 columns and stores the data vertically. I would like to transpose that data to a horizontal format.
The data will look like this if I just join and pull:
SELECT
a.app_id,
b.field_id,
c.field_name,
b.field_value
FROM table1 a
JOIN table2 b ON a.app_id = b.app_id
JOIN table3 c ON b.field_id = c.field_id --(table3 is a lookup table for field names)
Result:
app_id | field_id | field_name | field_value
-----------------------------------------------------
1234 | 101 | First Name | Joe
1234 | 102 | Last Name | Smith
1234 | 105 | DOB | 10/15/72
1234 | 107 | Mailing Addr | PO BOX 1234
1234 | 110 | Zip | 12345
1239 | 101 | First Name | Bob
1239 | 102 | Last Name | Johnson
1239 | 105 | DOB | 12/01/78
1239 | 107 | Mailing Addr | 1234 N Star Ave
1239 | 110 | Zip | 12456
Instead, I would like it to look like this:
app_id | First Name | Last Name | DOB | Mailing Addr | Zip
--------------------------------------------------------------------------
1234 | Joe | Smith | 10/15/72 | PO BOX 1234 | 12345
1239 | Bob | Johnson | 12/01/78 | 1234 N Star Ave | 12456
In the past, I just resorted to looking up all the field_id's I needed in my data and created CASE statements for each one. The app the users are using contains data for multiple products, and each product contains different fields. Considering the number of products supported and the number of fields for each product (many, many more than the basic example I showed, above) it takes a long time to look them up and write out huge chunks of CASE statements.
I was wondering if there's some cheat-code out there to achieve what I need without having to look up the field_ids and writing things out. I know the PIVOT function is likely what I'm looking for, however, I can't seem to get it to work correctly.
Think you guys could help out?
You can use the PIVOT function to convert your rows of data into columns.
Your original query can be used to retrieve all the data, the only change I would make to it would be to exclude the column b.field_id because this will alter the final display of the result.
If you have a known list of field_name values that you want to turn into columns, then you can hard-code your query:
select app_id,
[First Name], [Last Name], [DOB],
[Mailing Addr], [Zip]
from
(
SELECT
a.app_id,
c.field_name,
b.field_value
FROM table1 a
INNER JOIN table2 b
ON a.app_id = b.app_id
INNER JOIN table3 c
ON b.field_id = c.field_id
) d
pivot
(
max(field_value)
for field_name in ([First Name], [Last Name], [DOB],
[Mailing Addr], [Zip])
) piv;
See SQL Fiddle with Demo.
But if you are going to have an unknown number of values for field_name, then you will need to implement dynamic SQL to get the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(Field_name)
from Table3
group by field_name, Field_id
order by Field_id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT app_id,' + #cols + '
from
(
SELECT
a.app_id,
c.field_name,
b.field_value
FROM table1 a
INNER JOIN table2 b
ON a.app_id = b.app_id
INNER JOIN table3 c
ON b.field_id = c.field_id
) x
pivot
(
max(field_value)
for field_name in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. Both of these this will give a result:
| APP_ID | FIRST NAME | LAST NAME | DOB | MAILING ADDR | ZIP |
------------------------------------------------------------------------
| 1234 | Joe | Smith | 10/15/72 | PO Box 1234 | 12345 |
| 1239 | Bob | Johnson | 12/01/78 | 1234 N Star Ave | 12456 |
Try this
SELECT
[app_id]
,MAX([First Name]) AS [First Name]
,MAX([Last Name]) AS [Last Name]
,MAX([DOB]) AS [DOB]
,MAX([Mailing Addr]) AS [Mailing Addr]
,MAX([Zip]) AS [Zip]
FROM Table1
PIVOT
(
MAX([field_value]) FOR [field_name] IN ([First Name],[Last Name],[DOB],[Mailing Addr],[Zip])
) T
GROUP BY [app_id]
SQL FIDDLE DEMO
bluefeet's answer was the right one for me, but I needed distinct on the column list:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT Distinct ',' + QUOTENAME(Field_name)
from Table3
group by field_name, Field_id
order by ',' + QUOTENAME(Field_name)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT app_id,' + #cols + '
from
(
SELECT
a.app_id,
c.field_name,
b.field_value
FROM table1 a
INNER JOIN table2 b
ON a.app_id = b.app_id
INNER JOIN table3 c
ON b.field_id = c.field_id
) x
pivot
(
max(field_value)
for field_name in (' + #cols + ')
) p '
execute sp_executesql #query;
This would solve using group by and MAX function, instead of pivot:
SELECT PK_ID, MAX(PHONE) AS PHONE, MAX(MAIL) AS MAIL
FROM (
SELECT
PK_ID,
CASE
WHEN CONTACT_ALIAS.CONTACT_TYPE = 'COMPANY' THEN CONTACT_ALIAS.CONTACT_VALUE
END AS PHONE ,
CASE
WHEN CONTACT_ALIAS.CONTACT_TYPE = 'BUSINESS' THEN CONTACT_ALIAS.CONTACT_VALUE
END AS MAIL
FROM T_CONTACT_EMPLOYERS CONTACT_ALIAS
WHERE CONTACT_ALIAS.CONTACT_TYPE IN ('COMPANY' , 'BUSINESS')
) TEMP
GROUP BY PK_ID
USe of SQL Pivot
SELECT [Id], [FirstName], [LastName], [Email]
FROM
(
SELECT Id, Att_Id, Att_Value FROM VerticalTable
) as source
PIVOT
(
MAX(Att_Value) FOR Att_Id IN ([FirstName], [LastName], [Email])
) as target

Need help pivoting some data

I'm hoping someone can help me. I'm trying to pivot some data on SQL Server 2005 and can't quite get the results I'm looking for.
This is my current table schema:
| ProductCode | AttributeName | AttributeValue |
| 1 | AttributeA | 10 |
| 1 | AttributeB | 20 |
| 2 | AttributeA | 30 |
| 2 | AttributeB | 40 |
| 3 | AttributeA | 50 |
This is the results I'm trying to achieve:
| ProductCode | AttributeA | AttributeB |
| 1 | 10 | 20 |
| 2 | 30 | 40 |
| 3 | 50 | NULL |
I know that I can achieve this result with the following SQL:
SELECT DISTINCT ProductCode,
(SELECT AttributeValue
FROM attributes
WHERE ProductName = 'AttributeA' AND ProductCode=a.ProductCode) AttributeA,
(SELECT AttributeValue
FROM attributes
WHERE ProductName = 'AttributeB' AND ProductCode=a.ProductCode) AttributeB,
FROM attributes a
Although that SQL does produce the result I'm looking for, it's obviously not dynamic (in reality, I not only have more Attribute Types, but different products have different sets of attributes) and it also scans the table 3 times. It's also a maintenance nightmare.
I tried using the PIVOT functionality of SQL Server, but with no luck.
Can anyone help?
create table #attributes (ProductCode int,
AttributeName varchar(20),
AttributeValue int)
insert into #attributes values (1, 'AttributeA', 10)
insert into #attributes values (1, 'AttributeB', 20)
insert into #attributes values (2, 'AttributeA', 30)
insert into #attributes values (2, 'AttributeB', 40)
insert into #attributes values (3, 'AttributeA', 50)
declare #attributes_columns nvarchar(max)
set #attributes_columns
= (
select ', [' + AttributeName + ']'
from
(
select distinct AttributeName as AttributeName
from #attributes
) t
order by t.AttributeName
for xml path('')
)
set #attributes_columns = stuff(#attributes_columns,1,2,'')
declare #sql nvarchar(max)
set #sql = N'
select ProductCode, <attributes_columns>
from
(select ProductCode, AttributeName, AttributeValue
from #attributes )p
pivot
(
sum(AttributeValue) for AttributeName in (<attributes_columns>)
) as pvt
'
set #sql = replace(#sql, '<attributes_columns>', #attributes_columns)
print #sql
exec sp_executesql #sql
drop table #attributes

Resources