would any sql Wizards out there help with with this question:
suppose I have 3 tables:
tbltype tblvalue tblcost
id | type id | val | typeid id | cost| typeid
---------- ------------------ ------------------
1 | aaa 1 | 3 | 1 1 | 5 | 1
2 | bbb 2 | 2 | 1 2 | 3 | 1
3 | 2 | 2 3 | 1 | 2
4 | 1 | 2 4 | 4 | 2
When I run this query:
SELECT t.type, SUM(val), SUM(cost)
FROM
tbltype t
LEFT JOIN tblcost c ON (c.typeid = t.id)
LEFT JOIN tblvalue v ON (v.typeid = t.id)
GROUP BY t.type;
I get the wrong value of
type | SUM(val) | SUM(cost)
---------------------------
aaa | 10 | 16
bbb | 6 | 10
how do I get the right value of:
type | SUM(val) | SUM(cost)
---------------------------
aaa | 5 | 8
bbb | 3 | 5
and why does sql behaves like that?
To see why, take the group and sums out of your query and look at what it's summing:
SELECT t.type, val, cost
FROM
tbltype t
LEFT JOIN tblcost c ON (c.typeid = t.id)
LEFT JOIN tblvalue v ON (v.typeid = t.id)
You'll see you have each possible combination of the rows from tblcost and tblvalue in the output-- this means some of them get counted multiple times when you sum them.
You need to aggregate tblcost and tblvalue separately. You can then join them back onto tbltype. Gavin's answer already shows one way to do that. Another way is:
SELECT t.type, COALESCE(cost, 0) AS cost, COALESCE(val, 0) AS val
FROM tbltype t
LEFT JOIN (SELECT SUM(cost) AS cost, typeid FROM tblcost GROUP BY typeid) tc
ON tc.typeid = t.id
LEFT JOIN (SELECT SUM(val) AS val, typeid FROM tblvalue GROUP BY typeid) tv
ON tv.typeid = t.id
... which may or may not perform differently (and may or may not be better) depending on which database engine you're actually using.
SELECT t.type,
COALESCE((SELECT SUM(v.val) FROM tblvalue AS v WHERE v.typeid = t.id),0) AS val,
COALESCE((SELECT SUM(c.cost) FROM tblcost AS c WHERE c.typeid = t.id),0) AS cost
FROM tbltype AS t;
I think you've got enough answers suggesting how to solve your problem correctly. You've also got #araqnid's answer that helps you to see why you get such results in the end. The only thing that remains for me seems to be to explain the behaviour itself, as per your request.
Basically, the reason behind such behaviour is the fact that the second join is performed not on tbltype and tblvalue, as one might think, but on the result of the join between tbltype and tblcost, on the one hand, and the tblvalue table, on the other. Now, the first join produces duplicates of t.id, because they match the second table more than once:
tbltype tblcost
id type id cost typeid t.id t.type c.id c.cost c.typeid
-- ---- × -- ---- ------ = ---- ------ ---- ------ --------
1 aaa 1 5 1 1 aaa 1 5 1
1 bbb 2 3 1 1 aaa 2 3 1
3 1 2 2 bbb 3 1 2
4 4 2 2 bbb 4 4 2
The second join produces more duplicates, because:
every occurrence of t.id from the first join's result set is getting matched against v.typeid
and
the typeid values in the tblvalue table are duplicated too.
As a result, rows from both tblcost and tblvalue get duplicated in the process:
tblvalue
t.id t.type c.id c.cost c.typeid id val typeid
---- ------ ---- ------ -------- -- --- ------
1 aaa 1 5 1 × 1 3 1 =
1 aaa 2 3 1 2 2 1
2 bbb 3 1 2 3 2 2
2 bbb 4 4 2 4 1 2
t.id t.type c.id c.cost c.typeid v.id v.val v.typeid
---- ------ ---- ------ -------- ---- ----- --------
1 aaa 1 5 1 1 3 1
1 aaa 1 5 1 2 2 1
= 1 aaa 2 3 1 1 3 1
1 aaa 2 3 1 2 2 1
2 bbb 3 1 2 3 2 2
2 bbb 3 1 2 4 1 2
2 bbb 4 4 2 3 2 2
2 bbb 4 4 2 4 1 2
The only way out for you seems to be to aggregate each table separately. That doesn't necessarily imply separate queries, just separate subqueries, as you can now see from the answers.
QUickest solution is to split the query in two
SELECT
`tbltype`.`type`, SUM(val)
FROM
tbltype
LEFT JOIN `tblvalue` ON (`tblvalue`.`typeid` = `tbltype`.`id`)
GROUP BY `tbltype`.`type`;
and
SELECT
`tbltype`.`type`, SUM(cost)
FROM
tbltype
LEFT JOIN `tblcost` ON (`tblcost`.`typeid` = `tbltype`.`id`)
GROUP BY `tbltype`.`type`;
Something like this:
select t1.id, val, cost from (
select t.id, sum(val) as val
from tbltype t
join tblvalue v on t.id = v.typeId
group by t.id
) t1
join (
select t.id, sum(cost) as cost
from tbltype t
inner join tblcost c on t.id = c.typeid
group by t.id
) t2 on t1.id = t2.id
... or if tblcost and tblvalue are related by id:
select t.id, sum(val) as val, sum(cost) as cost
from tbltype t
inner join tblcost c on t.id = c.typeid
inner join tblvalue v on c.id = v.id
group by t.id
Related
I have two tables A and B.
My goal is to list every row from A, while attach the SUM of 'amount' from B.
like this:
SELECT a.name,
SUM(b.amount) as amount
FROM a
LEFT JOIN b ON a.id = b.a_id
GROUP BY a.id
No probleam until this, but i also need to check whether the B table's 'shop_id' matches a value, and if does, i want that single row's 'amount', and not the SUM of all groupped rows. I hope it is understandable.
Table A
id name
----------------
1 john
2 doe
3 smith
Table B
a_id amount shop
-----------------------
1 4 1
1 3 2
2 2 2
2 7 3
3 3 3
3 1 2
Desired result with 'shop'=1:
name amount shop
---------------------
john 4 1 //no SUM, only the value of amount where shop=1
doe 9 0 //sum(7,2) because shop is not 1
smith 4 0 //sum(3,1)
I was thinking of an if statement at the SUM() selection something similar to this, but the below statement returns not the desired groupped row value
SELECT a.name,
( CASE
WHEN b.shop <> 1 THEN Sum(b.amount)
ELSE b.amount
end ) AS amount,
( CASE
WHEN b.shop <> 1 THEN 0
ELSE b.shop
end ) AS shop
FROM a
LEFT JOIN b
ON a.id = b.a_id
GROUP BY a.id
Any ideas ? Is there a way to put this condition to the GROUP like: case shop<>1 THEN .... ELSE GROUP BY a.id ??
You will need to Left Join twice with the table b.
First Join will enable computation of Sum.
Second Join will join only when shop = 1. So if we get some non-null value due to second join, we will consider that, else the Sum
Try the following:
SELECT
a.id,
a.name,
COALESCE(MAX(b2.amount), SUM(b1.amount)) AS amount,
COALESCE(b2.shop, 0) AS shop
FROM a
LEFT JOIN b AS b1 ON a.id = b1.a_id
LEFT JOIN b AS b2 ON a.id = b2.a_id AND b2.shop = 1
GROUP BY a.id, a.name
Result
| id | name | amount | shop |
| --- | ----- | ------ | ---- |
| 1 | john | 4 | 1 |
| 2 | doe | 9 | 0 |
| 3 | smith | 4 | 0 |
View on DB Fiddle
You were on the right track to use what is called conditional aggregation. Here is a version which should work:
SELECT
a.name,
CASE WHEN MAX(CASE WHEN b.shop = 1 THEN 1 ELSE 0 END) > 0
THEN SUM(CASE WHEN b.shop = 1 THEN b.amount ELSE 0 END)
ELSE SUM(b.amount) END AS amount,
MAX(CASE WHEN b.shop = 1 THEN 1 ELSE 0 END) AS shop
FROM a
LEFT JOIN b
ON a.id = b.a_id
GROUP BY
a.name;
Demo
There are two tables article and category.
nid | title | status
---+-------------+-------
1 | abc | 1
2 | ggg | 1
3 | kkk | 0
4 | rrr | 1
5 | fff | 1
6 | ggg | 1
Where status = 1 is published.
cid | nid
---+-------------
1 | 1
2 | 2
2 | 3
3 | 4
1 | 5
2 | 6
Now I want to get a one nid for each cid, no double occurrence of cid where status is 1.
You can use GROUP BY with JOIN, e.g.:
SELECT t2.cid, MAX(t2.nid)
FROM table2 t2 JOIN table1 t1 ON t2.nid = t1.nid and t1.status = 1
GROUP BY t2.cid;
First of all you must decide which nid to show for a cid in case of multiple matches. Let's say you want the maximum nid. Select from category and look up articles for their status. Then aggregate.
select cid, max(nid)
from category
where nid in (select nid from article where status = 1)
group by cid;
You can use aggregation:
select c.cid, max(c.nid)
from category c join
article a
on c.nid = a.nid
where a.status = 1
group by c.cid;
Try this one.
SELECT DISTINCT cid
FROM category AS a1
INNER JOIN article AS a2 ON a1.nid = a2.nid
WHERE a1.[STATUS] = 1
I have a table
id value
1 a
2 a
3 b
4 b
5 b
6 c
My id is primary.
I have total 2 a , 3 b and 1 c. So I want to count total repeat value in each primary id which matches on it
I want this format
id value_count
1 2
2 2
3 3
4 3
5 3
6 1
Try this query:
SELECT a.id, b.valueCnt
FROM tableA a
INNER JOIN (SELECT a.value, COUNT(a.value) valueCnt
FROM tableA a GROUP BY a.value) AS B ON a.value = b.value;
Check the SQL FIDDLE DEMO
OUTPUT
| ID | VALUECNT |
|----|----------|
| 1 | 2 |
| 2 | 2 |
| 3 | 3 |
| 4 | 3 |
| 5 | 3 |
| 6 | 1 |
Try This
select id, value_count from tablename as a1
join (select count(*) as value_count, value from tablename group by value) as a2
on a1.value= a2.value
I suggest you use a subselect without any joins:
SELECT
a.id,(SELECT COUNT(*) FROM tableA WHERE value = a.value) as valueCnt
FROM tableA a
Fiddle Demo
You need to use subquery.
SELECT table.id , x.value_count
FROM table
INNER JOIN
(SELECT t1.value, count(t1.id) as value_count
FROM table t1
Group by t1.value
) x on x.value = table.value
I need a help from mySQL Join tables which should list all users in 'Table A' and also should show flag by matching records in 'Table B'
Table A (users)
=====================
id | name
=====================
1 | aaa
2 | bbb
3 | ccc
4 | ddd
5 | eee
Table B (users_likes)
=====================
like_by | like_to
=====================
1 | 2
1 | 3
2 | 3
4 | 1
5 | 1
if user 'aaa'(id:1) login to the system and performs a search to list all users except his details so the results will be
2 | bbb
3 | ccc
4 | ddd
5 | eee
also he needs to see a flag when listing which shows his 'like_by'
eg:
2 | bbb | (flag:TRUE)
3 | ccc | (flag:TRUE)
4 | ddd | flag:false)
5 | eee | (flag:false)
an easy solution for your problem is to use UNION and few SubQuery(although other solution may exist other than this)
SQLFiddle Demo
SELECT DISTINCT b.id, b.name,'TRUE' AS FLAG
FROM users_likes a
INNER JOIN users b
on a.like_to = b.id
WHERE like_by = 1
UNION
SELECT DISTINCT id, name,'FALSE' AS FLAG
FROM users
WHERE ID NOT IN
(
SELECT DISTINCT b.id
FROM users_likes a
INNER JOIN users b
on a.like_to = b.id
WHERE like_by = 1
)
AND id <> 1
or without using UNION and much simplier solution is the one below.
SQLFiddle Demo
SELECT a.id,
a.name,
IF(COALESCE(b.like_by, -1) = 1, 'TRUE', 'FALSE') AS `Flag`
FROM users a
LEFT JOIN users_Likes b
ON a.id = b.like_to
WHERE a.ID <> 1
GROUP BY a.id
Try this query
select
B.like_to,
BNames.name,
case when B.like_by = 1 then True else False end
from Table_A A
left outer join Table_B B on A.id = B.like_by
left outer join Table_A BNames on B.like_to = BNames.id
where B.like_to <> 1
hope it helps.
I think this is what you're after:
SELECT a.id,a.name,b.like_by IS NOT NULL
FROM users a
LEFT JOIN users_likes b ON (a.id = b.like_to)
GROUP BY a.id
This will give all items of 'a' with 1 where there is a like_to to that a and 0 where there isn't.
(You can add WHERE a.id != 1 to exclude user 1).
Edit:
Changing the third column to be true false based on user 1's likes. I originally thought it was any like.
SELECT a.id,a.username,b.like_by IS NOT NULL
FROM users a
LEFT JOIN user_likes b ON (a.id = b.like_to AND b.like_by = 1)
WHERE a.id != 1
GROUP BY a.id
http://sqlfiddle.com/#!2/ea34d/10
I have two tables that I need to join. These are:
art
id | art
--------
1 | A
2 | B
3 | C
4 | D
5 | E
6 | F
7 | G
8 | H
9 | I
and
Sess
artid | sessid
--------------
1 | 1
2 | 1
3 | 1
4 | 1
1 | 2
4 | 2
5 | 2
6 | 2
1 | 3
2 | 3
7 | 3
4 | 3
where Sess.artid is a foregin key to art.id.
From the tables above we can see that there are 3 sessions: A,B,C,D, A,D,E,F and A,B,G,D.
I want to get a ranking of the arts that occur along with art A. Something like:
D=3
B=2
How could I form such a query in mysql or postgres?
You need to join twice the session table to get the article sharing the same session.
Then join one time with article for the filter clause, and another time to get the name of the other article in the other session.
SELECT aSameSession.art, count(*)
FROM art a
INNER JOIN Sess s
ON a.id = s.artid
INNER JOIN Sess sSameArticle
ON sSameArticle.sessid = s.sessid
INNER JOIN art aSameSession
ON sSameArticle.artid = aSameSession.id
WHERE A.art = 'A'
AND aSameSession.art <> 'A'
GROUP BY aSameSession.art
Output :
B 2
C 1
D 3
E 1
F 1
G 1
This version could be a little difficult to understand, so here a version just with the ID of the article, which is much more simple :
SELECT sSameArticle.artid, count(*)
FROM Sess s
INNER JOIN Sess sSameArticle
ON sSameArticle.sessid = s.sessid
WHERE s.artid = 1
AND sSameArticle.artid != 1
GROUP BY sSameArticle.artid
Output :
2 2
3 1
4 3
5 1
6 1
7 1
Adding the name of the article is just cosmetic.
Something like this, perhaps:
select art,count(*)
from sessid
left join art on art.id=artid
where sessid in (select sessid from sess where artid=1)
group by artid;
?
Example of table structure and join queries on PostgreSQL
CREATE TABLE arts (
arts_id serial PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE sessions (
sessions_id integer NOT NULL,
arts_id integer NOT NULL REFERENCES arts
);
SELECT arts.name, count(sessions_id)
FROM arts
JOIN sessions USING (arts_id)
GROUP BY arts.name
ORDER BY count(sessions_id) DESC;
SELECT a.art, count(*) as ranking
FROM art a, sess s
WHERE a.id = s.artid
group by a.art
order by count(*) DESC;
For a statement in ANSI-92 syntax have a look at Konerak's answer.