Select all threads and order by the latest one
Assuming you want a single row per thread and not all rows for all posts.
DISTINCT ON
is still the most convenient tool. But the leading ORDER BY
items have to match the expressions of the DISTINCT ON
clause. If you want to order the result some other way, you need to wrap it into a subquery and add another ORDER BY
to the outer query:
SELECT *
FROM (
SELECT DISTINCT ON (t.id)
t.id, u.username, p.updated_at, t.title
FROM forum_threads t
LEFT JOIN forum_posts p ON p.thread_id = t.id
LEFT JOIN users u ON u.id = p.user_id
WHERE t.forum_id = 3
ORDER BY t.id, p.updated_at DESC
) sub
ORDER BY updated_at DESC;
If you are looking for a query without subquery for some unknown reason, this should work, too:
SELECT DISTINCT
t.id
, first_value(u.username) OVER w AS username
, first_value(p.updated_at) OVER w AS updated_at
, t.title
FROM forum_threads t
LEFT JOIN forum_posts p ON p.thread_id = t.id
LEFT JOIN users u ON u.id = p.user_id
WHERE t.forum_id = 3
WINDOW w AS (PARTITION BY t.id ORDER BY p.updated_at DESC)
ORDER BY updated_at DESC;
There is quite a bit going on here:
The tables are joined and rows are selected according to
JOIN
andWHERE
clauses.The two instances of the window function
first_value()
are run (on the same window definition) to retrieveusername
andupdated_at
from the latest post per thread. This results in as many identical rows as there are posts in the thread.The
DISTINCT
step is executed after the window functions and reduces each set to a single instance.ORDER BY
is applied last andupdated_at
references theOUT
column (SELECT
list), not one of the twoIN
columns (FROM
list) of the same name.
Yet another variant, a subquery with the window function row_number()
:
SELECT id, username, updated_at, title
FROM (
SELECT t.id
, u.username
, p.updated_at
, t.title
, row_number() OVER (PARTITION BY t.id
ORDER BY p.updated_at DESC) AS rn
FROM forum_threads t
LEFT JOIN forum_posts p ON p.thread_id = t.id
LEFT JOIN users u ON u.id = p.user_id
WHERE t.forum_id = 3
) sub
WHERE rn = 1
ORDER BY updated_at DESC;
Similar case:
- Return records distinct on one column but order by another column
You'll have to test which is faster. Depends on a couple of circumstances.
SQL query to select all forum threads but order them by the latest active thread
Add a column containing the last time a thread has been replied into your threads
table, and then use order by this col. Don't forget to update the column on new reply.
There are ways to do it manually, but it will be far more slower than a dedicated column.
MySQL: Select threads and posts, order by latest activity
SELECT t.*, IFNULL(MAX(p.`timestamp`), t.`timestamp`) AS pts FROM `threads` t, `posts` p WHERE p.`thread_id` = t.`id` AND t.`forum_id`=1 ORDER BY pts DESC
SQL: Select latest thread and latest post, grouped by forum, ordered by latest post
Since MySQL doesn't support window functions, I don't think there's any way to do this without a subquery:
SELECT f.id AS forum_id,
f.name AS forum_name,
t.id AS thread_id,
t.topic AS thread_topic,
t.ts AS thread_timestamp,
p.id AS post_id,
p.content AS post_content,
p.ts AS post_timestamp
FROM forums f
JOIN (SELECT t2.forum_id, max(p2.ts) as ts
FROM posts p2
JOIN threads t2 ON p2.thread_id = t2.id
GROUP BY t2.forum_id) max_p ON f.id = max_p.forum_id
JOIN posts p ON max_p.ts = p.ts
JOIN threads t ON f.id = t.forum_id AND p.thread_id = t.id
ORDER BY p.ts
Naturally, caching the latest results would let you do this without the performance penalty of calling MAX(), but with the right indices, this shouldn't be much of an issue...
UPDATE
The most concise way of including the threads without posts and forums without threads would be to use LEFT JOINs instead of an INNER JOINs:
SELECT f.id AS forum_id,
f.name AS forum_name,
t.id AS thread_id,
t.topic AS thread_topic,
t.ts AS thread_timestamp,
p.id AS post_id,
p.content AS post_content,
p.ts AS post_timestamp
FROM forums f
LEFT JOIN (SELECT t2.forum_id, max(COALESCE(p2.ts, t2.ts)) as ts, COUNT(p2.ts) as post_count
FROM threads t2
LEFT JOIN posts p2 ON p2.thread_id = t2.id
GROUP BY t2.forum_id) max_p ON f.id = max_p.forum_id
LEFT JOIN posts p ON max_p.ts = p.ts
LEFT JOIN threads t ON f.id = t.forum_id AND (max_p.post_count = 0 OR p.thread_id = t.id)
ORDER BY p.ts
MySql SELECT only newest message from distinct threads order by timestamp -Private Messages Inbox Similar to Facebooks(2013)
SELECT * FROM
(SELECT * FROM messages ORDER BY created_at DESC) messages
GROUP BY IF(from_user_id < to_user_id, CONCAT_WS('_', from_user_id, to_user_id), CONCAT_WS('_', to_user_id, from_user_id));
Selecting latest message per thread
Try this;)
select t1.*
from messages t1
inner join (
select max(date_posted) as date_posted, thread_id
from messages
group by thread_id
) t2 on t2.thread_id = t1.thread_id and t2.date_posted = t1.date_posted
order by t1.date_posted
Or you can use in
:
select *
from messages
where (date_posted, thread_id) in (
select max(date_posted) as date_posted, thread_id
from messages
group by thread_id
)
order by date_posted
SQLFiddle DEMO HERE
Select all threads with tag & the rest of the tags?
Something like
SELECT threads.*, GROUP_CONCAT(tags.title)
FROM threads AS t
LEFT JOIN thread_tags AS tt ON t.id = tt.threads_id
LEFT JOIN tags AS tt ON tt.tag_id = tags.id
WHERE t.id IN (
SELECT tt.threads_id
FROM thread_tags AS tt
JOIN tags ON tt.tag_id = tags.id
AND tags.title = "test"
)
GROUP BY t.id
Related Topics
How to Create a SQL Table Under a Different Schema
How to Force a Query to Not Use a Index on a Given Table
Remove Uniqueness of Index in Postgresql
Count Case and When Statement in MySQL
Delete the 'First' Record from a Table in SQL Server, Without a Where Condition
SQL Server Procedure Declare a List
Adding MySQL Alias Fields Together
Convert SQL Server Result Set into String
Select * from Table1 That Does Not Exist in Table2 with Conditional
SQL Management Studio Won't Recognize a Table Exists After Scripted Create
Querying Where Condition to Character Length
SQL Query Question: Select ... Not in
Select 2 Columns in One and Combine Them