SQL union two complex query - sql

There are 4 tables,
posts(id
post_share(shared_by, post_id
friendship(friend_one, friend_two, status)
add_viewer(id
By using above 4 tables I need to get posts to render in the user's news feed. Currently I only use the first query to do that which means I do not add shared post by user's friends to his/her news feed. Now I need to show the shared post, too just like in facebook.
For that I created new query ( 2nd one ). Now I want to connect both of them together and result only one table or I would like to know if there's a better way to handle this
By assuming currently logged in user_id is 22,
SELECT concat(a.fname, ' ', a.lname) as name, a.id as user_id , p.id as post_id, p.content, p.media FROM posts p
INNER JOIN add_viewers a
ON p.user_id = a.id
WHERE p.user_id in (
SELECT a.id FROM friendships f
INNER JOIN add_viewers a
ON f.friend_one = a.id OR f.friend_two = a.id
WHERE friend_one=22 OR friend_two=22 AND f.status='confirmed'
GROUP BY a.id
)
ORDER BY created_at DESC
LIMIT 10
OFFSET 0
in 2nd query, s.shared_by means the person who share the post and p.user_id means the person who created the post.
SELECT concat(a.fname, ' ', a.lname) as name, a.id as user_id , p.id as post_id, p.content, p.media FROM post_shares s
INNER JOIN posts p
ON p.id = s.post_id
INNER JOIN add_viewers a
ON p.user_id = a.id
WHERE s.shared_by in (
SELECT a.id FROM friendships f
INNER JOIN add_viewers a
ON f.friend_one = a.id OR f.friend_two = a.id
WHERE friend_one=22 OR friend_two=22 AND f.status='confirmed'
GROUP BY a.id
)
ORDER BY created_at DESC
LIMIT 10
OFFSET 0

Try this
With t as
(SELECT a.id as aid FROM friendships f
INNER JOIN add_viewers a
ON f.friend_one = a.id OR
f.friend_two = a.id
WHERE friend_one=22 OR
friend_two=22 AND
f.status='confirmed'
GROUP BY a.id)
SELECT concat(a.fname, ' ', a.lname) as name, a.id as user_id , p.id as post_id, p.content, p.media FROM
post_shares s
INNER JOIN posts p
ON p.id = s.post_id
INNER JOIN t
ON p.user_id = t.aid
OR s.shared_by = t.aid
ORDER BY created_at DESC
LIMIT 10
OFFSET 0

Related

Nested json_agg

I have this postgresql query, which returns a json containing all books titles, the first and lastname of their authors and an array of all reviews titles + the first and lastname of the reviewer.
SELECT json_build_object(
'title', a.title,
'author_firstname', b.firstname,
'author_lastname', b.lastname,
'reviews', json_agg(json_build_object(
'review_title', c.title,
'reviewer_firstname', d.firstname,
'reviewer_lastname', d.lastname
))
)
FROM book AS a
INNER JOIN person AS b ON a.author_id = b.id
LEFT JOIN review AS c ON c.book_id = a.id
INNER JOIN person AS d ON c.reviewer_id = d.id
GROUP BY a.id, b.id
This works and is quite fast. I need to extend this query to include all the titles of the books of the reviewers too.
I followed the logic I used for the first left join
SELECT json_build_object(
'title', a.title,
'author_firstname', b.firstname,
'author_lastname', b.lastname,
'reviews', json_agg(json_build_object(
'review_title', c.title,
'reviewer_firstname', d.firstname,
'reviewer_lastname', d.lastname,
'reviewer_books', json_agg(json_build_object(
'book_title', e.title
))
))
)
FROM book AS a
INNER JOIN person AS b ON a.author_id = b.id
LEFT JOIN review AS c ON c.book_id = a.id
INNER JOIN person AS d ON c.reviewer_id = d.id
LEFT JOIN book AS e ON e.author_id = d.id
GROUP BY a.id, b.id
But this does not work because aggregate function calls cannot be nested. Is there a solution to this, which can be extended to include even deeper relations (like all the reviews of all the books of the reviewer) and is relatively efficient.
Those databases are confusing me, so thank you for your help!
You need multiple levels of aggregation. This is something like:
SELECT json_build_object(
'title', b.title,
'author_firstname', p.firstname,
'author_lastname', p.lastname,
r.reviews
)
FROM book b INNER JOIN
person p
ON b.author_id = pb.id LEFT JOIN
(SELECT r.book_id,
json_agg(json_build_object('review_title', r.title,
'reviewer_firstname', pr.firstname,
'reviewer_lastname', pr.lastname,
'reviewer_books', rb.reviewer_books
)
) as reviews
FROM review r JOIN
person pr
on r.reviewer_id = pr.id JOIN
(SELECT r2.reviewer_id, json_agg(json_build_object('book_title', b2.title) as reviewer_books
FROM reviews r2 JOIN
book b2
ON r2.book_id = b2.id
GROUP BY r2.reviewer_id
) rb
on r.reviewer_id = rb.reviewer_id
) r
on r.book_id = b.id;

Group by and aggregate by multiple columns

Example tables
taccount
tuser
tproject
What I want to achieve:
accountName count(u.id) count(p.id)
-----------------------------------
Account A 1 1
Account B 1 1
Account C 2 3
In other words I want a single query to join these tables together and count user's and project's per account
I tried:
SELECT
a.name as "accountName",
count(u.name),
count(p.id)
FROM "taccount" a
INNER JOIN "tuser" u ON u.account_id = a.id
INNER JOIN "tproject" p ON p.admin_id = u.id
GROUP BY u.name, a.name, p.id
But it's not grouping by account. It's giving me the following result
Any advice?
You can try below
SELECT
a.name as "accountName",
count(distinct u.name),
count(p.id)
FROM "taccount" a
INNER JOIN "tuser" u ON u.account_id = a.id
INNER JOIN "tproject" p ON p.admin_id = u.id
GROUP BY a.name
When you do Aggregate Function and If there are Column are not do Aggregate you must put in your Group By, because Aggregate functions perform a calculation on a set of rows and return a single row.
SELECT
a.name as "accountName",
count(distinct u.name),
count(p.id)
FROM
"taccount" a
INNER JOIN "tuser" u ON u.account_id = a.id
INNER JOIN "tproject" p ON p.admin_id = u.id
GROUP BY
a.name
So you need just Group By your column "accountName"
change your group by column name
SELECT
a.name as "accountName",
count(distinct u.account_id),
count(p.id)
FROM "taccount" a
INNER JOIN "tuser" u ON u.account_id = a.id
INNER JOIN "tproject" p ON p.admin_id = u.id
GROUP BY a.name
this will work:
select a.name,count(u.id),count(p.id) from
taccount a,tuser b, tproject where
a.id=b.account_id and
b.id=c.admin_id
group by a.name;

SQL Get Only Most Recent Record for A User [duplicate]

This question already has answers here:
Get top 1 row of each group
(19 answers)
Closed 4 years ago.
I have a situation where I need to write a sql query to get all of the most recent responses for a student in a classroom. I basically want to show just their most recent response, not all of their responses. I have the query to get all of the responses and order them, however I can't figure out the part where it only grabs that user's most recent record.
Below is the query I have to this point. You can see from the sample data in the image it is pulling back all responses. What I basically want is either just the most recent for a particular student OR possibly just showing the max attempt for a particular Lesson/Page number combo. I have tried playing around with partition and group bys but I haven't found the right combination yet.
SELECT U.UserName, C.Name AS 'ClassroomName', U.FirstName, U.LastName, L.Name AS 'LessonName', P.PageNumber, R.Attempt, R.Created
FROM Responses R
INNER JOIN ClassroomUsers CU ON CU.UserId = R.UserId
INNER JOIN Classrooms C ON C.Id = CU.ClassroomId
INNER JOIN Questions Q ON Q.Id = R.QuestionId
INNER JOIN Pages P ON P.Id = Q.PageId
INNER JOIN Lessons L ON L.Id = P.LessonId
INNER JOIN AspNetUsers U ON U.Id = CU.UserId
WHERE CU.ClassroomId IN (
SELECT CU.ClassroomId
FROM ClassroomUsers CU
WHERE CU.UserId = #UserId
)
ORDER BY R.Created DESC
My favorite way of doing this is using Row_Number() which will number each row based upon the criteria you set - In your case, you'd partition by U.UserName since you want one row returned for each user and order by R.Created DESC to get the latest one.
That being the case, you'd only want to get back the rows that have RN=1, so you query that out as follows:
WITH cte AS
(
SELECT
ROW_NUMBER() OVER(PARTITION BY U.UserName ORDER BY R.Created DESC) AS RN
,U.UserName, C.Name AS 'ClassroomName', U.FirstName, U.LastName, L.Name AS 'LessonName', P.PageNumber, R.Attempt, R.Created
FROM Responses R
INNER JOIN ClassroomUsers CU ON CU.UserId = R.UserId
INNER JOIN Classrooms C ON C.Id = CU.ClassroomId
INNER JOIN Questions Q ON Q.Id = R.QuestionId
INNER JOIN Pages P ON P.Id = Q.PageId
INNER JOIN Lessons L ON L.Id = P.LessonId
INNER JOIN AspNetUsers U ON U.Id = CU.UserId
WHERE CU.ClassroomId IN (
SELECT CU.ClassroomId
FROM ClassroomUsers CU
WHERE CU.UserId = #UserId
)
)
SELECT * FROM cte WHERE RN = 1
Hope that makes sense / helps!!
Just another option is using the WITH TIES clause.
Example
SELECT top 1 with ties
U.UserName
, C.Name AS 'ClassroomName'
, U.FirstName
, U.LastName
, L.Name AS 'LessonName'
, P.PageNumber
, R.Attempt
, R.Created
FROM Responses R
INNER JOIN ClassroomUsers CU ON CU.UserId = R.UserId
INNER JOIN Classrooms C ON C.Id = CU.ClassroomId
INNER JOIN Questions Q ON Q.Id = R.QuestionId
INNER JOIN Pages P ON P.Id = Q.PageId
INNER JOIN Lessons L ON L.Id = P.LessonId
INNER JOIN AspNetUsers U ON U.Id = CU.UserId
WHERE CU.ClassroomId IN (
SELECT CU.ClassroomId
FROM ClassroomUsers CU
WHERE CU.UserId = #UserId
)
Order By ROW_NUMBER() OVER(PARTITION BY U.UserName ORDER BY R.Created DESC)

How can I select the highest post per topic and associate them given this schema design?

I am working on a small forum component for a site and I am creating a page where I want to display each topic along with its highest rated answer. Here are what the tables look like:
POST USER TOPIC
id id id
date name title
text bio date
views
likes
topic_id
author_id
My query looks like so:
select
u.id, u.name, u.bio,
p.id, p.date, p.text, p.views, p.likes,
t.id, t.title, t.date
from
( select p.id, max(p.likes) as likes, p.topic_id
from post as p group by p.topic_id ) as q
inner join post as p on q.id = p.id
inner join topic as t on t.id = q.topic_id
inner join user as u on u.id = p.author_id
order by date desc;
One of the problems I'm having running this is withing "q". Postgresql wont let me run the "q" query because it wants "p.id" to be in the "group by" clause or in an aggregate function. I tried to use "distinct on (p.id)" but I got the same error message: p.id must appear in the GROUP BY clause or be used in an aggregate function.
Without the p.id attribute, I cannot meaningfully link it to the other tables; is there another way of accomplishing this?
;WITH cte AS (
SELECT
u.id AS UserId
,u.name
,u.bio
,p.id AS PostId
,p.[date] AS PostDate
,p.text
,p.views
,p.Likes
,t.id AS TopidId
,t.title
,t.[date] AS TopicDate
,p.Likes
,ROW_NUMBER() OVER (PARTITION BY t.id ORDER BY p.Likes DESC, p.[date] DESC) AS RowNum
,DENSE_RANK() OVER (PARTITION BY t.id ORDER BY p.Likes DESC) AS RankNum
FROM
topic t
INNER JOIN post p
ON t.id = p.topic_id
INNER JOIN [user] u
ON p.author_id = u.id
)
SELECT *
FROM
cte
WHERE
RowNum = 1
;
switch RowNum to RankNum if you want to see ties for most liked
This is a common need: when grouping, show each group's first/last a ranked by some other criteria b. I don't have a name for it, but this seems to be the canonical question. You can see there are a lot of choices! My favorite solution is probably a lateral join:
SELECT u.id, u.name, u.bio,
p.id, p.date, p.text, p.views, p.likes,
t.id, t.title, t.date
FROM topic t
LEFT OUTER JOIN LATERAL (
SELECT *
FROM post
WHERE post.topic_id = t.id
ORDER BY post.likes DESC
LIMIT 1
) p
ON true
LEFT OUTER JOIN "user" u
ON p.author_id = u.id
;
SELECT
u.id AS uid, u.name, u.bio
, p.id AS pid, p."date" AS pdate, p.text, p.views, p.likes
, t.id AS tid, t.title, t."date" AS tdate
FROM post p
JOIN topic t ON t.id = p.topic_id
JOIN user u ON u.id = p.author_id
WHERE NOT EXISTS ( SELECT *
FROM post nx
WHERE nx.topic_id = p.topic_id
AND nx.likes > p.likes)
ORDER BY p."date" DESC
;

Incorrect syntax near the keyword 'select'. What I did wrong?

with Tagvote as (
SELECT --TOP 20
p.OwnerUserId,
t.TagName,
COUNT(*) over (partition by p.OwnerUserId Order by t.TagName)AS UpVotes
FROM Tags t
INNER JOIN PostTags pt ON pt.TagId = t.Id
INNER JOIN Posts p ON p.ParentId = pt.PostId
INNER JOIN Votes v ON v.PostId = p.Id
WHERE
v.VoteTypeId = 2
and OwnerUserId in
(select distinct Id from Users
where ID=114899)
GROUP BY p.OwnerUserId, t.TagName
),
list as (
select distinct Posts.OwnerUserId as userid, b.TagName from Posts,
(Select distinct t.TagName from Tags t) as b
where Posts.OwnerUserId in
(select distinct id from Users
where ID=114899)
select distinct
l.userid,
l.TagName,
Tagvote.UpVotes as [votetag]
from list l
left outer join Tagvote on Tagvote.TagName=l.TagName
and l.userid=Tagvote.OwnerUserId
ORDER BY v.userid asc, v.UpVote DESC
;