PIVOT Multiple Rows to Different Column with Additional Column - sql

I need your suggestion on this.
I have a before table like below, which I would like to transform to the after table,
(note: the table below is just an example, there is over 1000+ rows in the real table)
Before:
line_type
line_name
op_id
org_code
Internal
Storage 1
1
ABC
Makloon
Storage 2
1
DEF
Process
Storage 2
1
XYZ
Internal
Storage 3
2
XYZ
Makloon
Storage 1
2
ABC
Process
Storage 2
2
XYZ
After:
op_id
org_code internal
Internal
org_code Makloon
Makloon
org_code Process
Process
1
ABC
Storage 1
DEF
Storage 2
XYZ
Storage 2
2
XYZ
Storage 3
ABC
Storage 1
XYZ
Storage 2
Can I use PIVOT for this case? Or do I need to use another way?
I have only succeeded in using PIVOT for the line_name, I'm not sure how to PIVOT both of them (line_name and org_code)
Here is what I have tried:
SELECT
[op_id],
[Internal],
[Makloon],
[Process]
FROM
(SELECT
[line_type],
[line_name],
[op_id]
FROM
[database_name]
) pvt
PIVOT
(MAX(line_name)
FOR [line_type] IN ([Internal], [Makloon], [Process])
) AS pvt_table
ORDER BY
[op_id];
The result of this query:
op_id
Internal
Makloon
Process
1
Storage 1
Storage 2
Storage 2
2
Storage 3
Storage 1
Storage 2

Pivot is just a fancy CASE WHEN expression, and it's often easier to spell it out manually:
SELECT op_id
, MAX(case when line_type = 'Internal' THEN org_code END) AS org_code_internal
, MAX(case when line_type = 'Internal' THEN line_name END) AS internal
, MAX(case when line_type = 'Makloon' THEN org_code END) AS org_code_Makloon
, MAX(case when line_type = 'Makloon' THEN line_name END) AS internal_Makloon
, MAX(case when line_type = 'Process' THEN org_code END) AS org_code_Process
, MAX(case when line_type = 'Process' THEN line_name END) AS Process
FROM (
VALUES (N'Internal', N'Storage 1', 1, N'ABC')
, (N'Makloon', N'Storage 2', 1, N'DEF')
, (N'Process', N'Storage 2', 1, N'XYZ')
, (N'Internal', N'Storage 3', 2, N'XYZ')
, (N'Makloon', N'Storage 1', 2, N'ABC')
, (N'Process', N'Storage 2', 2, N'XYZ')
) t (line_type,line_name,op_id,org_code)
group by op_id
By this method, you can flip almost any kind of rows

Related

t-sql secondary pivot on pivoted table

How would I pivot this table again to account for the difference in names?
What I have so far:
CREATE TABLE Temp
(
badge nvarchar(4)
,name nvarchar(31)
,Job nvarchar(4)
,KDA float
,Match int
)
INSERT INTO Temp
VALUES ('T996', 'Darrien', 'AP', 1.0, 20),
('T996', 'Mark', 'ADC', 2.8, 16),
('T996', 'Kevin', 'TOP', 5.0, 120)
SELECT badge, [AP_KDA], [AP_Match], [ADC_KDA], [ADC_Match], [TOP_KDA], [TOP_Match], [Person]
FROM (
SELECT badge, Col, Val
FROM (
SELECT badge, Job + '_KDA' AS Col, CAST(KDA AS nvarchar(31)) AS Val
FROM Temp
UNION ALL
SELECT badge, Job + '_Match' AS Col, CAST(Match AS nvarchar(31)) AS Val
FROM Temp
UNION ALL
SELECT badge, 'Person' AS Col, name AS Val
FROM Temp
) AS t
) AS tt
PIVOT (MIN(Val) FOR Col IN ([AP_KDA], [AP_Match], [ADC_KDA], [ADC_Match], [TOP_KDA], [TOP_Match], [Person])
) AS pvt
Which outputs:
badge AP_KDA AP_Match ADC_KDA ADC_Match TOP_KDA TOP_Match Person
1 T996 1 20 2.8 16 5 120 Darrien
I would like it to be formatted as:
badge AP_KDA AP_Match ADC_KDA ADC_Match TOP_KDA TOP_Match Person1 Person2 Person3
1 T996 1 20 2.8 16 5 120 Darrien Mark Kevin
I believe I'm close but the final Pivot is throwing me off.
Any help would be appreciated.
Thanks!
You can try to use condition aggregate function, MAX with CASE WHEN to make pivot.
CREATE TABLE Temp
(
badge nvarchar(4),
name nvarchar(31),
Job nvarchar(4),
KDA float,
Match int
)
INSERT INTO Temp VALUES
( 'T996' , 'Darrien' , 'AP' , 1.0, 20),
('T996' , 'Mark' , 'ADC' , 2.8 , 16),
( 'T996' , 'Kevin' , 'TOP' , 5.0 , 120)
Query 1:
SELECT badge,
MAX(CASE WHEN Job = 'AP' THEN KDA END) AP_KDA,
MAX(CASE WHEN Job = 'AP' THEN Match END) AP_Match,
MAX(CASE WHEN Job = 'ADC' THEN KDA END) ADC_KDA,
MAX(CASE WHEN Job = 'ADC' THEN Match END) ADC_Match,
MAX(CASE WHEN Job = 'TOP' THEN KDA END) TOP_KDA,
MAX(CASE WHEN Job = 'TOP' THEN Match END) TOP_Match,
MAX(CASE WHEN Job = 'AP' THEN name END) Person1 ,
MAX(CASE WHEN Job = 'ADC' THEN name END) Person2 ,
MAX(CASE WHEN Job = 'TOP' THEN name END) Person3
FROM TEMP
GROUP BY badge
Results:
| badge | AP_KDA | AP_Match | ADC_KDA | ADC_Match | TOP_KDA | TOP_Match | Person1 | Person2 | Person3 |
|-------|--------|----------|---------|-----------|---------|-----------|---------|---------|---------|
| T996 | 1 | 20 | 2.8 | 16 | 5 | 120 | Darrien | Mark | Kevin |
Please try this...
SELECT badge, [AP_KDA], [AP_Match], [ADC_KDA], [ADC_Match], [TOP_KDA], [TOP_Match],
[Person1],[Person2],[Person3]
FROM (
SELECT badge, Col, Val
FROM (
SELECT badge, Job + '_KDA' AS Col, CAST(KDA AS nvarchar(31)) AS Val
FROM Temp
UNION ALL
SELECT badge, Job + '_Match' AS Col, CAST(Match AS nvarchar(31)) AS
Val
FROM Temp
UNION ALL
SELECT badge, 'Person'+ CAST ((ROW_NUMBER() OVER (PARTITION BY badge
ORDER BY name DESC)) AS VARCHAR) AS Col, CAST(name AS VARCHAR) AS Val
FROM Temp
) AS t
) AS tt
PIVOT (MIN(Val) FOR Col IN ([AP_KDA], [AP_Match], [ADC_KDA], [ADC_Match],
[TOP_KDA], [TOP_Match], [Person1],[Person2],[Person3])
) AS pvt
If you want to dynamically create columns, meaning lets say you had a person4, you just cannot do it using PIVOT alone. You have to use For xml to transpose your data or another option is to use cursors, but I personally prefer For Xml. See this post for for xml

SQL How do I transpose and group data into static columns? [duplicate]

This question already has answers here:
TSQL Pivot without aggregate function
(9 answers)
Closed 4 years ago.
I have a table with the following data:
UID LAST FIRST FUND AMOUNT STATUS
1 Smith John C 100 1
1 Smith John B 250 1
1 Smith John E 150 1
2 Jones Meg B 275 1
2 Jones Meg F 150 1
3 Carter Bill A 100 1
I would like to transpose the FUND, AMOUNT and STATUS values for each UID into a single row for each UID. The resulting table would have columns added for FUND_1, AMT_1, STATUS_1, FUND_2, AMT_2, STATUS_2, FUND_3, AMT_3, STATUS_3. Each UID may or may not have a total of 3 funds. If they do not, the remaining fund, amt, and status columns are left blank. The resulting table would appear as:
UID LAST FIRST FUND_1 AMT_1 STATUS_1 FUND_2 AMT_2 STATUS_2 FUND_3 AMT_3 STATUS_3
1 Smith John C 100 1 B 250 1 E 150 1
2 Jones Meg B 275 1 F 150 1
3 Carter Bill A 100 1
For clarification, this is how the data would move from the existing table to the resulting table for UID 1:
It seems I am unable to use PIVOT because FUND_1, FUND_2, FUND_3 will be different fund categories for each person. The question, TSQL Pivot without aggregate function helps but doesn't answer my question since I have multiple rows in what would be the the DBColumnName in that question.
This is a pretty common conditional aggregation. Notice how I posted consumable data as a table and insert statements. To be honest it took longer to do that portion than the actual code to select the data. You should do this in the future. Also you should avoid using keywords as column names.
declare #Something table
(
UID int
, LAST varchar(10)
, FIRST varchar(10)
, FUND char(1)
, AMOUNT int
, STATUS int
)
insert #Something values
(1, 'Smith', 'John', 'C', 100, 1)
, (1, 'Smith', 'John', 'B', 250, 1)
, (1, 'Smith', 'John', 'E', 150, 1)
, (2, 'Jones', 'Meg', 'B', 275, 1)
, (2, 'Jones', 'Meg', 'F', 150, 1)
, (3, 'Carter', 'Bill', 'A', 100, 1)
;
with SortedValues as
(
select *
, RowNum = ROW_NUMBER() over(partition by UID order by (select null))
from #Something
)
select UID
, Last
, First
, Fund_1 = max(case when RowNum = 1 then Fund end)
, Amt_1 = max(case when RowNum = 1 then Amount end)
, Status_1 = max(case when RowNum = 1 then Status end)
, Fund_2 = max(case when RowNum = 2 then Fund end)
, Amt_2 = max(case when RowNum = 2 then Amount end)
, Status_2 = max(case when RowNum = 2 then Status end)
, Fund_3 = max(case when RowNum = 3 then Fund end)
, Amt_3 = max(case when RowNum = 3 then Amount end)
, Status_3 = max(case when RowNum = 3 then Status end)
from SortedValues
group by UID
, Last
, First
order by UID
, Last
, First

how to separated One column to two with cases

I have a table and I would want to separate the data to multiple columns, how i can do it ?
I tried this:
select a.[batch],a.[Doc_Type],
Soaking Out =
CASE a.[Doc_Type]
WHEN 'BB' THEN 'Soaking Out'
END,
Soaking In =
CASE a.[Doc_Type]
WHEN 'AA' THEN 'Soaking In'
END,
FROM Transaction_Hdr a JOIN Transaction_dtl b
on a.Doc_Number=b.Doc_Number
Your original query would output the strings 'soaking in' or 'soaking out', but what is needed in those case expressions (after then) is the column [Qty] and it is that value which will be returned from the case expression.
What I don't know is which table [Qty] comes from but I assume it is the detail table (b) otherwise there isn't much point in joining that detail table.
SELECT
a.[Doc_Type]
, a.[batch]
, CASE a.[Doc_Type] WHEN 'BB' THEN b.Qty END [soaking out]
, CASE a.[Doc_Type] WHEN 'AA' THEN b.Qty END [soaking in]
FROM Transaction_Hdr a
JOIN Transaction_dtl b ON a.Doc_Number = b.Doc_Number
ORDER BY
a.[Doc_Type]
, a.[batch]
But: a "detail" table and a "header" table usually indicates many rows of detail for a single header. So you might need a SUM() and GROUP BY
SELECT
h.[Doc_Type]
, h.[batch]
, SUM(CASE h.[Doc_Type] WHEN 'BB' THEN d.Qty END) [soaking out]
, SUM(CASE h.[Doc_Type] WHEN 'AA' THEN d.Qty END) [soaking in]
FROM Transaction_Hdr h
JOIN Transaction_dtl d ON h.Doc_Number = d.Doc_Number
GROUP BY
h.[Doc_Type]
, h.[batch]
ORDER BY
h.[Doc_Type]
, h.[batch]
Note I have now used aliases "h" = "header" and "d" = "detail" as I am really not keen of aliases that rely on a sequence within the query (as that sequence can get messed with very easily). I find it way easier for an alias to easily identify its associated table by "first letter of each word in a table's name" or similar.
select a.[batch],a.[Doc_Type],
isnull(CASE WHEN a.[Doc_Type]='AA' THEN convert(real,a.Qty) END,0) as [Soaking In] ,
isnull(CASE WHEN a.[Doc_Type]='BB' THEN convert(real,a.Qty) END ,0)as [Soaking Out]
FROM Transaction_Hdr a
I think you are looking for quantity in result table, So you should use that instead of string 'Soaking In' and 'Soaking Out' as follows
select a.[batch],a.[Doc_Type],
SoakingOut =
CASE a.[Doc_Type]
WHEN 'BB' THEN Qty
END ,
SoakingIn =
CASE a.[Doc_Type]
WHEN 'AA' THEN Qty
END
FROM #temp a
BEGIN TRAN
CREATE TABLE #Data (
Doc_Type VARCHAR(10),
Batch INT,
Qty DECIMAL(4,2)
);
INSERT INTO #Data VALUES
('AA', 1, 20.5),
('BB', 2, 10 ),
('AA', 3, 6 ),
('BB', 4, 7 ),
('AA', 5, 8 );
SELECT ISNULL(CASE WHEN Doc_Type='AA'THEN CONVERT(NVARCHAR(10),QTY) END,'') Soaking_In ,
ISNULL(CASE WHEN Doc_Type='BB'THEN CONVERT(NVARCHAR(10),QTY) END,'') Soaking_Out
FROM #Data
ROLLBACK TRAN
Use CASE() and Modulus as below, assuming that Batch is always inceremnted by 1 and Doc_Type has always those two values AA and BB in the same order:
CREATE TABLE Data (
Doc_Type VARCHAR(10),
Batch INT,
Qty DECIMAL(4,2)
);
INSERT INTO Data VALUES
('AA', 1, 20.5),
('BB', 2, 10 ),
('AA', 3, 6 ),
('BB', 4, 7 ),
('AA', 5, 8 );
SELECT D.Doc_Type, D.Batch,
CASE WHEN D.Batch % 2 = 0 Then 0 ELSE D.Qty END AS Soaking_In,
CASE WHEN D.Batch % 2 = 1 Then 0 ELSE D.Qty END AS Soaking_Out
FROM Data D;
Results:
+----------+-------+------------+-------------+
| Doc_Type | Batch | Soaking_In | Soaking_Out |
+----------+-------+------------+-------------+
| AA | 1 | 20,50 | 0,00 |
| BB | 2 | 0,00 | 10,00 |
| AA | 3 | 6,00 | 0,00 |
| BB | 4 | 0,00 | 7,00 |
| AA | 5 | 8,00 | 0,00 |
+----------+-------+------------+-------------+
Demo

Mutually exclusive counts in SQL

The table that I need to query looks like this
ID - Account - Product
1 - 002 - Bike
2 - 003 - Bike
4 - 003 - Motor
5 - 004 - Car
I need to be able to retrieve the number of accounts that purchased the each of the products and combinations of the products, like this
Bike | Car | Motor | Bike&Car | Bike&Motor | Car&Motor | Bike&Car&Motor
Note that an account that purchased a combination of products will be counted as 1.
Please help me in retrieving this data.
You can do this using two levels of aggregation. One method puts the values on different rows:
select has_bike, has_motor, has_car, count(*)
from (select account,
max(case when product = 'bike' then 1 else 0 end) as has_bike,
max(case when product = 'motor' then 1 else 0 end) as has_motor,
max(case when product = 'car' then 1 else 0 end) as has_car
from t
group by account
) t
group by has_bike, has_motor, has_car;
Or in columns:
select sum(has_bike * (1 - has_motor) * (1 - has_car)) as has_only_bike,
sum((1 - has_bike) * has_motor * (1 - has_car)) as has_only_motor,
sum((1 - has_bike) * (1 - has_motor) * has_car) as has_only_car,
. . .
from (select account,
max(case when product = 'bike' then 1 else 0 end) as has_bike,
max(case when product = 'motor' then 1 else 0 end) as has_motor,
max(case when product = 'car' then 1 else 0 end) as has_car
from t
group by account
) t;
If you only have a limited set of Products, then you can use this:
-- Create sample data
CREATE TABLE #tbl(
ID INT,
Account VARCHAR(10),
Product VARCHAR(10)
);
INSERT INTO #tbl VALUES
(1, '002', 'Bike'),
(2, '003', 'Bike'),
(3, '003', 'Motor'),
(4, '004', 'Car');
WITH Cte AS(
SELECT t1.Account, a.Products
FROM #tbl t1
CROSS APPLY (
SELECT STUFF((
SELECT '&' + t2.Product
FROM #tbl t2
WHERE t2.Account = t1.Account
ORDER BY t2.Product
FOR XML PATH(''), type).value('.[1]','nvarchar(max)'),
1, 1, '') AS Products
) a
GROUP BY t1.Account, a.Products
)
SELECT
Bike = SUM(CASE WHEN Products = 'Bike' THEN 1 ELSE 0 END),
Car = SUM(CASE WHEN Products = 'Car' THEN 1 ELSE 0 END),
Motor = SUM(CASE WHEN Products = 'Motor' THEN 1 ELSE 0 END),
[Bike&Car] = SUM(CASE WHEN Products = 'Bike&Car' THEN 1 ELSE 0 END),
[Bike&Motor] = SUM(CASE WHEN Products = 'Bike&Motor' THEN 1 ELSE 0 END),
[Car&Motor] = SUM(CASE WHEN Products = 'Car&Motor' THEN 1 ELSE 0 END),
[Bike&Car&Motor] = SUM(CASE WHEN Products = 'Bike&Car&Motor' THEN 1 ELSE 0 END)
FROM Cte;
DROP TABLE #tbl; -- Remove sample data
The idea is to generate 1 row for each Account, together with a comma-delimited Products. If you execute the query inside the CTE, you will get:
Account Products
---------- ---------------
002 Bike
003 Bike&Motor
004 Car
With that, you can do a conditional aggregation. The above uses a static solution, if you do not know the number of Products, you may need to come up with a dynamic approach.
ONLINE DEMO

how to achieve distinct records based on the priority

I have a table with data like below,
id code data1 data2 country
1 1 A NULL IND
1 1 B B NZ
1 1 CA
1 1 C Z WI
1 1 D S UK
2 2 NULL NULL IND
2 2 S NULL NZ
2 2 NULL K CA
2 2 T T WI
2 2 R K UK
3 3 NULL A WI
3 3 NULL a UK
the record will be populates based on the priority on country field. the priority is IND,NZ,CA,WI,UK
if there is any NULL,blank in data1,data2 fields data will populates from the next priority record.
So, My expected result is :
target table:
id code data1 data2 country
1 1 A B IND
2 2 S K IND
3 3 NULL A WI
Can any one help me with the query to achive the above result set.
I have added few more rows for better understanding on the query.
Hive has the first_value() function, which can be used for this purpose:
select distinct id, code,
first_value(data1) over (partition by id, code
order by (case when data1 is not null then 1 else 2 end),
(case country when 'IND' then 1 when 'NZ' then 2 when 'CA' then 3 when 'WI' then 4 when 'UK' then 5 else 6 end)
) as data1,
first_value(data2) over (partition by id, code
order by (case when data2 is not null then 1 else 2 end),
(case country when 'IND' then 1 when 'NZ' then 2 when 'CA' then 3 when 'WI' then 4 when 'UK' then 5 else 6 end)
) as data2,
first_value(country) over (partition by id, code
order by (case when data1 is not null then 1 else 2 end),
(case country when 'IND' then 1 when 'NZ' then 2 when 'CA' then 3 when 'WI' then 4 when 'UK' then 5 else 6 end)
) as country
from t;
I am not a big fan of select distinct with window functions. In this case, it seems like the simplest solution.
Use case to get priorities and use first_value on it.
select id, max(code), max(data1), max(data2), max(country)
from (
select
id,
code,
first_value(data1) over (partition by id
order by case when data1 is null or data1 = '' then 1 else 0 end * 10 + priority) data1,
first_value(data2) over (partition by id
order by case when data2 is null or data2 = '' then 1 else 0 end * 10 + priority) data2,
first_value(country) over (partition by id
order by case when country is null or country = '' then 1 else 0 end * 10 + priority) country
from (
select
t.*,
case country
when 'IND' then 1
when 'NZ' then 2
when 'CA' then 3
when 'WI' then 4
when 'UK' then 5
end priority
from your_table t
) t
) t group by id;
Produces:
ID MAX(CODE) MAX(DATA1) MAX(DATA2) MAX(COUNTRY)
1 1 A B IND
2 2 S K IND
3 3 NULL A WI
EDIT:
You can alternatively use FIELD function (available in hive, MySQL) to produce the priorities as suggested by #Dudu in the comments below:
field(country,'IND','NZ','CA','WI','UK')
See:
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF
Another approach based on MIN of STRUCT.
For the order I'm using the function field ( field(country,'IND','NZ','CA','WI','UK')).
Since it was missing, I have added it to the documentation.
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF
select id
,min (code) as code
,min (case when coalesce(trim(data1),'') <> '' then struct(field(country,'IND','NZ','CA','WI','UK'),data1) end).col2 as data1
,min (case when coalesce(trim(data2),'') <> '' then struct(field(country,'IND','NZ','CA','WI','UK'),data2) end).col2 as data2
,min (struct(field(country,'IND','NZ','CA','WI','UK'),country)).col2 as country
from mytable
group by id
order by id
;
Demo
create table mytable
(
id int
,code int
,data1 string
,data2 string
,country string
);
insert into mytable values
(1 ,1 ,'A' ,NULL ,'IND')
,(1 ,1 ,'B' ,'B' ,'NZ' )
,(1 ,1 ,'' ,'' ,'CA' )
,(1 ,1 ,'C' ,'Z' ,'WI' )
,(1 ,1 ,'D' ,'S' ,'UK' )
,(2 ,2 ,NULL ,NULL ,'IND')
,(2 ,2 ,'S' ,NULL ,'NZ' )
,(2 ,2 ,NULL ,'K' ,'CA' )
,(2 ,2 ,'T' ,'T' ,'WI' )
,(2 ,2 ,'R' ,'K' ,'UK' )
,(3 ,3 ,NULL ,'A' ,'WI' )
,(3 ,3 ,NULL ,'a' ,'UK' )
;
select id
,min (code) as code
,min (case when coalesce(trim(data1),'') <> '' then struct(field(country,'IND','NZ','CA','WI','UK'),data1) end).col2 as data1
,min (case when coalesce(trim(data2),'') <> '' then struct(field(country,'IND','NZ','CA','WI','UK'),data2) end).col2 as data2
,min (struct(field(country,'IND','NZ','CA','WI','UK'),country)).col2 as country
from mytable
group by id
order by id
;
+----+------+-------+-------+---------+
| id | code | data1 | data2 | country |
+----+------+-------+-------+---------+
| 1 | 1 | A | B | IND |
| 2 | 2 | S | K | IND |
| 3 | 3 | NULL | A | WI |
+----+------+-------+-------+---------+