Dynamic Pivoting in SQL - Snowflake - sql

I have an input file
Customer PhoneNum Location Brand
John 1234 ABC Oppo
John 1234 DEF MI
John 1234 KLM RealMe
John 1234 LKM 1+
Joe 9934 ABC Apple
Joe 9934 DEF Samsung
The same phone number can be listed to multiple phone brands and the number of brands per phone number can be dynamic i.e. some can have 2 brands some can have 4 some 8 etc. I can pass the list of unique brands in the pivot query but that would create columns which might not have values.
the result i want is
Customer PhoneNum Brand1 Brand1Location Brand2 Brand2Location Brand3 Brand3Location Brand4 Brand4Location
John 1234 Oppo ABC MI DEF RealMe KLM 1+ LKM
Joe 9934 Apple ABC Samsung DEF
```
Here i dont need the list of brands but if i know the maximum record per number is say 4 i can have the output in above format, which I believe is a good way to read the result.
Is there any way in SQL to get the above result.
select * from phone_multiple_make
pivot(max(location),max(brand) for brand in
('MI','Oppo','RealMe') )as p;

If you're ok with not using a pivot function you can acheive your results like this:
WITH CTE AS (
SELECT 'John' CUSTOMER,1234 PHONENUM,'ABC' LOCATION,'Oppo' BRAND
UNION
SELECT 'John' CUSTOMER,1234 PHONENUM,'DEF' LOCATION,'MI' BRAND UNION
SELECT 'John' CUSTOMER,1234 PHONENUM,'KLM' LOCATION,'RealMe' BRAND UNION
SELECT 'John' CUSTOMER,1234 PHONENUM,'LKM' LOCATION,'1+' BRAND UNION
SELECT 'Joe' CUSTOMER,9934 PHONENUM,'ABC' LOCATION,'Apple' BRAND UNION
SELECT 'Joe' CUSTOMER,9934 PHONENUM,'DEF' LOCATION,'Samsung' BRAND )
SELECT CUSTOMER, PHONENUM
,J:BRAND1:BRAND::STRING BRAND1, J:BRAND1:LOCATION::STRING LOCATION1
,J:BRAND2:BRAND::STRING BRAND2, J:BRAND2:LOCATION::STRING LOCATION2
,J:BRAND3:BRAND::STRING BRAND3, J:BRAND3:LOCATION::STRING LOCATION3
,J:BRAND4:BRAND::STRING BRAND4, J:BRAND4:LOCATION::STRING LOCATION4
FROM (
SELECT CUSTOMER, PHONENUM, OBJECT_AGG(KEY,OBJ) J FROM (
SELECT CUSTOMER, PHONENUM
,'BRAND'||ROW_NUMBER()OVER(PARTITION BY CUSTOMER,PHONENUM ORDER BY BRAND)::STRING KEY
,OBJECT_CONSTRUCT( 'LOCATION', LOCATION, 'BRAND',BRAND) OBJ FROM CTE) GROUP BY 1,2)

Related

How to partially transpose a table

I have to create a table with a lists of contacts (ClientCode, Telephone, Name) from a table structured like this:
ClientCode
Telephone1
Name1
Telephone2
Name2
Telephone3
Name3
1234
55555
John M.
79879
Frank
897987
Paul
9884
84416
Richard
88416
Helen
11594
Katrin
I need to group by ClientCode same persons that work at the same client.
Table expected:
ClientCode
Telephone
Name
1234
55555
John M.
1234
79879
Frank
1234
897987
Paul
9884
84416
Richard
9884
88416
Helen
9884
1159
Katrin
I've tried the following solution (from this answer) but the output is not correct
SELECT UNPVTBL.CLIENTCODE, UNPVTBL.NAME
FROM (SELECT * FROM -ORIGIN_TABLE-) P
UNPIVOT
(NAME FOR CONTACTS IN
(NAME1, NAME2, NAME3)
)UNPVTBL
UNION
SELECT UNPVTBL.CLIENTCODE, UNPVTBL.TELEPHONE
FROM (SELECT * FROM -ORIGIN_TABLE-) G
UNPIVOT
(TELEPHONE FOR TELEPH IN
(TELEPHONE1, TELEPHONE2, TELEPHONE3)
)UNPVTBL
You can try doing it using the UNION ALL operator:
SELECT ClientCode, Telephone1 AS Telephone, Name1 AS Name FROM tab
UNION ALL
SELECT ClientCode, Telephone2 AS Telephone, Name2 AS Name FROM tab
UNION ALL
SELECT ClientCode, Telephone3 AS Telephone, Name3 AS Name FROM tab
Output:
ClientCode
Telephone
Name
1234
55555
John M.
9884
84416
Richard
1234
79879
Frank
9884
88416
Helen
1234
897987
Paul
9884
11594
Katrin
Check the demo here.
A way to do this is VALUES unpivot:
select t.ClientCode,x.name, x.Telephone
from (
VALUES (1234, 55555, N'John M.', 79879, N'Frank', 897987, N'Paul')
, (9884, 84416, N'Richard', 88416, N'Helen', 11594, N'Katrin')
) t (ClientCode,Telephone1,Name1,Telephone2,Name2,Telephone3,Name3)
cross apply (
VALUES (Telephone1, Name1)
, (Telephone2, Name2)
, (Telephone3, Name3)
) x (Telephone, Name)
Hopefully this exercise is to fix you design; if it isn't, it should be.
As for the solution, a VALUES table construct seems to do this simply enough:
SELECT YT.ClientCode,
V.Telephone,
V.[Name]
FROM dbo.YourTable YT
CROSS APPLY(VALUES(Telephone1,Name1),
(Telephone2,Name2),
(Telephone3,Name3))V(Telephone,Name);

SQL How to access element of a different table?

I have this table [pets]
Animal
prev_store
curr_store
Cat
ABC
DEF
Dog
ABC
GHI
Fish
DEF
XYZ
Snake
XYZ
JKM
I also have this other table [pet_store]
Store
Country
ABC
England
DEF
Denmark
GHI
England
XYZ
Denmark
JKM
Denmark
I want to check for each animal in pets table, whether the prev_store and curr_store is in the same Country, and if so, make a record of the Country.
Country
Occurrences
England
1
Denmark
2
SELECT pet_store.Country, count(pet_store.Country)
FROM pet_store, pets
WHERE pets.prev_store = pet_store.Store
and pets.curr_store = pet_store.Store
GROUP BY pet_store.Country
Unsure about how I would then select the animals's Country in relevance to the prev and curr store.
You will have to use pets_store twice, in two roles, in the query:
WITH
-- your data, don't use in query ..
pets(Animal,prev_store,curr_store) AS (
SELECT 'Cat' ,'ABC','DEF'
UNION ALL SELECT 'Dog' ,'ABC','GHI'
UNION ALL SELECT 'Fish' ,'DEF','XYZ'
UNION ALL SELECT 'Snake','XYZ','JKM'
)
,
pets_store(Store,Country) AS (
SELECT 'ABC','England'
UNION ALL SELECT 'DEF','Denmark'
UNION ALL SELECT 'GHI','England'
UNION ALL SELECT 'XYZ','Denmark'
UNION ALL SELECT 'JKM','Denmark'
)
-- real query starts here ...
SELECT
prev_stores.country AS country
, COUNT(*) AS occurrences
FROM pets
JOIN pets_store prev_stores ON prev_store = prev_stores.store
JOIN pets_store curr_stores ON curr_store = curr_stores.store
WHERE prev_stores.country = curr_stores.country
GROUP BY prev_stores.country;
-- out country | occurrences
-- out ---------+-------------
-- out Denmark | 2
-- out England | 1

Turning rows to columns in postgres

I have one table with rental contracts. (Postgres v10.18)
Like this:
Table Rental
Id Start Main_tenant_id Obect_id
101011 1.1.2021 1000 200
100222 1.1.2021 2050 210
If the Object has more than one Tenant the other ones a saved in a separate Table like this:
Table Rental_extra
Id rental_id xtra_tenant
20001 100222 3000
20002 100222 2700
20003 100222 2800
And i have a Person table like this:
Table Person
Id first_name last_name
1000 Peter Mcdonald
2050 Dan Hunt
3000 Steve Cole
2700 Ian Wright
2800 William Pears
Now i need to get this output:
Goal
Id tenant 1 tenant 2 tenant 3 tenant 4
101011 Peter Mcdonald null null null
100222 Dan Hunt Steve Cole Ian Wright William Pears
What's the best way? I tried with some crosstab example but couldn't make it work.
SELECT *
FROM crosstab(
$$
SELECT t.id, tenant_nr, concat_ws(' ', p.first_name, p.last_name)
FROM (
SELECT id, 0 AS tenant_nr, main_tenant_id AS tenant_id
FROM rental
UNION ALL
SELECT rental_id, row_number() OVER (PARTITION BY rental_id ORDER BY id), xtra_tenant
FROM extra_tenant
) t
JOIN person p ON p.id = t.tenant_id
ORDER BY 1 DESC, t.tenant_nr
$$
) AS goal(id int, tenant_1 text, tenant_2 text, tenant_3 text, tenant_4 text);
db<>fiddle here
Detailed explanation here:
PostgreSQL Crosstab Query
Postgres - Transpose Rows to Columns

SQL Combine duplicate rows while concatenating one column

I have a table (example) of orders show below. The orders are coming in with multiple rows that are duplicated for all columns except for the product name. We want to combine the product name into a comma delimited string with double quotes. I would like to create a select query to return the output format shown below.
INPUT
Name address city zip product name
-----------------------------------------------------------------
John Smith 123 e Test Drive Phoenix 85045 Eureka Copper Canyon, LX 4-Person Tent
John Smith 123 e Test Drive Phoenix 85045 The North Face Sequoia 4 Tent with Footprint
Tom Test 567 n desert lane Tempe 86081 Cannondale Trail 5 Bike - 2021
OUTPUT
Name address city zip product name
------------------------------------------------------------------
John Smith 123 e Test Drive Phoenix 85045 "Eureka Copper Canyon, LX 4-Person Tent", "The
North Face Sequoia 4 Tent with Footprint"
Tom Test 567 n desert lane Tempe 86081 Cannondale Trail 5 Bike - 2021
You can have List_AGG() OR GROUP_CONCAT and then join the results back to original table. Then you can remove duplicates using row_number which will create a same rank
if data is same
WITH ALL_DATA AS (
SELECT * FROM TABLE
),
LIST_OF_ITEMS_PER_PRODUCT AS (
SELECT
ALL_DATA.NAME,
LIST_AGG(ALL_DATA.PRODUCT_NAME , ",") AS ALL_PRODUCTS_PER_PERSON
-- IF YOUR SQL DON'T SUPPORT LIST_AGG() THEN USE GROUP_CONCAT INSTEAD
FROM
ALL_DATA
GROUP BY 1
),
LIST_ADDED AS (
SELECT
ALL_DATA.*,
LIST_OF_ITEMS_PER_PRODUCT.ALL_PRODUCTS_PER_PERSON
FROM
ALL_DATA
LEFT JOIN LIST_OF_ITEMS_PER_PRODUCT
ON ALL_DATA.NAME = LIST_OF_ITEMS_PER_PRODUCT.NAME
),
ADDING_ROW_NUMBER AS (
SELECT
* ,
ROW_NUMBER() over (partition by list_added.NAME, ADDRESS, CITY, ZIP ORDER BY NAME) AS ROW_NUMBER_
FROM LIST_ADDED
)
SELECT
* FROM
ADDING_ROW_NUMBER
WHERE ROW_NUMBER_ = 1

How to query: "for which do these values apply"?

I'm trying to match and align data, or resaid, count occurrences and then list for which values those occurrences occur.
Or, in a question: "How many times does each ID value occur, and for what names?"
For example, with this input
Name ID
-------------
jim 123
jim 234
jim 345
john 123
john 345
jane 234
jane 345
jan 45678
I want the output to be:
count ID name name name
------------------------------------
3 345 jim john jane
2 123 jim john
2 234 jim jane
1 45678 jan
Or similarly, the input could be (noticing that the ID values are not aligned),
jim john jane jan
----------------------------
123 345 234 45678
234 123 345
345
but that seems to complicate things.
As close as I am to the desired results is in SQL, as
for ID, count(ID)
from table
group by (ID)
order by count desc
which outputs
ID count
------------
345 3
123 2
234 2
45678 1
I'll appreciate help.
You seem to want a pivot. In SQL, you have to specify the number of columns in advance (unless you construct the query as a string).
But the idea is:
select ID, count(*) as cnt,
max(case when seqnum = 1 then name end) as name_1,
max(case when seqnum = 2 then name end) as name_2,
max(case when seqnum = 3 then name end) as name_3
from (select t.*,
row_number() over (partition by id order by id) as seqnum -- arbitrary ordering
from table t
) t
group by ID
order by count desc;
If you have an unknown number of columns, you can aggregate the values into an array:
select ID, count(*) as cnt,
array_agg(name order by name) as names
from table t
group by ID
order by count desc
the query would look similar to this if that's what you're looking for.
SELECT
name,
id,
COUNT(id) as count
FROM
dataSet
WHERE
dataSet.name = 'input'
AND dataSet.id = 'input'
GROUP BY
name,
id