How to do group by in postgresql with multiple joins? - sql

I need to group by dest_spot_id but couldn't able to get the desired result.
SELECT
ddsd.dest_spot_id
, ddsd.status
, ddsd.data_type
, ddsd.destination_id
, ddsd.created_on
, ddsd.data_id
, gi.tp_destination_name AS dest_or_spot_name
, mt.taluk_name
, md.district_name
, tgi.tp_destination_name AS destination_name
FROM dm_dest_spot_data ddsd
LEFT JOIN m_district md ON md.id_district = ddsd.district_id
LEFT JOIN m_taluk mt ON mt.id_taluk = ddsd.taluk
JOIN tp_general_info gi ON gi.id_tp_general_info = ddsd.dest_spot_id
LEFT JOIN tp_general_info tgi ON tgi.id_tp_general_info = ddsd.destination_id
GROUP BY
1
, 2
, 3
, 4
, 5
, 6
, 7
, 8
, 9
, 10
ORDER BY
ddsd.data_id
dest_spot_id have common_ids but i want get one from it.
lets say this is my table
dest_spot_id | status| so on
302 | 1 | 5
303 | 2 | 3
302 | 1 | 2
303 | 2 | 1
401 | 1 | 3
308 | 2 | 2
i want the outputs as :
dest_spot_id | status| so on
302 | 1 | 5
303 | 2 | 3
401 | 1 | 3
308 | 2 | 2
please give my some advice to achieve this. i am new to postgresql

You didn't specify what you want to do with the other columns, but let's say you want the maximum value of everything except the dest_spot_id:
SELECT
dest_spot_id
, MAX(other_colum1)
, MAX(other_colum2)
, …
FROM my_table
GROUP BY
dest_spot_id
So you only group by the column(s) that should be unique in the output.
For the other columns you can apply some aggregation function like MIN, MAX, SUM, AVG, etc.
It does not matter whether there are multiple table with JOINS or just one table.
The one thing that is different with multiple tables is that you have to qualify the column in the GROUP BY clause or use the fields position.
SELECT
t1.dest_spot_id
, MAX(t2.other_colum)
, MAX(t3.other_colum)
, …
FROM my_table1 t1
JOIN my_table2 t2 ON …
JOIN my_table3 t3 ON …
GROUP BY
t1.dest_spot_id
or
SELECT
t1.dest_spot_id
, MAX(t2.other_colum)
, MAX(t3.other_colum)
, …
FROM my_table1 t1
JOIN my_table2 t2 ON …
JOIN my_table3 t3 ON …
GROUP BY
1

here is my updated code after jens explanation. may it help someone..
SELECT
ddsd.dest_spot_id
,max(ddsd.status) as dest_spot_status
,max(gi.tp_destination_name)as dest_or_spot_name
,max(ddsd.created_on) as createdon
,max(mt.taluk_name) as taluk_name
,max(md.district_name) as district_name
,max(ddsd.data_type) as dataType
,max(ddsd.destination_id) as destination_idd
,max(tgi.tp_destination_name) as destination_name
,max(ddsd.data_id) as dataid
FROM dm_dest_spot_data ddsd
LEFT JOIN m_district md ON md.id_district = ddsd.district_id
LEFT JOIN m_taluk mt ON mt.id_taluk = ddsd.taluk
JOIN tp_general_info gi ON gi.id_tp_general_info = ddsd.dest_spot_id
LEFT JOIN tp_general_info as tgi ON tgi.id_tp_general_info = ddsd.destination_id
GROUP BY ddsd.dest_spot_id
ORDER BY max(ddsd.data_id) DESC

Related

SQL referrals view

I have a table with users:
id referred_by_user_id
-----------------------------------
1 null
2 1
3 1
4 2
I need to write request to get number of people referred by user in two levels.
First - direct referral (example: user 1 referred users 2 3. count for level 1 = 2)
Second - user 1 referred to users 2 and 3, user 2 referred to user 4. So count for level 2 should be 1
Result of query should be:
id referred_user_tier_one_total referred_user_tier_two_total
------------------------------------------------------------------------------
1 2 1
2 1 null
3 null null
4 null null
I figured out how to count referred_user_tier_one_total:
select
"id", referred_user_tier_one_total
from
"user"
inner join
(select
count(*) as referred_user_tier_one_total, referred_by_user_id
from
"user"
where
"user".referred_by_user_id is not null
group by
"user".referred_by_user_id) ur on "user".id = ur.referred_by_user_id
But I don't understand how to calculate referred_user_tier_two_total. Please, help
UPD:
Thanks #Stoff for SQL Server solution.
Here is the script rewritten for Postgres
WITH RECURSIVE agg AS
(
SELECT
a.ID, a.referred_by_user_id,
COUNT(b.referred_by_user_id) AS "count"
FROM
"user" a
LEFT JOIN
"user" b ON a.ID = b.referred_by_user_id
GROUP BY
a.ID, a.referred_by_user_id
)
SELECT
a.ID,
a.Count AS referred_user_tier_one_total,
CASE
WHEN SUM(b.count) IS NULL
THEN 0
ELSE SUM(b.count)
END AS referred_user_tier_two_total
FROM
agg a
LEFT JOIN
agg b ON a.ID = b.referred_by_user_id
GROUP BY
a.ID, a.Count
ORDER BY
a.ID
Here is a solution which works in Postgres.
We could carry on writing more levels in the same way.
create table referals(
id int,
referred_by_user_id int);
insert into referals values
(1 , null),
(2 , 1),
(3 , 1),
(4 , 2 );
select
t0.id,
count(t1.id) tier1,
count(t2.id) tier2
from
referals t0
left join referals t1
on t0.id = t1.referred_by_user_id
left join referals t2
on t1.id = t2.referred_by_user_id
group by t0.id
id | tier1 | tier2
-: | ----: | ----:
1 | 2 | 1
2 | 1 | 0
3 | 0 | 0
4 | 0 | 0
db<>fiddle here

GROUP BY with SUM without removing empty (null) values

TABLES:
Players
player_no | transaction_id
----------------------------
1 | 11
2 | 22
3 | (null)
1 | 33
Transactions
id | value |
-----------------------
11 | 5
22 | 10
33 | 2
My goal is to fetch all data, maintaining all the players, even with null values in following query:
SELECT p.player_no, COUNT(p.player_no), SUM(t.value) FROM Players p
INNER JOIN Transactions t ON p.transaction_id = t.id
GROUP BY p.player_no
nevertheless results omit null value, example:
player_no | count | sum
------------------------
1 | 2 | 7
2 | 1 | 10
What I would like to have is mention about the empty value:
player_no | count | sum
------------------------
1 | 2 | 7
2 | 1 | 10
3 | 0 | 0
What do I miss here?
Actually I use QueryDSL for that, but translated example into pure SQL since it behaves in the same manner.
using LEFT JOIN and coalesce function
SELECT p.player_no, COUNT(p.player_no), coalesce(SUM(t.value),0)
FROM Players p
LEFT JOIN Transactions t ON p.transaction_id = t.id
GROUP BY p.player_no
Change your JOIN to a LEFT JOIN, then add IFNULL(value, 0) in your SUM()
left join keeps all the rows in the left table
SELECT p.player_no
, COUNT(*) as count
, SUM(isnull(t.value,0))
FROM Players p
LEFT JOIN Transactions t
ON p.transaction_id = t.id
GROUP BY p.player_no
You might be looking for count(t.value) rather than count(*)
I'm just offering this so you have a correct answer:
SELECT p.player_no, COUNT(t.id) as [count], COALESCE(SUM(t.value), 0) as [sum]
FROM Players p LEFT JOIN
Transactions t
ON p.transaction_id = t.id
GROUP BY p.player_no;
You need to pay attention to the aggregation functions as well as the JOIN.
Please Try This:
SELECT P.player_no,
COUNT(*) as count,
SUM(isnull(T.value,0))
FROM Players P
LEFT JOIN Transactions T
ON P.transaction_id = T.id
GROUP BY P.player_no
Hope this helps.

Sum Group By Column

I have a column (PL.UNITS) that I need to Total at the bottom of the results of a query, is it possible to sum PL.UNITS that is already summed?
Please see query below.
SELECT ID.DUEDATE AS [DUE DATE], CD.RENEWALDATE, CD.RENEWALSTATUS, CD.CONTRACTNUMBER, L.LOCNAME, L.LOCADDRESS1, L.LOCADDRESS2, L.LOCADDRESS3, L.LOCADDRESS4, L.POSTCODE, SUM(PL.UNITS) AS UNITS from CLIENTDETAILS CD
INNER JOIN LOCATIONS L ON CD.CLIENTNUMBER = L.CLIENTNUMBER
INNER JOIN ITEMDETAILS ID ON L.LOCNUMBER = ID.LOCNUMBER
INNER JOIN PLANT PL ON ID.CODE = PL.CODE
WHERE L.OWNER = 210 and L.STATUSLIVE = 1 and ID.DUEDATE > '01/01/2017'
GROUP BY ID.DUEDATE, CD.RENEWALDATE, CD.RENEWALSTATUS, CD.CONTRACTNUMBER, L.LOCNAME, L.LOCADDRESS1, L.LOCADDRESS2, L.LOCADDRESS3, L.LOCADDRESS4, L.POSTCODE
It's probably best to do this sort of thing in front end development. Nevertheless, here is an example (quick and dirty, but shows the idea) for sql-server:
SELECT COALESCE(a.id, 'total') AS id
, SUM(a.thing) AS thing_summed
FROM (
SELECT '1' id
, 1 thing
UNION
SELECT '2'
, 2 thing
UNION
SELECT '1'
, 3 thing
) AS a
GROUP BY ROLLUP(a.id)
Result:
+-------+--------------+
| id | thing_summed |
+-------+--------------+
| 1 | 4 |
| 2 | 2 |
| total | 6 |
+-------+--------------+

Count / sum values in subquery and order by it

I have tables like below:
user
id | status
1 | 0
gallery
id | status | create_by_user_id
1 | 0 | 1
2 | 0 | 1
3 | 0 | 1
media
id | status
1 | 0
2 | 0
3 | 0
gallery_media
fk gallery.id fk media.id
id | gallery_id | media_id | sequence
1 | 1 | 1 | 1
2 | 2 | 2 | 1
3 | 2 | 3 | 2
monitor_traffic
1:gallery 2:media
id | anonymous_id | user_id | endpoint_code | endpoint_id
1 | 1 | | 1 | 2 gallery.id 2
2 | 2 | | 1 | 2 gallery.id 2
3 | | 1 | 2 | 3 media.id 3 include in gallery.id 2
these means gallery.id 2 contain 3 rows
gallery_information
fk gallery.id
id | gallery_id
gallery includes media.
monitor_traffic.endpoint_code: 1 .. gallery; 2 .. media
If 1 then monitor_traffic.endpoint_id references gallery.id
monitor_traffic.user_id, monitor_traffic.anonymous_id integer or null
Objective
I want to output gallery rows sort by count each gallery rows in monitor_traffic, then count the gallery related media rows in monitor_traffic. Finally sum them.
The query I provide only counts media in monitor_traffic without summing them and also does not count gallery in monitor_traffic.
How to do this?
This is part of a function, input option then output build query, something like this. I hope to find a solution (maybe with a subquery) that does not require to change other parts of the query.
Query:
SELECT
g.*,
row_to_json(gi.*) as gallery_information
FROM gallery g
LEFT JOIN gallery_information gi ON gi.gallery_id = g.id
LEFT JOIN "user" u ON u.id = g.create_by_user_id
-- start
LEFT JOIN gallery_media gm ON gm.gallery_id = g.id
LEFT JOIN (
SELECT
endpoint_id,
COUNT(*) as mt_count
FROM monitor_traffic
WHERE endpoint_code = 2
GROUP BY endpoint_id
) mt ON mt.endpoint_id = m.id
-- end
ORDER BY mt.mt_count desc NULLS LAST;
sql fiddle
I suggest a CTE to count both types in one aggregation and join to it two times in the FROM clause:
WITH mt AS ( -- count once for both media and gallery
SELECT endpoint_code, endpoint_id, count(*) AS ct
FROM monitor_traffic
GROUP BY 1, 2
)
SELECT g.*, row_to_json(gi.*) AS gallery_information
FROM gallery g
LEFT JOIN mt ON mt.endpoint_id = g.id -- 1st join to mt
AND mt.endpoint_code = 1 -- gallery
LEFT JOIN (
SELECT gm.gallery_id, sum(ct) AS ct
FROM gallery_media gm
JOIN mt ON mt.endpoint_id = gm.media_id -- 2nd join to mt
AND mt.endpoint_code = 2 -- media
GROUP BY 1
) mmt ON mmt.gallery_id = g.id
LEFT JOIN gallery_information gi ON gi.gallery_id = g.id
ORDER BY mt.ct DESC NULLS LAST -- count of galleries
, mmt.ct DESC NULLS LAST; -- count of "gallery related media"
Or, to order by the sum of both counts:
...
ORDER BY COALESCE(mt.ct, 0) + COALESCE(mmt.ct, 0) DESC;
Aggregate first, then join. That prevents complications with "proxy-cross joins" that multiply rows:
Two SQL LEFT JOINS produce incorrect result
The LEFT JOIN to "user" seems to be dead freight. Remove it:
LEFT JOIN "user" u ON u.id = g.create_by_user_id
Don't use reserved words like "user" as identifier, even if that's allowed as long as you double-quote. Very error-prone.

SQL left join two tables independently

If I have these tables:
Thing
id | name
---+---------
1 | thing 1
2 | thing 2
3 | thing 3
Photos
id | thing_id | src
---+----------+---------
1 | 1 | thing-i1.jpg
2 | 1 | thing-i2.jpg
3 | 2 | thing2.jpg
Ratings
id | thing_id | rating
---+----------+---------
1 | 1 | 6
2 | 2 | 3
3 | 2 | 4
How can I join them to produce
id | name | rating | photo
---+---------+--------+--------
1 | thing 1 | 6 | NULL
1 | thing 1 | NULL | thing-i1.jpg
1 | thing 1 | NULL | thing-i2.jpg
2 | thing 2 | 3 | NULL
2 | thing 2 | 4 | NULL
2 | thing 2 | NULL | thing2.jpg
3 | thing 3 | NULL | NULL
Ie, left join on each table simultaneously, rather than left joining on one than the next?
This is the closest I can get:
SELECT Thing.*, Rating.rating, Photo.src
From Thing
Left Join Photo on Thing.id = Photo.thing_id
Left Join Rating on Thing.id = Rating.thing_id
You can get the results you want with a union, which seems the most obvious, since you return a field from either ranking or photo.
Your additional case (have none of either), is solved by making the joins left join instead of inner joins. You will get a duplicate record with NULL, NULL in ranking, photo. You can filter this out by moving the lot to a subquery and do select distinct on the main query, but the more obvious solution is to replace union all by union, which also filters out duplicates. Easier and more readable.
select
t.id,
t.name,
r.rating,
null as photo
from
Thing t
left join Rating r on r.thing_id = t.id
union
select
t.id,
t.name,
null,
p.src
from
Thing t
left join Photo p on p.thing_id = t.id
order by
id,
photo,
rating
Here's what I came up with:
SELECT
Thing.*,
rp.src,
rp.rating
FROM
Thing
LEFT JOIN (
(
SELECT
Photo.src,
Photo.thing_id AS ptid,
Rating.rating,
Rating.thing_id AS rtid
FROM
Photo
LEFT JOIN Rating
ON 1 = 0
)
UNION
(
SELECT
Photo.src,
Photo.thing_id AS ptid,
Rating.rating,
Rating.thing_id AS rtid
FROM
Rating
LEFT JOIN Photo
ON 1 = 0
)
) AS rp
ON Thing.id IN (rp.rtid, rp.ptid)
MySQL has no support for full outer joins so you have to hack around it using a UNION:
Here's the fiddle: http://sqlfiddle.com/#!2/d3d2f/13
SELECT *
FROM (
SELECT Thing.*,
Rating.rating,
NULL AS photo
FROM Thing
LEFT JOIN Rating ON Thing.id = Rating.thing_id
UNION ALL
SELECT Thing.*,
NULL,
Photo.src
FROM Thing
LEFT JOIN Photo ON Thing.id = Photo.thing_id
) s
ORDER BY id, photo, rating