SQL/Bigquery: Pivot combinations of rows into columns, keeping all pairs - sql

Say I have a table like:
| id | brand | fuel | mpg |
|:--:|:------:|:------:|:---:|
| 1 | ford | diesel | 14 |
| 1 | ford | gas | 20 |
| 1 | toyota | diesel | 30 |
| 1 | toyota | gas | 35 |
and I wish to pivot the columns such that the result is like:
| id | ford | toyota | toyota_mpg | ford_mpg |
|:--:|:------:|--------|:----------:|:--------:|
| 1 | diesel | diesel | 30 | 14 |
| 1 | gas | gas | 35 | 20 |
| 1 | diesel | gas | 35 | 14 |
| 1 | gas | diesel | 30 | 20 |
So far, I have
SELECT id,
MAX(CASE WHEN end_use = 'ford' THEN fuel ELSE NULL END) ford,
SUM(CASE WHEN end_use = 'ford' THEN mpg ELSE NULL END) ford_mpg,
MAX(CASE WHEN end_use = 'toyota' THEN fuel ELSE NULL END) toyota,
SUM(CASE WHEN end_use = 'toyota' THEN mpg ELSE NULL END) toyota_mpg,
FROM table GROUP BY id, fuel
which results below, giving me the correct result for when the fuels align:
| id | ford | toyota | toyota_mpg | ford_mpg |
|:--:|:------:|--------|:----------:|:--------:|
| 1 | diesel | diesel | 30 | 14 |
| 1 | gas | gas | 35 | 20 |
But I am not able to get the combinations of fuels (where they mismatch).

Try below
select id,
t1.fuel ford,
t2.fuel toyota,
t1.mpg ford_mpg,
t2.mpg toyota_mpg
from data t1
join data t2
using (id)
where t1.brand < t2.brand
if applied to sample data in your question - output is

You can explore the pivot operator from the big query. Though I am not sure about your use-case to find out sum/avg/max/min on id level!
WITH
result AS (
SELECT
1 AS id,
'ford' AS brand,
'diesel' AS fuel,
14 AS mpg
UNION ALL
SELECT
1 AS id,
'ford' AS brand,
'gas' AS fuel,
20 AS mpg
UNION ALL
SELECT
1 AS id,
'toyota' AS brand,
'diesel' AS fuel,
30 AS mpg
UNION ALL
SELECT
1 AS id,
'toyota' AS brand,
'gas' AS fuel,
35 AS mpg ),
pivot_result AS (
SELECT
id,
ford,
toyota,
mpg_ford,
mpg_toyota
FROM (
SELECT
*
FROM (
SELECT
id,
fuel,
brand brand_,
brand,
mpg
FROM
result ) PIVOT ( AVG(mpg) mpg FOR brand IN ('ford',
'toyota')) ) PIVOT (MAX(fuel) FOR brand_ IN ('ford',
'toyota')) )
SELECT
f.id,
f.ford,
t.toyota,
t.mpg_toyota,
f.mpg_ford
FROM
pivot_result f
INNER JOIN (
SELECT
id,
toyota,
mpg_toyota
FROM
pivot_result) t
ON
t.id = f.id
WHERE
(f.ford IS NOT NULL
AND f.mpg_ford IS NOT NULL
AND t.toyota IS NOT NULL
AND t.mpg_toyota IS NOT NULL)
GROUP BY
1,
2,
3,
4,
5

Related

How to Merge Identical Rows with different column?

I have a table like this:
--------------------------------------------
| Job | Class | Employee | PayType | Hours |
| 212 A John 1 20 |
| 212 A John 2 10 |
| 911 C Rebekah 1 15 |
| 911 C Rebekah 2 10 |
--------------------------------------------
I want to convert this table so i can get following output
------------------------------------
| Job | Class | Employee | OT | ST |
| 212 | A | John | 20 | 10 |
| 911 | C | Rebekah | 15 | 10 |
------------------------------------
Here I've set 1 for OT and 2 for ST
You can conditional aggregation:
select
job,
class,
employee
sum(case when paytype = 1 then hours else 0 end) ot,
sum(case when paytype = 2 then hours else 0 end) st
from mytable
group by
jobs,
class,
employee
Using PIVOT TABLE:
select
Job,
Class,
Employee,
[1] as OT,
[2] as ST from
(
select * from test2
) as t
pivot
(
sum([Hours])
for paytype in([1],[2])
) as pvt;

eSQL multiple join but with conditions

I've 3 tables as under
MERCHANDISE
+-----------+-----------+---------------+
| MERCH_NUM | MERCH_DIV | MERCH_SUB_DIV |
+-----------+-----------+---------------+
| 1 | car | awd |
| 1 | car | awd |
| 2 | bike | 1kcc |
| 3 | cycle | hybrid |
| 3 | cycle | city |
| 4 | moped | fixie |
+-----------+-----------+---------------+
PRIORITY
+----------+-----------+---------+---------+------------+------------+---------------+
| CUST_NUM | SALES_NUM | DOC_NUM | BALANCE | PRIORITY_1 | PRIORITY_2 | PRIORITY_CODE |
+----------+-----------+---------+---------+------------+------------+---------------+
| 90 | 1000 | 10 | 23 | 1 | 6 | NO |
| 91 | 1001 | 20 | 32 | 3 | 7 | PRI |
| 92 | 1002 | 30 | 11 | 2 | 8 | LATE |
| 93 | 1003 | 40 | 22 | 5 | 9 | 1MON |
+----------+-----------+---------+---------+------------+------------+---------------+
ORDER
+----------+-----------+---------+---------+-----------+-----------+
| CUST_NUM | SALES_NUM | DOC_NUM | COUNTRY | MERCH_NUM | MERCH_DIV |
+----------+-----------+---------+---------+-----------+-----------+
| 90 | 1000 | 10 | INDIA | 1 | car |
| 91 | 1001 | 20 | CHINA | 2 | bike |
| 92 | 1002 | 30 | USA | 3 | cycle |
| 93 | 1003 | 40 | UK | 4 | moped |
+----------+-----------+---------+---------+-----------+-----------+
I want to join the left joined table from the last two tables with the first one such that the MERCH_SUB_DIV 'awd' appears only once for each unique combination of merch_num and merch_div
the code I came up with is as under, but I'm not sure how do I eliminate the duplicate row just for the awd
select
ROW#, MERCH.MERCH_NUMBER, ORDPRI.MERCH_NUMBER, ORDPRI.CUST_NUM,
BALANCE, SALES_NUM, ITEM_NUM, RANK, PRIORITY_1
from (
select
ROW_NUMBER() OVER(
PARTITION BY ORD.DOC_NUM, ORD.ITEM_NUM
ORDER BY ORD.DOC_NUM, ORD.ITEM_NUM ASC
) AS Row#,
ORD.CUST_NUM, PRI.CUST_NUM, ORD.MERCH_NUM, ORD.MERCH_DIV, PRI.BALANCE,
pri.DOC_NUM, pri.SALES_NUM, pri.PRIORITY_1, pri.PRIORITY_2
from ORDER as ORD
left join PRIORITY as PRI on ORD.DOC_NUM = PRI.DOC_NUM
and ORD.SALES_NUMBER = PRI.SALES_NUM
where country_name in ('USA', ‘INDIA’)
) as ORDPRI
left join MERCHANDISE as MERCH on ORDPRI.DIV = MERCH.DIV
and ORDPRI.MERCH_NUM = MERCH.MERCH_NUM
You have to use 'DISTINCT' keyword to get unique values, but if your 'Priority table' & 'Order table' contains different values for Same MERCH_NUM then the final result contains the repetation of the 'MERCH_NUM'.
SELECT DISTINCT M.MERCH_NUMBER, O.MERCH_NUMBER, O.CUST_NUM, BALANCE, SALES_NUM,ITEM_NUM,RANK,PRIORITY_1
FROM priority_table P
LEFT JOIN order_table O ON P.CUST_NUM = O.CUST_NUM AND P.SALES_NUM=O.SALES_NUM AND P.DOC_NUM = O.DOC_NUM
LEFT JOIN merchandise_table M ON M.MERCH_NUM = O.MERCH_NUM
A way around can be to add one new Row_Number() in the outermost query having Partition by MERCH_SUB_DIV + all the columns in the final list and then filter final results based on the New Row_Number() . Follows a pseudo code that might help:
select
-- All expected columns in final result except the newRow#
ROW#, MERCH_NUM, CUST_NUM,
BALANCE, SALES_NUM, PRIORITY_1
from (
select
ROW#,
-- the new row number includes all column you want to show in final result
row_number() over ( PARTITION BY MERCH.MERCH_SUB_DIV ,
MERCH.MERCH_NUM, ORDPRI.MERCH_NUM, ORDPRI.CUST_NUM,
BALANCE, SALES_NUM, PRIORITY_1
order by (select 1 )) as newRow# ,
MERCH.MERCH_NUM, ORDPRI.CUST_NUM,
BALANCE, SALES_NUM, PRIORITY_1
from (
-- main query goes here
select
ROW_NUMBER() OVER(
PARTITION BY ORD.DOC_NUM --, ORD.ITEM_NUM
ORDER BY ORD.DOC_NUM ASC --, ORD.ITEM_NUM
) AS Row#,
ORD.CUST_NUM, ORD.MERCH_NUM, ORD.MERCH_DIV as DIV, PRI.BALANCE,
pri.DOC_NUM, pri.SALES_NUM, pri.PRIORITY_1, pri.PRIORITY_2
from #ORDER as ORD
left join #PRIORITY as PRI on ORD.DOC_NUM = PRI.DOC_NUM
and ORD.SALES_NUMBER = PRI.SALES_NUM
where country_name in ('USA', 'INDIA')
) as ORDPRI
left join #MERCHANDISE as MERCH on ORDPRI.DIV = MERCH.DIV
and ORDPRI.MERCH_NUM = MERCH.MERCH_NUM
) as T
-- final filter to get distinct values
where newRow# = 1
Sample code here .. Hope this helps!!

NULL fields using PIVOT

I request your collaboration because pivot on a table and separating the records by null, but still leave the fields at 0 with NVL
Table
product | value
----------+-------
Shirts | 1200
Caps | 0
Stocks | 0
Glasses | 100
Shoes | 0
Código pivot
select * from products
PIVOT (sum(value)
for titles in ('product', 'value')) AS pivot_product
Result:
product | Shirts | Caps | Stocks | Glasses | Shoes
---------+-----------+--------+-------------+---------+----------
value | NULL | NULL | NULL | 100 | NULL
value | 1200 | NULL | NULL | NULL | NULL
Expected result:
product | Shirts | Caps | Stocks | Glasses | Shoes
---------+-----------+--------+-------------+-------+----------
valor | 1200 | NULL | NULL | 100 | NULL
Optional
product | Shirts | Caps | Stocks | Glasses | Shoes
---------+-----------+--------+-------------+-------+----------
valor | 1200 | 0 | 0 | 100 | 0
You need to put the column values in the pivot list:
Oracle Setup:
CREATE TABLE test_data ( product, value ) AS
SELECT 'Shirts', 1200 FROM DUAL UNION ALL
SELECT 'Caps', 0 FROM DUAL UNION ALL
SELECT 'Stocks', 0 FROM DUAL UNION ALL
SELECT 'Glasses', 100 FROM DUAL UNION ALL
SELECT 'Shoes', 0 FROM DUAL
Query:
SELECT 'value' AS product,
p.*
FROM test_data
PIVOT ( SUM( value ) FOR product IN (
'Shirts' AS Shirts,
'Caps' AS Caps,
'Stocks' AS Stocks,
'Glasses' AS Glasses,
'Shoes' AS Shoes
) ) p
Output:
PRODUCT | SHIRTS | CAPS | STOCKS | GLASSES | SHOES
:------ | -----: | ---: | -----: | ------: | ----:
value | 1200 | 0 | 0 | 100 | 0
db<>fiddle here
Just use conditional aggregation. It is more flexible:
select 'valor' as product,
sum(case when product = 'Shirts' then value end) as shirts,
sum(case when product = 'Caps' then value end) as caps,
sum(case when product = 'Stocks' then value end) as stockes,
sum(case when product = 'Shirts' then value end) as shirts,
sum(case when product = 'Glasses' then value end) as glasses,
sum(case when product = 'Shoes' then value end) as shoes
from test_data;

Query to fetch distinct rows with below requirement

Sample date in the table:
+--------+---------+---------+--------------+-----------+------------+---+
| School | Class | Student | Student desc | Section | Date | |
+--------+---------+---------+--------------+-----------+------------+---+
| ABC | Grade 2 | Stud 1 | AAA | Mango | 5/12/2015 | 1 |
| DEF | Grade 2 | Stud 1 | AAA | Mango | 12/25/2018 | |
| DEF | Grade 2 | Stud 1 | AAA | Orange | 9/8/2016 | |
| GHI | Grade 3 | Stud 2 | BBB | Apple | 12/28/2016 | 2 |
| JKL | Grade 3 | Stud 2 | BBB | Pear | 12/19/2016 | |
| ABC | Grade 2 | Stud 3 | CCC | Guava | 12/28/2016 | 3 |
| GHI | Grade 3 | Stud 4 | DDD | StarFruit | 9/8/2018 | 4 |
+--------+---------+---------+--------------+-----------+------------+---+
Ideally mapping should be 1 student is only get assigned to one section in a class.
I need to build the query to fetch the data to meet below requirement:-
Irrespective of the School need to show the distinct data for those students which get assigned to multiple sections within same class.
+--------+---------+---------+--------------+----------+------------+
| School | Class | Student | Student desc | Section | Date |
+--------+---------+---------+--------------+----------+------------+
| DEF | Grade 2 | Stud 1 | AAA | Mango | 12/25/2018 |
| DEF | Grade 2 | Stud 1 | AAA | Orange | 9/8/2016 |
| GHI | Grade 3 | Stud 2 | BBB | Apple | 12/28/2016 |
| JKL | Grade 3 | Stud 2 | BBB | Pear | 12/19/2016 |
+--------+---------+---------+--------------+----------+------------+
Below is the query that provides the correct data if school information is fetched:
select distinct a.class
,a.student
,a.Stud desc
,a.section
,to_date(max(a.date),'MM-DD-YYYY')"Date"
from Table1 a,
( select class
,student
,count(distinct section) cot
from Table1 c
where 1=1
and class is not null
and incoming_qty >= 1
group by class
,student
Having count(distinct section) > 1
) b
where 1=1
and a.class = b.class
and a.student=b.student
and b.cot > 1
and b.class is not null
and a.incoming_qty_new >= 1
group by a.class,a.student,a.Stud desc,a.section
order by a.class,a.student,a.Stud desc,a.section;
But query not working as per expectation while trying to fetch the school detail.
Please suggest.
Here is example of analytic functions usage for your data. Try to extend it for your specific case.
WITH t(School, Class, Student, StudentDesc, SectionName, Dates) AS
(
SELECT 'ABC','Grade 2','Stud 1','AAA','Mango',date'2015-05-12' FROM dual UNION ALL
SELECT 'DEF','Grade 2','Stud 1','AAA','Mango',date'2018-12-25' FROM dual UNION ALL
SELECT 'DEF','Grade 2','Stud 1','AAA','Orange',date'2016-09-08' FROM dual UNION ALL
SELECT 'GHI','Grade 3','Stud 2','BBB','Apple',date'2016-12-28' FROM dual UNION ALL
SELECT 'JKL','Grade 3','Stud 2','BBB','Pear',date'2016-12-19' FROM dual UNION ALL
SELECT 'ABC','Grade 2','Stud 3','CCC','Guava',date'2016-12-28' FROM dual UNION ALL
SELECT 'GHI','Grade 3','Stud 4','DDD','StarFruit',date'2018-09-08' FROM dual
)
SELECT *
FROM (
SELECT t.*,
COUNT(DISTINCT SectionName) OVER (PARTITION BY Class, Student) AS cntStudentSections,
ROW_NUMBER() OVER (PARTITION BY Class, Student ORDER BY Dates) AS StudentRowNumber
FROM t
)
WHERE cntStudentSections > 1 AND StudentRowNumber = 1;
You can use analytic functions:
select t1.*
from (select t1.*,
count(*) over (partition by class, student, section) as cnt
from table1 t1
) t1
where cnt >= 2;

sql Group by columns to the same row without join

I have grouped sales from a sales view with sales below using
Select id, name, Count(*) as [Sales], product, amount
from vwSales
Group by
id,name, product, amount
ID | Name | Sales | Product | Amount
1 | Bob | 4 | Fridge | 40
1 | Bob | 12 | Washer | 120
2 | Anne | 5 | Fridge | 50
2 | Anne | 4 | Washer | 40
Is it possible to group these in to one row without using a join? So table looks something like
ID | Name | Fridge Sales | fridge Amt | Washer sales | washer amt
1 | Bob | 4 | 40 | 12 | 120
2 | Anne | 5 | 50 | 4 | 40
You can do conditional aggregation :
select id, name,
sum(case when Product = 'Fridge' then 1 else 0 end) as [Fridge Sales],
sum(case when Product = 'Fridge' then Amount else 0 end) as [fridge Amt],
sum(case when Product = 'Washer' then 1 else 0 end) as [Washer Sales],
sum(case when Product = 'Washer' then Amount else 0 end) as [Washer Amt]
from vwSales
Group by id, name;