Is there a simpler way to write this query? - sql

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

Related

Counting associations from multiple tables

I want to see how many association each of my records in a given table have. Some of these association have some conditions attached to them
So far I have
-- Count app associations
SELECT
distinct a.name,
COALESCE(v.count, 0) as visitors,
COALESCE(am.count, 0) AS auto_messages,
COALESCE(c.count, 0) AS conversations
FROM apps a
LEFT JOIN (SELECT app_id, count(*) AS count FROM visitors GROUP BY 1) v ON a.id = v.app_id
LEFT JOIN (SELECT app_id, count(*) AS count FROM auto_messages GROUP BY 1) am ON a.id = am.app_id
LEFT JOIN (
SELECT DISTINCT c.id, app_id, count(c) AS count
FROM conversations c LEFT JOIN messages m ON m.conversation_id = c.id
WHERE m.visitor_id IS NOT NULL
GROUP BY c.id) c ON a.id = c.app_id
WHERE a.test = false
ORDER BY visitors DESC;
I run into problem with the last join statement for conversations. I want to count the number of conversations that have at least 1 message where the visitor_id is not null. For some reason, I get multiple records for each app, ie. the conversations are not being grouped properly.
Any ideas?
My gut feeling, based on limited understanding of the big picture: in the nested query selecting from conversations,
remove DISTINCT
remove c.id from SELECT list
GROUP BY c.app_id instead of c.id
EDIT: try this
...
LEFT JOIN (
SELECT app_id, count(*) AS count
FROM conversations c1
WHERE
EXISTS (
SELECT *
FROM messages m
WHERE m.conversation_id = c1.id and
M.visitor_id IS NOT NULL
)
GROUP BY c1.app_id) c
ON a.id = c.app_id

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

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.

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.

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 );

Simple SQL question about getting rows and associated counts

this oughta be an easy one.
My question is very similar to this one; basically, I've got a table of posts, a table of comments with a foreign key for the post_id, and a table of votes with a foreign key for the post id. I'd like to do a single query and get back a result set containing one row per post, along with the count of associated comments and votes.
From the question I've linked to above, it seems that for getting a table back containing just a row for each post and a comment count, this is the right approach:
SELECT a.ID, a.Title, COUNT(c.ID) AS NumComments
FROM Articles a
LEFT JOIN Comments c ON c.ParentID = a.ID
GROUP BY a.ID, a.Title
I thought adding vote count would be as easy as adding another left join, as in
SELECT a.ID, a.Title, COUNT(c.ID) AS NumComments, COUNT(v.id AS NumVotes)
FROM Articles a
LEFT JOIN Comments c ON c.ParentID = a.ID
LEFT JOIN Votes v ON v.ParentID = a.ID
GROUP BY a.ID, a.Title
but I'm getting bad numbers back. What am I missing?
SELECT
a.ID,
a.Title,
COUNT(DISTINCT c.ID) AS NumComments,
COUNT(DISTINCT v.id) AS NumVotes
FROM
Articles a
LEFT JOIN Comments c ON c.ParentID = a.ID
LEFT JOIN Votes v ON v.ParentID = a.ID
GROUP BY
a.ID,
a.Title
SELECT id, title,
(
SELECT COUNT(*)
FROM comments c
WHERE c.ParentID = a.ID
) AS NumComments,
(
SELECT COUNT(*)
FROM votes v
WHERE v.ParentID = a.ID
) AS NumVotes
FROM articles a
try:
COUNT(DISTINCT c.ID) AS NumComments
You are thinking in trees, not recordsets.
In the recordset the you get each Comment and each Vote returned multiple times combined with each other. Run the query without the group by and the count to see what I mean.
The solution is simple: use COUNT(DISCTINCT c.ID) and COUNT(DISTINCT v.ID)