Still produce result from multiple inner joins if one join fails - mysql

I have the following query which makes 2 inner joins. This works fine unless there are no entries for the account_id in the ratings table.
SELECT c.comment_id, a.account_id, a.first_name, a.second_name, a.points, a.image_url, c.body, c.creation_time, AVG(r.rating_overall)
FROM comments AS c
INNER JOIN accounts AS a
ON c.account_id=a.account_id
INNER JOIN ratings AS r
ON r.baker_id=a.account_id
WHERE c.blog_id = ?
GROUP BY c.comment_id, a.account_id, a.first_name, a.second_name, a.points, a.image_url, c.body, c.creation_time
ORDER BY c.creation_time DESC
How do I make this query return a result even if there are no entries in the ratings table. In other words produce AVG(r.rating_overall) = 0 whenever there are no ratings?

You should use a LEFT JOIN:
SELECT
...
FROM comments AS c
INNER JOIN accounts AS a
ON c.account_id=a.account_id
LEFT JOIN ratings AS r
ON r.baker_id=a.account_id
....
that will return all rows from the previous join, and only the rows that matches the last join. If there's no match, all columns from rating tables will be null.
You learn more about joins on this visual explanation of SQL joins.

Related

Select all rows from one table and the sum from the second table based on the third table

I have a problem with creating a query that will show me all rows from one table and the sum of rows from another table with the same id.
I've written a query that is not valid because it skips the rows that are empty.
SELECT g.name, sum(w.points), sum(w.points2)
FROM groups g, works w, students s
WHERE g.group_id=s.group_id AND w.student_id=s.student_id
GROUP BY g.group_id;
I tried also LEFT JOIN, but my query have error:
SELECT g.name, s.name, sum(w.points) points
FROM groups g
LEFT JOIN students s
ON s.group_id=g.group_id
LEFT JOIN works w
ON w.student_id=s.student_id
GROUP BY g.group_id;
Here fiddle
If there is no point in the group, it does not show it at all, and I would like all groups to be displayed, even with the sum of 0.
Example tables
Expected result
This works when I run it:
SELECT g.name, sum(w.points), sum(w.points2)
FROM groups g LEFT JOIN
students s
ON g.group_id = s.group_id LEFT JOIN
works w
ON w.student_id = s.student_id
GROUP BY g.group_id;
If you want zeroes, use COALESCE():
SELECT g.name, COALESCE(sum(w.points), 0),
COALESCE(sum(w.points2), 0)
FROM groups g LEFT JOIN
students s
ON g.group_id = s.group_id LEFT JOIN
works w
ON w.student_id = s.student_id
GROUP BY g.group_id;
select subtable.name, sum(subtable.points),sum(subtable.points2) from
(select gr.name,wr.points,wr.points2 from students as st
inner join works as wr on st.student_id = wr.student_id
inner join groups as gr on gr.group_id = st.group_id) subtable
group by subtable.name;
this should also work

Returning all results of an outer query and getting a count of attached items

So I'm struggling to write a query that returns me all categories regardless of what filter I have applied but the count changes based on how many returned recipes there will be in this filter.
This query works nice if I don't apply any filters to it. The count's seem right, but as soon as I add something like this: where c.parent_id is not null and r.time_cook_minutes > 60 I am filtering out most of the categories instead of just getting a count of zero.
here's an example query that I came up with that does not work the way I want it to:
select t.id, t.name, t.parent_id, a.cntr from categories as t,
(select c.id, count(*) as cntr from categories as c
inner join recipe_categories as rc on rc.category_id = c.id
inner join recipes as r on r.id = rc.recipe_id
where c.parent_id is not null and r.time_cook_minutes > 60
group by c.id) as a
where a.id = t.id
group by t.id
so this currently, as you might imagine, returns only the counts of recipes that exist in this filter subset... what I'd like is to get all of them regardless of the filter with a count of 0 if they don't have any recipes under that filter.
any help with this would be greatly appreciated. If this question is not super clear let me know, and I can elaborate.
No need for nested join if you move the condition into a regular outer join:
select t.id, t.name, t.parent_id, count(r.id)
from categories as t
left join recipe_categories as rc on rc.category_id = c.id
left join recipes as r on r.id = rc.recipe_id
and r.time_cook_minutes > 60
where c.parent_id is not null
group by 1, 2, 3
Notes:
Use left joins so you always get every category
Put r.time_cook_minutes > 60 on the left join condition. Leaving it on the where clause cancels the effect of left
Simply use conditional aggregation, moving the WHERE clause into a CASE (or IF() for MySQL) statement wrapped in a SUM() of 1's and 0's (i.e., counts). Also, be sure to consistently use the explicit join, the current industry practice in SQL. While your derived table uses this form of join, the outer query uses implicit join matching IDs in WHERE clause.
select t.id, t.name, t.parent_id, a.cntr
from categories as t
inner join
(select c.id, sum(case when c.parent_id is not null and r.time_cook_minutes > 60
then 1
else 0
end) as cntr
from categories as c
inner join recipe_categories as rc on rc.category_id = c.id
inner join recipes as r on r.id = rc.recipe_id
group by c.id) as a
on a.id = t.id
group by t.id
I believe you want:
select c.id, c.name, c.parent_id, count(r.id)
from categories c left join
recipe_categories rc
on rc.category_id = c.id left join
recipes r
on r.id = rc.recipe_id and r.time_cook_minutes > 60
where c.parent_id is not null and
group by c.id, c.name, c.parent_id;
Notes:
This uses left joins for all the joins.
It aggregates by all the non-aggregated columns.
It counts matching recipes rather than all rows.
The condition on recipes is moved to the on clause from the where clause.

How can I add multiple tables via LEFT JOIN and count the rows of each table?

I am counting the results of a table I LEFT JOINED:
SELECT p.*,COUNT(po.name) AS posts
FROM projects p
left join posts po on p.name = po.name
group by p.id
http://sqlfiddle.com/#!9/3e9d4b/4
But now I want to add another table via LEFT JOIN and count it also:
SELECT p.*,COUNT(po.name) AS posts,
COUNT(ta.name) AS tasks
FROM projects p
left join posts po on p.name = po.name
left join tasks ta on p.name = ta.name
group by p.id
http://sqlfiddle.com/#!9/ee068/2
But now the counting is wrong. For cat I have only 2 posts and 3 tasks. Where is the number 6 coming from?
Left joins are not the right tool for this. You should use subselects:
SELECT p.*,
(SELECT COUNT(*) FROM posts po WHERE p.name = po.name) AS posts,
(SELECT COUNT(*) FROM tasks ta WHERE p.name = ta.name) AS tasks
FROM projects p
You can still use joins, but instead of a single top level aggregation, you can aggregate each of the two tables in separate subqueries:
SELECT
p.name,
COALESCE(t1.posts_cnt, 0) AS posts_cnt,
COALESCE(t2.tasks_cnt, 0) AS tasks_cnt
FROM projects p
LEFT JOIN
(
SELECT name, COUNT(*) AS posts_cnt
FROM posts
GROUP BY name
) t1
ON p.name = t1.name
LEFT JOIN
(
SELECT name, COUNT(*) AS tasks_cnt
FROM tasks
GROUP BY name
) t2
ON p.name = t2.name
Your current queries have problems, because you are aggregating by id but selecting other columns. From what I see, you want to be aggregating in all three tables using the name column.
This approach should outperform an approach using correlated subqueries.

MySQL inner join different results

I am trying to work out why the following two queries return different results:
SELECT DISTINCT i.id, i.date
FROM `tblinvoices` i
INNER JOIN `tblinvoiceitems` it ON it.userid=i.userid
INNER JOIN `tblcustomfieldsvalues` cf ON it.relid=cf.relid
WHERE i.`tax` = 0
AND i.`date` BETWEEN '2012-07-01' AND '2012-09-31'
and
SELECT DISTINCT i.id, i.date
FROM `tblinvoices` i
WHERE i.`tax` = 0
AND i.`date` BETWEEN '2012-07-01' AND '2012-09-31'
Obviously the difference is the inner join here, but I don't understand why the one with the inner join is returning less results than the one without it, I would have thought since I didn't do any cross table references they should return the same results.
The final query I am working towards is
SELECT DISTINCT i.id, i.date
FROM `tblinvoices` i
INNER JOIN `tblinvoiceitems` it ON it.userid=i.userid
INNER JOIN `tblcustomfieldsvalues` cf ON it.relid=cf.relid
WHERE cf.`fieldid` =5
AND cf.`value`
REGEXP '[A-Za-z]'
AND i.`tax` = 0
AND i.`date` BETWEEN '2012-07-01' AND '2012-09-31'
But because of the different results that seem incorrect when I add the inner join (it removes some results that should be valid) it's not working at present, thanks.
INNER JOIN statement will retrieve rows that are stored in both table of the jion statement.
Try a LEFT JOIN statement. This will return rows that are in first table but not necessary in the second one :
SELECT DISTINCT i.id, i.date
FROM `tblinvoices` i
LEFT JOIN `tblinvoiceitems` it ON it.userid=i.userid
LEFT JOIN `tblcustomfieldsvalues` cf ON it.relid=cf.relid
WHERE i.`tax` = 0
AND i.`date` BETWEEN '2012-07-01' AND '2012-09-31'
INNER JOIN means show only records where the same ID value exists in both tables.
LEFT JOIN means to show all records from left table (i.e. the one that precedes in SQL statement) regardless of the existance of matching records in the right table.
Try LEFT Join instead of INNER JOIN
SELECT DISTINCT i.id, i.date
FROM `tblinvoices` i
LEFT JOIN `tblinvoiceitems` it ON it.userid=i.userid
LEFT JOIN `tblcustomfieldsvalues` cf ON it.relid=cf.relid
WHERE i.`tax` = 0
AND i.`date` BETWEEN '2012-07-01' AND '2012-09-31'

Mysql how to do this query

SELECT u.username, count(t.tid) as total
FROM tbl2 t
JOIN users u ON t.userid = u.userid
GROUP BY t.userid
The above query works fine and all, it returns the number of total task for each user that has atleast one task in the tbl2 table.
But what i want to do is return all users, Even if the user doesn't have any records associated to him in the second tbl2 table. I want the total to show as 0 for those users who doesn't have any records, how can i accomplish this?
The problem with the other answers given is that you want to select all users that have no associated records; using a LEFT JOIN, the users table is on the wrong (nullable) side of the join. You could replace that LEFT JOIN with a RIGHT JOIN, but that syntax always feels unintuitive to me.
The standard answer is to reverse the order of the tables while using a LEFT JOIN:
SELECT u.username, count(t.tid) as total
FROM users u
LEFT JOIN tbl2 t ON t.userid = u.userid
GROUP BY u.username
Note that it's better practice (and, in some DBMSes, required) to group on the non-aggregated columns in the SELECT list, rather than grouping on userid and selecting username.
You should use a LEFT JOIN insteed of an INNER JOIN (default type of join), something like this:
SELECT u.username, count(t.tid) as total
FROM tbl2 t
LEFT JOIN users u ON t.userid = u.userid
GROUP BY t.userid
You'd want to use a LEFT JOIN then instead
SELECT u.username, count(t.tid) as total
FROM tbl2 t
LEFT JOIN users u ON t.userid = u.userid
GROUP BY t.userid
LEFT JOIN will join the table and show null column results for tbl2 if an associated entry doesn't exist.