mysql row to column with group_concat and count? - mysql

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

Related

Joining 3 Tables based on specific conditions

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

MySQL query broke after adding join

I have a query that loads data for a loop in my CMS to display posts. Everything was working fine in the vote columns until I left-joined the comment column. Comments display OK, but the totalvote, upvote and downvote counts are wildly off. Let me know if you need to see the tables.
SELECT
count(DISTINCT comment.comment ) AS Comment,
idea.dateofcreation AS timestamp,
idea.userId AS userId,
idea.id AS ID,
idea.text AS Idea,
page.permalink AS Permalink,
user.name AS Username,
COUNT(CASE WHEN votelog.vote !="" THEN 1 END) AS 'totalvotes',
COUNT(CASE WHEN votelog.vote = '1' THEN 1 END) AS 'upvote',
COUNT(CASE WHEN votelog.vote = '-1' THEN 1 END) AS 'downvote'
FROM idea
LEFT JOIN votelog ON idea.id = votelog.ideaid
LEFT JOIN user ON idea.userId = user.id
LEFT JOIN page ON idea.id = page.ideaid
LEFT join comment ON comment.ideaid = idea.id
GROUP BY idea.id
ORDER BY totalvotes DESC
It seems you decided to join the table comments only to count how many comments are there.
But by doing so, you are now producing multiple rows, one per comment, and that gets all other counts off.
I would suggest using a scalar subquery to count the comments, and removing the join. Something like:
SELECT
(select count(DISTINCT comment) from comment c where c.ideaid = idea.id) AS Comment,
idea.dateofcreation AS timestamp,
idea.userId AS userId,
idea.id AS ID,
idea.text AS Idea,
page.permalink AS Permalink,
user.name AS Username,
COUNT(CASE WHEN votelog.vote !="" THEN 1 END) AS 'totalvotes',
COUNT(CASE WHEN votelog.vote = '1' THEN 1 END) AS 'upvote',
COUNT(CASE WHEN votelog.vote = '-1' THEN 1 END) AS 'downvote'
FROM idea
LEFT JOIN votelog ON idea.id = votelog.ideaid
LEFT JOIN user ON idea.userId = user.id
LEFT JOIN page ON idea.id = page.ideaid
GROUP BY idea.id
ORDER BY totalvotes DESC
You have more than one comment. So, aggregate before joining:
SELECT c.num_comments,
i.dateofcreation AS timestamp, i.userId AS userId, i.id AS ID, i.text AS Idea,
p.permalink AS Permalink, p.name AS Username,
SUM( vl.vote <> '' ) AS totalvotes,
SUM( vl.vote = 1 ) AS upvote,
SUM( vl.vote = -1 ) AS downvote
FROM idea i LEFT JOIN
votelog vl
ON i.id = vl.ideaid LEFT JOIN
user u
ON i.userId = u.id LEFT JOIN
page p
ON i.id = p.ideaid LEFT JOIN
(SELECT c.ideaid, COUNT(*) as num_comments
FROM comment c
GROUP BY c.ideaid
) c
ON c.ideaid = i.id
GROUP BY c.num_comments,
i.dateofcreation i.userId, i.id, i.text,
p.permalink, p.name
ORDER BY totalvotes DESC;
Notes:
Table aliases make a query easier to write and to read.
It is a good practice to include all non-aggregated columns in the GROUP BY (and more recent versions of MySQL tend to enforce this).
I assume the votes are numbers. Compare them to numbers, not to strings.
MySQL has a nice shorthand for counting the number of times a boolean expression is true.

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;

Query not returning the expected results

SELECT u.* ,
(select CASE u.ID
WHEN u.ID in (select RequestedUserID from user_requestes where userID=3) THEN 0
ELSE 1
END ) AS Accepted
FROM users u
WHERE u.ID <>3
and u.id not in (select friends.FriendID
from friends
where friends.UserID=3 or friends.FriendID=3)
order by u.Name asc
i am trying to execute this query using phpmyadmin
select RequestedUserID from user_requestes where userID=3
the above query return 79 as result
and if i execute the original query i found this
Accepted should be 0 and not 1
And what if you write your query like this?
SELECT
u.*,
CASE
WHEN u.ID IN (select RequestedUserID from user_requestes where userID=3) THEN 0
ELSE 1
END AS Accepted
FROM
users u
WHERE
u.ID <>3
and u.id not in (
select
friends.FriendID
from
friends
where
friends.UserID=3 or friends.FriendID=3
)
order by
u.Name asc
Don't use SELECT before CASE;
If you go for CASE WHEN... syntax, you must provide values and not search conditions (See MySQL documentation here)
There is a mistake in your case expression
CASE u.ID WHEN u.ID in (...)
reads as follows: look up u.id in the subquery. Found = true, not found = false. In MySQL true = 1 and false = 0.
CASE u.ID WHEN <either 1 or 0>
You are mistakenly comparing the user ID with the boolean result 1 or 0.
You want this instead:
SELECT
u.* ,
CASE
WHEN u.ID in (select RequestedUserID from user_requestes where userID=3) THEN 0
ELSE 1
END AS Accepted
FROM ...
By the way: There is probably a semantical mistake in your friends subquery, as it is always FriendID you are returning. I suppose that should be:
and u.id not in
(
select case when FriendID = 3 then UserID else FriendID end
from friends
where UserID = 3 or FriendID = 3
)
or simply
and u.id not in (select FriendID from friends where UserID = 3)
and u.id not in (select UserID from friends where FriendID = 3)
This might work better:
SELECT u.*,
IF(EXISTS (
SELECT *
from user_requestes
where userID = 3
AND RequestedUserID = u.ID ),
0, 1 ) AS Accepted
FROM users AS u
LEFT JOIN friends AS f ON f.FriendID = u.ID
WHERE u.ID != 3
AND f.FriendID IS NULL
AND ( f.UserID = 3 or f.FriendID = 3 )
Note the use of EXISTS as being more efficient than IN ( SELECT ... ). Ditto for LEFT JOIN ... IS NULL.

Select the users are not friend of a specific user

Hi, I have these two tables: users and friends (friend_status = 1 means the request is sent, friend_status = 2 means they are friends). Now I want to select all users are not friend of a specific user. How to do?
Assuming the current user is 1. I tried this SQL. It works but it's too long and slow. The first selects all users sent request to user1 but not accepted. The second selects all users receive request from user1. The third and the fourth selects all users is not in "friends" table.
SELECT user_id, name, email
FROM
(
SELECT user_id, name, email
FROM users u INNER JOIN friends f ON u.user_id = f.sender
WHERE f.receiver = 1 AND friend_status <> 2
UNION
SELECT user_id, name, email
FROM users u INNER JOIN friends f ON u.user_id = f.receiver
WHERE f.sender = 1 AND friend_status <> 2
UNION
SELECT u.user_id, u.name, u.email
FROM users u LEFT JOIN friends f ON u.user_id = f.sender
WHERE f.receiver IS NULL
GROUP BY user_id
UNION
SELECT u.user_id, u.name, u.email
FROM users u LEFT JOIN friends f ON u.user_id = f.receiver
WHERE f.sender IS NULL
GROUP BY user_id
) T
GROUP BY user_id
Update: Add a pic.
SELECT
a.user_id,
a.name,
a.email,
b.status IS NOT NULL AS friend_status
FROM
users a
LEFT JOIN
friends b ON
a.user_id IN (b.sender, b.receiver) AND
1 IN (b.sender, b.receiver)
WHERE
(b.friend_id IS NULL OR b.status <> 2) AND
a.user_id <> 1
You had asked a question previously here - "Select users who aren't friends with anyone", and I provided an answer which utilized a LEFT JOIN.
Building off of that, to select users who aren't friends with a specific user, we just simply need to add that specific user's ID to the LEFT JOIN condition (1 IN (b.sender, b.receiver).
Minor Edit: Unless the user can friend him/herself, it wouldn't make sense to also select the user who we're selecting against!! So I added WHERE a.user_id <> 1.
Assuming you want to perform the query on user_id 1:
SELECT user_id, name, email
FROM users AS u
WHERE NOT EXISTS (SELECT * FROM friends AS f
WHERE (f.sender = u.user_id AND f.receiver = 1 AND f.friend_status = 2)
OR (f.sender = 1 AND f.receiver = u.user_id AND f.friend_status = 2)
)
AND u.user_id <> 1
The subquery fetches all the established friendship relationship in which user 1 is either the sender or the receiver. The outer query selects all users for which no such relationship exists. The user with ID 1 is excluded from the query using the last line, as, even if he cannot be friend with himself, I suppose that he should not appear in the final query result.
You may be able to simplify this by using something like this:
SELECT user_id, name, email
FROM
(
SELECT u.user_id, u.name, u.email
FROM users u LEFT JOIN friends f ON u.user_id = f.sender
WHERE IFNULL(friend_status,0) <> 2
GROUP BY user_id
UNION
SELECT u.user_id, u.name, u.email
FROM users u LEFT JOIN friends f ON u.user_id = f.receiver
WHERE IFNULL(friend_status,0) <> 2
GROUP BY user_id
) T
GROUP BY user_id
The IFNULL function returns the value of the first parameter, replacing NULLs with the value of the value second parameter. In this case it means that friend_status will be treated as 0 if there is no matching friend in the friends table, which allows you to reduce the number of selects in the UNION by half.
Try this query
select
u.user_id,
u.name,
u.email,
ifnull(f.friend_status,0) as Relation
from users as u
left join friends as f
on f.sender = u.user_id
where u.user_id not in(select
sender
from friends
where sender = 1)
Here sender = 1 means the user id = 1. You can pass user id to restrict this condition. Also status 0 means he is not friend. and 1 , 2 , 3 are according to your rules