SQL INNER JOIN and COUNT - mysql

I have 3 tables (user, item, userlike) and 2 sql queries. How can I unify these two queries?
SELECT item.userid, item.id, user.name FROM item
INNER JOIN user ON item.userid = user.id
SELECT userid,itemid, COUNT(*) AS `liked` FROM userlike
WHERE userid=9
GROUP BY itemid
I want to know whether a specific user (9) has liked the item or not.
Result should be somthing like this
itemid userid name liked* (*whether 'user 9' liked this item or not)
1 7 foo 0
2 4 asd 1
Thanks

You want to use an OUTER JOIN for this
SELECT i.id itemid, u.id userid, u.name, COALESCE(liked, 0) liked
FROM item i JOIN user u
ON i.userid = u.id LEFT JOIN
(
SELECT itemid, COUNT(*) liked
FROM userlike
WHERE userid = 9
GROUP BY itemid
) l
ON i.id = l.itemid;
or
SELECT i.id itemid, u.id userid, u.name, l.userid IS NOT NULL liked
FROM item i JOIN user u
ON i.userid = u.id LEFT JOIN userlike l
ON i.id = l.itemid
AND l.userid = 9;
Sample output:
| ITEMID | USERID | NAME | LIKED |
|--------|--------|-------|-------|
| 2 | 4 | user4 | 1 |
| 1 | 7 | user7 | 0 |
Here is SQLFiddle demo

SELECT item.id, item.userid, user.name, userlike.liked
FROM item
JOIN user ON user.id = item.userid
JOIN userlike ON item.id = userlike.itemid
WHERE userlike.liked = 1
GROUP BY item.id
OR
SELECT item.id, item.userid, user.name, userlike.liked
FROM item
JOIN user ON user.id = item.userid
JOIN userlike ON item.id = userlike.itemid
WHERE COUNT(userlike.liked) >= 1
GROUP BY item.id

You don't need to use COUNT. You just need to know if there is an entry for a specific item and a specific user in the userlike table :
SELECT i.id as itemid, u.id as userid, u.name,
case when ul.userid is null then 0 else 1 end as liked
FROM item i
INNER JOIN user u ON i.userid = u.id
LEFT OUTER JOIN userlike ul ON i.id = ul.itemid AND ul.userid=9
ORDER BY i.id

Related

Split table row into two fields and count each

So far I have wrote the following query:
SELECT forum_topics.*, users.id as userid, users.username, users.avatar, forum_categories.name as cat_name
FROM forum_topics
INNER JOIN users
ON users.id = forum_topics.author_id
INNER JOIN forum_categories
ON forum_categories.id = forum_topics.category_id
WHERE forum_topics.id = 64
But I also want to add another table votes that has the following structure:
___________________________________________________________
| id | object_type | object_id | receiver | giver | type |
___________________________________________________________
| 128| topic | 64 | 21 | 22 | like |
| 129| topic_reply | 55 | 21 | 22 | dislike |
___________________________________________________________
Basically the relation between the two tables is forum_topics.id from Table 1 and object_id from Table 2 (the bottom one). This is for a forum and I want to display likes/dislikes for each topic and reply. type could be like and dislike. receiver is the user that made the post, giver is the user that voted. I want to INNER JOIN the votes table in the first query and count all likes and dislikes into two separate fields. Something like:
Select votes.count(*) as likes WHERE type = 'like and votes.count(*) as dislikes WHERE type = 'dislike'
The query got so complicated and I am so confused.
Edit: So I figured it out for forum_topics. Here is how I did it:
SELECT forum_topics.*, users.id as userid, users.username, users.avatar, forum_categories.name as cat_name,
count(CASE WHEN votes.type = 'like' AND votes.object_type = 'topic' then 1 else null end) as votes_likes,
count(CASE WHEN votes.type = 'dislike' AND votes.object_type = 'topic' then 1 else null end) as votes_dislikes
FROM forum_topics
INNER JOIN users
ON users.id = forum_topics.author_id
INNER JOIN forum_categories
ON forum_categories.id = forum_topics.category_id
INNER JOIN votes
ON votes.object_id = forum_topics.id
WHERE forum_topics.id = ?
Now for forum_posts It is not working..
SELECT forum_posts.*, users.id as userid, users.username, users.avatar,
count(CASE WHEN votes.type = 'like' AND votes.object_type = 'topic_post' then 1 else null end) as votes_likes,
count(CASE WHEN votes.type = 'dislike' AND votes.object_type = 'topic_post' then 1 else null end) as votes_dislikes
FROM forum_posts
INNER JOIN users
ON users.id = forum_posts.author_id
LEFT JOIN votes
ON votes.object_id = forum_posts.id
WHERE forum_posts.topic_id = 64
ORDER BY forum_posts.id
Any ideas how to fix it? In HeidiSQL it returns one row with everything NULL.
You need a GROUP BY
SELECT forum_posts.id
, forum_posts.author_id
, forum_posts.editor_id
, forum_posts.topic_id
, forum_posts.content
, forum_posts.date_created
, forum_posts.updated
, users.id as userid
, users.username
, users.avatar
, count(CASE WHEN votes.type = 'like' AND votes.object_type = 'topic_post' THEN 1 ELSE NULL END) AS votes_likes
, count(CASE WHEN votes.type = 'dislike' AND votes.object_type = 'topic_post' THEN 1 ELSE NULL END) AS votes_dislikes
FROM forum_posts
INNER JOIN users ON users.id = forum_posts.author_id
LEFT JOIN votes ON votes.object_id = forum_posts.id
WHERE forum_posts.topic_id = 64
GROUP BY forum_posts.id
, forum_posts.author_id
, forum_posts.editor_id
, forum_posts.topic_id
, forum_posts.content
, forum_posts.date_created
, forum_posts.updated
, users.id
, users.username
, users.avatar
try using group by;
SELECT type, COUNT(*)
FROM votes
GROUP BY type;

How to Select max date in this query?

I would like to write a query which retrieves name, id, and last modified date for each User. The below query gives the name, id, and last modified date from tables UserDetails1 and UserDetails2.
How could I modify this query to return a single date value, the max date for a given user_id in either of the details tables?
SELECT
id,
name,
MAX(userdetails1.date_modified),
MAX(userdetails2.date_modified)
FROM User user
INNER JOIN UserDetails1 userdetails1
ON userdetails1.user_id = user.id
INNER JOIN UserDetails2 userdetails2
ON userdetails2.user_id = user.id
User
id | name
---------
1 | name1
2 | name2
3 | name3
UserDetails1
user_id | date_modified
---------------------
1 | 2016-11-28 16:28:26
....
UserDetails2
user_id | date_modified
---------------------
1 | 2016-11-29 16:29:26
....
Try this, although I think there can be a more optimized way to write it.
SELECT
id,
name,
(CASE
WHEN MAX(userdetails1.date_modified) > MAX(userdetails2.date_modified)
THEN MAX(userdetails1.date_modified)
ELSE MAX(userdetails2.date_modified)
END)
FROM User user
INNER JOIN UserDetails1 userdetails1
ON userdetails1.user_id = user.id
INNER JOIN UserDetails2 userdetails2
ON userdetails2.user_id = user.id
GROUP BY id, name
One option is to UNION your two date tables together. This can be done before or after you JOIN. I personally would UNION before JOINING as it is simpler in my mind and to write.
Please excuse the SQL Server-esque syntax.
Before JOINing:
SELECT
u.id,
u.name,
MAX(d.date_modified) last_modified
FROM [User] u
INNER JOIN
(
SELECT user_id, date_modified
FROM UserDetails1
UNION ALL
SELECT user_id, date_modified
FROM UserDetails2
) d
ON u.id = d.user_id
GROUP BY u.id, u.name
After JOINing:
SELECT
id,
name,
max(date_modified) last_modified
FROM
(
SELECT
u.id, u.name, d.date_modified
FROM [User] u
INNER JOIN UserDetails1 d
ON d.user_id = user.id
UNION ALL
SELECT
u.id, u.name, d.date_modified
FROM [User] u
INNER JOIN UserDetails2 d
ON d.user_id = u.id
)
GROUP BY id, name

How to exclude rows when using a LEFT JOIN (MySQL)

I have users with many posts. I want to build an SQL query that would do the following in 1 query (no subquery), and hopefully no unions if possible. I know I can do this with union but I want to learn if this can be done using only joins.
I want to get a list of distinct active users who:
have no posts
have no approved posts
Here's what I have so far:
SELECT DISTINCT u.*
FROM users u
LEFT JOIN posts p
ON p.user_id = u.id
LEFT JOIN posts p2
ON p2.user_id = u.id
WHERE u.status = 'active'
AND (p.status IS NULL
OR p2.status != 'approved');
The problem is when a user has multiple posts and one is active. This will still return the user which I do not want. If a user has an active post, he should be removed from the result set. Any ideas?
Here's what the data looks like:
mysql> select * from users;
+----+---------+
| id | status |
+----+---------+
| 1 | active |
| 2 | pending |
| 3 | pending |
| 4 | active |
| 5 | active |
+----+---------+
5 rows in set (0.00 sec)
mysql> select * from posts;
+----+---------+----------+
| id | user_id | status |
+----+---------+----------+
| 1 | 1 | approved |
| 2 | 1 | pending |
| 3 | 4 | pending |
+----+---------+----------+
3 rows in set (0.00 sec)
The answer here should be only users 4 and 5. 4 doesn't have an approved post and 5 doesn't have a post. It should not include 1, which has an approved post.
Not exists:
SELECT u.*
FROM users u
WHERE NOT EXISTS (
SELECT 1
FROM posts p
WHERE p.user_id = u.id AND p.status = 'approved');
Or equivalent LEFT JOIN
SELECT u.*
FROM users u
LEFT JOIN posts p
ON p.user_id = u.id AND p.status = 'approved'
WHERE p.user_id IS NULL;
Taking your requirements and translating them literally to SQL, I get this:
SELECT users.id,
COUNT(posts.id) as posts_count,
COUNT(approved_posts.id) as approved_posts_count
FROM users
LEFT JOIN posts ON posts.user_id = users.id
LEFT JOIN posts approved_posts
ON approved_posts.status = 'approved'
AND approved_posts.user_id = users.id
WHERE users.status = "active"
GROUP BY users.id
HAVING (posts_count = 0 OR approved_posts_count = 0);
For your test data above, this returns:
4|1|0
5|0|0
i.e. users with ids 4 and 5, the first of which has 1 post but no approved posts and the second of which has no posts.
However, it seems to me that this can be simplified since any user that has no approved posts will also have no posts, so the union of conditions is unnecessary.
In that case, the SQL is simply:
SELECT users.id,
COUNT(approved_posts.id) as approved_posts_count
FROM users
LEFT JOIN posts approved_posts
ON approved_posts.status = 'approved'
AND approved_posts.user_id = users.id
WHERE users.status = "active"
GROUP BY users.id
HAVING approved_posts_count = 0;
This also returns the same two users. Am I missing something?
Please explain why you don't want JOINs or UNIONs. If it is because of performance, then consider the following:
CREATE TABLE t ( PRIMARY KEY(user_id) )
SELECT user_id, MIN(status) AS z
FROM Posts
GROUP BY user_id;
SELECT u.id AS user,
IFNULL(z, 'no_posts') AS status
FROM users u
WHERE u.status = 'active'
LEFT JOIN t ON t.user_id = u.id
HAVING status != 'approved';
It will make only one pass over each table, thereby being reasonably efficient (considering the complexity of the query).
This one may help:
SELECT DISTINCT u.*
FROM users u
LEFT JOIN posts p ON 1=1
-- matches only if user has any post
AND p.user_id = u.id
-- matches only if user has any active post
AND p.status = 'approved'
WHERE 1=1
-- matches only active users
AND u.status = 'active'
-- matches only users with no matches on the LEFT JOIN
AND p.status IS NULL
;
I think this should be easy.
SELECT u.`id`, u.`status` FROM `users` u
LEFT OUTER JOIN `post` p ON p.`user_id` = u.`id` AND p.`status` = 'approved'
WHERE u.`status` = 'active' AND p.`id` IS NULL
Gives a result of 4 & 5.
[Edit] Just wanted to add why this works:
u.status = 'active'
This results into exclusion of all users that are not active.
p.status = 'approved'
This excludes all posts that are approved.
Hence, by using these two lines, we have excluded all users that qualify as approved for your criteria.
[Edit 2]
If you also need to know how many pending and how many approved, here is an updated version:
SELECT u.`id`, u.`status`, SUM(IF(p.`status` = 'approved', 1, 0)) AS `Approved_Posts`, SUM(IF(p.`status` = 'pending', 1, 0)) AS `Pending_Posts`
FROM `test_users` u
LEFT OUTER JOIN `test_post` p ON p.`user_id` = u.`id`
WHERE u.`status` = 'active'
GROUP BY u.`id`
HAVING SUM(IF(p.`id` IS NOT NULL, 1, 0))
Try this
SELECT DISTINCT u.*
FROM users u LEFT JOIN posts p
ON p.user_id = u.id
WHERE p.status IS NULL
OR p.status != 'approved';
Can you try with the below query:
SELECT DISTINCT u.*
FROM users u
LEFT JOIN posts p
ON p.user_id = u.id
WHERE
u.status = 'active' AND (
p.user_id IS NULL
OR p.status != 'approved');
EDIT
As per the updated question, the above query will include User 1. If we want to prevent that, and don't want to use inner query, we can use group_concat function of MySQL to get all the (distinct) statuses and see if it contains 'active' status, below query should give the desired output:
SELECT u.id, group_concat(distinct p.status) as statuses
FROM users u
LEFT JOIN posts p
ON u.id = p.user_id
WHERE
u.status = 'active'
group by u.id
having (statuses is null or statuses not like '%approved%');

select and count rows

I have 2 tables and I need to select and count rows in one query, maybe somebody can help me with that, my query is:
SELECT
c.id, c.first_name, c.last_name, c.speciality, c.level, c.email, c.skype, c.city,
s.status_type, c.status_id, c.linkedin, c.link_cv, c.interview_res, c.createdAt,
c.updatedAt, c.recruiter_id, u.first_name AS fname, u.last_name AS lname
FROM
Candidates c
JOIN Users u
ON c.recruiter_id = u.id
JOIN Statuses s
ON s.id = c.status_id
WHERE
c.deleted = false
and I need to get count of the rows that respond my select and count them.
example output (what I want):
count | fname | lname | ..... |
---------------------------------
3 | Ihor | Shmidt | ... |
3 | Andre | Agassi | .... |
3 | Roger | Federer| ..... |
i.e. I want to have my fields that I select and their count
Before the "from" portion of the query, please add count (*). It will give you the count of the rows.
Select ......, count (*) from Candidates c
JOIN Users u ON c.recruiter_id = u.id
JOIN Statuses s on s.id = c.status_id
WHERE c.deleted = false;
COUNT(*) as count or COUNT(c.id) as count
SELECT COUNT(*) as count,c.id, c.first_name, c.last_name, c.speciality, c.level, c.email,
c.skype, c.city, s.status_type, c.status_id, c.linkedin, c.link_cv, c.interview_res,
c.createdAt, c.updatedAt, c.recruiter_id, u.first_name AS fname, u.last_name AS lname
FROM Candidates c
JOIN Users u
ON c.recruiter_id = u.id
JOIN Statuses s
ON s.id = c.status_id
WHERE c.deleted = false;
SELECT c.id, ( SELECT COUNT(*) FROM Candidates c
JOIN Users u ON c.recruiter_id = u.id
JOIN Statuses s ON s.id = c.status_id
WHERE c.deleted = false ) AS count FROM Candidates
c JOIN Users u ON c.recruiter_id = u.id
JOIN Statuses s ON s.id = c.status_id
WHERE c.deleted = false ;

LEFT JOIN in MySQL across multiple tables with NULL values

I have the following table structure with data
TABLE: USER
USER ID | USER NAME
1 | Joe
2 | Mary
TABLE : USER GROUP
USER ID | GROUP ID
1 | 1
1 | 2
TABLE : GROUP
GROUP ID | GROUP NAME
1 | Company 1
2 | Company 2
TABLE : ROLE
ROLE ID | ROLE NAME
1 | Administrator
2 | Users
TABLE : USER ROLE
USER ID | ROLE ID
1 | 1
2 | 1
As you can see user #2 does not belong to any group. Roles & Groups are optional forcing me to left joint but when I run a query as below
`SELECT a.user_id,
a.user_name
GROUP_CONCAT(r.role_name) AS role_names,
GROUP_CONCAT(g.group_name) AS group_names
FROM user a
LEFT JOIN role_map m ON a.user_id = m.user_id
INNER JOIN role r ON m.role_id = r.role_id
LEFT JOIN user_group s ON a.user_id = s.user_id
INNER JOIN group g ON s.group_id = g.group_id
GROUP BY a.user_id`
I get a cartesian product in the role_names column - the result looks like this
Joe | Administrators, Administrators | Company 1, Company 2
What am I doing wrong?
The easiest way to solve this is by using DISTINCT in your GROUP_CONCAT (SQL Fiddle). Also, you will need to add GROUP BY a.user_id in order to group per user:
SELECT a.user_id,
a.user_name,
GROUP_CONCAT(DISTINCT r.role_name) AS role_names,
GROUP_CONCAT(DISTINCT g.group_name) AS group_names
FROM `user` a
LEFT JOIN `user_role` m ON a.user_id = m.user_id
LEFT JOIN `role` r ON m.role_id = r.role_id
LEFT JOIN `user_group` s ON a.user_id = s.user_id
LEFT JOIN `group` g ON s.group_id = g.group_id
GROUP BY a.user_id;