t-sql secondary pivot on pivoted table - sql

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

Related

How put grouping variable to columns in SQL/

I have following dataset
and want to get this
How can I do it?
Using SQL Server, you can use a PIVOT, such as :
SELECT Time, [a],[b],[c]
FROM
(
SELECT time, [group],value
FROM dataset) d
PIVOT
(
SUM(value)
FOR [group] IN ([a],[b],[c])
) AS pvt
You can try it on the following fiddle.
Changed the column names to not conflict with reserved words. You would have to put them into single quotes otherwise.
WITH
-- the input
indata(grp,tm,val) AS (
SELECT 'a',1,44
UNION ALL SELECT 'a',2,22
UNION ALL SELECT 'a',3, 1
UNION ALL SELECT 'b',1, 1
UNION ALL SELECT 'b',2, 5
UNION ALL SELECT 'b',3, 6
UNION ALL SELECT 'c',1, 7
UNION ALL SELECT 'c',2, 8
UNION ALL SELECT 'c',3, 9
)
SELECT tm
, SUM(CASE grp WHEN 'a' THEN val END) AS a
, SUM(CASE grp WHEN 'b' THEN val END) AS b
, SUM(CASE grp WHEN 'c' THEN val END) AS c
FROM indata
GROUP BY tm
;
tm | a | b | c
----+----+---+---
1 | 44 | 1 | 7
2 | 22 | 5 | 8
3 | 1 | 6 | 9
select * from
(
select
time,[group],value
from yourTable
group by time,[group],value
)
as table
pivot
(
sum([value])
for [group] in ([a],[b],[c])
) as p
order by time
This is the result
for Vertica,
SELECT time
, SUM(value) FILTER (WHERE group = a) a
, SUM(value) FILTER (WHERE group = b) b
, SUM(value) FILTER (WHERE group = c) c
FROM yourTable
GROUP BY time

how to separate and sum 2 columns based on condition

I'm doing a select statement and I have a column I would like to separate into 2 columns based on their type, and then get the sum of the amounts grouped by an ID
I want all the gold and platinum types in one column, and all the silver and bronze in a 2nd column, then summed and grouped by the ID so it looks like this :
I tried doing a union like this:
SELECT
ID,
SUM(Amount) AS "Gold/Platinum",
0 AS "Bronze/Silver"
FROM
table
WHERE
Type IN ('gold', 'platinum')
GROUP BY
ID
UNION ALL
SELECT
ID,
SUM(Amount) AS "Bronze/Silver",
0 AS "Gold/Platinum"
FROM
table
WHERE
Type IN ('bronze', 'silver')
GROUP BY
ID
The gold/platinum column will be correct, but I get nothing in the bronze/silver column
Use conditional aggregation:
select id,
sum(case when Type in ('gold', 'platinum') then amount else 0 end) as gold_platinum,
sum(case when Type in ('bronze', 'silver') then amount else 0 end) as bronze_silver
from t
group by id
order by id;
You can run this in SSMS:
DECLARE #data TABLE( [ID] INT, [Type] VARCHAR(10), [Amount] INT );
INSERT INTO #data ( [ID], [Type], [Amount] ) VALUES
( 1, 'gold', 100 )
, ( 1, 'gold', 50 )
, ( 1, 'bronze', 75 )
, ( 2, 'silver', 10 )
, ( 2, 'bronze', 20 )
, ( 3, 'gold', 35 )
, ( 4, 'silver', 20 )
, ( 4, 'platinum', 30 );
SELECT
[ID]
, SUM( CASE WHEN [Type] IN ( 'gold', 'platinum' ) THEN Amount ELSE 0 END ) AS [Gold/Platinum]
, SUM( CASE WHEN [Type] IN ( 'bronze', 'silver' ) THEN Amount ELSE 0 END ) AS [Bronze/Silver]
FROM #data
GROUP BY [ID]
ORDER BY [ID];
Returns
+----+---------------+---------------+
| ID | Gold/Platinum | Bronze/Silver |
+----+---------------+---------------+
| 1 | 150 | 75 |
| 2 | 0 | 30 |
| 3 | 35 | 0 |
| 4 | 30 | 20 |
+----+---------------+---------------+

Pivot group multi column SQL

In SQL, how can I merge multiple columns into one column with multiple rows?
Example:
name | age | gender
------+-------+---------
John | 20 | M
Jill | 21 | F
Exam | 22 | M
I want to get this table:
Exam | John | Jill
------+-------+---------
22 | 21 | 20
M | F | M
You can do like this.
Use two PIVOT query with UNION ALL to combine them
SELECT CAST(Exam AS VARCHAR(10)) Exam,
CAST(Jill AS VARCHAR(10)) Jill,
CAST(John AS VARCHAR(10)) John
FROM
(
select age,name
from T
) as x
PIVOT
(
MAX(Age) FOR name IN ([Exam],[John],[Jill])
)AS P1
UNION ALL
SELECT Exam,Jill,John FROM
(
select name,gender
from T
) as x
PIVOT
(
MAX(gender) FOR name IN ([Exam],[John],[Jill])
)AS P1
sqlfiddle:http://sqlfiddle.com/#!18/a437d/6
You can do this using a single query -- basically unpivot and conditional aggregation:
select max(case when v.name = 'Exam' then v.val end) as exam,
max(case when v.name = 'John' then v.val end) as john,
max(case when v.name = 'Jill' then v.val end) as jill
from t cross apply
(values (t.name, cast(t.age as varchar(10)), 1),
(t.name, t.gender, 2)
) v(name, val, which)
group by which;
Here is the SQL Fiddle.
You can convert the values to whatever character type you like for compatibility among the values. You want to put numeric values and strings in the same column, so they have to have the same type.

Calculating averages in SQL Server without ignoring null values

I have a query like below:
DECLARE #t TABLE
(
EmpName VARCHAR(10)
, Qty INT
, Item VARCHAR(12)
)
INSERT INTO #t
VALUES ('Jane',3,'Dog')
, ('Carle',1,'Cat')
, ('Abay',5,'Goat')
, ('Jane',1,'Dog')
, ('Carle',10,'Cat')
, ('Jane',2,'Dog')
, ('Jane',8,'Goat')
, ('Jane',3,'Ram')
, ('Carle',2,'Dog')
--SELECT * FROM #t
SELECT
EmpName, [Dog], [Cat], [Goat], [Ram]
FROM
(SELECT
EmpName, Qty, Item
FROM #t) AS b
PIVOT(SUM(Qty) FOR Item IN ([Dog], [Cat], [Goat], [Ram])) AS p
And the result is as seen in the screenshot below:
I want to calculate the average Qty across Item without ignoring null values in the calculation. For example, in row 1, EmpName Abay should be 5 divided by 4 (number of columns), as seen in this screenshot:
How do I get the average column?
I'm not really familiar with the PIVOT query, so here is an alternative using conditional aggregation:
SELECT
Empname,
Dog = SUM(CASE WHEN Item = 'Dog' THEN Qty ELSE 0 END),
Cat = SUM(CASE WHEN Item = 'Cat' THEN Qty ELSE 0 END),
Goat = SUM(CASE WHEN Item = 'Goat' THEN Qty ELSE 0 END),
Ram = SUM(CASE WHEN Item = 'Ram' THEN Qty ELSE 0 END),
Average = SUM(ISNULL(Qty, 0))/ 4.0
FROM #t
GROUP BY EmpName;
Note that this will only work if you only have 4 Items. Otherwise, you need to resort to dynamic crosstab.
ONLINE DEMO
For dynamic crosstab, I used a temporary table instead of a table variable:
DECLARE #sql NVARCHAR(MAX) = '';
SELECT #sql =
'SELECT
Empname' + CHAR(10);
SELECT #sql = #sql +
' , SUM(CASE WHEN Item = ''' + Item + ''' THEN Qty ELSE 0 END) AS ' + QUOTENAME(Item) + CHAR(10)
FROM (
SELECT DISTINCT Item FROM #t
) t;
SELECT #sql = #sql +
' , SUM(ISNULL(Qty, 0)) / (SELECT COUNT(DISTINCT Item) * 1.0 FROM #t) AS [Average]' + CHAR(10) +
'FROM #t
GROUP BY EmpName;';
ONLINE DEMO
Try a combination of AVG and ISNULL, i.e. AVG(ISNULL(Dog, 0)).
One simple method is:
select empname, goat, cat, dog, ram,
(coalesce(goat, 0) + coalesce(cat, 0) + coalesce(dog, 0) + coalesce( ram, 0)
) / 4.0 as average
from t;
Another simple method uses outer apply:
select t.*, v.average
from t outer apply
(select avg(coalesce(x, 0))
from (values (t.goat), (t.cat), (t.dog), (t.ram)
) v(x)
) v(average);
DECLARE #t TABLE
(
EmpName VARCHAR(10)
, Qty INT
, Item VARCHAR(12)
)
INSERT INTO #t
VALUES ('Jane',3,'Dog')
, ('Carle',1,'Cat')
, ('Abay',5,'Goat')
, ('Jane',1,'Dog')
, ('Carle',10,'Cat')
, ('Jane',2,'Dog')
, ('Jane',8,'Goat')
, ('Jane',3,'Ram')
, ('Carle',2,'Dog')
SELECT EmpName
, [Dog]
, [Cat]
, [Goat]
, [Ram]
,p.total/4.0 as av
FROM (SELECT EmpName, Qty, Item,SUM(qty)OVER(PARTITION BY EmpName) AS total FROM #t) AS b
PIVOT(SUM(Qty) FOR Item IN([Dog],[Cat],[Goat],[Ram])) AS p
EmpName Dog Cat Goat Ram av
---------- ----------- ----------- ----------- ----------- ---------------------------------------
Abay NULL NULL 5 NULL 1.250000
Carle 2 11 NULL NULL 3.250000
Jane 6 NULL 8 3 4.250000
V2: Dynamic script:
CREATE TABLE #t
(
EmpName VARCHAR(10)
, Qty INT
, Item VARCHAR(12)
)
INSERT INTO #t
VALUES ('Jane',3,'Dog')
, ('Carle',1,'Cat')
, ('Abay',5,'Goat')
, ('Jane',1,'Dog')
, ('Carle',10,'Cat')
, ('Jane',2,'Dog')
, ('Jane',8,'Goat')
, ('Jane',3,'Ram')
, ('Carle',2,'Dog')
INSERT #t ( EmpName, Qty, Item )VALUES('Abay',100,'abc')
DECLARE #cols VARCHAR(max),#sql VARCHAR(MAX),#cnt INT
SELECT #cols=ISNULL(#cols+',[','[')+Item+']',#cnt=ISNULL(#cnt+1,1) FROM #t GROUP BY Item
PRINT #cols
PRINT #cnt
SET #sql='SELECT EmpName, '+#cols+',p.total*1.0/'+LTRIM(#cnt)+' as av'+CHAR(13)
+' FROM (SELECT EmpName, Qty, Item,SUM(qty)OVER(PARTITION BY EmpName) AS total FROM #t) AS b'+CHAR(13)
+' PIVOT(SUM(Qty) FOR Item IN('+#cols+')) AS p'
EXEC(#sql)
EmpName abc Cat Dog Goat Ram av
---------- ----------- ----------- ----------- ----------- ----------- ---------------------------------------
Carle NULL 11 2 NULL NULL 2.600000
Jane NULL NULL 6 8 3 3.400000
Abay 100 NULL NULL 5 NULL 21.000000
Avoid NULL from your pivot sentence and compute AVG.
;with ct as
(
SELECT EmpName
, ISnull([Dog],0) Dog
, ISnull([Cat],0) Cat
, ISnull([Goat],0) Goat
, ISnull([Ram],0) Ram
FROM (SELECT EmpName, Qty, Item FROM #t) AS b
PIVOT(SUM(Qty) FOR Item IN([Dog],[Cat],[Goat],[Ram])) AS p
)
select empname, avg(dog) dog, avg(cat) cat, avg(goat) goat, avg(ram) ram
from ct
group by empname;
+---------+-----+-----+------+-----+
| empname | dog | cat | goat | ram |
+---------+-----+-----+------+-----+
| Abay | 0 | 0 | 5 | 0 |
+---------+-----+-----+------+-----+
| Carle | 2 | 11 | 0 | 0 |
+---------+-----+-----+------+-----+
| Jane | 6 | 0 | 8 | 3 |
+---------+-----+-----+------+-----+
SELECT EmpName
, [Dog]
, [Cat]
, [Goat]
, [Ram]
,(isnull(p.cat,0)+isnull(p.dog,0)+isnull(p.Goat,0)+isnull(p.Ram,0))/4.0 as average
FROM (SELECT EmpName, Qty, Item FROM #t) AS b
PIVOT(SUM(Qty) FOR Item IN([Dog],[Cat],[Goat],[Ram])) AS p

Display multiple rows and column values into a single row, multiple column values

I have to show multiple incomes, type of income and employer name values for a single individual in a single row. So, if 'A' has three different incomes from three different sources,
id | Name | Employer | IncomeType | Amount
123 | XYZ | ABC.Inc | EarningsformJob | $200.00
123 | XYZ | Self | Self Employment | $300.00
123 | XYZ. | ChildSupport| Support | $500.00
I need to show them as
id | Name | Employer1 | Incometype1| Amount1 | Employer2 | incometype2 | Amount2| Employer3 | Incometype3| Amount3.....
123 |XYZ | ABC.Inc |EarningsformJob | $200.00|Self | Self Employment | $300.00|ChildSupport| Support | $500.00.....
I need both 'fixed number of columns' (where we know how many times employer, incometype and amount colums are going to repeat)logic and 'dynamic display of columns' ( unknown number of times these columns are going to repeat)
Thanks.
Since you are using SQL Server there are several ways that you can transpose the rows of data into columns.
Aggregate Function / CASE: You can use an aggregate function with a CASE expression along with row_number(). This version would require that you have a known number of values to become columns:
select id,
name,
max(case when rn = 1 then employer end) employer1,
max(case when rn = 1 then IncomeType end) IncomeType1,
max(case when rn = 1 then Amount end) Amount1,
max(case when rn = 2 then employer end) employer2,
max(case when rn = 2 then IncomeType end) IncomeType2,
max(case when rn = 2 then Amount end) Amount2,
max(case when rn = 3 then employer end) employer3,
max(case when rn = 3 then IncomeType end) IncomeType3,
max(case when rn = 3 then Amount end) Amount3
from
(
select id, name, employer, incometype, amount,
row_number() over(partition by id order by employer) rn
from yourtable
) src
group by id, name;
See SQL Fiddle with Demo.
PIVOT/UNPIVOT: You could use the UNPIVOT and PIVOT functions to get the result. The UNPIVOT converts your multiple columns of Employer, IncomeType and Amount into multiples rows before applying the pivot. You did not specific what version of SQL Server, assuming you have a known number of values then you could use the following in SQL Server 2005+ which uses CROSS APPLY with UNION ALL to unpivot:
select id, name,
employer1, incometype1, amount1,
employer2, incometype2, amount2,
employer3, incometype3, amount3
from
(
select id, name, col+cast(rn as varchar(10)) col, value
from
(
select id, name, employer, incometype, amount,
row_number() over(partition by id order by employer) rn
from yourtable
) t
cross apply
(
select 'employer', employer union all
select 'incometype', incometype union all
select 'amount', cast(amount as varchar(50))
) c (col, value)
) src
pivot
(
max(value)
for col in (employer1, incometype1, amount1,
employer2, incometype2, amount2,
employer3, incometype3, amount3)
) piv;
See SQL Fiddle with Demo.
Dynamic Version: Lastly, if you have an unknown number of values then you will need to use dynamic SQL to generate the result.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+cast(rn as varchar(10)))
from
(
select row_number() over(partition by id order by employer) rn
from yourtable
) d
cross apply
(
select 'employer', 1 union all
select 'incometype', 2 union all
select 'amount', 3
) c (col, so)
group by col, rn, so
order by rn, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id, name,' + #cols + '
from
(
select id, name, col+cast(rn as varchar(10)) col, value
from
(
select id, name, employer, incometype, amount,
row_number() over(partition by id order by employer) rn
from yourtable
) t
cross apply
(
select ''employer'', employer union all
select ''incometype'', incometype union all
select ''amount'', cast(amount as varchar(50))
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. All versions give a result:
| ID | NAME | EMPLOYER1 | INCOMETYPE1 | AMOUNT1 | EMPLOYER2 | INCOMETYPE2 | AMOUNT2 | EMPLOYER3 | INCOMETYPE3 | AMOUNT3 |
-------------------------------------------------------------------------------------------------------------------------------------
| 123 | XYZ | ABC.Inc | EarningsformJob | 200 | ChildSupport | Support | 500 | Self | Self Employment | 300 |