SQL: how to include records that dont exist - mysql

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.

Related

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

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.

Query for multiple count values

SELECT cm.commenter_id,
cm.comment,
m.id,
(
SELECT COUNT(*) AS r_count
FROM comments
GROUP BY comments.commenter_id
) AS count,
m.display_name
FROM comments cm
INNER JOIN members m
ON cm.commenter_id = m.id
From this query I want to get the display_name for the person with the highest count of comments. Any guidance is appreciated.
SELECT m.id, m.display_name, COUNT(*) totalComments
FROM comments cm
INNER JOIN members m
ON cm.commenter_id = m.id
GROUP BY m.id, m.display_name
HAVING COUNT(*) =
(
SELECT COUNT(*) totalCount
FROM Comments
GROUP BY commenter_id
ORDER BY totalCount DESC
LIMIT 1
)
SQLFiddle Demo
SQLFiddle Demo (with duplicates)
I think the simplest way is just to sort your query and take the first row:
SELECT cm.commenter_id,
cm.comment,
m.id,
(
SELECT COUNT(*) AS r_count
FROM comments
GROUP BY comments.commenter_id
) AS count,
m.display_name
FROM comments cm
INNER JOIN members m
ON cm.commenter_id = m.id
order by count desc
limit 1

MySQL INNER JOIN select only one row from second table

I have a users table and a payments table, for each user, those of which have payments, may have multiple associated payments in the payments table. I would like to select all users who have payments, but only select their latest payment. I'm trying this SQL but i've never tried nested SQL statements before so I want to know what i'm doing wrong. Appreciate the help
SELECT u.*
FROM users AS u
INNER JOIN (
SELECT p.*
FROM payments AS p
ORDER BY date DESC
LIMIT 1
)
ON p.user_id = u.id
WHERE u.package = 1
You need to have a subquery to get their latest date per user ID.
SELECT u.*, p.*
FROM users u
INNER JOIN payments p
ON u.id = p.user_ID
INNER JOIN
(
SELECT user_ID, MAX(date) maxDate
FROM payments
GROUP BY user_ID
) b ON p.user_ID = b.user_ID AND
p.date = b.maxDate
WHERE u.package = 1
SELECT u.*, p.*
FROM users AS u
INNER JOIN payments AS p ON p.id = (
SELECT id
FROM payments AS p2
WHERE p2.user_id = u.id
ORDER BY date DESC
LIMIT 1
)
Or
SELECT u.*, p.*
FROM users AS u
INNER JOIN payments AS p ON p.user_id = u.id
WHERE NOT EXISTS (
SELECT 1
FROM payments AS p2
WHERE
p2.user_id = p.user_id AND
(p2.date > p.date OR (p2.date = p.date AND p2.id > p.id))
)
These solutions are better than the accepted answer because they work correctly when there are multiple payments with same user and date. You can try on SQL Fiddle.
SELECT u.*, p.*, max(p.date)
FROM payments p
JOIN users u ON u.id=p.user_id AND u.package = 1
GROUP BY u.id
ORDER BY p.date DESC
Check out this sqlfiddle
SELECT u.*
FROM users AS u
INNER JOIN (
SELECT p.*,
#num := if(#id = user_id, #num + 1, 1) as row_number,
#id := user_id as tmp
FROM payments AS p,
(SELECT #num := 0) x,
(SELECT #id := 0) y
ORDER BY p.user_id ASC, date DESC)
ON (p.user_id = u.id) and (p.row_number=1)
WHERE u.package = 1
You can try this:
SELECT u.*, p.*
FROM users AS u LEFT JOIN (
SELECT *, ROW_NUMBER() OVER(PARTITION BY userid ORDER BY [Date] DESC) AS RowNo
FROM payments
) AS p ON u.userid = p.userid AND p.RowNo=1
There are two problems with your query:
Every table and subquery needs a name, so you have to name the subquery INNER JOIN (SELECT ...) AS p ON ....
The subquery as you have it only returns one row period, but you actually want one row for each user. For that you need one query to get the max date and then self-join back to get the whole row.
Assuming there are no ties for payments.date, try:
SELECT u.*, p.*
FROM (
SELECT MAX(p.date) AS date, p.user_id
FROM payments AS p
GROUP BY p.user_id
) AS latestP
INNER JOIN users AS u ON latestP.user_id = u.id
INNER JOIN payments AS p ON p.user_id = u.id AND p.date = latestP.date
WHERE u.package = 1
#John Woo's answer helped me solve a similar problem. I've improved upon his answer by setting the correct ordering as well. This has worked for me:
SELECT a.*, c.*
FROM users a
INNER JOIN payments c
ON a.id = c.user_ID
INNER JOIN (
SELECT user_ID, MAX(date) as maxDate FROM
(
SELECT user_ID, date
FROM payments
ORDER BY date DESC
) d
GROUP BY user_ID
) b ON c.user_ID = b.user_ID AND
c.date = b.maxDate
WHERE a.package = 1
I'm not sure how efficient this is, though.
SELECT U.*, V.* FROM users AS U
INNER JOIN (SELECT *
FROM payments
WHERE id IN (
SELECT MAX(id)
FROM payments
GROUP BY user_id
)) AS V ON U.id = V.user_id
This will get it working
Matei Mihai given a simple and efficient solution but it will not work until put a MAX(date) in SELECT part so this query will become:
SELECT u.*, p.*, max(date)
FROM payments p
JOIN users u ON u.id=p.user_id AND u.package = 1
GROUP BY u.id
And order by will not make any difference in grouping but it can order the final result provided by group by. I tried it and it worked for me.
My answer directly inspired from #valex very usefull, if you need several cols in the ORDER BY clause.
SELECT u.*
FROM users AS u
INNER JOIN (
SELECT p.*,
#num := if(#id = user_id, #num + 1, 1) as row_number,
#id := user_id as tmp
FROM (SELECT * FROM payments ORDER BY p.user_id ASC, date DESC) AS p,
(SELECT #num := 0) x,
(SELECT #id := 0) y
)
ON (p.user_id = u.id) and (p.row_number=1)
WHERE u.package = 1
This is quite simple do The inner join and then group by user_id and use max aggregate function in payment_id assuming your table being user and payment query can be
SELECT user.id, max(payment.id)
FROM user INNER JOIN payment ON (user.id = payment.user_id)
GROUP BY user.id
If you do not have to return the payment from the query you can do this with distinct, like:
SELECT DISTINCT u.*
FROM users AS u
INNER JOIN payments AS p ON p.user_id = u.id
This will return only users which have at least one record associated in payment table (because of inner join), and if user have multiple payments, will be returned only once (because of distinct), but the payment itself won't be returned, if you need the payment to be returned from the query, you can use for example subquery as other proposed.

mysql multiple fields from subquery

SELECT
(SELECT date FROM forums WHERE topic_id=f.id OR id=f.id ORDER BY id DESC LIMIT 1) as last_reply,
f.*, p.id as pid, p.name FROM forums f
INNER JOIN players p ON p.id = f.author
WHERE f.topic_id=0 ORDER BY f.id DESC
In the subquery, I'd like to return not only the date field, but also the author field as well. how can I do this?
looked at a similar post but can't apply it to mine.
I would do it something like this:
SELECT
(SELECT date FROM forums WHERE topic_id=f.id OR id=f.id ORDER BY id DESC LIMIT 1) as last_reply,
(SELECT author FROM forums WHERE topic_id=f.id OR id=f.id ORDER BY id DESC LIMIT 1) as last_author,
f.*, p.id as pid, p.name FROM forums f
INNER JOIN players p ON p.id = f.author
WHERE f.topic_id=0 ORDER BY f.id DESC
I would actually repeat the subquery again
Maybe something like this can help you :
SELECT MAX(f.date), f.author
FROM forums f
INNER JOIN players p ON
p.id = f.author
WHERE f.tpoic_id = 0
GROUP BY f.author
ORDER BY f.id DESC
But it's difficult with no structure of tables.
Good luck.

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;