MySQL query with LEFT OUTER JOIN and WHERE - mysql

I have three tables: stories, story_types, and comments
The following query retrieves all of the records in the stories table, gets their story_types, and the number of comments associated with each story:
SELECT s.id AS id,
s.story_date AS datetime,
s.story_content AS content,
t.story_type_label AS type_label,
t.story_type_slug AS type_slug,
COUNT(c.id) AS comment_count
FROM stories AS s
LEFT OUTER JOIN story_types AS t ON s.story_type_id = t.id
LEFT OUTER JOIN comments AS c ON s.id = c.story_id
GROUP BY s.id;
Now what I want to do is only retrieve a record from stories WHERE s.id = 1 (that's the primary key). I have tried the following, but it still returns all of the records:
SELECT s.id AS id,
s.story_date AS datetime,
s.story_content AS content,
t.story_type_label AS type_label,
t.story_type_slug AS type_slug,
COUNT(c.id) AS comment_count
FROM stories AS s
LEFT OUTER JOIN story_types AS t ON s.story_type_id = t.id
AND s.id = 1
LEFT OUTER JOIN comments AS c ON s.id = c.story_id
GROUP BY s.id;
I have also tried a WHERE clause at the end, which throws an error.
Can someone point out the correct syntax for a condition like this in this situation?
I'm using MySQL 5.1.47. Thanks.

I'm guessing you put the WHERE after the GROUP BY, which is illegal. See this reference on the SELECT syntax in MySQL.
Try this:
SELECT
s.id AS id,
s.story_date AS datetime,
s.story_content AS content,
t.story_type_label AS type_label,
t.story_type_slug AS type_slug,
COUNT(c.id) AS comment_count
FROM
stories AS s
LEFT JOIN story_types AS t ON s.story_type_id = t.id
LEFT JOIN comments AS c ON s.id = c.story_id
WHERE
s.id = 1
GROUP BY
s.id;
editor's note: I reformatted the code to highlight the query structure

Following up this comment on the accepted answer:
It is not intuitive to me that this WHERE would go in the second JOIN
This is just to outline how proper code formatting enhances understanding. Here is how I usually format SQL:
SELECT
s.id AS id,
s.story_date AS datetime,
s.story_content AS content,
t.story_type_label AS type_label,
t.story_type_slug AS type_slug,
COUNT(c.id) AS comment_count
FROM
stories AS s
LEFT JOIN story_types AS t ON t.id = s.story_type_id
LEFT OUTER JOIN comments AS c ON s.id = c.story_id
WHERE
s.id = 1
GROUP BY
s.id;
The WHERE is not on the second join. There is only one WHERE clause allowed in a SELECT statement, and it always is top level.
PS: Also note that in many database engines (apart from MySQL) it is illegal to use a GROUP BY clause and then selecting columns without aggregating them via functions like MIN(), MAX(), or COUNT(). IMHO this is bad style and a bad habit to get into.

Related

What is the difference in mySQL AND vs Where

I'm using mysql and I confused with "And", "Where"
Somby dy can tell me what is difference between these.
SELECT *,COUNT(comment.id) as comment_count from posts LEFT JOIN comment on posts.post_id =comment.post_id AND comment.approve = 1 GROUP BY posts.post_id
SELECT *,COUNT(comment.id) as comment_count from posts LEFT JOIN comment on posts.post_id =comment.post_id WHERE comment.approve = 1 GROUP BY posts.post_id
They are not the same, first one will return the associations for all, and the second will do it just for the rows in the where match.
In this other duplicate question you can see the full explanation and examples
SQL JOIN - WHERE clause vs. ON clause
Simply change the query to use an inner join like this:
select tableA.id, tableA.name, tableB.details
from tableA
inner join tableB ...
here is the definition of left join:
The LEFT JOIN (also called LEFT OUTER JOIN) keyword returns all rows from the left table (table_name1), even if there are no matches in the right table (table_name2).
whereas the definition of the inner join is:
The INNER JOIN keyword return rows when there is at least one match in both tables.

SQL query optimization and sort by other row if first is empty

SQL Query:
SELECT
T.*,
U.nick AS author_nick,
P.id AS post_id,
P.name AS post_name,
P.author AS post_author_id,
U2.nick AS post_author
FROM
zero_topics T
LEFT JOIN
zero_posts P
ON
T.id = P.topic_id
LEFT JOIN
zero_players U
ON
T.author = U.uuid
LEFT JOIN
zero_players U2
ON
P.author = U2.uuid
ORDER BY
P.id DESC
Questions:
I need to double left join to get user nick from UUID for topic and post
Not all topics will have post, as you see i sort from post id(it will be date) but it shows on first place topics with last post, and on bottom topics without replies, how can i define order when posts doesn't exists?
1.You will need to double left join if you need to show the nicks in different columns
2.You could use a case in you order by
ORDER BY
CASE
WHEN P.id is null THEN T.ID
ELSE P.ID
END ASC
Final Query:-
SELECT
T.*,
U.nick AS author_nick,
P.id AS post_id,
P.name AS post_name,
P.author AS post_author_id,
U2.nick AS post_author
FROM
zero_topics T
LEFT JOIN
zero_posts P
ON
T.id = P.topic_id
LEFT JOIN
zero_players U
ON
T.author = U.uuid
LEFT JOIN
zero_players U2
ON
P.author = U2.uuid
ORDER BY
CASE
WHEN P.id is null THEN T.ID
ELSE P.ID
END ASC
You actually have two join chains from the topics table. One chain ties an author directly to the topic and one ties an author to each post about the topic, either one or both may be left joined. But once you start a left join in a chain, it must then be continued down the rest of the chain or you nullify the left join. Actually, the topic author is in a chain of length 1 so you don't have to worry about that one.
If every topic has an author, you don't need to left join the first players table (T.author = U.uuid) as that would always link. You would left join down the post chain to see topics even if they have no posts written on them.
Assuming that is what you want to see, then the order by clause could well stay just as you wrote it. What you would get is a list of posts, ordered by ID, with the topics scattered around however they ended up. Any topics with no posts would be clumped all either at the beginning or at the end of the result set, depending on your settings and the DBMS.
If, however, you wrote the order by like this:
order by t.Title, p.id;
Then you would get all the topic ordered by title, with the posts written about that topic ordered by ID within each topic. Any topic with no posts would have a single row (assuming only one topic author) in the proper title order but showing only topic data.
So it all depends on what you want to see.

Sorting results from joins

While running this query:
SELECT
a.id,
pub.name AS publisher_name,
pc.name AS placement_name,
b.name AS banner_name,
a.lead_id,
a.partner_id,
a.type,
l.status,
s.correctness,
a.landing_page,
t.name AS tracker_name,
a.date_view,
a.date_action
FROM actions AS a
LEFT JOIN publishers AS pub ON a.publisher_id = pub.id
LEFT JOIN placements AS pc ON pc.publisher_id = pub.id
LEFT JOIN banners AS b ON b.campaign_id = a.campaign_id
LEFT JOIN leads l ON
l.lead_id = a.lead_id
AND l.created = (
SELECT MAX(created) from leads l2 where l2.lead_id = l.lead_id
)
LEFT JOIN statuses AS s ON l.status = s.status
LEFT JOIN trackers AS t ON t.id = a.tracker_id
LIMIT 10
I am able to sort by every column from actions table. However when I try to for example ORDER BY b.name (from banners table, joined on actions.banner_id) or ORDER BY l.lead_id (joined from leads on more complex condition as seen above) MySQL is running query for a loooong time (most tables have tens of thousands records). Is it possible, performance-wise, to sort by joined columns?
You should rewrite the query with a inner join on the table where the column you want to sort on is.
For example, if you sort on actions.banner_id
SELECT ...
FROM actions AS a
JOIN banners AS b ON b.campaign_id = a.campaign_id
LEFT JOIN *rest of the query*
You will get the same results unless there is not enough banners that can be joined to action to produce a total of 10 rows.
I'm guessing it's not the case otherwise you wouldn't be sorting on banner_id.
You could first filter (order by, where, etc.) your records in a subquery and then join the result with the rest of the tables.

MYSQL subquery SELECT in JOIN clause

Ok... well I have to put the subquery in a JOIN clause since it selects more than one column and putting it in the SELECT clause does not allow that as it gives me an error of an operand.
Anywho, this is my query:
SELECT
c.id,
c.title,
c.description,
c.icon,
p.id as topic_id,
p.title AS topic_title,
p.date,
p.username
FROM forum_cat c
LEFT JOIN (
SELECT
ft.id,
ft.cat_id,
ft.title,
fp.date,
u.username
FROM forum_topic ft
JOIN forum_post fp ON fp.topic_id = ft.id
JOIN user u ON u.user_id = fp.author_id
WHERE ft.cat_id = c.id
ORDER BY fp.date DESC
LIMIT 1
) p ON p.cat_id = c.id
WHERE c.main_cat = ?
ORDER BY c.list_no
Now the important thing I need here... FOR EACH category, I want to show the latest post and topic title in each category.
However, this select statement is going INSIDE a foreach loop looping around the general categories which is found my main_cat.
So there are 5 main categories with 3-8 subcategories.. this is the subcategory query. BUT FOR EACH subcategory, I need to grab the latest post.. However, it only runs this SELECT query for each main category so it's only select THE LATEST post between all subcategories combined... I want to get the latest post of EACH subcategory, but I rather not run this query for each subcategory... since I want the page load to be fast.
BUT REMEMBER, some subcategories WILL NOT have a latest post since some of them may not even contain a topic yet! So hence the left join.
Does anyone know how to go about doing this?
AND BTW, there is an error it gives me (WHERE ft.cat_id = c.id) in the subquery because c.id is an unknown column. But I'm trying to reference it from the outer query so can someone help me on that issue as well?
Thank you!
All tables:
forum_cat (Subcategories)
-----------------------------------------------
ID, Title, Description, Icon, Main_cat, List_no
forum_topic (Topics in each subcategory)
--------------------------------------------
ID, Author_id, Cat_id, Title, Sticky, Locked
forum_post (Posts in each topic)
--------------------------------------------
ID, Topic_id, Author_id, Body, Date, Hidden'
The main categories are listed in a function. I didn't store them in the database since it was a waste of space since they never change. There are 7 main categories though.
It's hard to tell without seeing DDL of your tables, relevant sample data and desired output.
I could've got your requirements wrong, but try this:
SELECT *
FROM forum_cat c LEFT JOIN
(SELECT t.cat_id,
p.topic_id,
t.title,
p.id,
p.body,
MAX(p.`date`) AS `date`,
p.author_id,
u.username
FROM forum_post p INNER JOIN
forum_topic t ON t.id = p.topic_id INNER JOIN
`user` u ON u.user_id = p.author_id
GROUP BY t.cat_id) d ON d.cat_id = c.id
WHERE c.main_cat = 1
ORDER BY c.list_no

MySQL - A conditional WHERE

See, I've got this bulletin board. This query here works just fine.
SELECT bulletin.date,
bulletin.title,
bulletin.content,
bulletin.id,
bulletin.made_by,
users.ID,
users.name
FROM bulletin, users
WHERE bulletin.id = '12345'
AND bulletin.made_by = users.ID
LIMIT 1
Now, I'm running into troubles because when a user deletes his account there is no information about said person left, he is deleted from the users table. I know there may be other ways to deal with this but I can't fix that now.
So I ask: How can I make that query return bulletin.date, bulletin.title and bulletin.content where bulletin.id is equal to 12345 even though I've got "AND bulletin.made_by = users.ID"? You see, I need to have that for all the other posts where the user still exists.
Any suggestions?
Use a LEFT OUTER JOIN
SELECT b.date,
b.title,
b.content,
b.id,
b.made_by,
b.ID,
b.name
FROM bulletin b
LEFT OUTER JOIN users u on b.made_by = u.ID
WHERE b.id = '12345'
LIMIT 1
See this great explanation of joins
Currently you are using INNER JOIN, Use LEFT JOIN in order to solve your problem.
SELECT a.date,
a.title,
a.content,
a.id,
a.made_by,
a.ID,
a.name
FROM bulletin a
LEFT JOIN users c
ON a.made_by = c.ID
WHERE a.id = '12345'
LIMIT 1
LEFT JOIN basically fetches all the records from the left table whether it has a matching row in the second table or not.