Split table row into two fields and count each - mysql

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;

Related

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%');

using a limit on one table when joining two tables in mysql

How do I correctly write an sql statement using a limit and join in this case?
SELECT u.userName, u.userFriends, u.userJoined, f.freindName (only 3)
FROM user u inner join friends f ON u.userId = f.addedUserId
WHERE u.userId=1
AND f.userId=1
I tried
SELECT u.userName, u.userFriends, u.userJoined, group_concat(f.freindName)
FROM user u inner join friends f ON u.userId = f.userId
WHERE u.userId = 1 and f.addedUserId = 1
GROUP by u.userName limit 5 /* but this does not work */
Can you help with the limiting the f.freindName to 3
'gid' | 'userName' | 'userId' | 'userFriends' |'userJoined'
-------|-------------|----------|-------------------|--------------
'1' | 'Jason' | '1' | '5' |'14-Aug-2014'
'gid' | 'friendName'| 'thisUserId' |'addedUserId'
-------|-------------|------------------|-----------------
'1' | 'James' | '2' |'1'
'2' | 'Lars' | '3' |'1'
'3' | 'Kirk' | '4' |'1'
'4' | 'Rob' | '5' |'1'
'5' | 'Dave' | '5' |'1'
I'm assuming that the tables users and friends are joined by the columns userId and addedUserId. And I assume further that you want a list of three friends of the user with the userId = 1.
You can use a derived table by using a subselect to get the desired result:
SELECT u.userName, u.userFriends, u.userJoined, group_concat(f.friendName)
FROM user u
INNER JOIN (
SELECT
userId,
friendName
FROM
friends
LIMIT 3
) f
ON u.userId = f.addedUserId
WHERE u.userId = 1
GROUP by u.userName
should work.
This is the query you are looking for. I think there is some problems with the field names (for instance f.freindName instead of f.friendname) but fixing that, This should work
SELECT u.userName, u.userFriends, u.userJoined, f.freindName
FROM user u inner join friends f ON u.userId = f.userId
(SELECT DISTINCT f.freindName
FROM user u2 inner join friends f2 ON u2.userId = f2.userId
WHERE u2.userId = u.userId
LIMIT 3) as f
WHERE u.userId=1

How to mention two aggregation functions in the same query?

I have an sql query to get information from three different tables as following:
select users.username, users.id, users.avatar, users.daily_tahmin, users.alert, f1.comments_no, f2.tahmins_no, f3.monthly_tahmins_no from users LEFT join
(SELECT count(comments) AS comments_no, user_id
FROM comments
Where user_id = 12
) AS f1 on users.id = f1.user_id left join
(
SELECT count(tahmin) AS tahmins_no, user_id
FROM tahminler
Where user_id = 12
) AS f2 on users.id = f2.user_id left join
(
SELECT count(tahmin) AS monthly_tahmins_no, user_id, matches_of_comments.match_id
FROM tahminler
INNER JOIN matches_of_comments on tahminler.match_id = matches_of_comments.match_id
Where user_id = 12 AND (MONTH( STR_TO_DATE( matches_of_comments.match_date, '%d.%m.%Y' ) ) = '01' AND YEAR( STR_TO_DATE( matches_of_comments.match_date, '%d.%m.%Y' ) ) = '2014')
) AS f3 on users.id = f3.user_id
where users.id = 12
and it gives the following result :
+------------+----+----------------+--------------+-------+-------------+------------+--------------------+
| username | id | avatar | daily_tahmin | alert | comments_no | tahmins_no | monthly_tahmins_no |
+------------+----+----------------+--------------+-------+-------------+------------+--------------------+
| cold heart | 12 | 1389002263.jpg | 0 | 0 | 65 | 258 | 10 |
+------------+----+----------------+--------------+-------+-------------+------------+--------------------+
The previous code was not optimized after i do some EXPLIAN and I tried to optimized it and I got the following query:
SELECT m.*,count(comments.id)
FROM comments
JOIN
(SELECT users.username, users.id, users.avatar, users.daily_tahmin, users.alert
FROM users
WHERE id=12)as m ON m.id = comments.user_id
My problem is that I can not get (tahmins_no,monthly_tahmins_no) every time i add them to the query it gives wrong result I can not find a way to add them correctly to the query to be in optimized way?? can I have any advice from anybody here?
Your simplified query is:
select m.*, count(c.id)
from comments c join
users m
on m.id = c.user_id
where m.id = 12
group by m.id;
You should be able to add in the monthly number:
select m.*, count(c.id), f3.*
from comments c join
users m
on m.id = c.user_id join
(select count(tahmin) AS monthly_tahmins_no, user_id, moc.match_id
from tahminler t join
matches_of_comments moc
on t.match_id = moc.match_id
Where user_id = 12 AND
MONTH( STR_TO_DATE( moc.match_date, '%d.%m.%Y' ) ) = 1 AND
YEAR( STR_TO_DATE( moc.match_date, '%d.%m.%Y' ) ) = 2014
) f3
on f3.user_id = m.id
where m.id = 12
group by m.id;
The month() and year() function return numbers, not strings. I don't understand why the field match_date would be stored as a string -- seems like a silly choice for a column whose name contains date.

SQL INNER JOIN and COUNT

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

mysql row to column with group_concat and count?

i have 2 tables, users and follows. table follows has a column named status. I would like to count how many follows each user has grouping by the status.
The query below returns a record for each status type for each user.
SELECT users.name as user_name, f.status, count(f.id)
FROM users
JOIN application_follows f ON f.user_id = users.id
GROUP BY users.id, f.status
ORDER BY users.id
returns something like:
user_name status count
mike new 10
mike old 5
tom new 8
tom old 9
but i would like something more friendly like:
user_name new old
mike 10 5
tom 8 9
tried using group_concat and count but didnt work. Any clues?
SELECT user_name,
MAX(CASE WHEN status = 'new' THEN totalFollowers ELSE NULL END) `NEW`,
MAX(CASE WHEN status = 'old' THEN totalFollowers ELSE NULL END) `OLD`
FROM
(
SELECT users.name as user_name,
f.status,
count(f.id) totalFollowers
FROM users
LEFT JOIN application_follows f
ON f.user_id = users.id
GROUP BY users.id, f.status
) derivedTable
GROUP BY user_name
or maybe (not sure about this one)
SELECT users.name as user_name,
MAX(CASE WHEN f.status = 'new' THEN count(f.id) ELSE NULL END) `NEW`,
MAX(CASE WHEN f.status = 'old' THEN count(f.id) ELSE NULL END) `OLD`
FROM users
LEFT JOIN application_follows f
ON f.user_id = users.id
GROUP BY users.id, f.status