How can I add a column in sql query from nested join? - sql

I am using Access and having three tables: Category, Topic and Post. What I am trying to achieve is to include in the result the date of the last post creation.
Post table has a CreatedOn column.
Currently my query looks like this:
SELECT
category.id,
category.CategoryName,
category.Description,
count(tp.topic.id) AS NumberOfTopics,
Sum(numofposts) AS NumberOfPosts
FROM category
LEFT JOIN (
SELECT
topic.id,
topic.categoryId,
count(post.id) AS numofposts
FROM
topic
LEFT JOIN post ON topic.id = post.topicId
GROUP BY topic.id, topic.categoryId
) AS TP ON category.id=TP.categoryid
GROUP BY category.id, category.CategoryName, category.Description;
My best attempt (in my opinion) was to extend a query in a following way:
SELECT
category.id,
category.CategoryName,
category.Description,
COUNT(topic.id) AS NumberOfTopics,
sum(numofposts) AS NumberOfPosts,
"DUMMY" AS last
FROM category
LEFT JOIN (
SELECT
topic.id,
COUNT(ps.id) AS numofposts,
topic.categoryId
FROM topic
LEFT JOIN (
SELECT
id,
CreatedOn,
topicId
FROM post
ORDER BY post.CreatedOn DESC
) AS ps ON topic.id = ps.topicId
GROUP BY topic.id, topic.categoryId
) AS TP ON category.id=TP.categoryid
GROUP BY category.id, category.CategoryName, category.Description;
Unfortunately I've tried a many different ways to get it, but I am still unsuccessful.
Thanks in advance.

Using inline SELECT clauses:
SELECT
c.id,
c.CategoryName,
c.Description,
(SELECT count(t.id)
FROM topic t
WHERE t.categoryId = c.id
) AS NumberOfTopics,
(SELECT count(p.id)
FROM post p
JOIN topic t ON p.topicId = t.id
WHERE t.categoryId = c.id
) AS NumberOfPosts,
(SELECT max(p.createdOn) FROM post p
JOIN topic t ON p.topicId = t.id
WHERE t.categoryId = c.id
) AS LastPostDate
FROM category c;
This may not be the most efficient query, but produces the right results.
See http://www.sqlfiddle.com/#!3/165d1/2 for a demo.

Related

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
;

Get latest date in multiple join

I'm struggling with this one and even though there are some somewhat similar questions on Stack Overflow I can't find the right solution. I have three tables: categories, topics and posts where I want to join them and show each category in categories together with the subject of the latest topic from topics that had something posted in it and finally the latest date that post was posted from the posts table.
I successfully get everything to display like I want except that I can't get the latest post record from each category.
Here's the query:
SELECT c.cat_id, c.cat_name, c.cat_description, t.topic_subject, t.topic_id, p.post_date FROM categories c
LEFT JOIN topics t ON c.cat_id = t.topic_cat
LEFT JOIN posts p ON p.post_topic = t.topic_id AND p.post_date = ( SELECT MAX(post_date) as post_date FROM posts WHERE post_topic = t.topic_id )
GROUP BY c.cat_id ORDER BY UNIX_TIMESTAMP(post_date) DESC
I realize it should have something to do with that the topic_id in the WHERE clause is not the one I'm looking for. ANother one like this:
SELECT c.cat_id, c.cat_name, c.cat_description, t.topic_subject, t.topic_id, p.post_date FROM categories c
LEFT JOIN topics t ON c.cat_id = t.topic_cat AND t.topic_id = ( SELECT post_topic FROM posts ORDER BY UNIX_TIMESTAMP(post_date) DESC LIMIT 1 )
LEFT JOIN posts p ON p.post_topic = t.topic_id AND p.post_date = ( SELECT MAX(post_date) as post_date FROM posts WHERE post_topic = t.topic_id )
GROUP BY c.cat_id ORDER BY UNIX_TIMESTAMP(post_date) DESC
will obviously only show the right post for the category with the latest post overall in it. How to get the latest post for all of the categories?
SELECT c.cat_id, c.cat_name, c.cat_description, tp.topic_subject, tp.topic_id, tp.maxPostDate
FROM categories c
LEFT JOIN (select t.topic_cat,t.topic_subject,t.topic_id, max(post_date) maxPostDate from topics t
LEFT JOIN posts p ON p.post_topic = t.topic_id
group by t.topic_cat,t.topic_subject,t.topic_id) tp
on (c.cat_id=tp.topic_cat)
where tp.maxPostDate = (select max(post_date) from topics t2
LEFT JOIN posts p2 ON p2.post_topic = t2.topic_id
where t2.topic_cat=c.cat_id
)
ORDER BY UNIX_TIMESTAMP(tp.maxPostDate) DESC
Note: This query can output for example 2 rows for one category if it has 2 different topics with the same maximum post date.
First: in your Select queries you must use some agregate functions for the fields that you don't include in the GROUP BY clause.
Now. For gettin the post with max timestamp for each category, you have to make a two steps query. In first step you will get the max timestamp for category and in the second all the other fields. Yes, we'll use cat_id + timestamp as primary key. It could provide some duplicate records for the same cat_id, but it will be very extrange that in real time ocours.
The code will be similar to:
SELECT c.cat_id, MAX( p.post_date ) as max_date
into #tmp_table
FROM categories c
LEFT JOIN topics t ON c.cat_id = t.topic_cat
LEFT JOIN posts p ON p.post_topic = t.topic_id
GROUP BY c.cat_id
SELECT * -- what you'll need
from #tmp_table as tmp
LEFT JOIN categories as c on c.cat_id = tmp.cad_id
LEFT JOIN topics t ON c.cat_id = t.topic_cat
LEFT JOIN posts p ON p.post_topic = t.topic_id AND p.post_date = tmp.max_date
-- order by -- what you'll need
It's a solution,
better one could be nice.

Is there a simpler way to write this query?

I have three tables. Categories, topics, and posts. Each topic has a foreign key that references the category that it's under. Each post has a foreign key that references the topic that it's under.
The purpose of this query is to basically be the front page query. I want each category along with the number of topics and number of posts in each category. This is the query I have, and it works. Is this the simplest way of going about it?
SELECT c.*,
COUNT(t.idCategory) AS tCount,
p.pCount
FROM categories AS c
LEFT JOIN topics AS t
ON c.id = t.idCategory
LEFT JOIN (SELECT t.idCategory,
COUNT(p2.idTopic) AS pCount
FROM topics AS t
LEFT JOIN posts AS p2
ON t.id = p2.idTopic
GROUP BY t.idCategory) AS p
ON c.id = p.idCategory
GROUP BY t.idCategory
ORDER BY c.id
Thanks!
If you are talking of simplicity I guess this could be an answer:
Select
c.*,
(Select count(*) from topic t where c.id = t.idCategory) as tCount,
(Select count(*) from posts p join topics t2 on t2.id = p.idTopic where c.id = t2.idCategory) as pCount
From categories c
You can put together the topics and posts inside the derived table first before joining with the categories:
SELECT
c.id,
COUNT(tp.id) AS TotalTopics,
tp.TotalPosts
FROM categories AS c
LEFT JOIN (
SELECT
t.id,
t.idCategory,
COUNT(p.id) AS TotalPosts
FROM topics AS t
LEFT JOIN posts AS p ON t.id = p.idTopic
GROUP BY
t.id,
t.idCategory) AS tp ON c.id = tp.idCategory
GROUP BY
c.id,
tp.TotalPosts
ORDER BY c.id

Single SQL query on many to many relationship

I have a simple database with few tables (and some sample columns):
Posts (ID, Title, Content)
Categories (ID, Title)
PostCategories (ID, ID_Post, ID_Category)
Is there a way to create single SQL query which will return posts with categories that are assigned to each post?
You can use the GROUP_CONCAT function
select p.*, group_concat(DISTINCT c.title ORDER BY c.title DESC SEPARATOR ', ')
from Posts p
inner join PostCategories pc on p.ID = pc.ID_Post
inner join Categories c on pc.ID_Category = c.ID
group by p.id, p.title, p.content
Simple joins work well.
SELECT posts.id, posts.title, categories.id, categories.title
FROM posts
JOIN posts_categories ON posts.id = posts_categories.post_id
JOIN categories ON posts_categories.category_id = categories.id
select p.*, c.*
from Posts p
inner join PostCategories pc on p.ID = pc.ID_Post
inner join Categories c on pc.ID_Category = c.ID
If you mean with only one record per post, I will need to know what database platform you are using.
Sure. If I understand your question correctly, it should be as simple as
SELECT Posts.title, Categories.title
FROM Posts, Categories, PostCategories
WHERE PostCategories.ID_Post = Posts.ID AND PostCategories.ID_Category = Categories.ID
ORDER BY Posts.title, Categories.title;
Getting one row per Post will be a little more complicated, and will depend on what RDBMS you're using.
We can use this query also.
select e.*,c.* from Posts e, Categories c, PostCategories cp where cp.id in ( select s.id from PostCategories s where s.empid=e.id and s.companyid=c.id );

SQL Join and Count can't GROUP BY correctly?

So let's say I want to select the ID of all my blog posts and then a count of the comments associated with that blog post, how do I use GROUP BY or ORDER BY so that the returned list is in order of number of comments per post?
I have this query which returns the data but not in the order I want? Changing the group by makes no difference:
SELECT p.ID, count(c.comment_ID)
FROM wp_posts p, wp_comments c
WHERE p.ID = c.comment_post_ID
GROUP BY c.comment_post_ID;
I'm not familiar with pre-SQL92 syntax, so I'll express it in a way that I'm familiar with:
SELECT c.comment_post_ID, COUNT(c.comment_ID)
FROM wp_comments c
GROUP BY c.comment_post_ID
ORDER BY COUNT(c.comment_ID) -- ASC or DESC
What database engine are you using? In SQL Server, at least, there's no need for a join unless you're pulling more data from the posts table. With a join:
SELECT p.ID, COUNT(c.comment_ID)
FROM wp_posts p
JOIN wp_comments c ON c.comment_post_ID = p.ID
GROUP BY p.ID
ORDER BY COUNT(c.comment_ID)
SELECT p.ID, count(c.comment_ID) AS [count]
FROM wp_posts p, wp_comments c
WHERE p.ID = c.comment_post_ID
GROUP BY c.comment_post_ID;
ORDER BY [count] DESC
probably there are no related data on the comments table, so please try grouping it by the post ID, and please learn JOIN statements, it is very helpful and produces better results
SELECT p.ID, count(c.comment_ID)
FROM wp_posts p
LEFT JOIN wp_comments c ON (p.ID = c.comment_post_ID)
GROUP BY p.ID
I also encountered that kind of situation in my SQL query journeys :)