I have the following 3 tables:
users: [id, name, admin ...]
events: [id, user_id, type ...]
messages: [id, user_id, ...]
I want to construct a query that does the following:
-> Select all users from the table users who have not scheduled an event of the type "collection"
-> And who have less than 3 messages of the type "collection_reminder"
-> And who are not admin
I've managed to figure out the first part of this query, but it all goes a bit pear shaped when I try to add the 3 table, do the count, etc.
Here is a query that might get the job done. Each of the requirement is represented as a condition in the WHERE clause, using correlated subqueries when needed:
SELECT u.*
FROM users u
WHERE
NOT EXISTS (
SELECT 1
FROM events e
WHERE e.user_id = u.id AND e.type = 'collection'
)
AND (
SELECT COUNT(*)
FROM messages m
WHERE m.userid = u.id AND m.type = 'collection_reminder'
) <= 3
AND u.admin IS NULL
Ill try this on the top of the head so expect some synthax issues, but the idea is the following.
You can filter out who have no events schedule using a left join. On a left join the elements on the second part of the query will show up as null.
select * from users u
left join events e on e.user_id = u.id
where e.user_id is null
Now, i dont think this is the most performant way, but a simple way to search for everyone that has 3 or less messages:
select * from users u
left join events e on e.user_id = u.id
where u.id in (
select COUNT(*) from messages m where m.user_id = u.id HAVING COUNT(*)>3;
)
and e.user_id is null
Then filtering who is not admin is the easiest :D
select * from users u
left join events e on e.user_id = u.id
where u.id in (
select COUNT(*) from messages m where m.user_id = u.id HAVING COUNT(*)>3;
)
and e.user_id is null
and u.admin = false
Hope it helps.
This is pretty much a direct translation of your requirements, in the order you listed them:
SELECT u.*
FROM users AS u
WHERE u.user_id NOT IN (SELECT user_id FROM events WHERE event_type = 'Collection')
AND u.user_id IN (
SELECT user_id
FROM messages
WHERE msg_type = 'Collection Reminder'
GROUP BY user_id
HAVING COUNT(*) < 3
)
AND u.admin = 0
or alternatively, this can be accomplished completely with joins:
SELECT u.*
FROM users AS u
LEFT JOIN events AS e ON u.user_id = e.user_id AND e.event_type = 'Collection'
LEFT JOIN messages AS m ON u.user_id = m.user_id AND m.msg_type = 'Collection Reminder'
WHERE u.admin = 0
AND e.event_id IS NULL -- No event of type collection
GROUP BY u.user_id -- Note: you should group on all selected fields, and
-- some configuration of MySQL will require you do so.
HAVING COUNT(DISTINCT m.message_id) < 3 -- Less than 3 collection reminder messages
-- distinct is optional, but
-- if you were to remove the "no event" condition,
-- multiple events could multiply the message count.
;
This query uses joins to link the 3 tables, filters the result using the where clause, and using Group by, having limiting the result to only those who satisfy the less than count condition..
SELECT a.id,
SUM(CASE WHEN b.type = 'collection' THEN 1 ELSE 0 END),
SUM(CASE WHEN c.type = 'collection_reminder' THEN 1 ELSE 0 END
FROM users a
left join events b on (b.user_id = a.id)
left join messages c on (c.user_id = a.id)
WHERE a.admin = false
GROUP BY a.id
HAVING SUM(CASE WHEN b.type = 'collection' THEN 1 ELSE 0 END) = 0
AND SUM(CASE WHEN c.type = 'collection_reminder' THEN 1 ELSE 0 END) < 3
Related
I want to display all users data, who User 'A' is following. And then further check if User 'B' is also following some users of User 'A'.
I managed to get al users data, who User 'A' is following. But don't understand how to query for the second condition.
Here is my Fiddle link with an example: https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=29a7d1e29f794a8f18a89fe45c06eaa9
You can try to let your User 'B' in a subquery then do OUTER JOIN
SELECT u.*,
IF(friend_id IS NULL,0,1) amIfollowing
FROM users u
LEFT JOIN (
Select friend_id
from friends
where user_id = 5
) f ON f.friend_id = u.id
WHERE u.id IN (SELECT f.friend_id
FROM friends f
WHERE f.user_id = 1)
ORDER BY u.id
sqlfiddle
If I understand correctly you can try to use only one subquery for friends and then use the condition aggregate function to get the result.
SELECT u.id,
u.image_width,
MAX(CASE WHEN f.user_id = 5 THEN 1 ELSE 0 END) amIfollowing
FROM users u
JOIN (
Select friend_id,user_id
from friends
where user_id IN (1,5)
) f ON f.friend_id = u.id
GROUP BY u.id,
u.image_width
ORDER BY u.id
You could use exists here to check if the corresponding IDs exist:
SELECT *,
case when exists (
select * from friends f
where f.friend_id = u.id and f.user_id = 5
) then 1 else 0 end amIfollowing
FROM users u
WHERE u.id IN (SELECT f.friend_id
FROM friends f
WHERE f.user_id = 1);
Example Fiddle
Looks like a JOIN will do, with distinct
SELECT distinct u.*, (f2.user_Id is not null) amIfollowing
FROM users u
JOIN friends f ON u.id = f.friend_id
LEFT JOIN friends f2 on f2.friend_id = f.friend_id and f2.user_id = 5
WHERE f.user_id = 1
ORDER BY u.id
I have the following query, which works exactly as intended (thanks to the helpful people at stackoverflow). However, I realized that in addition to using the count to validate whether messages is <= 3 I also want to retrieve the actual number/count for each row that is returned in the results.
This is so that I can customize the logic depending on how many messages each of the returned users has.
I have tried a few different options but it's not quite working.
SELECT u.*
FROM users u
WHERE
NOT EXISTS (
SELECT 1
FROM events e
WHERE e.user_id = u.id AND e.type = 'collection'
)
AND (
SELECT COUNT(*)
FROM messages m
WHERE m.user_id = u.id AND m.message_type = 'collection_reminder'
) <= 3
AND u.admin IS NULL
Move that subquery to the select list. Put the condition in a having clause at the end (MySQL special trick).
SELECT u.*,
(
SELECT COUNT(*)
FROM messages m
WHERE m.user_id = u.id AND m.message_type = 'collection_reminder'
) as cnt
FROM users u
WHERE
NOT EXISTS (
SELECT 1
FROM events e
WHERE e.user_id = u.id AND e.type = 'collection'
)
AND u.admin IS NULL
HAVING cnt <= 3
A slight modification of my answer from the original question:
SELECT u.*, COUNT(DISTINCT m.message_id)
FROM users AS u
LEFT JOIN events AS e ON u.user_id = e.user_id AND e.event_type = 'Collection'
LEFT JOIN messages AS m ON u.user_id = m.user_id AND m.msg_type = 'Collection Reminder'
WHERE u.admin = 0
AND e.event_id IS NULL -- No event of type collection
GROUP BY u.user_id -- Note: you should group on all selected fields, and
-- some configuration of MySQL will require you do so.
HAVING COUNT(DISTINCT m.message_id) < 3 -- Less than 3 collection reminder messages
-- distinct is optional, but
-- if you were to remove the "no event" condition,
-- multiple events could multiply the message count.
;
Below is a query which takes 30+ seconds to run. Based on similar queries I have running, I can't see where the hold up is here. My only thought is joining the job user id to job_applicants user id, but they need to be mapped.
SELECT DISTINCT u.user_id, u.first_name, u.last_name FROM users u
LEFT OUTER JOIN employee_access ea ON ea.user_id = u.user_id
LEFT OUTER JOIN confirmation c ON c.user_id = u.user_id
LEFT OUTER JOIN job_applicants a ON a.user_id = u.user_id
LEFT OUTER JOIN job j ON j.job_id = a.job_id
WHERE ea.access_id = 4 OR c.access_id = 4 OR (a.process_level = 0 AND j.access_id = 4)
ORDER BY u.last_name asc
Use exists:
select u.*
from users u
where exists (select 1
from employee_access ea
where ea.user_id = u.user_id and ea.access_id = 4
) or
exists (select 1
from confirmation c
where c.user_id = u.user_id and c.access_id = 4
) or
exists (select 1
from job_applicants a join
job j
on j.job_id = a.job_id
where a.user_id = u.user_id and
a.process_level = 0 AND j.access_id = 4
)
order by u.last_name;
This will prevent all the Cartesian products and the final removal of duplicates.
I would recommend indexes on:
users(last_name, user_id)
employee_access(user_id, access_id)
confirmation(user_id, access_id)
job_applicants(user_id, process_level, job_id)
job(job_id, access_id)
Yet another approach. This has the advantage of first gathering the list of user_ids, then reaching into users for the other columns:
SELECT u.user_id, u.first_name, u.last_name
FROM users u
JOIN (
( SELECT user_id FROM employee_access WHERE access_id = 4 )
UNION DISTINCT
( SELECT user_id FROM confirmation WHERE access_id = 4 )
UNION DISTINCT
( SELECT a.user_id
FROM job_applicants a
JOIN job j USING(job_id)
WHERE a.process_level = 0
AND j.access_id = 4 )
) AS x USING(user_id)
ORDER BY u.last_name ASC
Indexes:
employee_access: INDEX(access_id, user_id) -- (covering)
confirmation: INDEX(access_id, user_id) -- (covering)
job: INDEX(access_id, job_id) -- (covering)
job_applicants: INDEX(process_level, job_id, user_id) -- (covering)
users: PRIMARY KEY(user_id)
See if this will shave off most of the remaining 8 seconds.
This should work. It is similar in concept to Gordon's answer but I have a borderline pathological distrust of correlated subqueries.
SELECT DISTINCT u.user_id, u.first_name, u.last_name
FROM users u
WHERE u.user_id IN (SELECT user_id FROM employee_access WHERE access_id = 4)
OR u.user_id IN (SELECT user_id FROM confirmation WHERE access_id = 4)
OR u.user_id IN (
SELECT a.user_id
FROM job_applicants a
INNER JOIN job j ON j.job_id = a.job_id
WHERE a.process_level = 0 AND j.access_id = 4
)
ORDER BY u.last_name asc
I am trying to write an SQL SELECT statement with a sub query. There is no error returned but I don't get the results I am expecting. The value for r.related is always NULL.
SELECT
l.id,
u.id as user_id,
u.name,
r.related
FROM
list l
INNER JOIN user u ON u.id = l.user_id
LEFT JOIN (
SELECT COUNT(u.id) AS related, b.group_id
FROM user u
INNER JOIN booking b ON b.user_id = u.id
WHERE u.id != l.user_id
AND b. = 0) AS r ON r.group_id = l.group_id
WHERE
l.group_id = 22
GROUP BY l.id, u.id
ORDER BY l.id
I am writing the sub query correctly?
Here's the problem:
SELECT COUNT(u.id) AS related, b.group_id
FROM user u
INNER JOIN booking b ON b.user_id = u.id
WHERE u.id != b.user_id
AND b. = 0
Look, you are joining user and booking table on booking.user_id = user.id
and
then you are just discarding those matching rows between these two tables in your where condition WHERE user.id != booking.user_id;
It's more like you are looking the differences between Set A and Set B in A intersection B. So in this case you won't find any (i.e. NULL).
I am having problem with fetching data from multiple tables with some conditions in MySQL.
I have follwing three tables:
Like Table
Like_id photoID userID
1 1 1
2 2 2
3 2 1
BookMark Table
bookmark_id photoID userID
1 1 1
2 2 2
3 2 1
Users Table
User_id Name Email
1 Max B maxb#gmailcom
2 Tom Smith toms#gmailcom
CONDITIONS:
At first i want to check whether there is any data from the LIKE table for the userID = 2. If there is no data it should return "false" otherwise it should return "true".
Similarly, i want to check whether there is any data from the BOOKMARK table for the userID = 2. If there is no data it should return "false" otherwise it should return "true".
Finally, i want to fetch the Name and Email from the USERS table for the userID = 2.
WANTED:
I want to achieve all these information in a SINGLE QUERY with the above mentioned conditions from these three tables.
SO FAR tried with this QUERY:
select Like.Like_id from (Like left join Users on Like.userID = Users.User_id)
left join BookMark on Users.User_id = BookMark.bookmark_id
where Users.User_id = 2
With #Gervs suggestion:
SELECT
u.user_id,
u.name,
u.email,
(CASE WHEN ISNULL(l.user_id) THEN 'false' ELSE 'true' END) AS 'likes',
(CASE WHEN ISNULL(b.user_id) THEN 'false' ELSE 'true' END) AS 'bookmarks'
FROM
users u
LEFT JOIN
likes l
ON u.user_id = l.user_id
LEFT JOIN
bookmarks
ON u.user_id = b.user_id
WHERE u.user_id = 2
GROUP BY u.user_id
What will be the easiest but efficient single query to fetch these information?
Will VIEW be a best option for these conditions?
Advanced thanks for your participation.
You can inner join both like table and bookmark table on users table, that is if you want only users that have entries in both tables.
SELECT
u.user_id,
u.name,
u.email,
COUNT(l.user_id) likes,
COUNT(b.user_id) bookmarks
FROM
users u
JOIN
likes l
ON u.user_id = l.user_id
JOIN
bookmarks b
ON u.user_id = b.user_id
WHERE u.user_id = 2
GROUP BY u.user_id
If you always want the user, just change the inner joins into left joins and likes and/or bookmarks will be zero if no entries are found
SELECT
u.user_id,
u.name,
u.email,
CASE WHEN COUNT(l.user_id) > 0 THEN 'true' ELSE 'false' END likes,
CASE WHEN COUNT(b.user_id) > 0 THEN 'true' ELSE 'false' END bookmarks
FROM
users u
LEFT JOIN
likes l
ON u.user_id = l.user_id
LEFT JOIN
bookmarks b
ON u.user_id = b.user_id
WHERE u.user_id = 2
GROUP BY u.user_id