Querying for reaction counts - mysql

I have tables:
Users
user_id
name
Posts
post_id
user_id
post
image_path
Reactions
reaction_id
post_id
reaction
user_id
Reactions have values of {1: 'like', 2: 'dislike'}
Now I want to get all the posts with the corresponding counts with each react.
So end result would be like:
{
post_id: 1,
post: "Hello World",
image_path: "public/upload_123.png",
likes: 23,
dislikes: 18,
my_reaction: 1
}

You can do it by joining the tables with a LEFT join (just in case there are no reactions for a post) and then with conditional aggregation:
select p.post_id, p.post, p.image_path,
sum(r.reaction = 1) likes,
sum(r.reaction = 2) dislikes
from posts p left join reactions r
on r.post_id = p.post_id
group by p.post_id, p.post, p.image_path

I finally got the query. It goes as follows:
SELECT `posts`.`posts_id`, `users`.`user_id`, `posts`.`post`, `posts`.`file_path`, `posts`.`created_at`, `users`.`fname`, `users`.`mname`, `users`.`lname`, `r1`.`reaction`, IF(SUBSTRING_INDEX(posts.file_path, '.', -1) IN ('jpeg', 'jpg', 'png'), 'image', 'document') AS file_type,
SUM(reactions.reaction = 1) AS likes,
SUM(reactions.reaction = 2) AS hearts,
SUM(reactions.reaction = 3) AS dislikes FROM `posts`
LEFT JOIN `users` ON `posts`.`user_id` = `users`.`user_id` LEFT JOIN `reactions` ON `reactions`.`posts_id` = `posts`.`posts_id`
LEFT JOIN `reactions` AS `r1` ON `r1`.`posts_id` = posts.posts_id AND r1.user_id = 1 WHERE `posts`.`deleted_at` IS NULL
GROUP BY `posts`.`posts_id`
ORDER BY `posts`.`created_at` DESC

Just run this code..
SELECT a.post_id, a.post,a.image_path,b.reaction,count(b.reaction)
from posts a
INNER join reactions b on a.post_id=b.post_id
group by a.post_id, a.post,a.image_path,b.reaction

Related

Combined ORDER BY of two SELECT queries using UNION

How can I get combined order by results retrieved from these two queries joined by a UNION?
SELECT u.id, u.name, u.gender, n.user, n.other_user, n.type, n.notification, n.membership, n.link, n.created_at, p.photo FROM notifications n
INNER JOIN users u ON
CASE
WHEN n.user = :me THEN u.id = n.other_user
WHEN n.other_user = :me THEN u.id = n.user
END
LEFT JOIN photos p ON
CASE
WHEN n.user = :me THEN p.user = n.other_user AND p.order_index = (SELECT MIN(order_index) FROM photos WHERE user = n.other_user)
WHEN n.other_user = :me THEN p.user = n.user AND p.order_index = (SELECT MIN(order_index) FROM photos WHERE user = n.user)
END
UNION
SELECT '', '', '', '', '', '', n.notification, n.membership, n.link, n.created_at, '' FROM notifications n WHERE type = 'admin'
I want the returned records to be sorted in descending order as per their ids. For example, if the records returned from first query are 3,5,4,6,7 and from second query are 2,1,9 then all the records should be combined sorted like this 9,7,6,5,4,3,2,1.
I have tried this:
SELECT * FROM
(
*THE WHOLE QUERY ABOVE*
) AS x
ORDER BY x.id
This is not returning correct results. It is sorting the results from first query in descending order 7,6,5,4,3 and results from the 2nd query in ascending order 1,2,9. They are getting sorted individually instead of getting sorted together. How can I get them sorted combined together for 9,7,6,5,4,3,2,1.
Add notification id in both the queries and give them aliases as you haven't used aliases in your tables (I guess). Then just order by using the alias of the notification id as answered by "Thorsten Kettner".
SELECT u.id as uid, n.id as nid, u.name, u.gender, n.user, n.other_user, n.type, n.notification, n.membership, n.link, n.created_at, p.photo FROM notifications n
INNER JOIN users u ON
CASE
WHEN n.user = :me THEN u.id = n.other_user
WHEN n.other_user = :me THEN u.id = n.user
END
LEFT JOIN photos p ON
CASE
WHEN n.user = :me THEN p.user = n.other_user AND p.order_index = (SELECT MIN(order_index) FROM photos WHERE user = n.other_user)
WHEN n.other_user = :me THEN p.user = n.user AND p.order_index = (SELECT MIN(order_index) FROM photos WHERE user = n.user)
END
UNION
SELECT '', n.id as nid, '', '', '', '', '', n.notification, n.membership, n.link, n.created_at, '' FROM notifications n WHERE type = 'admin'
ORDER BY nid DESC
You have already found the issue yourself; you confused user ID and notification ID. So select the two, use alias names that tell which is which and sort:
select u.id as user_id, ..., n.id as notification_id, ...
from ...
union all
select ... from ...
order by notification_id;

SQL query returns column with data from another column

I have a table Notices connected to tables Likes and Comments. When I return the notices for a user I also create columns: number_of_likes, number_of_comments and liked_by_me. The query is working correctly when the user making the query hasn't liked a notice (liked_by_me = 0) . But if they have (liked_by_me = 1) the value I get for number_of_likes is wrong and is the same as number_of_comments.
Example:
1)
- liked by me = false
- likes = 1
- comments = 5
Returned values:
- liked_by_me = 0
- number_of_likes = 1
- number_of_comments = 5
2)
- liked by me = true
- likes = 2
- comments = 5
Returned values:
- liked_by_me = 1
- number_of_likes = 5
- number_of_comments = 5
Here is the query I am using:
SELECT notices.*
, count(comment.id) as number_of_comments
, count(like1.user_id) as number_of_likes
, like2.user_id IS NOT NULL AS liked_by_me
, boards.name as board_name
FROM notices
LEFT JOIN comments as comment
ON (comment.notice_id = notices.id)
LEFT JOIN likes as like1
ON (like1.notice_id = notices.id)
LEFT JOIN likes as like2
ON (like2.notice_id = notices.id
AND like2.user_id = $1)
LEFT JOIN boards
ON (boards.id = notices.board_id)
LEFT OUTER JOIN board_users
ON (board_users.board_id = notices.board_id)
WHERE board_users.user_id = $1
GROUP BY notices.id
, boards.name
, like2.user_id
, userId
Any help would be appreciated. I have been on this for hours and I don't think I will be able to find the problem.
Thanks!
Solution:
Here is the working query
SELECT notices.*,
(SELECT COUNT(user_id) from likes WHERE likes.notice_id = notices.id) AS number_of_likes,
(SELECT user_id IS NOT NULL from likes WHERE likes.notice_id = notices.id AND likes.user_id = $1) AS liked_by_me,
count(comments.id) as number_of_comments, boards.name as board_name
FROM notices LEFT JOIN comments ON (comments.notice_id = notices.id)
LEFT JOIN boards ON (boards.id = notices.board_id)
LEFT OUTER JOIN board_users ON (board_users.board_id = notices.board_id)
WHERE board_users.user_id = $1 GROUP BY notices.id, boards.name", user);
You will have to use subeselects.
Excellent article on this problem: The GROUPing pitfall
TL;DR: Basically, you have to realize, that all your comments and likes are being multiplicated by one another. Try to display the result of the query without the group clause to see, that duplicate likes/comments are being counted.
EDIT: I didn't test this, but it's how the query might look:
(that is if user can only like one notice once, otherwise you would have to group current user likes too)
SELECT
notices.*,
comments.number_of_comments,
likes.number_of_likes
current_user_likes.user_id IS NOT NULL AS liked_by_me
boards.name AS board_name
FROM notices
LEFT JOIN (
SELECT
COUNT(*) AS number_of_comments,
notice_id
FROM comments
GROUP BY notice_id
) AS comments ON comments.notice_id = notices.id
LEFT JOIN (
SELECT
COUNT(*) AS number_of_likes,
notice_id
FROM likes
GROUP BY notice_id
) AS likes ON likes.notice_id = notices.id
LEFT JOIN likes AS current_user_likes
ON current_user_likes.notice_id = notices.id
AND current_user_likes.user_id = $1
LEFT JOIN boards ON boards.id = notices.board_id
INNER JOIN board_users
ON board_users.board_id = notices.board_id
AND board_users.user_id = $1;

Selecting from three tables

I am trying to SELECT from one table and count from two other tables based on the rows from the first table. I tried the following code below but the rows keep coming empty.
SELECT list.id, list.title, list.body, list.poster, list.created_at, count(comments.id) as comcount, count(supports.topic_id) as supcount
FROM (
SELECT *
FROM topics
ORDER BY created_at DESC
LIMIT 5
) AS list, comments, supports
WHERE
list.id = comments.id OR
list.id = supports.topic_id
Through in this scenario table topics has only two rows and tables comments and supports have no rows in them, but yet still I should be able to get two rows with their aliases supcount and comcount each having a value 0 as an output.
I got the solution to the above but am trying something else with the solution provided which I explained in the comment area of the solution provided.
SELECT
t.id,
t.title,
t.body,
t.poster,
t.created_at,
s.supporter,
IFNULL((SELECT COUNT(*) FROM comments c WHERE c.id = t.id), 0) AS comcount,
IFNULL((SELECT COUNT(*) FROM supports s WHERE s.topic_id = t.id), 0) AS supcount,
CASE WHEN (s.supporter = "Davies Alex") THEN '1' ELSE '0' END sup,
CASE WHEN (c.commenter = "Davies Alex") THEN '1' ELSE '0' END com
FROM topics t, comments c, supports s
ORDER BY created_at DESC
This gonna be working, give a try (using subquery for just counting entries in another table is more suitable):
SELECT
id,
title,
body,
poster,
created_at,
IFNULL((SELECT COUNT(*) FROM comments c WHERE c.id = t.id), 0) AS comcount,
IFNULL((SELECT COUNT(*) FROM supports s WHERE s.topic_id = t.id), 0) AS supcount
FROM topics t
ORDER BY created_at DESC
LIMIT 5
Update for new requirement:
SELECT
t.id,
t.title,
t.body,
t.poster,
t.created_at,
s.supporter,
IFNULL(COUNT(c.id), 0) AS comcount,
IFNULL(COUNT(s.id), 0) AS supcount,
SUM(IF(s.supporter IS NOT NULL AND s.supporter = "Davies Alex", 1, 0)) > 0 AS sup,
SUM(IF(c.commenter IS NOT NULL AND c.commenter = "Davies Alex", 1, 0)) > 0 AS com
FROM topics t
LEFT JOIN comments c ON c.id = t.id
LEFT JOIN supports s ON s.topic_id = t.id
GROUP BY t.id
ORDER BY created_at DESC
In your query, you require list.id to either match comments.id or supports.topic_id. If you use an outer join, you'll be able to retrieve data from the initial table even though the joined tables don't match or contain any data.
SELECT
topics.id, topics.title, topics.body, topics.poster, list.created_at,
count(comments.id) as comcount,
count(supports.topic_id) as supcount
FROM lists
LEFT JOIN comments ON comments.id = topics.id
LEFT JOIN supports ON supports.topic_id = topics.id
ORDER BY created_at DESC
LIMIT 5

MySQL case when using JOIN/GROUP BY

I have a posts table. I am joining it with votes table via:
SELECT posts.*,
(CASE
WHEN vs.user_id = 1 THEN 0
ELSE 1
END) AS voted_by_me
FROM `posts`
LEFT OUTER JOIN votes AS vs ON vs.post_id = posts.id
GROUP BY posts.id
Basically I want know if the joined value vs.user_id = 1. This obviously doesn't work because it will just use a random returned value in CASE WHEN condition . I am wondering if there's a way to find out if the join values group by contains a specific value.
To clarify:
I want to get all posts, and also for each post have a column called voted_by_me where if posts was voted by user with id 1, then make it value 0, otherwise 1.
Is that what you want?
SELECT posts.id,
sum(vs.user_id = 1 AND vs.option = 0) > 0 AS downvoted_by_me,
sum(vs.user_id = 1 AND vs.option = 1) > 0 AS upvoted_by_me
FROM `posts`
LEFT OUTER JOIN votes AS vs ON vs.post_id = posts.id
GROUP BY posts.id
SELECT posts.id,
ifnull(vs.isvoted, 0) AS voted_by_me
FROM `posts`
LEFT OUTER JOIN (select distinct postid , 1 as isvoted from votes where user_id=1) AS vs
ON vs.post_id = posts.id

I need get all records from table joined where at least one case with condition

I have three tables:
cp_projeto (id, nome...)
cp_habilidade_projeto (id, id_projeto)
cp_habilidade (id, nome...)
I need all projects with all cp_habilidade where cp_projeto have one cp_habilidade. My actual query:
SELECT
p.id as id_projeto,
p.nome as nome_projeto,
p.id_tipo_projeto,
p.dhPostagem,
cp_habilidade_projeto.id as id_habilidade_projeto,
cp_habilidade.nome as nome_habilidade
FROM (
SELECT * FROM cp_projeto
WHERE (id_status_projeto = 2)
ORDER BY dhPostagem DESC LIMIT 0, 10
) AS p
inner JOIN cp_habilidade_projeto ON (cp_habilidade_projeto.id_projeto = p.id)
inner JOIN cp_habilidade ON (cp_habilidade.id = cp_habilidade_projeto.id_habilidade)
JOIN cp_sub_categoria ON (cp_sub_categoria.id = p.id_sub_categoria)
WHERE (
p.nome like '%CSS%'
OR cp_habilidade.nome like '%CSS%'
)
This returns only cp_habilidade.nome = %CSS%, I need it all.
Thanks!
SELECT
p.id as id_projeto,
p.nome as nome_projeto,
p.id_tipo_projeto,
p.dhPostagem,
hp.id as id_habilidade_projeto,
h.nome as nome_habilidade
FROM cp_projecto p
JOIN cp_habilidade_projeto hp ON p.id = hp.id_projeto
JOIN cp_habilidade h ON h.id = hp.id_habilidade
WHERE p.id IN ( SELECT cp_habilidade_projeto.id_projeto
FROM cp_habilidade
JOIN cp_habilidade_projeto ON cp_habilidade.id = cp_habilidade_projeto.id_habilidade
WHERE cp_habilidade.nome LIKE '%CSS%' )