Transpose rows to column and join table - sql

Hi I have two tables Attribute, Instance
Attribute Table
id | key_info | value_info
2 | amount | 1009
2 | currency | USD
2 | Level | Level 5
3 | amount | 2017
3 | currency | CAD
Instance Table
id | status
2 | Pending
3 | Approved
I want to join two tables like this-
New table
id | amount | currrency | level | status
2 | 1001 | USD | Level 5 | Pending
3 | 2017 | CAD | | Approved
All the fields in the Attribute and Instance are optional except id.

select
a.id,
max(case when a.key_info = 'amount' then a.value_info end) as amount,
max(case when a.key_info = 'currency' then a.value_info end) as currency,
max(case when a.key_info = 'level' then a.value_info end) as level,
i.status
from
attribute a
join instance i on a.id = i.id
group by
a.id,
i.status
Sql Fiddle

Starting in SQL Server 2005, the PIVOT function can perform this conversion from rows to columns:
select id,
amount,
currency,
level,
status
from
(
select i.id, i.status,
a.key_info,
a.value_info
from instance i
inner join attribute a
on i.id = a.id
) src
pivot
(
max(value_info)
for key_info in (amount, currency, level)
) piv
See SQL Fiddle with Demo.
If you have an unknown number of key_values then you can use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(key_info)
from Attribute
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id, ' + #cols + ', status from
(
select i.id, i.status,
a.key_info,
a.value_info
from instance i
inner join attribute a
on i.id = a.id
) x
pivot
(
max(value_info)
for key_info in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo. Both give the result:
| ID | AMOUNT | CURRENCY | LEVEL | STATUS |
-----------------------------------------------
| 2 | 1009 | USD | Level 5 | Pending |
| 3 | 2017 | CAD | (null) | Approved |

Related

SQL Server 2014 STUFF with 2 Inner Join

My question really similar to this Use STUFF with INNER JOIN Query, but I've been trying what if the first column is a varchar type, currently this is my table. As for note I'm using SQL Server 2014
Table Customer
Cust_ID | Cust_Name
--------+---------
1 | Name1
2 | Name2
Table Order
Order_ID | Order_NO | Cust_ID
---------+----------+---------
1 | No.001 | 1
2 | No.002 | 2
Table Item
Item_ID | Order_ID | Item_Name | Quantity | Price
--------+----------+-----------+----------+-------
1 | 1 | A | 1 | 10
2 | 1 | B | 1 | 20
3 | 1 | C | 1 | 30
4 | 2 | D | 1 | 40
A few queries that I've tried:
SELECT
TBL_Sales_SO.SO_NO AS int, Tbl_Customer.Customer_Name,
ITEMS = STUFF ((SELECT DISTINCT ',' + TBL_SO_LIITEM.Item_Name
FROM Tbl_Customer
INNER JOIN TBL_Sales_SO ON Tbl_Customer.Com_Customer_ID = TBL_Sales_SO.Com_Customer_ID
INNER JOIN TBL_SO_LIITEM ON TBL_Sales_SO.Sales_SO_ID = TBL_SO_LIITEM.Sales_So_ID
WHERE TBL_Sales_SO.SO_NO = TBL_Sales_SO.Sales_SO_ID
FOR XML PATH('')), 1, 1,'')
FROM
TBL_Sales_SO, Tbl_Customer
ORDER BY
SO_NO
and the query that for me I think almost hit
SELECT
TBL_Sales_SO.SO_NO, Tbl_Customer.Customer_Name, TBL_SO_LIITEM.Item_Name,
TBL_SO_LIITEM.Quantity, TBL_SO_LIITEM.Price
FROM
((Tbl_Customer
INNER JOIN
TBL_Sales_SO on Tbl_Customer.Com_Customer_ID = TBL_Sales_SO.Com_Customer_ID)
INNER JOIN
TBL_SO_LIITEM on TBL_Sales_SO.Com_Customer_ID = TBL_SO_LIITEM.Sales_So_ID)
While tinkering the first code give me some various error, the second one im just not sure how make possible something like my target
Order_No | Name | Item_Name | Quantity | Price
no.001 | Name1 | A,B,C | 3 | 60
no.002 | Name2 | D | 1 | 40
UPDATE
Following Gordon Answer, i tinkering the code again, manage to the following table
Order_No| Name | Item_Name
no.001 | Name1 | A,B,C
no.001 | Name2 | D
no.002 | Name2 | D
no.002 | Name1 | A,B,C
and the query I use:
SELECT
s.SO_NO AS int, c.Cust_Name,
ITEMS = STUFF((SELECT DISTINCT ','+ Item_Name
FROM TBL_SO_LIITEM item
INNER JOIN TBL_Sales_SO s ON s.Sales_SO_ID = item.Sales_So_ID
WHERE c.Cust_ID = s.Cust_ID
FOR XML PATH('')), 1, 1, '')
FROM
TBL_Sales_SO s, Tbl_Customer c
WHERE
c.Cust_Name IN ('Name1','Name2')
ORDER BY
SO_NO
You seem to want:
SELECT s.SO_NO AS int, c.Customer_Name,
STUFF( (SELECT DISTINCT ',' + oi.Item_Name
FROM TBL_SO_LIITEM oi
WHERE s.Sales_SO_ID = oi.Sales_So_ID
FOR XML PATH('')), 1, 1,''
) as items
FROM TBL_Sales_SO s JOIN
Tbl_Customer c
ON s.cust_id = c.cust_id
ORDER BY SO_NO;
Note that the tables are only included once, either in the subquery or in the outer query. The naming conventions for your data is quite inconsistent, so the above is my best guess.

Converting multiple rows to columns with heading from the same row

I have a table that holds details for flats - contents of this table is similar to the following:
| flat | description | Amount | Date
--------------------------------------
| flat1 | electricity | 1 |1/1/2016
| flat1 | water | 2 |1/1/2016
| flat1 | levy | 3 |1/1/2016
| flat2 | electricity | 1 |1/1/2016
| flat2 | water | 2 |1/1/2016
| flat2 | levy | 3 |1/1/2016
I need a SQL view that can produce something like the following and any help would be appreciated:
| Flat | electricity | water|levy | next description| and so on |
---------------------------------------------------------------
| flat1 | 1 | 2 | 3 | next amount | next amount |
| flat2 | 1 | 2 | 3 | next amount | next amount |
This is called table pivoting. Here's one option using conditional aggregation assuming you know the number of potential columns:
select flat,
max(case when description = 'electricity' then amount end) electricity,
max(case when description = 'water' then amount end) water,
max(case when description = 'levy' then amount end) levy
from yourtable
group by flat
If you don't know the maximum number of columns, look up dynamic pivot -- there are lots of examples on how to do it:
https://stackoverflow.com/a/10404455/1073631
drop table #temp
create table #temp (flat varchar(5), description varchar(128), amount int, date datetime)
insert into #temp (flat, description, amount, date) values
('flat1','electricity',1,'1/1/2016'),
('flat1','water',2,'1/1/2016'),
('flat1','levy',3,'1/1/2016'),
('flat2','electricity',1,'1/1/2016'),
('flat2','water',2,'1/1/2016'),
('flat2','levy',3,'1/1/2016')
select * from #temp
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(description)
FROM #temp
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT flat, ' + #cols + ' from
(select flat,amount, description from #temp) x
pivot
(
max(amount)
for description in (' + #cols + ')
) p '
execute(#query)
You can add a date filter by placing a WHERE clause in teh #query portion. i.e. from #temp where date between '1/1/2016' and '3/1/2016'

grouping and switching the columns and rows

I don't know if this would officially be called a pivot, but the result that I would like is this:
+------+---------+------+
| Alex | Charley | Liza |
+------+---------+------+
| 213 | 345 | 1 |
| 23 | 111 | 5 |
| 42 | 52 | 2 |
| 323 | | 5 |
| 23 | | 1 |
| 324 | | 5 |
+------+---------+------+
my input data is in this form:
+-----+---------+
| Apt | Name |
+-----+---------+
| 213 | Alex |
| 23 | Alex |
| 42 | Alex |
| 323 | Alex |
| 23 | Alex |
| 324 | Alex |
| 345 | Charley |
| 111 | Charley |
| 52 | Charley |
| 1 | Liza |
| 5 | Liza |
| 2 | Liza |
| 5 | Liza |
| 1 | Liza |
| 5 | Liza |
+-----+---------+
because I have approximately 100 names, I don't want to have to do a ton of sub queries lik this
select null, null, thirdcolumn from...
select null, seconcolumn from...
select firstcolumn from...
Is there a way to do this with PIVOT or otherwise?
You can do this with dynamic PIVOT and the ROW_NUMBER() function:
DECLARE #cols AS VARCHAR(1000),
#query AS VARCHAR(8000)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(Name)
FROM (SELECT DISTINCT Name
FROM #test
)sub
ORDER BY Name
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)')
,1,1,'')
PRINT #cols
SET #query = '
WITH cte AS (SELECT DISTINCT *
FROM #test)
,cte2 AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Name ORDER BY Apt)RowRank
FROM cte)
SELECT *
FROM cte2
PIVOT (max(Apt) for Name in ('+#cols+')) p
'
EXEC (#query)
SQL Fiddle - Distinct List, Specific Order
Edit: If you don't want the list to be distinct, eliminate the first cte above, and if you want to keep arbitrary ordering change the ORDER BY to (SELECT 1):
DECLARE #cols AS VARCHAR(1000),
#query AS VARCHAR(8000)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(Name)
FROM (SELECT DISTINCT Name
FROM #test
)sub
ORDER BY Name
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)')
,1,1,'')
PRINT #cols
SET #query = '
WITH cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Name ORDER BY (SELECT 1))RowRank
FROM #test)
SELECT *
FROM cte
PIVOT (max(Apt) for Name in ('+#cols+')) p
'
EXEC (#query)
SQL Fiddle - Full List, Arbitrary Order
And finally, if you didn't want the RowRank field in your results, just re-use the #cols variable in your SELECT:
SET #query = '
WITH cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Name ORDER BY (SELECT 1))RowRank
FROM #test)
SELECT '+#cols+'
FROM cte
PIVOT (max(Apt) for Name in ('+#cols+')) p
'
EXEC (#query)
Oh, this is something of a pain, but you can do it with SQL. You are trying to concatenate the columns.
select seqnum,
max(case when name = 'Alex' then apt end) as Alex,
max(case when name = 'Charley' then apt end) as Charley,
max(case when name = 'Liza' then apt end) as Liza
from (select t.*, row_number() over (partition by name order by (select NULL)) as seqnum
from t
) t
group by seqnum
order by seqnum;
As a note: there is no guarantee that the original ordering will be the same within each column. As you know, SQL tables are inherently unordered, so you would need a column to specify the ordering.
To handle multiple names, I'd just get the list using a query such as:
select distinct 'max(case when name = '''+name+''' then apt end) as '+name+','
from t;
And copy the results into the query.

Pivot Assistance Required

I have a table like this . Table 1.
Customer Product Value Quantity Order Number
Dave Product 1 15 1 154
Dave Product 2 25 5 154
Dave Product 3 45 4 15
Rob Product 2 222 33 233
Now I want this , Table 2.
Customer Product 1 Quantity Product 1 Value Price per item ( Value /Quantity) for Product 1 Product 2 Quantity Product 2 Value Price per item ( Value /Quantity) for Product 2 Product 3 Quantity Product 3 Value Price per item ( Value /Quantity) for Product 3 Order Number
Dave 1 15 15 5 25 5 null null null 154
Dave null null null null null Null 4 45 11.25 15
Rob null null null 33 222 6.727272727 null null null 233
I was thinking about some pivot but wasn't sure how to construct it . Also the number of products is not fixed and change in Table 1.
In order to get the result, I would advise applying both an unpivot and a pivot to the data.
The UNPIVOT will convert the column data from your table into rows. Once the data is unpivoted, then you can apply a pivot.
Since you are using SQL Server 2008+ you can use CROSS APPLY with the VALUES clause to unpivot. Prior to 2008, you could use the UNPIVOT function. The code to unpivot the data is:
select t.customer,
replace(t.product, ' ', '')+'_'+c.col piv_col,
c.val,
t.ordernumber
from table1 t
cross apply
(
values
('value', cast(value as varchar(10))),
('quantity', cast(quantity as varchar(10))),
('PricePerUnit', cast((value/quantity) *1.0 as varchar(10)))
) c (col, val);
See Demo. This converts the data into the following format:
| CUSTOMER | PIV_COL | VAL | ORDERNUMBER |
---------------------------------------------------------
| Dave | Product1_value | 15 | 154 |
| Dave | Product1_quantity | 1 | 154 |
| Dave | Product1_PricePerUnit | 15.0 | 154 |
| Dave | Product2_value | 25 | 154 |
You can see that the row for Dave order 154 has been turned into rows and I have created the new column names that will be used for the pivot (piv_col). This column has concatenated the Product Name to the from of the previous column headers (value, quantity).
Since the data is in a single row, you can easily apply the pivot function to the data. The final code will be:
select customer,
Product1_quantity, Product1_value, Product1_PricePerUnit,
Product2_quantity, Product2_value, Product2_PricePerUnit,
Product3_quantity, Product3_value, Product3_PricePerUnit,
orderNumber
from
(
select t.customer,
replace(t.product, ' ', '')+'_'+c.col piv_col,
c.val,
t.ordernumber
from table1 t
cross apply
(
values
('value', cast(value as varchar(10))),
('quantity', cast(quantity as varchar(10))),
('PricePerUnit', cast((value/quantity) *1.0 as varchar(10)))
) c (col, val)
) d
pivot
(
max(val)
for piv_col in(Product1_quantity, Product1_value, Product1_PricePerUnit,
Product2_quantity, Product2_value, Product2_PricePerUnit,
Product3_quantity, Product3_value, Product3_PricePerUnit)
) piv;
See SQL Fiddle with Demo.
The above works great if you have a known number of products, but if not, then you will need to use dynamic SQL.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(replace(t.product, ' ', '')+'_'+c.col)
from Table1 t
cross apply
(
values ('value', 1), ('quantity', 0),('PricePerUnit', 3)
) c (col, so)
group by product, col, so
order by product, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT customer, ' + #cols + ', ordernumber
from
(
select t.customer,
replace(t.product, '' '', '''')+''_''+c.col piv_col,
c.val,
t.ordernumber
from table1 t
cross apply
(
values
(''value'', cast(value as varchar(10))),
(''quantity'', cast(quantity as varchar(10))),
(''PricePerUnit'', cast((value/quantity) *1.0 as varchar(10)))
) c (col, val)
) d
pivot
(
max(val)
for piv_col in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. These queries give the result:
| CUSTOMER | PRODUCT1_QUANTITY | PRODUCT1_VALUE | PRODUCT1_PRICEPERUNIT | PRODUCT2_QUANTITY | PRODUCT2_VALUE | PRODUCT2_PRICEPERUNIT | PRODUCT3_QUANTITY | PRODUCT3_VALUE | PRODUCT3_PRICEPERUNIT | ORDERNUMBER |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Dave | (null) | (null) | (null) | (null) | (null) | (null) | 4 | 45 | 11.0 | 15 |
| Dave | 1 | 15 | 15.0 | 5 | 25 | 5.0 | (null) | (null) | (null) | 154 |
| Rob | (null) | (null) | (null) | 33 | 222 | 6.0 | (null) | (null) | (null) | 233 |

Write advanced SQL Select

Item table:
| Item | Qnty | ProdSched |
| a | 1 | 1 |
| b | 2 | 1 |
| c | 3 | 1 |
| a | 4 | 2 |
| b | 5 | 2 |
| c | 6 | 2 |
Is there a way I can output it like this using SQL SELECT?
| Item | ProdSched(1)(Qnty) | ProdSched(2)(Qnty) |
| a | 1 | 4 |
| b | 2 | 5 |
| c | 3 | 6 |
You can use PIVOT for this. If you have a known number of values to transform, then you can hard-code the values via a static pivot:
select item, [1] as ProdSched_1, [2] as ProdSched_2
from
(
select item, qty, prodsched
from yourtable
) x
pivot
(
max(qty)
for prodsched in ([1], [2])
) p
see SQL Fiddle with Demo
If the number of columns is unknown, then you can use a dynamic pivot:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(prodsched)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT item,' + #cols + ' from
(
select item, qty, prodsched
from yourtable
) x
pivot
(
max(qty)
for prodsched in (' + #cols + ')
) p '
execute(#query)
see SQL Fiddle with Demo
SELECT Item,
[ProdSched(1)(Qnty)] = MAX(CASE WHEN ProdSched = 1 THEN Qnty END),
[ProdSched(2)(Qnty)] = MAX(CASE WHEN ProdSched = 2 THEN Qnty END)
FROM dbo.tablename
GROUP BY Item
ORDER BY Item;
Let's hit this in two phases. First, although this is not the exact format you wanted, you can get the data you asked for as follows:
Select item, ProdSched, max(qty)
from Item1
group by item,ProdSched
Now, to get the data in the format you desired, one way of accomplishing it is a PIVOT table. You can cook up a pivot table in SQL Server as follows:
Select item, [1] as ProdSched1, [2] as ProdSched2
from ( Select Item, Qty, ProdSched
from item1 ) x
Pivot ( Max(qty) for ProdSched in ([1],[2])) y