How can I addition of 3 counts fields in MySQL one query? - mysql

I have 3 counts count(like), COUNT(comment), COUNT(views), what I want is to addition of this 3 in that query and get the total addition of this 3 counts.
Note:- I am using 3 joins for get this 3 count and group by for group by posts.
e.g. -
COUNT(like) = 5
COUNT(comment) = 3
COUNT(views) = 12
so i need a key total_count = 20
Is this possible?
SELECT up.id, COUNT(upl.id) as likes_count, COUNT(upc.id) as collected_count, COUNT(upvb.id) as viewed_by_count
FROM posts as up
LEFT JOIN post_likes as upl ON upl.post_id = up.id
LEFT JOIN post_viewd_by as upvb ON upvb.post_id = up.id
LEFT JOIN post_collected as upc ON upc.post_id = up.id WHERE up.status = 'Active' GROUP BY up.id
ORDER BY likes_count DESC, up.insertdate DESC

try this
SELECT up.id, COUNT(upl.id) as likes_count, COUNT(upc.id) as collected_count, COUNT(upvb.id) as viewed_by_count , COUNT(upl.id)+COUNT(upc.id)+COUNT(upvb.id) AS Total
FROM posts as up
LEFT JOIN post_likes as upl ON upl.post_id = up.id
LEFT JOIN post_viewd_by as upvb ON upvb.post_id = up.id
LEFT JOIN post_collected as upc ON upc.post_id = up.id
WHERE up.status = 'Active'
GROUP BY up.id
ORDER BY likes_count DESC, up.insertdate DESC

If you want correct counts, then one method is correlated subqueries:
SELECT up.id,
(likes_count + viewed_by_count + collected_count) as total
FROM (SELECT up.*,
(SELECT COUNT(*)
FROM post_likes upl
WHERE upl.post_id = up.id
) as likes_count,
(SELECT COUNT(*)
FROM post_viewd_by upvb
WHERE upvb.post_id = up.id
) as viewed_by_count,
(SELECT COUNT(*)
FROM post_collected pc
WHERE pc.post_id = up.id
) as collected_count
FROM posts up
WHERE up.status = 'Active'
) up
ORDER BY likes_count DESC, up.insertdate DESC;
Assuming you have an index on the post_id in all the subsidiary tables, this should also be the fastest method.

Related

MySQL - Right join OR Right join

I'm building a social application.
I'd like to select all the posts, that the user has interacted with in some way (liked, commented, or liked a comment)
How could I achieve that? I'm imagining something like this:
SELECT p.* FROM posts p
RIGHT JOIN postLikes pl ON pl.postId = p.id AND pl.userId = :userId
OR
RIGHT JOIN postComments pc ON pc.postId = p.id AND pc.userId = :userId
OR
RIGHT JOIN postCommentLikes pcl ON pcl.postId = p.id AND pcl.userId = :userId
GROUP BY p.id
ORDER BY p.id DESC LIMIT :startIndex, 20
I'd like to achieve this in one query, because I have a paging system (20 posts / page)
Using UNION ALL would mess up this system.
See:
SELECT res.* FROM ((SELECT p.*, p.id AS postId FROM posts p
RIGHT JOIN postLikes pl ON pl.postId = p.id AND pl.userId = :userId)
UNION ALL
(SELECT * FROM posts p.*, p.id AS postId
RIGHT JOIN postComments pc ON pc.postId = p.id AND pc.userId = :userId)
UNION ALL
(SELECT * FROM posts p.*, p.id AS postId
RIGHT JOIN postCommentLikes pcl ON pcl.postId = p.id AND pcl.userId = :userId)) AS res
GROUP BY postId
ORDER BY postId DESC LIMIT :startIndex, 20
This way the order would be messed up, and therefore I wouldn't know what's the right startIndex
You can use EXISTS like this:
SELECT p.* FROM posts p
WHERE
EXISTS (SELECT 1 FROM postLikes pl WHERE pl.postId = p.id AND pl.userId = :userId)
OR
EXISTS (SELECT 1 FROM postComments pc WHERE pc.postId = p.id AND pc.userId = :userId)
OR
EXISTS (SELECT 1 FROM postCommentLikes pcl WHERE pcl.postId = p.id AND pcl.userId = :userId)
ORDER BY p.id DESC LIMIT :startIndex, 20
or with UNION and the operator IN:
SELECT * FROM posts
WHERE id IN (
SELECT postId FROM postLikes WHERE userId = :userId
UNION
SELECT postId FROM postComments WHERE userId = :userId
UNION
SELECT postId FROM postCommentLikes WHERE userId = :userId
)
ORDER BY id DESC LIMIT :startIndex, 20

SQL: how to include records that dont exist

I am making a query where I sort posts by upvotes. I have table posts and votes(post_id, user_id, vote), where vote can be 1 or -1. So now my problem is if post does not have any upvotes it won't show in result at all.
My query:
SELECT P.* FROM `vicoteka-api`.posts P
INNER JOIN (
SELECT post_id, COUNT(*) vote_count FROM `vicoteka-api`.votes
WHERE vote = 1 GROUP BY post_id
) V ON P.id = V.post_id
ORDER BY V.vote_count DESC
How can I include posts that don't exist in votes pivot table?
Use a left join with coalesce to get posts with no votes.
SELECT P.*,COALESCE(v.vote_count,0) as vote_count
FROM `vicoteka-api`.posts P
LEFT JOIN (SELECT post_id, COUNT(*) vote_count
FROM `vicoteka-api`.votes
WHERE vote = 1
GROUP BY post_id
) V ON P.id = V.post_id
ORDER BY vote_count DESC
Vamsi's answer is correct. If performance is a consideration, you might want to compare it to:
SELECT P.*, COUNT(v.post_id) as vote_count
FROM vicoteka-api.posts P LEFT JOIN
vicoteka-api.votes v
ON P.id = V.post_id AND v.vote = 1
GROUP BY p.id
ORDER BY vote_count DESC;
Or, what might even be better:
SELECT p.*,
(SELECT COUNT(*)
FROM vicoteka-api.votes v
WHERE P.id = V.post_id AND v.vote = 1
) as vote_count
FROM vicoteka-api.posts p
ORDER BY vote_count DESC;
The latter two allow an index to be used for the JOIN. The last one even saves effort on the GROUP BY.
SELECT P.*, V.POST_ID, V_COUNT, VOTE_COUNT FROM `vicoteka-api`.posts P
RIGHT OUTER JOIN (
SELECT post_id, COUNT( NVL(vote_count),0) AS V_COUNT, VOTE_COUNT
FROM `vicoteka-api`.votes
GROUP BY post_id, vote_count
) V ON P.id = V.post_id and V.vote_count <> -1
ORDER BY V.vote_count DESC
Correct me if I am wrong.

mysql query optimization steps or how to optimze query

I don't know much about query optimization but I know the order in which queries get executed
FROM clause
WHERE clause
GROUP BY clause
HAVING clause
SELECT clause
ORDER BY clause
This the query I had written
SELECT
`main_table`.forum_id,
my_topics.topic_id,
(
SELECT MAX(my_posts.post_id) FROM my_posts WHERE my_topics.topic_id = my_posts.topic_id
) AS `maxpostid`,
(
SELECT my_posts.admin_user_id FROM my_posts WHERE my_topics.topic_id = my_posts.topic_id ORDER BY my_posts.post_id DESC LIMIT 1
) AS `admin_user_id`,
(
SELECT my_posts.user_id FROM my_posts WHERE my_topics.topic_id = my_posts.topic_id ORDER BY my_posts.post_id DESC LIMIT 1
) AS `user_id`,
(
SELECT COUNT(my_topics.topic_id) FROM my_topics WHERE my_topics.forum_id = main_table.forum_id ORDER BY my_topics.forum_id DESC LIMIT 1
) AS `topicscount`,
(
SELECT COUNT(my_posts.post_id) FROM my_posts WHERE my_topics.topic_id = my_posts.topic_id ORDER BY my_topics.topic_id DESC LIMIT 1
) AS `postcount`,
(
SELECT CONCAT(admin_user.firstname,' ',admin_user.lastname) FROM admin_user INNER JOIN my_posts ON my_posts.admin_user_id = admin_user.user_id WHERE my_posts.post_id = maxpostid ORDER BY my_posts.post_id DESC LIMIT 1
) AS `adminname`,
(
SELECT forum_user.nick_name FROM forum_user INNER JOIN my_posts ON my_posts.user_id = forum_user.user_id WHERE my_posts.post_id = maxpostid ORDER BY my_posts.post_id DESC LIMIT 1
) AS `nickname`,
(
SELECT CONCAT(ce1.value,' ',ce2.value) AS fullname FROM my_posts INNER JOIN customer_entity_varchar AS ce1 ON ce1.entity_id = my_posts.user_id INNER JOIN customer_entity_varchar AS ce2 ON ce2.entity_id=my_posts.user_id WHERE (ce1.attribute_id = 1) AND (ce2.attribute_id = 2) AND my_posts.post_id = maxpostid ORDER BY my_posts.post_id DESC LIMIT 1
) AS `fullname`
FROM `my_forums` AS `main_table`
LEFT JOIN `my_topics` ON main_table.forum_id = my_topics.forum_id
WHERE (forum_status = '1')
And now I want to know if there is any way to optimize it ? Because all the logic is written in Select section not From, but I don't know how to write the same logic in From section of the query ?
Does it make any difference or both are same ?
Thanks
Correlated subqueries should really be a last resort, they often end up being executed RBAR, and given that a number of your subqueries are very similar, trying to get the same result using joins is going to result in a lot less table scans.
The first thing I note is that all of your subqueries include the table my_posts, and most contain ORDER BY my_posts.post_id DESC LIMIT 1, those that don't have a count with no group by so the order and limit are redundant anyway, so my first step would be to join to my_posts:
SELECT *
FROM my_forums AS f
LEFT JOIN my_topics AS t
ON f.forum_id = t.forum_id
LEFT JOIN
( SELECT topic_id, MAX(post_id) AS post_id
FROM my_posts
GROUP BY topic_id
) AS Maxp
ON Maxp.topic_id = t.topic_id
LEFT JOIN my_posts AS p
ON p.post_id = Maxp.post_id
WHERE forum_status = '1';
Here the subquery just ensures you get the latest post per topic_id. I have shortened your table aliases here for my convenience, I am not sure why you would use a table alias that is longer than the actual table name?
Now you have the bulk of your query you can start adding in your columns, in order to get the post count, I have added a count to the subquery Maxp, I have also had to add a few more joins to get some of the detail out, such as names:
SELECT f.forum_id,
t.topic_id,
p.post_id AS `maxpostid`,
p.admin_user_id,
p.user_id,
t2.topicscount,
maxp.postcount,
CONCAT(au.firstname,' ',au.lastname) AS adminname,
fu.nick_name AS nickname
CONCAT(ce1.value,' ',ce2.value) AS fullname
FROM my_forums AS f
LEFT JOIN my_topics AS t
ON f.forum_id = t.forum_id
LEFT JOIN
( SELECT topic_id,
MAX(post_id) AS post_id,
COUNT(*) AS postcount
FROM my_posts
GROUP BY topic_id
) AS Maxp
ON Maxp.topic_id = t.topic_id
LEFT JOIN my_posts AS p
ON p.post_id = Maxp.post_id
LEFT JOIN admin_user AS au
ON au.admin_user_id = p.admin_user_id
LEFT JOIN forum_user AS fu
ON fu.user_id = p.user_id
LEFT JOIN customer_entity_varchar AS ce1
ON ce1.entity_id = p.user_id
AND ce1.attribute_id = 1
LEFT JOIN customer_entity_varchar AS ce2
ON ce2.entity_id = p.user_id
AND ce2.attribute_id = 2
LEFT JOIN
( SELECT forum_id, COUNT(*) AS topicscount
FROM my_topics
GROUP BY forum_id
) AS t2
ON t2.forum_id = f.forum_id
WHERE forum_status = '1';
I am not familiar with your schema so the above may need some tweaking, but the principal remains - use JOINs over sub-selects.
The next stage of optimisation I would do is to get rid of your customer_entity_varchar table, or at least stop using it to store things as basic as first name and last name. The Entity-Attribute-Value model is an SQL antipattern, if you added two columns, FirstName and LastName to your forum_user table you would immediately lose two joins from your query. I won't get too involved in the EAV vs Relational debate as this has been extensively discussed a number of times, and I have nothing more to add.
The final stage would be to add appropriate indexes, you are in the best decision to decide what is appropriate, I'd suggest you probably want indexes on at least the foreign keys in each table, possibly more.
EDIT
To get one row per forum_id you would need to use the following:
SELECT f.forum_id,
t.topic_id,
p.post_id AS `maxpostid`,
p.admin_user_id,
p.user_id,
MaxT.topicscount,
maxp.postcount,
CONCAT(au.firstname,' ',au.lastname) AS adminname,
fu.nick_name AS nickname
CONCAT(ce1.value,' ',ce2.value) AS fullname
FROM my_forums AS f
LEFT JOIN
( SELECT t.forum_id,
COUNT(DISTINCT t.topic_id) AS topicscount,
COUNT(*) AS postCount,
MAX(t.topic_ID) AS topic_id
FROM my_topics AS t
INNER JOIN my_posts AS p
ON p.topic_id = p.topic_id
GROUP BY t.forum_id
) AS MaxT
ON MaxT.forum_id = f.forum_id
LEFT JOIN my_topics AS t
ON t.topic_ID = Maxt.topic_ID
LEFT JOIN
( SELECT topic_id, MAX(post_id) AS post_id
FROM my_posts
GROUP BY topic_id
) AS Maxp
ON Maxp.topic_id = t.topic_id
LEFT JOIN my_posts AS p
ON p.post_id = Maxp.post_id
LEFT JOIN admin_user AS au
ON au.admin_user_id = p.admin_user_id
LEFT JOIN forum_user AS fu
ON fu.user_id = p.user_id
LEFT JOIN customer_entity_varchar AS ce1
ON ce1.entity_id = p.user_id
AND ce1.attribute_id = 1
LEFT JOIN customer_entity_varchar AS ce2
ON ce2.entity_id = p.user_id
AND ce2.attribute_id = 2
WHERE forum_status = '1';

Mysql - LEFT JOIN - get first entry

I have this structure in MySql
I am trying to get:
FIRST post, from LAST topic WHERE category is 'News'
In this example it is row from post where id = 2 as marked on image
So far I got this query:
SELECT *
FROM forum_post AS p
LEFT JOIN forum_topic AS t ON p.topic_id = t.id
LEFT JOIN forum_category AS c ON t.category_id = c.id
WHERE c.title = 'News' AND t.id = MAX(t.id)
ORDER BY p.id ASC LIMIT 1
EDIT:
Dirty solution:
SELECT * FROM forum_post
WHERE topic_id = (SELECT MAX(id) FROM forum_topic WHERE category_id = 1)
ORDER BY id ASC LIMIT 1
You can still use a joined query instead of a subquery to get the first post from last topic of your category,note the subquery in join will run only once to get the result set and in your case subquery will run for each iteration
SELECT * FROM
forum_post AS p
JOIN
(SELECT
t.id
FROM
forum_topic AS t
JOIN forum_category AS c
ON t.category_id = c.id
WHERE c.title = 'News'
ORDER BY t.id DESC
LIMIT 1) t
ON p.topic_id = t.id
ORDER BY p.id ASC
LIMIT 1
select fp.* from forum_post fp,
(select min(fp.id) from forum_post fp where topic_id in
(select max(ft.id) from forum_topic ft inner join forum_category fc
on fc.id = ft.category_id where fc.title = 'News'))T
where fp.id = T.id
[In case there are no forum_posts, no row will be returned]
Edit:
Updated [Although I haven't tried executing it]
I haven't test it, but it shoud be something like this:
SELECT fm.remply
FROM forum_topic ft
JOIN forum_category fc
ON ft.category_id = fc.category_id
AND fc.title = 'News'
JOIN forum_post fm
ON ft.id = fm.topic_id
ORDER BY ft.id DESC
,fm.id DESC
LIMIT 1

Query for finding Persons with most points

I am using Mysql and have these tables: (only important columns shown)
Person
id, primary key
Post
id, primary key
points, INT
Visit
id, primary key
person_id, refers to Person
post_id, refers to Post
What I want to find is the Persons (top 5) with most points overall? And the persons with most points on each Post.
Can anyone please guide me? Any help is deeply apreciated!
Top 5 persons with most points overall:
SELECT
p.id,
SUM(Post.points) AS total_points
FROM
Person p
INNER JOIN Visit v
ON p.id = v.person_id
INNER JOIN Post
ON v.post_id = Post.id
GROUP BY
p.id
ORDER BY
SUM(Post.points) DESC
LIMIT 5
Top 5 persons with most points in one post:
SELECT
p.id,
MAX(Post.points) AS best_post_points
FROM
Person p
INNER JOIN Visit v
ON p.id = v.person_id
INNER JOIN Post
ON v.post_id = Post.id
GROUP BY
p.id
ORDER BY
MAX(Post.points) DESC
LIMIT 5
Top 5 posts:
SELECT
p.id,
Post.points
FROM
Person p
INNER JOIN Visit v
ON p.id = v.person_id
INNER JOIN Post
ON v.post_id = Post.id
ORDER BY
Post.points DESC
LIMIT 5
For each Post
SELECT id FROM Person where id in (SELECT person_id FROM Visit where post_id in
(SELECT id FROM Post order by points DESC limit 5))
Overall (not sure if will work, not tested)
SELECT id FROM Person where id in (SELECT distinct(person_id) FROM Visit where post_id in
(SELECT id FROM Post order by points DESC limit 5) GROUP BY person_id )
SELECT *
FROM
(
SELECT P.id , SUM(PP.points)
FROM PERSON P JOIN VISIT V ON ( V.person_id = P.id )
JOIN POST PP JOIN ON ( PP.id = V.post_id )
GROUP BY P.id
ORDER BY PP.points DESC
)
LIMIT 5;
SELECT *
FROM
(
SELECT P.id , COUNT(*) NUM_OF_POST
FROM PERSON P JOIN VISIT V ON ( V.person_id = P.id )
JOIN POST PP JOIN ON ( PP.id = V.post_id )
GROUP BY P.id
ORDER BY NUM_OF_POST DESC
)
LIMIT 5;