Is there a better way to do this MYSQL statement? - mysql

This mysql statement gets posts that have not been flagged by the user.
As it is now, I am getting the flagged post ids, and then not getting posts in that set of ids.
SELECT * FROM posts WHERE posts.id NOT IN
(SELECT p2.id FROM posts p2 LEFT JOIN flagged_posts
ON flagged_posts.user_id = ? WHERE flagged_posts.post_id = p2.id)
I feel there is probably a better (faster) way to do this, for example with just one select and one join, but I am not sure

You can do this using clause EXISTS.
SELECT * FROM posts
WHERE
NOT EXISTS
(SELECT 1
FROM flagged_posts
WHERE flagged_posts.post_id=posts.post_id AND flagged_posts.user_id=?)
Alternatively you can do this using LEFT OUTER JOIN.
SELECT *
FROM posts
LEFT OUTER JOIN flagged_posts ON posts.post_id = flagged_posts.post_id
AND flagged_posts.user_id=?
WHERE flagged_posts.post_id IS NULL

Related

Nested query performance

I have two queries below. The first one has a nested select. The second one makes use of a group by clause.
select
posts.*,
(select count(*) from comments where comments.post_id = posts.id and comments.is_approved = 1) as comments_count
from
posts
select
posts.*,
count(comments.id) comments_count
from
posts
left join comments on
comments.post_id = posts.id
group by
posts.*
From my understanding the first query is worse because it has to do a select for each record in posts where as the second query does not.
Is this true or false?
As with all performance questions, you should test the performance on your system with your data.
However, I would expect the first to perform better, with the right indexes. The right index for:
select p.*,
(select count(*)
from comments c
where c.post_id = p.id and c.is_approved = 1
) as comments_count
from posts p
is comments(post_id, is_approved).
MySQL implements a group by by doing a file sort. This version saves a file sort on all the data. My guess is that will be faster than the second method.
As a note: group by posts.* is not valid syntax. I assume this was intended for illustration purposes only.
This is the standard way I would do it (the use of LEFT JOIN, and SUM lets you also know which posts have no comments.)
SELECT posts.*
, SUM(IF(comments.id IS NULL, 0, 1)) AS comments_count
FROM posts
LEFT JOIN comments USING (post_id)
GROUP BY posts.post_id
;
But if I were trying for faster, this might be better.
SELECT posts.*, IFNULL(subQ.comments_count, 0) AS comments_count
FROM posts
LEFT JOIN (
SELECT post_id, COUNT(1) AS comments_count
FROM comments
GROUP BY post_id
) As subQ
USING (post_id)
;
After a bit more research I found no time difference between the two queries
Benchmark.bm do |b|
b.report('joined') do
1000.times do
ActiveRecord::Base.connection.execute('
select
p.id,
(select count(c.id) from comments c where c.post_id = p.id) comment_count
from
posts l;')
end
end
b.report('nested') do
1000.times do
ActiveRecord::Base.connection.execute('
select
p.id,
count(c.id) comment_count
from
posts File.join(File.dirname(__FILE__), *%w[rel path here])
left join comments c on
c.post_id = p.id
group by
p.id;')
end
end
end
user system total real
nested 2.120000 0.900000 3.020000 ( 3.349015)
joined 2.110000 0.990000 3.100000 ( 3.402986)
However I did notice that when running an explain for both queries, more indexes are possible in the first query. Which makes me think it is a better option if the attributes needed in the select changed.

LEFT JOIN "Not unique table/alias"

I want to load all the posts and its tags with one query from my database.I think LEFT JOIN is the appropriate one for that, do you have any other suggestion?
Here is my SQL query:
SELECT * FROM posts, tags, tags_map
LEFT JOIN posts on posts.cid = tags_map.pid
WHERE tags.tag_id = tags_map.tid
It showing an error Not unique table/alias: 'posts' where is wrong, cause i am pointing to a table named 'posts', any idea?
Remove posts table from "FROM" section:
SELECT * FROM tags, tags_map
LEFT JOIN posts on posts.cid = tags_map.pid
WHERE tags.tag_id = tags_map.tid
Do not mix old and new style joins. In fact, always use explicit join syntax. Never use commas in the from clause:
SELECT *
FROM posts p JOIN
tags_map tm
ON p.cid = tm.pid JOIN
tags t
ON t.tag_id = tm.tid;
I'm not sure what the left join is for. If you really need it, add it to this version of the query.

Join condition in mysql need to ignore null field

I have two tables: post, post_image
I am currently trying to join them like so:
$sql = 'SELECT post.*, post_image.name AS img FROM post, post_image
WHERE post_image.postId=post.id LIMIT 10';
Here's my problem. Some posts do not have any entries in post_image, but I still need them returned as well. I know that mysql can check for null, but I'm not sure where it would go in my statement if that is the solution. I'm also not entirely sure I can do this with the shorthand join I am using.
Please help :)
Edit:
This is working as expected now, but I also need to make sure that it only pulls the post_image with field ordinal=0 as there can be multiple post_image entries. I tried adding a WHERE clause and it seemed to only pull posts with images.Here's what I have:
SELECT post.*, post_image.name AS img FROM post LEFT JOIN post_image ON post_image.postId=post.id WHERE post_image.ordinal=0 LIMIT 10
Use LEFT JOIN like this:
SELECT
post.*,
post_image.name AS img
FROM post
LEFT JOIN post_image ON post_image.postId = post.id
LIMIT 10;
Your query is INNER JOIN the two tables using the old join syntax which uses the WHERE clause to specify the join condition. Better off use the explicit JOIN syntax using the INNER JOIN condition instead in future queries.
For more information:
A Visual Explanation of SQL Joins.
Bad habits to kick : using old-style JOINs.
How about using LEFT JOIN? I think it will show the post although it does not have image.
SELECT * FROM post p LEFT JOIN post_image pi ON p.id = pi.postId

Count Number of UnViewed Posts

I have a db structure like:
posts
id
title
content
users
id
....
post_reads
post_id
user_id
How can I count the number of posts for which a particular user with an id say, x does not have a read record.
My SQL query currently looks like:
SELECT COUNT(posts.id) AS c
FROM `posts`
LEFT JOIN `post_reads` ON (`posts`.`id` = `post_reads`.`post_id`)
LEFT JOIN `users` ON (post_reads.user_id = `users`.`id` AND post_reads.user_id = x)
WHERE users.id IS NULL
AND post_reads.user_id IS NULL
I know I'm doing something wrong, although I'm not sure what that is.
This should to the trick
SELECT COUNT(posts.id) AS c
FROM posts
LEFT JOIN post_reads ON posts.id = post_reads.post_id AND post_reads.user_id = x
LEFT JOIN users ON post_reads.user_id = users.id
WHERE users.id IS NULL
Note that if you're not interested in doing anything with table users you can shorten this query to:
SELECT COUNT(posts.id) AS c
FROM posts
LEFT JOIN post_reads ON posts.id = post_reads.post_id AND post_reads.user_id = x
WHERE post_reads.user_id IS NULL
The first join you were doing is really an inner join, because it will never 'misfire'.
The second join will sometimes misfire, because you have the extra condition in there.
Therefore using the post_reads.some_id is null will never be true.
In order for that to work you'd have to repeat the AND post_reads.user_id = x in that join condition as well, but putting it in twice is silly and not needed, once will do.
PS don't forget to replace the 'x' with something more useful :-)
I tried this a few ways just using JOINS/WHERE, but they tend to miss certain cases (i.e. you can exclude posts joined to a read record for the given user, but the posts' ids will still be returned if they also join to read records for other users).
The simplest way may be something like this:
SELECT COUNT(DISTINCT id)
FROM posts
WHERE id NOT IN (SELECT DISTINCT post_id FROM post_reads WHERE user_id = #x)
Also, note that I don't believe you need to surround identifiers in backticks unless they are MySQL keywords.

Need an alternative to two left joins

Hey guys quick question, I always use left join, but when I left join twice I always get funny results, usually duplicates. I am currently working on a query that Left Joins twice to retrieve the necessary information needed but I was wondering if it were possible to build another select statement in so then I do not need two left joins or two queries or if there were a better way. For example, if I could select the topic.creator in table.topic first AS something, then I could select that variable in users and left join table.scrusersonline. Thanks in advance for any advice.
SELECT * FROM scrusersonline
LEFT JOIN users ON users.id = scrusersonline.id
LEFT JOIN topic ON users.username = topic.creator
WHERE scrusersonline.topic_id = '$topic_id'
The whole point of this query is to check if the topic.creator is online by retrieving his name from table.topic and matching his id in table.users, then checking if he is in table.scrusersonline. It produces duplicate entries unfortunately and is thus inaccurate in my mind.
You use a LEFT JOIN when you want data back regardless. In this case, if the creator is offline, getting no rows back would be a good indication - so remove the LEFT joins and just do regular joins.
SELECT *
FROM scrusersonline AS o
JOIN users AS u ON u.id = o.id
JOIN topic AS t ON u.username = t.creator
WHERE o.topic_id = '$topic_id'
One option is to group your joins thus:
SELECT *
FROM scrusersonline
LEFT JOIN (users ON users.id = scrusersonline.id
JOIN topic ON users.username = topic.creator)
WHERE scrusersonline.topic_id = '$topic_id'
Try:
select * from topic t
left outer join (
users u
inner join scrusersonline o on u.id = o.id
) on t.creator = u.username
If o.id is null, the user is offline.
Would not it be better to match against topic_id in the topics table by moving the condition to the join. I think it will solve your problem, since duplicates come from joining with the topics table:
SELECT * FROM scrusersonline
JOIN users
ON users.id = scrusersonline.id
LEFT JOIN topic
ON scrusersonline.topic_id = '$topic_id'
AND users.username = topic.creator
By the way, LEFT JOIN with users is not required since you seem to search for the intersection between scrusersonline and users