Howto rewrite MySQL NOT IN query using a join? - mysql

i use a sql query like this to get some results i need:
SELECT
*
FROM
pictures p
WHERE
p.id NOT IN
(
SELECT
picture_id
FROM
guesses g
WHERE
g.user_id = XXX
)
AND
p.user_id != XXX
;
Relation is as follows: A user has many pictures and a picture belongs to one user. A user has many guesses and a guess belongs to one picture. The tricky part is that a user is only allowed one guess for the same picture.
XXX = $user_id
I guess that there is a way to rewrite this sub-select using a left join but i can't get it working.
Can anyone help?
Anja

Because it is a NOT IN condition you should use a LEFT OUTER JOIN. This is the direct translation to left outer join of your query:
SELECT
distinct p.*
FROM
pictures p
LEFT OUTER JOIN
guesses g ON g.picture_id = p.id and g.user_id = XXX
WHERE
p.user_id != XXX
and g.user_id is null
;

Okay, here we go. I guess that is the correct answer:
SELECT DISTINCT
p.*
FROM
pictures p
LEFT OUTER JOIN
guesses g
ON
g.picture_id = p.id and g.user_id = 1
WHERE
g.user_id is null
and p.user_id != 1
;

Related

MySQL not null in left join condition

My query looks like:
SELECT *
FROM users U
LEFT JOIN posts P ON P.userId = U.id AND P.userId IS NOT NULL;
Why the query also return result where userId is null ?
I know that for my needs I can use INNER JOIN to get only posts related to user but is so strange that LEFT JOIN support multiple conditions, but not work with NOT NULL conditions.
This is because "posts" does not contain the null-values and hence they can´t be filtered at that stage. The Null-values are only generated trough the join, when the server can´t find a corresponding row on the right table. So just put the not null in the where clause and it will work:
SELECT * FROM users U LEFT JOIN posts P ON P.userId = U.id WHERE userId IS NOT NULL;
(EDIT: You should use an inner join for productive work though, as it is the proper way and will give you much greater performance.)
You can also see all users who don´t have posts by inverting that:
SELECT * FROM users U LEFT JOIN posts P ON P.userId = U.id WHERE userId IS NULL;
You are outer joining the posts table. This means for every users record that has no match in posts you still get this record with all posts columns null.
So say you have a users record with userid = 5 and there is no posts record with id = 5.
ON P.userId = U.id AND P.userId IS NOT NULL
The two combined conditions are not met (there is no record with userid 5), so you get the users record with all posts columns set to null in your results.
Maybe you are simply looking for an inner join? All users records with their posts data?
This query:
SELECT *
FROM users U LEFT JOIN
posts P
ON P.userId = U.id AND P.userId IS NOT NULL;
Returns all rows in the users as well as all columns from posts, regardless of whether or not they match. This is true, regardless of whether the ON clause evaluates to TRUE or FALSE.
What you want is a WHERE. In addition, you should only select the columns from users:
SELECT u.*
FROM users U LEFT JOIN
posts P
ON P.userId = U.id
WHERE P.userId IS NOT NULL;
Note that you can also accomplish this using NOT IN or NOT EXISTS.
Because the LEFT JOIN must return every row from the left table by it's definition. The raw may be augmented with the data of the right table depending on the ON clause evaluation. So the following code must return a row.
select u.*, p.*
from (
select 1 as id
) u
left join (
-- no data at all
select 2 as id where 1=2
) p on 3 = 4 -- never is true
Try this
SELECT * FROM users U LEFT JOIN posts P ON P.userId = U.id
SELECT * FROM users U LEFT JOIN posts P ON P.userId = U.id where P.userId IS NOT NULL;
IS NOT NULL WITH JOINS
SELECT * FROM users
LEFT JOIN posts ON post.user_id = users.id
WHERE user_id IS NOT NULL;

Get results when where value is 'grade' or where it doesn't exist

I have a wordpress site that is used to store student grades on various lessons (from the quizzes on the site). I am trying to create a query that will pull out all of the lessons for all students in a certain group (using buddypress groups) and the students grades in each lesson.
I have created this query:
SELECT u.display_name, p.post_title, cm.meta_value
FROM wp_users u
JOIN wp_bp_groups_members gm
ON u.ID = gm.user_id
JOIN wp_comments c
ON u.ID = c.user_id
JOIN wp_commentmeta cm
ON c.comment_ID = cm.comment_id
JOIN (SELECT * FROM wp_posts WHERE post_type LIKE 'lesson') p
ON c.comment_post_id = p.ID
WHERE gm.group_id = 4 AND cm.meta_key LIKE 'grade'
This currently returns all the grades for all students in a group in the lessons they have attempted the test. However it does not return any lessons they have not attempted the test in, which I need still.
Just to be clear: lessons are posts, grades are meta_values in a record with a meta_key of 'grades'. These are stored as comments, and comment_meta.
I hope this is all clear and you can help. Thanks.
After Ollie Jones help I made this:
SELECT u.display_name, p.post_title, IFNULL(cm.meta_value,'--nothing--') grade
FROM wp_users u
JOIN wp_comments c
LEFT JOIN wp_commentmeta cm
ON c.comment_ID = cm.comment_id AND cm.meta_key = 'grade'
JOIN wp_bp_groups_members gm
ON u.ID = gm.user_id
JOIN wp_posts p
ON c.comment_post_id = p.ID
WHERE gm.group_id = 4 AND p.post_type LIKE 'lesson'
Which almost works but returns all student grades, not just the ones in the group (though it only gives the one name of the student in the group).
This is the familiar key/value store problem, that comes up with commentmeta, postsmeta, and usermeta. When you JOIN two tables and the left one might not have a corresponding row, you need to use LEFT JOIN. When the left one is a key/value table, you need to adjust the ON condition accordingly. Finally, when you LEFT JOIN two tables and there's no matching row in it it, you get back NULLs for those columns, so you must allow for that.
So this kind of SQL pattern will do the trick for you
SELECT whatever, IFNULL(cm.meta_value,'--nothing--') grade
FROM whatever
LEFT JOIN wp_comments c ON whatever.id = c.user_id
LEFT JOIN wp_commentmeta cm ON c.comment_id = cm.comment_id
AND cm.meta_key = 'grade'
JOIN whatever
Is there a user_id in your comments table? I dont see where you joined which user posted which comment. So when you join the group_members to the users table, you are specifying which users to show, but since you are not joining on user id when you join to your comments table, it will show all the comments for all the users. Im not sure if this will work for your table, but try:
SELECT u.display_name, p.post_title, IFNULL(cm.meta_value,'--nothing--') grade
FROM wp_users u
LEFT JOIN wp_comments c
JOIN wp_commentmeta cm
ON c.comment_ID = cm.comment_id AND cm.meta_key = 'grade'
JOIN wp_bp_groups_members gm
ON c.user_ID = gm.user_id
ON u.user_id = c.user_id
JOIN wp_posts p
ON c.comment_post_id = p.ID
WHERE gm.group_id = 4 AND p.post_type LIKE 'lesson'
Hope this helps!

sql query is behaving strange

I have a query
select c.CommentId
,c.CommentText
, c.CommenterId
, c.CommentDate
, u.first_name
, u.last_name
, i.ImageName
, i.Format
from comment c
join users u
on c.CommenterId = u.user_id
join user_profile_image i
on u.user_id = i.UserId
where PostId = 76
order
by CommentDate desc
limit 10
This query returns empty results when i.ImageName field is empty in the table. I want to return the row if the ImageName field is emty. How should I do this?
JOIN defaults to INNER JOIN for MySQL - try changing
join user_profile_image i
to
LEFT join user_profile_image i
The accepted answer here has a good visual explanation: Difference in MySQL JOIN vs LEFT JOIN
To include the rows when the ImageName field is empty, use LEFT JOIN, like this:
SELECT c.CommentId,c.CommentText, c.CommenterId, c.CommentDate, u.first_name,
u.last_name,i.ImageName,i.Format
FROM comment c
INNER JOIN users u ON c.CommenterId=u.user_id
LEFT JOIN user_profile_image i ON u.user_id=i.UserId
WHERE PostId = 76
ORDER BY CommentDate DESC
LIMIT 10;
The issue isn't exactly that i.ImageName is empty. The issue is that there is no image associated with the user. The join doesn't find an image, and without a match, the user isn't returned.
The solution is to use left join. My inclination is to write the query entirely with left join:
select c.CommentId, c.CommentText, c.CommenterId, c.CommentDate,
u.first_name, u.last_name,
i.ImageName, i.Format
from comment c left join
users u
on c.CommenterId = u.user_id left join
user_profile_image i
on u.user_id = i.UserId
where PostId = 76
order by c.CommentDate desc
limit 10;
Note: This assumes that PostId is in the comment table, which seems reasonable given the table names.

Mysql performance/optimization help

So this was a small site that got extremely popular very fast and now and im having major problems with the below sql query.
I understand that my DB design is not great. I have text field for subjects and programs witch contains a serialized array and i search it using like.
the below query takes about a minute.
SELECT p.*, e.institution
FROM cv_personal p
LEFT JOIN cv_education e
ON p.id = e.user_id
LEFT JOIN cv_literacy l
ON p.id = l.user_id
WHERE 1 = 1
AND (e.qualification LIKE '%php%' OR e.subjects LIKE '%php%' OR l.programs LIKE '%php%')
GROUP BY p.id
ORDER BY p.created_on DESC
What an EXPLAIN show ?
I think you can add conditions to a join to reduce number of records which are used :
SELECT p.*, e.institution
FROM cv_personal p
LEFT JOIN cv_education e
ON (e.qualification LIKE '%php%' OR e.subjects LIKE '%php%') AND p.id = e.user_id
LEFT JOIN cv_literacy l
ON l.programs LIKE '%php%' AND p.id = l.user_id
ORDER BY p.created_on DESC
And why do you use GROUP BY ?

MySQL query problem

How can I add the following code example 1 to example 2 without messing up my query.
Example 1
INNER JOIN users ON users_articles.user_id = users.user_id
Example 2.
SELECT users.*
FROM users_articles
INNER JOIN articles_comments ON users_articles.id = articles_comments.article_id
INNER JOIN users ON articles_comments.user_id = users.user_id
WHERE users.active IS NULL
AND users.deletion = 0
ORDER BY articles_comments.date_created DESC
LIMIT 50
If I understand you correctly, you want to join table users twice, once for comments, and once for articles? In that case, you need to alias the table. I usually use single- or two-letter aliases for brevity even when I do not double tables, but it is not important.
SELECT ...
FROM users_articles UA
INNER JOIN articles_comments AC ON UA.id = AC.article_id
INNER JOIN users UC ON AC.user_id = UC.user_id
AND UC.active IS NULL
AND UC.deletion = 0
INNER JOIN users UA ON UA.user_id = users.user_id
AND UA.active IS NULL
AND UA.deletion = 0
ORDER BY AC.date_created DESC
LIMIT 50
BTW, Don't use SELECT *, it is almost always better to list specifically what you want.
Disclaimer: I might have misunderstood what you are trying to do; posting a bit of context to your code is usually a good idea. In this case, the table names threw me a bit (if it's what I think it is, I'd've just gone with users, articles and comments).