How to inverse a key to array in PostgreSQL? - sql

Not sure if I have got the wording correct here, but I have this table:
| name | pets |
|-------|---------------|
| bob | cat, dog |
| steve | cat, parrot |
| dave | dog |
and I want it to become this:
| pet | names |
|--------|--------------|
| dog | bob, dave |
| cat | bob, steve |
| parrot | steve |

select regexp_split_to_table(pets, '\W\s') as pet
,string_agg(name, ', ') as names
from t
group by regexp_split_to_table(pets, '\W\s')
pet
names
cat
bob, steve
dog
bob, dave
parrot
steve
Fiddle

You can unnest the array and use a cross join to regroup:
select v, array_agg(t.name) from tbl t cross join unnest(t.pets) v group by v
See fiddle.

Related

SQL most frequent values on multiple columns group by other column

I'm struggling with a query to solve this:
I have this data in athena AWS
name | hobby | food | other_col
cris | sports | pasta | sdsd
cris | music | pizza | qfrfe
cris | sports | pizza | dcfrfe
cris | sports | pizza | koioio
arnold | sports | pasta | joiuhiu
arnold | art | salad | ojouju
arnold | art | pasta | jiojo
jenny | dance | sushi | sdkwdk
jenny | dance | sushi | lkjlj
jenny | ski | pizza | sdkwdk
jenny | dance | pasta | jlkjlkj
And i need to get the most frequents values in columns group by name, something like this:
name | hobby | food |
cris | sports | pizza |
arnold | art | pasta |
jenny | dance | sushi |
Anyone could help me please?
You can use GROUPING SETS to determine the max counts in every group (name-hobby and name-food) and then use MAX_BY to get the maximum:
-- sample data
with dataset(name, hobby, food) as (
values ('cris' , 'sports', 'pasta'),
('cris' , 'music' , 'pizza'),
('cris' , 'sports', 'pizza'),
('cris' , 'sports', 'pizza'),
('arnold', 'sports', 'pasta'),
('arnold', 'art' , 'salad'),
('arnold', 'art' , 'pasta'),
('jenny' ,'dance' ,'sushi'),
('jenny' ,'dance' ,'sushi'),
('jenny' , 'ski' ,'pizza'),
('jenny' ,'dance' ,'pasta')
)
-- query
select name,
max_by(hobby, if(hobby is not null, cnt)) hobby,
max_by(food, if(food is not null, cnt)) food
from (select name,
hobby,
food,
count(*) cnt
from dataset
group by grouping sets ((name, hobby), (name, food)))
group by name;
Output:
name
hobby
food
jenny
dance
sushi
cris
sports
pizza
arnold
art
pasta
Another approach - use histogram and some map functions magic to determine maximum element in the map:
-- query
select name,
map_keys(
map_filter(
h,
(k, v) -> v = array_max(map_values(h))))[1] hobby,
map_keys(
map_filter(
f,
(k, v) -> v = array_max(map_values(f))))[1] food
from (select name,
histogram(hobby) h,
histogram(food) f
from dataset
group by name);

SQL query question on selecting grade for student's academic history

If I have a student_grade_history table containing past grade history for students, for example:
student | course | year | grade
--------+----------+------------+----------
steve | Math1A | 2018spring | A+
steve | English | 2018spring | B-
steve | Science | 2018spring | B+
steve | Biology | 2018spring | C
and I would like to count the grade each student received A+, A , A- into grade A and B+,B,B- into grade B and C+,C,C- into grade C.
I am able to create a table like the following:
student | year | # of A received | # of B received | # of C received
--------+------------+-----------------+-----------------+-----------------
steve | 2018spring | 1 | 2 | 1
However, I am trying to creating a table that has the following format based on student_grade_history table, but I cannot think of a way to do it.
student | year | grade | count
--------+------------+-------+------
steve | 2018spring | A | 1
steve | 2018spring | B | 2
steve | 2018spring | C | 1
Can I get some hints how to approach this?
You seem to want aggregation:
select student, year, left(grade, 1) as grade, count(*)
from student_grade_history
group by student, year, left(grade, 1);
Not all databases support left() (but most do). All have the functionality, even if it goes by a slightly different syntax.

JOIN, aggregate and convert in postgres between two tables

Here are the two tables i have: [all columns in both tables are of type "text"], Table name and the column names are in bold fonts.
Names
--------------------------------
Name | DoB | Team |
--------------------------------
Harry | 3/12/85 | England
Kevin | 8/07/86 | England
James | 5/05/89 | England
Scores
------------------------
ScoreName | Score
------------------------
James-1 | 120
Harry-1 | 30
Harry-2 | 40
James-2 | 56
End result i need is a table that has the following
NameScores
---------------------------------------------
Name | DoB | Team | ScoreData
---------------------------------------------
Harry | 3/12/85 | England | "{"ScoreName":"Harry-1", "Score":"30"}, {"ScoreName":"Harry-2", "Score":"40"}"
Kevin | 8/07/86 | England | null
James | 5/05/89 | England | "{"ScoreName":"James-1", "Score":"120"}, {"ScoreName":"James-2", "Score":"56"}"
I need to do this using a single SQL command which i will use to create a materialized view.
I have gotten as far as realising that it will involve a combination of string_agg, JOIN and JSON, but haven't been able to crack it fully. Please help :)
I don't think the join is tricky. The complication is building the JSON object:
select n.name, n.dob, n.team,
json_agg(json_build_object('ScoreName', s.name,
'Score', s.score)) as ScoreData
from names n left join
scores s
ons.name like concat(s.name, '-', '%')
group by n.name, n.dob, n.team;
Note: json_build_object() was introduced in Postgres 9.4.
EDIT:
I think you can add a case statement to get the simple NULL:
(case when s.name is null then NULL
else json_agg(json_build_object('ScoreName', s.name,
'Score', s.score))
end) as ScoreData
Use json_agg() with row_to_json() to aggregate scores data into a json value:
select n.*, json_agg(row_to_json(s)) "ScoreData"
from "Names" n
left join "Scores" s
on n."Name" = regexp_replace(s."ScoreName", '(.*)-.*', '\1')
group by 1, 2, 3;
Name | DoB | Team | ScoreData
-------+---------+---------+---------------------------------------------------------------------------
Harry | 3/12/85 | England | [{"ScoreName":"Harry-1","Score":30}, {"ScoreName":"Harry-2","Score":40}]
James | 5/05/89 | England | [{"ScoreName":"James-1","Score":120}, {"ScoreName":"James-2","Score":56}]
Kevin | 8/07/86 | England | [null]
(3 rows)

Flattening a One to Many Relationship with SQL Query

I have a very simple data set that I would like to be able to query and get the results as a single record.
Members Table
ID | FirstName | LastName | HeroName
42 | Bruce | Wayne | Batman
1337 | Bruce | Banner | Hulk
1033 | Clark | Kent | Newspaper Boy
Skills Tables
ID | Skill
42 | Martial Arts
42 | Engineering
42 | Intimidation
1337 | Anger Management
1337 | Thermo Nuclear Dynamics
1033 | NULL
I want the result to be
ID | FirstName | LastName | HeroName | Skill1 | Skill2 | Skill3 | ... | Skilln
42 Bruce | Wayne | Batman | Martial Arts | Engineering | Intimidation
The query I have so far is
SELECT m.ID, m.FirstName, m.LastName, m.HeroName, s.Skill
FROM Members m
JOIN Skills s
ON m.ID = s.ID
WHERE m.ID = 42 and s.Skill IS NOT NULL
which returns
ID | FirstName | LastName | HeroName | Skill
42 | Bruce | Wayne | Batman | Martial Arts
42 | Bruce | Wayne | Batman | Engineering
42 | Bruce | Wayne | Batman | Intimidation
Short of iterating over the results and only extracting the fields I want is there a way to return this as a single record? I've seen topics on PIVOT, and XmlPath but from what I've read neither of these does quite what I want it to. I'd like an arbitrary number of Skills to be returned and no nulls are returned.
EDIT:
The problem with PIVOT is that it will turn one of the rows into a column header. If There is a way to fill in a generic column header than it might work.

MySQL Results as comma separated list

I need to run a query like:
SELECT p.id, p.name,
(SELECT name
FROM sites s
WHERE s.id = p.site_id) AS site_list
FROM publications p
But I'd like the sub-select to return a comma separated list, instead of a column of data. Is this even possible, and if so, how?
You can use GROUP_CONCAT to perform that, e.g. something like
SELECT p.id, p.name, GROUP_CONCAT(s.name) AS site_list
FROM sites s
INNER JOIN publications p ON(s.id = p.site_id)
GROUP BY p.id, p.name;
Now only I came across this situation and found some more interesting features around GROUP_CONCAT. I hope these details will make you feel interesting.
simple GROUP_CONCAT
SELECT GROUP_CONCAT(TaskName)
FROM Tasks;
Result:
+------------------------------------------------------------------+
| GROUP_CONCAT(TaskName) |
+------------------------------------------------------------------+
| Do garden,Feed cats,Paint roof,Take dog for walk,Relax,Feed cats |
+------------------------------------------------------------------+
GROUP_CONCAT with DISTINCT
SELECT GROUP_CONCAT(TaskName)
FROM Tasks;
Result:
+------------------------------------------------------------------+
| GROUP_CONCAT(TaskName) |
+------------------------------------------------------------------+
| Do garden,Feed cats,Paint roof,Take dog for walk,Relax,Feed cats |
+------------------------------------------------------------------+
GROUP_CONCAT with DISTINCT and ORDER BY
SELECT GROUP_CONCAT(DISTINCT TaskName ORDER BY TaskName DESC)
FROM Tasks;
Result:
+--------------------------------------------------------+
| GROUP_CONCAT(DISTINCT TaskName ORDER BY TaskName DESC) |
+--------------------------------------------------------+
| Take dog for walk,Relax,Paint roof,Feed cats,Do garden |
+--------------------------------------------------------+
GROUP_CONCAT with DISTINCT and SEPARATOR
SELECT GROUP_CONCAT(DISTINCT TaskName SEPARATOR ' + ')
FROM Tasks;
Result:
+----------------------------------------------------------------+
| GROUP_CONCAT(DISTINCT TaskName SEPARATOR ' + ') |
+----------------------------------------------------------------+
| Do garden + Feed cats + Paint roof + Relax + Take dog for walk |
+----------------------------------------------------------------+
GROUP_CONCAT and Combining Columns
SELECT GROUP_CONCAT(TaskId, ') ', TaskName SEPARATOR ' ')
FROM Tasks;
Result:
+------------------------------------------------------------------------------------+
| GROUP_CONCAT(TaskId, ') ', TaskName SEPARATOR ' ') |
+------------------------------------------------------------------------------------+
| 1) Do garden 2) Feed cats 3) Paint roof 4) Take dog for walk 5) Relax 6) Feed cats |
+------------------------------------------------------------------------------------+
GROUP_CONCAT and Grouped Results
Assume that the following are the results before using GROUP_CONCAT
+------------------------+--------------------------+
| ArtistName | AlbumName |
+------------------------+--------------------------+
| Iron Maiden | Powerslave |
| AC/DC | Powerage |
| Jim Reeves | Singing Down the Lane |
| Devin Townsend | Ziltoid the Omniscient |
| Devin Townsend | Casualties of Cool |
| Devin Townsend | Epicloud |
| Iron Maiden | Somewhere in Time |
| Iron Maiden | Piece of Mind |
| Iron Maiden | Killers |
| Iron Maiden | No Prayer for the Dying |
| The Script | No Sound Without Silence |
| Buddy Rich | Big Swing Face |
| Michael Learns to Rock | Blue Night |
| Michael Learns to Rock | Eternity |
| Michael Learns to Rock | Scandinavia |
| Tom Jones | Long Lost Suitcase |
| Tom Jones | Praise and Blame |
| Tom Jones | Along Came Jones |
| Allan Holdsworth | All Night Wrong |
| Allan Holdsworth | The Sixteen Men of Tain |
+------------------------+--------------------------+
USE Music;
SELECT ar.ArtistName,
GROUP_CONCAT(al.AlbumName)
FROM Artists ar
INNER JOIN Albums al
ON ar.ArtistId = al.ArtistId
GROUP BY ArtistName;
Result:
+------------------------+----------------------------------------------------------------------------+
| ArtistName | GROUP_CONCAT(al.AlbumName) |
+------------------------+----------------------------------------------------------------------------+
| AC/DC | Powerage |
| Allan Holdsworth | All Night Wrong,The Sixteen Men of Tain |
| Buddy Rich | Big Swing Face |
| Devin Townsend | Epicloud,Ziltoid the Omniscient,Casualties of Cool |
| Iron Maiden | Somewhere in Time,Piece of Mind,Powerslave,Killers,No Prayer for the Dying |
| Jim Reeves | Singing Down the Lane |
| Michael Learns to Rock | Eternity,Scandinavia,Blue Night |
| The Script | No Sound Without Silence |
| Tom Jones | Long Lost Suitcase,Praise and Blame,Along Came Jones |
+------------------------+----------------------------------------------------------------------------+
Instead of using group concat() you can use just concat()
Select concat(Col1, ',', Col2) as Foo_Bar from Table1;
edit this only works in mySQL; Oracle concat only accepts two arguments. In oracle you can use something like select col1||','||col2||','||col3 as foobar from table1;
in sql server you would use + instead of pipes.
In my case i have to concatenate all the account number of a person who's mobile number is unique. So i have used the following query to achieve that.
SELECT GROUP_CONCAT(AccountsNo) as Accounts FROM `tblaccounts` GROUP BY MobileNumber
Query Result is below:
Accounts
93348001,97530801,93348001,97530801
89663501
62630701
6227895144840002
60070021
60070020
60070019
60070018
60070017
60070016
60070015