sql limit left side of left join - sql

I've got a database on which I'm trying to execute the following query:
select blog.name, blog.createDate,
post.author, post.content,
comments.author, comments.createDate
from blogs
left join post on post.blogId = blog.id
left join comments on comments.postId = post.id
limit 0,10;
The problem is that I want to query all blogs with posts with comments for the first 10 blogs. Now I'm querying the first 10 comments instead of the first 10 blogs. I want the first 10 blogs with all posts containing all comments. How do I achieve this?

You need to limit the number of blogs you are querying
Select b.Name, b.CreateDate, post.author, post.content,
comments.author, comments.createDate
From Comments c
Join Post p on c.PostId = p.Id
Join (Select Id, Name, CreateDate From Blogs limit 0, 10) b on p.BlogId = b.Id
We join the blogs as a subquery that limits to the first 10 (you would add an Order By section there if you want to define how you want to retrieve that first 10), and then join to the other tables.

Related

Group by on join and calculate max of groups

I have 3 tables as described below:
All three tables and output image
posts post table
post_comments posts comments
comments comments
Now I want to fetch the posts that have highest liked comments and the status of that comment should be active in Postgres.
OUTPUT:
posts resultant posts
NOTE: Since for post 1, the highest liked comment is inactive.
I've tried something like this:
select "posts".*
from "posts"
inner join (select id, max(likes) l from comments innner join post_comments on comments.id = post_comments.alert_id and post_comments.post_id = posts.id) a on posts.id = a.cid ...
This is not complete but I'm unable to do this.
In Postgres, you can get the active comment with the most likes for each post using distinct on:
select distinct on (pc.post_id) pc.*
from post_comments pc join
comments c
on pc.comment_id = c.id
where c.status = 'active'
order by pc.post_id, c.likes desc;
I think this is quite related to what you want.
Try something like this:
SELECT posts.*, MAX(likes) l
FROM posts
JOIN post_comments ON post_id = posts.id
LEFT JOIN comments ON comment_id = comments.id
GROUP BY posts.id

SQL Server: Join two tables and only get data of one table without identical rows

I have two tables, not the actual ones, I am trying to replicate the situation here:
tblMstPost with a large number of columns(say 20). I am adding just two here.
PostId Title
1 First Post
2 Second Post
3 Third post
tblTrnComment
CommentId PostId Comment
1 1 Hello
2 1 Hi
3 1 Hey
4 2 Test
5 3 Hello
Now I want data from post table only. I don't need any data from Comment table.
The condition to get data from post table is that I need posts which have the comment "Hello" and "Hi".
Now I can write something like this:
SELECT p.*
FROM tblMstPost AS p
INNER JOIN tblTrnComment AS c
ON p.PostId = c.PostId
WHERE c.CommentId IN (1, 2, 5)
Above query will give results with two identical rows with PostId 1.
PostId Title
1 First Post
1 First Post
3 Third post
Now I want to remove one of the identical rows. I have tried DISTINCT but one of my columns on Post table has text data type and for this reason, DISTINCT is not working. When I GROUP BY p.PostId SQL server asks the same for all of the columns: [column] is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause. Please note that post table has a large number of columns and I don't want to add all of them to GROUP BY statement.
Is there any solution other than using WHERE IN subquery?
Update: I have found this video on Youtube which clearly explains exists and my question: https://youtu.be/zfgJ3ZmAgNw
Try
select * from tblMstPost p
where exists(select 1 from tblTrnComment
where PostId = p.PostId and Comment in ('Hi', 'Hello'));
JOIN might produce duplicate rows as because of tblTrnComment table has many rows associate with PostId from tblMstPost table. So, what you need is IN or EXISTS
So, you don't need to use JOIN
select p.*
from tblMstPost p
where p.PostId in (select PostId
from tblTrnComment
where Comment in ('Hi', 'Hello')
);
Now, i would suggest EXISTS instead
select p.*
from tblMstPost p
where exists(select 1
from tblTrnComment c
where c.PostId = p.PostId and
c.Comment in ('Hi', 'Hello')
);
For grouping you need to group all of the rows, as you put group by id the compiler didnt know what to do with the titles, does it group the same, should it aggregate them all. so you list all non aggregated columns in your group by like below.
SELECT p.*
FROM tblMstPost AS p
INNER JOIN tblTrnComment AS c
ON p.PostId = c.PostId
GROUP BY PostId, Title
WHERE c.CommentId IN (1, 2, 5)
To avoid a WHERE IN clause, you could join to a distinct set of Post Ids which have the appropriate comment:
SELECT p.*
FROM tblMstPost p
JOIN ( SELECT DISTINCT PostId
FROM tblTrnComment
WHERE Comment = 'Hello'
OR Comment = 'Hi' -- Or have an IN here, or a lookup etc
) t
ON p.PostId = t.PostId

Order by join column but use distinct on another

I'm building a system in which there are the following tables:
Song
Broadcast
Station
Follow
User
A user follows stations, which have songs on them through broadcasts.
I'm building a "feed" of songs for a user based on the stations they follow.
Here's the query:
SELECT DISTINCT ON ("broadcasts"."created_at", "songs"."id") songs.*
FROM "songs"
INNER JOIN "broadcasts" ON "songs"."shared_id" = "broadcasts"."song_id"
INNER JOIN "stations" ON "broadcasts"."station_id" = "stations"."id"
INNER JOIN "follows" ON "stations"."id" = "follows"."station_id"
WHERE "follows"."user_id" = 2
ORDER BY broadcasts.created_at desc
LIMIT 18
Note: shared_id is the same as id.
As you can see I'm getting duplicate results, which I don't want. I found out from a previous question that this was due to selecting distinct on broadcasts.created_at.
My question is: How do I modify this query so it will return only unique songs based on their id but still order by broadcasts.created_at?
Try this solution:
SELECT a.maxcreated, b.*
FROM
(
SELECT bb.song_id, MAX(bb.created_at) AS maxcreated
FROM follows aa
INNER JOIN broadcasts bb ON aa.station_id = bb.station_id
WHERE aa.user_id = 2
GROUP BY bb.song_id
) a
INNER JOIN songs b ON a.song_id = b.id
ORDER BY a.maxcreated DESC
LIMIT 18
The FROM subselect retrieves distinct song_ids that are broadcasted by all stations the user follows; it also gets the latest broadcast date associated with each song. We have to encase this in a subquery because we have to GROUP BY on the columns we're selecting from, and we only want the unique song_id and the maxdate regardless of the station.
We then join that result in the outer query to the songs table to get the song information associated with each unique song_id
You can use Common Table Expressions (CTE) if you want a cleaner query (nested queries make things harder to read)
I would look like this:
WITH a as (
SELECT bb.song_id, MAX(bb.created_at) AS maxcreated
FROM follows aa
INNER JOIN broadcasts bb ON aa.station_id = bb.station_id
INNER JOIN songs cc ON bb.song_id = cc.shared_id
WHERE aa.user_id = 2
GROUP BY bb.song_id
)
SELECT
a.maxcreated,
b.*
FROM a INNER JOIN
songs b ON a.song_id = b.id
ORDER BY
a.maxcreated DESC
LIMIT 18
Using a CTE offers the advantages of improved readability and ease in maintenance of complex queries. The query can be divided into separate, simple, logical building blocks. These simple blocks can then be used to build more complex, interim CTEs until the final result set is generated.
Try by adding GROUP BY Songs.id
I had a very similar query I was doing between listens, tracks and albums and it took me a long while to figure it out (hours).
If you use a GROUP_BY songs.id, you can get it to work by ordering by MAX(broadcasts.created_at) DESC.
Here's what the full SQL looks like:
SELECT songs.* FROM "songs"
INNER JOIN "broadcasts" ON "songs"."shared_id" = "broadcasts"."song_id"
INNER JOIN "stations" ON "broadcasts"."station_id" = "stations"."id"
INNER JOIN "follows" ON "stations"."id" = "follows"."station_id"
WHERE "follows"."user_id" = 2
GROUP BY songs.id
ORDER BY MAX(broadcasts.created_at) desc
LIMIT 18;

SQL picking same entry multiple times

SELECT p.*
FROM StatusUpdates p
JOIN FriendRequests fr
ON (fr.From = p.AuthorId OR fr.To = p.AuthorId)
WHERE fr.To = ".$Id." OR fr.From = ".$Id."
AND fr.Accepted = 1
ORDER BY p.DatePosted DESC
I'm using this SQL code at the moment which somone wrote for me on a different question. I'm using PHP, but that shouldn't make much difference, since the only thing I'm doing with it is concatenating a variable into it.
What it's meant to do is go through all your friends and get all their status posts, and order them. It works fine, but it picks out "$Id"'s posts either not at all, or the amount of friends you have
Eg, if you had 5 friends, it would pick our your posts 5 times. I only want it to do this once. How could I do this?
You need to use LEFT JOIN instead of join if you want to display post id regardless of number of friends. And GROUP BY p.id in order not to display the same posts more than 1 time:
SELECT p.*
FROM StatusUpdates p
LEFT JOIN FriendRequests fr
ON ((fr.From = p.AuthorId OR fr.To = p.AuthorId) AND fr.Accepted = 1)
WHERE p.AuthorId = ".$Id."
GROUP BY p.id
ORDER BY p.DatePosted DESC
Either GROUP BY p.id or SELECT DISTINCT p.id, p.*

SQL: Get all posts with any comments

I need to construct some rather simple SQL, I suppose, but as it's a rare event that I work with DBs these days I can't figure out the details.
I have a table 'posts' with the following columns:
id, caption, text
and a table 'comments' with the following columns:
id, name, text, post_id
What would the (single) SQL statement look like which retrieves the captions of all posts which have one or more comments associated with it through the 'post_id' key? The DBMS is MySQL if it has any relevance for the SQL query.
select p.caption, count(c.id)
from posts p join comments c on p.id = c.post_id
group by p.caption
having count (c.id) > 0
SELECT DISTINCT p.caption, p.id
FROM posts p,
comments c
WHERE c.post_ID = p.ID
I think using a join would be a lot faster than using the IN clause or a subquery.
SELECT DISTINCT caption
FROM posts
INNER JOIN comments ON posts.id = comments.post_id
Forget about counts and subqueries.
The inner join will pick up all the comments that have valid posts and exclude all the posts that have 0 comments. The DISTINCT will coalesce the duplicate caption entries for posts that have more then 1 comment.
I find this syntax to be the most readable in this situation:
SELECT * FROM posts P
WHERE EXISTS (SELECT * FROM Comments WHERE post_id = P.id)
It expresses your intent better than most of the others in this thread - "give me all the posts ..." (select * from posts) "... that have any comments" (where exist (select * from comments ... )). It's essentially the same as the joins above, but because you're not actually doing a join, you don't have to worry about getting duplicates of the records in Posts, so you'll just get one record per post.
SELECT caption FROM posts
INNER JOIN comments ON comments.post_id = posts.id
GROUP BY posts.id;
No need for a having clause or count().
edit: Should be a inner join of course (to avoid nulls if a comment is orphaned), thanks to jishi.
Just going off the top of my head here but maybe something like:
SELECT caption FROM posts WHERE id IN (SELECT post_id FROM comments HAVING count(*) > 0)
You're basically looking at performing a subquery --
SELECT p.caption FROM posts p WHERE (SELECT COUNT(*) FROM comments c WHERE c.post_id=p.id) > 1;
This has the effect of running the SELECT COUNT(*) subquery for each row in the posts table. Depending on the size of your tables, you might consider adding an additional column, comment_count, into your posts table to store the number of corresponding comments, such that you can simply do
SELECT p.caption FROM posts p WHERE comment_count > 1