This is a common greatest-n-per-group question, but with an extra problem.
What i want is to find the latest 20 posts of a user, and for each post, load its latest 5 (or n) comments. Moreover, for pagination needs, i need to know how many other comments each post has. Is it possible to find it out with the same query? (Or at least with the less efficient queries needed)
What i've done now is finding the latest posts and their comments(all):
Finding latest posts of user:
SELECT up.primkey
, up.sender
, up.comment
, up.date
, up.admin_approved
, u.username
, u.avatar
FROM users_posts up
JOIN users u
ON up.sender=u.userkey
WHERE up.sender=?
AND up.admin_approved=0
ORDER
BY primkey DESC LIMIT $from,$to;
Then,i store each primkey in an array, in order to retrieve the comments of these posts.
Finding the comments of the posts:
SELECT c.primkey,c.post_id, c.sender, c.comment, c.date, u.username, u.avatar
FROM users_posts_comments c
LEFT JOIN users u ON c.sender=u.userkey
WHERE c.post_id IN('.implode(",", $posts_array).') AND c.`admin_approved`=0
ORDER BY c.primkey DESC;
After that, i store each comment to a new array ($comments[$post_id][]=comment info) and then i output the result.
What i want is to modify the second query and limit the comments to the 5 recent, and also to find somehow how many are the total comments of each post in order to show the pagination.
Here's a fiddle... sqlfiddle.com/#!2/e92a6/1
Expected result:
post8
post7
comment-7
comment-6
comment-5
{pages}
post6
...
I know it would be difficult, so what would you recommend me to do(the most efficient way)?
Thanks.
A faster solution uses variables - but I'm old school...
This query gives you the latest 5 posts for each sender...
SELECT x.* ,COUNT(*)
FROM users_posts x
JOIN users_posts y
ON y.sender = x.sender
AND y.date >= x.date
GROUP
BY x.primkey
HAVING COUNT(*) <= 5
So now you can extend that idea to return the 3 most recent comments (if any) for each of those last 5 posts (for each sender)
SELECT a.*, upc.*
FROM
( SELECT x.*
FROM users_posts x
JOIN users_posts y
ON y.sender = x.sender
AND y.date >= x.date
GROUP
BY x.primkey
HAVING COUNT(*) <= 5
) a
LEFT
JOIN users_posts_comments upc
ON upc.post_id = a.primkey
LEFT
JOIN users_posts_comments z
ON z.post_id = upc.post_id
AND z.date >= upc.date
GROUP
BY a.sender
, a.primkey
, upc.primkey
HAVING COUNT(upc.post_id) <= 3;
Related
I guess I can't explain my problem properly. I want to explain this to you with a picture.
Picture 1
In the first picture you can see the hashtags in the trend section. These hashtags are searched for the highest total and it is checked whether the date has passed. If valid data is available, the first 5 hashtags are taken.
Picture 2
In the second picture, it is checked whether the posts in the hashtag are in the post, if any, the oldest date value is taken, LIMIT is set to 1 and the id value from the oyuncular table is matched with sid. Thus, the name of the person sharing can be accessed.
Picture 3
My English is a little bad, I hope I could explain it properly.
SELECT
social_trend.hashtag,
social_trend.total,
social_trend.tarih,
social_post.sid,
social_post.tarih,
social_post.post,
oyuncular.id,
oyuncular.isim
FROM
social_trend
INNER JOIN
social_post
ON
social_post.post LIKE '%social_trend.hashtag%' ORDER BY social_post.tarih LIMIT 1
INNER JOIN
oyuncular
ON
oyuncular.id = social_post.sid
WHERE
social_trend.tarih > UNIX_TIMESTAMP() ORDER BY social_trend.total DESC LIMIT 5
YOu should use a sibquery
and add a proper join between subqiery and social_trend
(i assumed sing both sid)
SELECT
social_trend.hashtag,
social_trend.total,
social_trend.tarih,
t.sid,
t.tarih,
t.post,
oyuncular.id,
oyuncular.isim
FROM (
select social_post.*
from social_post
INNER JOIN social_trend ON social_post.post LIKE concat('%',social_trend.hashtag,'%' )
ORDER BY social_post.tarih LIMIT 1
) t
INNER JOIN social_trend ON social_trend.hashtag= t.post
INNER JOIN oyuncular ON oyuncular.id = t.sid
WHERE
social_trend.tarih > UNIX_TIMESTAMP() ORDER BY social_trend.total DESC LIMIT 5
but looking to your new explanation and img seems you need
SELECT
t.hashtag,
t.total,
t.tarih_trend,
t.sid,
t.tarih,
t.post,
oyuncular.id,
oyuncular.isim
FROM (
select social_post.sid
, social_post.tarih
, social_post.post
, st.hashtag
, st.total
, st.tarih tarih_trend
from social_post
INNER JOIN (
select * from social_trend
WHERE social_trend.tarih > UNIX_TIMESTAMP()
order by total DESC LIMIT 5
) st ON social_post.post LIKE concat('%',st.hashtag,'%' )
ORDER BY social_post.tarih LIMIT 5
) t
INNER JOIN oyuncular ON oyuncular.id = t.sid
Question about SQL. The schema is given below:
User(userID:int, userName:varchar(30), email:varchar(30), password:varchar(30), status:varchar(15))
Video(videoID:int, userID:int, videoTitle:varchar(60), likeCount:int, dislikeCount:int, datePublished:date)
Comment(commentID:int, userID:int, videoID:int, commentText:varchar(1000), dateCommented:date)
Watch(userID:int, videoID:int, dateWatched: date)
Same variables will be using as foreign key. Because of this I do not need to write foreign keys.
A)
List the trending top three videos for a given time interval
(String dateStart, String dateEnd)
A trending video is defined to be the most viewed video in the given interval (i.e., video that is viewed the highest numberof times among all).
You should include dateStart and dateEnd in the result, it is a CLOSED interval.
Output: videoTitle, userName, number of times that the video watched.
I could not figured out how can i find top tree videos. While building inner query what should I do?
If you want the top 3 videos for a period of time, you can do:
select v.videotitle, u.username, count(*) no_watches
from watch w
inner join video v on v.videoid = w.videoid
inner join usr u on u.userid = v.userid
where w.date_watched between ? and ?
group by v.videoid, u.userid
order by no_watches desc
limit 3
The between predicate is how I understand the "closed interval". The ? relate to query parameters that contain the start and end date.
Pre-aggregation might make the query more efficient:
select v.videotitle, u.username, w.no_watches
from (
select videoid, count(*) as no_watches
from watch
where date_watched between ? and ?
group by videoid
order by no_watches desc limit 3
) w
inner join video v on v.videoid = w.videoid
inner join usr u on u.userid = v.userid
order by w.no_watches desc
limit 3
I have written an sql statement that besides all the other columns should return the number of comments and the number of likes of a certain post. It works perfectly when I don't try to get the number of times it has been shared too. When I try to get the number of time it was shared instead it returns a wrong number of like that seems to be either the number of shares and likes or something like that. Here is the code:
SELECT
[...],
count(CS.commentId) as shares,
count(CL.commentId) as numberOfLikes
FROM
(SELECT *
FROM accountSpecifics
WHERE institutionId= '{$keyword['id']}') `AS`
INNER JOIN
account A ON A.id = `AS`.accountId
INNER JOIN
comment C ON C.accountId = A.id
LEFT JOIN
commentLikes CL ON C.commentId = CL.commentId
LEFT JOIN
commentShares CS ON C.commentId = CS.commentId
GROUP BY
C.time
ORDER BY
year, month, hour, month
Could you also tell me if you think this is an efficient SQL statement or if you would do it differently? thank you!
Do this instead:
SELECT
[...],
(select count(*) from commentLikes CL where C.commentId = CL.commentId) as shares,
(select count(*) from commentShares CS where C.commentId = CS.commentId) as numberOfLikes
FROM
(SELECT *
FROM accountSpecifics
WHERE institutionId= '{$keyword['id']}') `AS`
INNER JOIN account A ON A.id = `AS`.accountId
INNER JOIN comment C ON C.accountId = A.id
GROUP BY C.time
ORDER BY year, month, hour, month
If you use JOINs, you're getting back one result set, and COUNT(any field) simply counts the rows and will always compute the same thing, and in this case the wrong thing. Subqueries are what you need here. Good luck!
EDIT: as posted below, count(distinct something) can also work, but it's making the database do more work than necessary for the answer you want to end up with.
Quick fix:
SELECT
[...],
count(DISTINCT CS.commentId) as shares,
count(DISTINCT CL.commentId) as numberOfLikes
Better approach:
SELECT [...]
, Coalesce(shares.numberOfShares, 0) As numberOfShares
, Coalesce(likes.numberOfLikes , 0) As numberOfLikes
FROM [...]
LEFT
JOIN (
SELECT commentId
, Count(*) As numberOfShares
FROM commentShares
GROUP
BY commentId
) As shares
ON shares.commentId = c.commentId
LEFT
JOIN (
SELECT commentId
, Count(*) As numberOfLikes
FROM commentLikes
GROUP
BY commentId
) As likes
ON likes.commentId = c.commentId
I cannot figure out why this is not working. Basically, I am running a subquery to count all rows of p.songid WHERE trackDeleted=0. The subquery works fine when I execute it by itself, but when I implement I get "subquery returned more than 1 row".
SELECT u.username, u.id, u.score, s.genre, s.songid, s.songTitle, s.timeSubmitted, s.userid, s.insWanted, s.bounty,
(SELECT COUNT(p.songid)
FROM songs s
LEFT JOIN users u
ON u.id = s.userid
LEFT JOIN posttracks p
ON s.songid = p.songid
WHERE p.trackDeleted=0
GROUP BY s.timeSubmitted ASC
LIMIT 25)
AS trackCount
FROM songs s
LEFT JOIN users u
ON u.id = s.userid
LEFT JOIN posttracks p
ON s.songid = p.songid
WHERE paid=1 AND s.timeSubmitted >= ( CURDATE() - INTERVAL 60 DAY )
GROUP BY s.timeSubmitted ASC
LIMIT 25
Obviously, a sub-query can't return more than one row, as this makes no sense. You only expect one value to be returned - COUNT(p.songid) - yet you GROUP BY s.timeSubmitted, which will make it return multiple rows, and multiple counts of p.songid.
Think about it this way, a subquery in the SELECT statement like you have needs to return a single value since it is going to act like just another column in your select list. Since you have a LIMIT 25 on yours, you're obviously expecting more than one value back, which is inocrrect for this usage.
OK, your query is a mess. Not only is the subquery broken, but I'm pretty sure the GROUP BY s.timeSubmitted ASC isn't doing what you think think it does. (Did you mean ORDER BY instead?) It might help if you explained in words what you're trying to accomplish.
Anyway, I'm going to take a wild guess and suggest that this might be what you want:
SELECT
u.username, u.id, u.score, s.genre, s.songid, s.songTitle,
s.timeSubmitted, s.userid, s.insWanted, s.bounty,
COUNT(p.songid) AS trackCount
FROM songs s
LEFT JOIN users u ON u.id = s.userid
LEFT JOIN posttracks p ON p.songid = s.songid AND p.trackDeleted = 0
WHERE paid = 1 AND s.timeSubmitted >= ( CURDATE() - INTERVAL 60 DAY )
GROUP BY s.songid
ORDER BY s.timeSubmitted ASC
LIMIT 25
Edit: Fixed the COUNT() so that it will correctly return 0 if there are no matching tracks.
I have a theme gallery. In the dashboard i have to display the most viewed themes BY date (today, last 7 days, last 30 days, all time).
These are the 2 involved tables:
theme
id_theme
title
views
id_view
id_theme
date
The $timestamp values are calculated with mktime() (no prob in there).
This is my current SQL query:
SELECT t.id_theme,t.title,
(SELECT COUNT(*)
FROM views
WHERE views.id_theme=t.id_theme
AND views.date BETWEEN '.$timestamp1.' AND '.$timestamp2.')
AS q
FROM theme AS t
INNER JOIN views ON t.id_theme = views.id_theme
GROUP BY views.id_theme
ORDER BY q
DESC LIMIT 10
The problem is that The catch, is that sometimes it receives themes with 0 views, and that should not happen. I tried changing the INNER JOIN with RIGHT JOIN with no results. Any ideas?
Hmm. not sure why you're using subqueries for this, seems like this would work better:
SELECT theme.id_theme, theme.title, COUNT(views.id_view) as view_count
FROM theme
LEFT JOIN views ON (theme.id_theme = views.id_theme)
GROUP BY theme.id_theme
WHERE views.date > DATE_SUB(now() INTERVAL 30 day)
ORDER BY view_count DESC
HAVING view_count > 0
SELECT t.id_theme, t.title, COUNT(*) AS q
FROM theme AS t
INNER JOIN views ON t.id_theme = views.id_theme
AND views.date BETWEEN '.$timestamp1.' AND '.$timestamp2.'
GROUP BY t.id_theme, t.title
ORDER BY q
DESC LIMIT 10