aggregate 3 Tables to json postgresql - sql

I have 3 tables:
customers:
_______________
| id | name |
|-----|-------|
| 1 | John |
| 2 | Adam |
| 3 | Smith |
---------------
cities:
__________________
| id | city |
|-----|----------|
| 1 | LA |
| 2 | PHI |
| 3 | HOU |
| 4 | DET |
------------------
customers_cities:
________________________________
| id | customer_id | city_id |
|-----|--------------|----------
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 3 |
| 4 | 3 | 2 |
| 5 | 1 | 3 |
--------------------------------
I wanted to aggregate and show in a json format all the cities and their customers listed in them. Some customers can be in different cities too.
Output should look like:
- LA:
|- John
- PHI
|- John
|- Smith
- Hou
|- Adam
|-John
The JSON output looks like this:
"data":{
"customers_in_cities": [
"LA" : [
{
"id": 1,
"name": "John"
}],
"PHI" : [
{
"id": 1,
"name": "John"
},
{
"id": 3,
"name": "Adam"
}],
"HOU" : [
{
"id": 3,
"name": "Adam"
},
{
"id": 1,
"name": "John"
}],
]
}
Something like the above json output.
I tried:
SELECT cus.name AS customer_name, cty.city AS city_name
FROM cities cty
INNER JOIN customers_cities cc ON cc.city_id = cty.id
INNER JOIN customers cus ON cc.customer_id = cus.id;
will appreciate any help

I am going to post the SQL query that took me a couple of hours to find out! actually paying the credit to this post Return as array of JSON objects in SQL (Postgres)
e by Erwin Brandstetter
the sql query goes like this:
SELECT c.*,
json_agg(row_to_json(c)) AS customers_in_cities
FROM cities c
LEFT JOIN customers_cities cc ON cc.city_id = c.id
LEFT JOIN customers cust ON cust.id = cc.customer_id
GROUP BY c.id;
thank you

Related

PostreSQL, string_agg() join through association table

I have 2 tables linked together via 3rd association table:
TABLE NAME: lot
id | description | <other multiple columns> |
1 | descr_string_1 | ... |
2 | descr_string_2 | ... |
TABLE NAME: id_class
id | code | name |
1 | 01 | class_1 |
2 | 02 | class_2 |
3 | 03 | class_3 |
TABLE NAME: association_lot_id_class
lot_id | class_id |
1 | 1 |
1 | 2 |
2 | 3 |
I'm trying to make a new table based on lot containing concatenated data on related classes:
TABLE NAME: new_table_lot
id | description | <other multiple columns> | class_codes | class_names |
1 | descr_string_1 | ... | "01, 02" | "class_1, class_2" |
2 | descr_string_2 | ... | "03" | "class_3" |
I've tried to use string_agg with different (definitely, wrong) variations based on other SOF answers (e.g. PostgreSQL - JOIN on string_agg) but no luck
SELECT alic.id_class_id, alic.lot_id, ic.code
FROM association_lot_id_class alic
JOIN id_class ic
JOIN (
SELECT id_class_id, string_agg(id.code, ',') AS codes
FROM codes
GROUP BY id_class)

SQL: Group By and create a new json column

I have a table
-----------------------------------------------------
| id | name | result |
-----------------------------------------------------
| 1 | Maria | {type: download, status: pass} |
| 1 | Maria | {type: login, status: fail} |
| 2 | Tony | {type: download, status: fail} |
| 3 | Kate | {type: download, status: pass} |
| 3 | Kate | {type: login, status: pass} |
| 3 | Kate | {type: logout, status: pass} |
| 4 | Tom | {type: download, status: pass} |
-----------------------------------------------------
And I want to try to get this but not sure that it possible:
-----------------------------------------------------------
| id | name | download | action |
-----------------------------------------------------------
| 1 | Maria | pass | {login: fail} |
| 2 | Tony | fail | |
| 3 | Kate | pass | {login: pass, logout: pass} |
| 4 | Tom | pass | |
-----------------------------------------------------------
I found way to create a new simple column:
SELECT id, name
results.download AS download
FROM
(
SELECT id, name jsonb_agg(result) AS results
FROM my_table
GROUP BY id, name
) AS r
ORDER BY id, name;
but also I want to move login and logout field to action column. Is it possible?
The formatting is not exactly as you want but we have the right information in the right place.
create table my_table (
id int,
name varchar(10),
result text
);
insert into my_table values
(1,'Maria','{ "type": "download", "status": "pass"}'),
(1,'Maria','{ "type": "login", "status": "fail"}'),
(2,'Tony','{ "type": "download", "status": "fail"}'),
(3,'Kate','{ "type": "download", "status": "pass"}'),
(3,'Kate','{ "type": "login", "status": "pass"}'),
(3,'Kate','{ "type": "logout", "status": "pass"}'),
(4,'Tom','{ "type": "download", "status": "pass"}');
✓
7 rows affected
SELECT id, name
results.download AS download
FROM
(
SELECT id, name jsonb_agg(result) AS results
FROM my_table
GROUP BY id, name
) AS r
ORDER BY id, name;
ERROR: syntax error at or near "."
LINE 2: results.download AS download
^
select
id,
name,
result::json->'type' AS type,
result::json->'status' AS status
FROM my_table
id | name | type | status
-: | :---- | :--------- | :-----
1 | Maria | "download" | "pass"
1 | Maria | "login" | "fail"
2 | Tony | "download" | "fail"
3 | Kate | "download" | "pass"
3 | Kate | "login" | "pass"
3 | Kate | "logout" | "pass"
4 | Tom | "download" | "pass"
with cte as
(select
id,
name,
case when cast(result::json->'type' as text) = '"download"'
then cast(result::json->'status' as text) end as "download" ,
case when cast(result::json->'type' as text) in ('"login"','"logout"')
then concat(result::json->'type',': ',result::json->'status') end as "action"
from my_table)
select
id,
name,
max(download),
string_agg(action,', ') "action"
from cte
group by
id,
name
order by id
id | name | max | action
-: | :---- | :----- | :--------------------------------
1 | Maria | "pass" | "login": "fail"
2 | Tony | "fail" | null
3 | Kate | "pass" | "login": "pass", "logout": "pass"
4 | Tom | "pass" | null
db<>fiddle here
You can aggregate the downloads and the "other" items into two separate columns:
with data as (
select id,
name,
jsonb_object_agg('download', result ->> 'status') filter (where result ->> 'type' = 'download') as download,
jsonb_object_agg(result ->> 'type', result ->> 'status') filter (where result ->> 'type' <> 'download') as result
from my_table
group by id, name
)
select id, name,
download ->> 'download' as download,
result
from data;
The CTE returns this:
id | name | download | results
---+-------+----------------------+------------------------------------
1 | Maria | {"download": "pass"} | {"login": "fail"}
2 | Tony | {"download": "fail"} |
3 | Kate | {"download": "pass"} | {"login": "pass", "logout": "pass"}
4 | Tom | {"download": "pass"} |
And the final then just extracts the value for the download key.
Online example

Select from a concatenation of two columns after a left join

Problem description
Let the tables C and V have those values
>> Table V <<
| UnID | BillID | ProductDesc | Value | ... |
| 1 | 1 | 'Orange Juice' | 3.05 | ... |
| 1 | 1 | 'Apple Juice' | 3.05 | ... |
| 1 | 2 | 'Pizza' | 12.05 | ... |
| 1 | 2 | 'Chocolates' | 9.98 | ... |
| 1 | 2 | 'Honey' | 15.98 | ... |
| 1 | 3 | 'Bread' | 3.98 | ... |
| 2 | 1 | 'Yogurt' | 8.55 | ... |
| 2 | 1 | 'Ice Cream' | 7.05 | ... |
| 2 | 1 | 'Beer' | 9.98 | ... |
| 2 | 2 | 'League of Legends RP' | 40.00 | ... |
>> Table C <<
| UnID | BillID | ClientName | ... |
| 1 | 1 | 'Alexander' | ... |
| 1 | 2 | 'Tom' | ... |
| 1 | 3 | 'Julia' | ... |
| 2 | 1 | 'Tom' | ... |
| 2 | 2 | 'Alexander' | ... |
Table C have the values of each product, which is associated with a bill number. Table V has the relationship between the client name and the bill number. However, the bill number has a counter that is dependent on the UnId, which is the store unity ID. That being said, each store has it`s own Bill number 1, number 2, etc. Also, the number of bills from each store are not equal.
Solution description
I'm trying to make select between the C left join V without sucess. Because each BillID is dependent on the UnID, I have to make the join considering the concatenation between those two columns.
I've used this script, but it gives me an error.
SELECT
SUM(C.Value),
V.ClientName
FROM
C
LEFT JOIN
V
ON
CONCAT(C.UnID, C.BillID) = CONCAT(V.UnID, V.BillID)
GROUP BY
V.ClientName
and SQL server returns me this 'CONCAT' is not a recognized built-in function name.
I'm using Microsoft SQL Server 2008 R2
Is the use of CONCAT wrong? Or is it the way I tried to SELECT? Could you give me a hand?
[OBS: The tables I've present you are just for the purpose of explaining my difficulties. That being said, if you find any errors in the explanation, please let me know to correct them.]
You should be joining on the equality of the UnID and BillID columns in the two tables:
SELECT
c.ClientName,
COALESCE(SUM(v.Value), 0) AS total
FROM C c
LEFT JOIN V v
ON c.UnID = v.UnID AND
c.BillID = v.BillID
GROUP BY
c.ClientName;
In theory you could try joining on CONCAT(UnID, BillID). However, you could run into problems. For example, UnID = 1 with BillID = 23 would, concatenated together, be the same as UnID = 12 and BillID = 3.
Note: We wrap the sum with COALESCE, because should a given client have no entries in the V table, the sum would return NULL, which we then replace with zero.
concat is only available in sql server 2012.
Here's one option.
SELECT
SUM(C.Value),
V.ClientName
FROM
C
LEFT JOIN
V
ON
cast(C.UnID as varchar(100)) + cast(C.BillID as varchar(100)) = cast(V.UnID as varchar(100)) + cast(V.BillID as varchar(100))
GROUP BY
V.ClientName

Postgres Join Query is SOMETIMES taking the cartesian product

I'm attempting to join multiple tables for one query and I am getting inconsistent results from the database, I believe my query is taking the cartesian product of all the users, when I only want users who are in the DirectConversation.
The Schema for reference:
The query is (where $id stands for the variable User.id):
SELECT c.*, count(dm.id),
u1.first_name, u1.last_name, u1.company, u1.picture,
u2.first_name, u2.last_name, u2.company, u2.picture
FROM "DirectConversation" as c, "DirectMessage" as dm, "Profile" as u1, "Profile" as u2
WHERE u1."id_User" = c."id_User1"
AND u2."id_User" = c."id_User2"
AND c.id = dm."id_DirectConversation"
AND dm.viewed = 'f' AND dm.deleted = 'f'
AND c."id_User1" = $id OR c."id_User2" = $id
GROUP BY c.id, u1.id, u2.id;
The expected result (the result when the user id = 1 ):
id | id_User1 | id_User2 | count | first_name | last_name | company | picture | first_name | last_name | company | picture
----+----------+----------+-------+------------+-----------+--------------------+-----------------------------------------------------------------------------+------------+-----------+----------------+--------------------------------------------------------------------------
1 | 1 | 2 | 3 | Albert | Einstein | alberts inventions | http://upload.wikimedia.org/wikipedia/commons/d/d3/Albert_Einstein_Head.jpg | Nikola | Tesla | Teslas Widgets | http://upload.wikimedia.org/wikipedia/commons/7/79/Tesla_circa_1890.jpeg
(1 row)
(END)
The error result (the result when the user id= 2):
id | id_User1 | id_User2 | count | first_name | last_name | company | picture | first_name | last_name | company | picture
----+----------+----------+-------+------------+-----------+--------------------+----------------------------------------------------------------------------------------------------------------+------------+-----------+--------------------+----------------------------------------------------------------------------------------------------------------
1 | 1 | 2 | 4 | Albert | Einstein | alberts inventions | http://upload.wikimedia.org/wikipedia/commons/d/d3/Albert_Einstein_Head.jpg | Albert | Einstein | alberts inventions | http://upload.wikimedia.org/wikipedia/commons/d/d3/Albert_Einstein_Head.jpg
1 | 1 | 2 | 4 | Albert | Einstein | alberts inventions | http://upload.wikimedia.org/wikipedia/commons/d/d3/Albert_Einstein_Head.jpg | Nikola | Tesla | Teslas Widgets | http://upload.wikimedia.org/wikipedia/commons/7/79/Tesla_circa_1890.jpeg
1 | 1 | 2 | 4 | Albert | Einstein | alberts inventions | http://upload.wikimedia.org/wikipedia/commons/d/d3/Albert_Einstein_Head.jpg | Rosalind | Franklin | DNA R US | http://upload.wikimedia.org/wikipedia/en/9/97/Rosalind_Franklin.jpg
1 | 1 | 2 | 4 | Albert | Einstein | alberts inventions | http://upload.wikimedia.org/wikipedia/commons/d/d3/Albert_Einstein_Head.jpg | Charles | Babbage | Babbages Cabbages | http://upload.wikimedia.org/wikipedia/commons/6/6b/Charles_Babbage_-_1860.jpg
... Note this was truncated for brevity. I believe this is taking the cartesian product of all the users, however I am unaware as to why
The version of postgres I'm using:
version
-----------------------------------------------------------------------------------------------------
PostgreSQL 9.3.6 on x86_64-unknown-linux-gnu, compiled by gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2, 64-bit
I just moved your where clauses for the joins to the ON statement and made proper joins, if this doesn't work I'll set up a sqlfiddle and see what the problem with this sql is
SELECT c.*, count(dm.id),
u1.first_name, u1.last_name, u1.company, u1.picture,
u2.first_name, u2.last_name, u2.company, u2.picture
FROM "DirectConversation" c
JOIN "DirectMessage" dm ON c.id = dm."id_DirectConversation"
JOIN "Profile" u1 ON u1."id_User" = c."id_User1"
JOIN "Profile" u2 ON u2."id_User" = c."id_User2"
WHERE
dm.viewed = 'f' AND dm.deleted = 'f'
AND (c."id_User1" = $id OR c."id_User2" = $id)
GROUP BY c.id, u1.id, u2.id;
Edit: grouped the OR clause just to be safe

Select the top 5 most frequent rows and query data from another table

Let's say I have 2 tables on my database,
The first one:
TABLE "SONGS"
+----+-------+--------+
| id | title | artist |
+----+-------+--------+
| 1 | song1 | blah |
| 2 | song2 | wut? |
| 3 | song3 | random |
+----+-------+--------+
And another one:
TABLE "PLAYS"
+----+---------+--------+
| id | song_id | time |
+----+---------+--------+
| 1 | 2 | 13:04 |
| 2 | 1 | 13:07 |
| 3 | 1 | 14:30 |
| 4 | 3 | 14:41 |
| 5 | 2 | 14:59 |
| 6 | 1 | 15:32 |
+----+---------+--------+
I was trying to have my script query out the most frequent song_id from the table plays then join it with the title and artist in the songs table to make the music chart for a radio station, but so far no luck on getting the result.
This is the expected result:
[
{"song_id": 1, "title": "song1", "artist": "blah", "plays": 3},
{"song_id": 2, "title": "song2", "artist": "wut?", "plays": 2},
{"song_id": 3, "title": "song3", "artist": "random", "plays": 1}
]
Thank you in advance.
You can do a regular JOIN between the tables, counting the number of rows per id/artist/title and ordering by that;
SELECT s.id, s.title, s.artist, COUNT(*) plays
FROM songs s
JOIN plays p
On s.id = p.song_id
GROUP BY s.id, s.title, s.artist
ORDER BY plays DESC
An SQLfiddle to test with.
To just get the play order with just an id, no need to join;
SELECT song_id, COUNT(*) plays
FROM plays p
GROUP BY song_id
ORDER BY plays DESC
Another SQLfiddle.
To just get the top 5 results, a LIMIT 5 can be appended added to either query.