Get count of related records in two joined tables - sql

Firstly, I apologize for my English. I want get auctions with count of bids and buys. It should look like this:
id | name | bids | buys
-----------------------
1 | Foo | 4 | 1
2 | Bar | 0 | 0
I have tables like following:
auction:
id | name
---------
1 | Foo
2 | Bar
auction_bid:
id | auction_id
---------------
1 | 1
2 | 1
3 | 1
4 | 1
auction_buy:
id | auction_id
---------------
1 | 1
I can get numbers in two queries:
SELECT *, COUNT(abid.id) AS `bids` FROM `auction` `t` LEFT JOIN auction_bid abid ON (t.id = abid.auction) GROUP BY t.id
SELECT *, COUNT(abuy.id) AS `buys` FROM `auction` `t` LEFT JOIN auction_buy abuy ON (t.id = abuy.auction) GROUP BY t.id
But when i combined it into one:
SELECT *, COUNT(abid.id) AS `bids`, COUNT(abuy.id) AS `buys` FROM `auction` `t` LEFT JOIN auction_bid abid ON (t.id = abid.auction) LEFT JOIN auction_buy abuy ON (t.id = abuy.auction) GROUP BY t.id
It was returning wrong amount (bids as much as buys).
How to fix this and get counts in one query?

You'll need to count DISTINCT abuy and abid IDs to eliminate the duplicates;
SELECT t.id, t.name,
COUNT(DISTINCT abid.id) `bids`,
COUNT(DISTINCT abuy.id) `buys`
FROM `auction` `t`
LEFT JOIN auction_bid abid ON t.id = abid.auction_id
LEFT JOIN auction_buy abuy ON t.id = abuy.auction_id
GROUP BY t.id, t.name;
An SQLfiddle to test with.

Try this:
SELECT t.*,COUNT(abid.id) as bids,buys
FROM auction t LEFT JOIN
auction_bid abid ON t.id = abid.auction_id LEFT JOIN
(SELECT t.id, Count(abuy.id) as buys
FROM auction t LEFT JOIN
auction_buy abuy ON t.id = abuy.auction_id
GROUP BY t.id) Temp ON t.id=Temp.id
GROUP BY t.id
Result:
ID NAME BIDS BUYS
1 Foo 2 0
2 Bar 1 1
Result in SQL Fiddle.

Related

SQL case/if condition Join tbl A or tbl B

SQL Server...
I need to Join tbl_B or tbl_C with tbl_A. Case tblA.id = 1 Join B or Case tblA.id = 2 Join C
let's say this example:
Table: tblFood
Id_Food | Fk_Id_Foodtype
1 | 1
1 | 2
Table: tabVegetable
Id | Mame |Color
1 | eggplant |black
Table: tabFrute
Id | Name |Color
1 | apple |red
On the table tblFood above...
if Fk_Id_Foodtype = 1 join Id_Food on tabVegetable
if Fk_Id_Foodtype = 2 join Id_Food on tabFrute
So I can return execute a select like this: SELECT tblFood.Id_Food, (tabVegetable or tabFrute).name, (tabVegetable or tabFrute).color
Note: I only have tblFruts and tblVegetable, so I always check these two option.
Thank you!
Join to tblFood the other tables with LEFT joins and in the ON clauses specify the condition for Fk_Id_Foodtype.
In the SELECT list of columns use COALESCE() to get the values of the columns from each table:
SELECT f.*,
COALESCE(v.Name, fr.Name) Name,
COALESCE(v.Color, fr.Color) Color
FROM tblFood f
LEFT JOIN tabVegetable v ON f.Fk_Id_Foodtype = 1 AND v.Id = f.Id
LEFT JOIN tabFrute fr ON f.Fk_Id_Foodtype = 2 AND fr.Id = f.Id

Select from multiple table, eliminating duplicates values

I have these tables and values:
Person Account
------------------ -----------------------
ID | CREATED_BY ID | TYPE | DATA
------------------ -----------------------
1 | 1 | T1 | USEFUL DATA
2 | 2 | T2 |
3 | 3 | T3 |
4 | 4 | T2 |
Person_account_link
--------------------------
ID | PERSON_ID | ACCOUNT_ID
--------------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 3
4 | 3 | 4
I want to select all persons with T1 account type and get the data column, for the others persons they should be in the result without any account information.
(I note that person 1 has two accounts : account_id_1 and account_id_2 but only one row must be displayed (priority for T1 type if exist otherwise null)
The result should be :
Table1
-----------------------------------------------------
PERSON_ID | ACCOUNT_ID | ACCOUNT_TYPE | ACCOUNT_DATA
-----------------------------------------------------
1 | 1 | T1 | USEFUL DATA
2 | NULL | NULL | NULL
3 | NULL | NULL | NULL
4 | NULL | NULL | NULL
You can do conditional aggregation :
SELECT p.id,
MAX(CASE WHEN a.type = 'T1' THEN a.id END) AS ACCOUNT_ID,
MAX(CASE WHEN a.type = 'T1' THEN 'T1' END) AS ACCOUNT_TYPE,
MAX(CASE WHEN a.type = 'T1' THEN a.data END) AS ACCOUNT_DATA
FROM person p LEFT JOIN
Person_account_link pl
ON p.id = pl.person_id LEFT JOIN
account a
ON pl.account_id = a.id
GROUP BY p.id;
You would need an outer join, starting with Person and then to the other two tables. I would also aggregate with group by and min to tackle the situation where a person would have two or more T1 accounts. In that case one of the data is taken (the min of them):
select p.id person_id,
min(a.id) account_id,
min(a.type) account_type,
min(a.data) account_data
from Person p
left join Person_account_link pa on p.id = pa.person_id
left join Account a on pa.account_id = a.id and a.type = 'T1'
group by p.id
In Postgres, I like to use the FILTER keyword. In addition, the Person table is not needed if you only want persons with an account. If you want all persons:
SELECT p.id,
MAX(a.id) FILTER (a.type = 'T1') as account_id,
MAX(a.type) FILTER (a.type = 'T1') as account_type,
MAX(a.data) FILTER (a.type = 'T1') as account_data
FROM Person p LEFT JOIN
Person_account_link pl
ON pl.person_id = p.id LEFT JOIN
account a
ON pl.account_id = a.id
GROUP BY p.id;

Type of join based on condition postgres

I have 2 tables, where based on type of and item in table A, I would either like to force existence in another table or not require it (in order to return this id)
I wrote the following however I am getting SQL error.
How can I achieve this behaviour?
SELECT
item.id, delivery
FROM
item
(CASE
WHEN item.id not in (select ad_object_id from delivery) THEN
LEFT JOIN
ad_object_delivery
ON
item.id = ad_object_id
ELSE
JOIN
ad_object_delivery
ON
item.id = ad_object_id
END
)
Example data:
item
id | name | type
1 | John | socks
2 | Daniel | pants
3 | Barak | shirt
delivery
id | item_id | delivery
1 | 1 | UK
1 | 1 | US
definition
id | item_id | definition
1 | 1 | UK
1 | 2 | IL
I would like to get as a result only John and Barak records, because Daniel appears only in delivery but not in definition. Barak appears in neither so it's ok.
I think you want something like this:
SELECT i.id, COALESCE(d.delivery, aod2.delivery)
FROM item i LEFT JOIN
delivery d
ON i.id = d.ad_object_id LEFT JOIN
ad_object_delivery aod2
ON i.id = aod2.ad_object_id AND d.ad_object_id IS NULL
WHERE d.ad_object_id IS NOT NULL OR aod2.ad_object_id IS NOT NULL;
This matches to ad_object_delivery only when delivery does not exist. Note that you need to adjust the SELECT clause to select columns from the two tables.
If understand correctly you need JOIN with UNION ALL :
select i.id, i.name, dl.delivery
from item i inner join
delivery dl
on dl.item_id = i.id inner join
definition df
on df.item_id = i.id
union all
select i.id, i.name, null
from item i
where not exists (select 1 from delivery dl where dl.item_id = i.id) and
not exists (select 1 from definition df where df.item_id = i.id);
A confusing requirement, but I think its this:
select i.*
from item i
left join (select distinct item_id from delivery) del on del.item_id=i.id
left join (select distinct item_id from def) def on def.item_id=i.id
where (del.item_id is null and def.item_id is null)
or (del.item_id is not null and def.item_id is not null)

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.

Linking three tables SQL

I have three tables towns , patientsHome,patientsRecords
towns
Id
1
2
3
patientsHome
Id | serial_number
1 | 11
2 | 12
2 | 13
patientsRecords
status | serial_number
stable | 11
expire | 12
expire | 13
I want to count stable and expire patients from patients records against each Id from towns.
output should be like
Result
Id| stableRecords |expiredRecords
1| 1 | 0
2| 0 | 2
3| 0 | 0
Try like this :
select t.id,case when tt.StableRecords is null then 0 else tt.StableRecords end
as StableRecords,case when tt.expiredRecords is null then 0 else tt.expiredRecords
end as expiredRecords from towns t left join
(select ph.id, count(case when pr.status='stable' then 1 end) as StableRecords,
count(Case when pr.status='expire' then 1 end) as expiredRecords
from patientsRecords pr inner join
patientsHome ph on ph.serial_number=pr.serial_number
group by ph.id ) as tt
on t.id=tt.id
Assuming patientsHome.ID is in fact a foreign key to towns.ID, you can join the 3 tables, filter as appropriate, group by Town, and count the rows:
SELECT t.Id, COUNT(*) as patientCount
FROM towns t
INNER JOIN patientsHome ph
on t.Id = ph.Id
INNER JOIN patientsRecords pr
on ph.serialNumber = pr.serialNumber
WHERE pr.status in ('stable', 'expire')
GROUP BY t.Id;
If you also want to classify the status per town:
SELECT t.Id, pr.status, COUNT(*) as patientCount
... FROM, WHERE same as above
GROUP BY t.Id, pr.status;
Try this:
SELECT t.id, pr.status,
COUNT(*) AS countByStatus
FROM patientsRecords pr
INNER JOIN patientsHome ph
ON ph.serial_number = pr.serial_number
INNER JOIN towns t
ON t.id=ph.id
WHERE pr.status IN ('stable', 'expire')
GROUP BY t.id, pr.status;
See the sqlfiddle: http://sqlfiddle.com/#!2/028545/4