How do I sort two selections by date? - mysql

I have the following command:
SELECT *
FROM Posts P
WHERE P.ThreadId = 0
ORDER BY
(SELECT MAX(R.Time)
FROM Posts R
WHERE R.ThreadId = P.Id) DESC
This selects all threads and orders them by the time of their last reply. Threads without a reply are always behind threads with replies and in random order. I want this command to also order threads without replies by their creation time intermingled with the other threads.
How can I achieve this?
(Side note: Threads and Replies are using the same table "Post". A thread has the ThreadId 0 while a reply has the ThreadId of a parent post.)

When there is no element in the subquery fullfilling the condition (ie there is not reply to it) the subquery will return NULL thus you have to provide a default value. The function for that is COALESCE, which returns the first non-null value of its arguments.
SELECT *
FROM Posts P
WHERE P.ThreadId = 0
ORDER BY
COALESCE((SELECT MAX(R.Time)
FROM Posts R
WHERE R.ThreadId = P.Id), P.Time) DESC
So Coalesce( _thesubquery_, P.Time) will return the result of the subquery, if it's not null, or P.Time (ie the creation time of the non-answered post) otherwise.
You can also apply the COALESCE function just around the max instead of the whole subquery. This will lead to the same result
SELECT *
FROM Posts P
WHERE P.ThreadId = 0
ORDER BY
(SELECT COALESCE(MAX(R.Time), P.Time)
FROM Posts R
WHERE R.ThreadId = P.Id) DESC

SELECT p1.*,
COALESCE(p3.time, p1.time) last_thread_time
FROM posts p1
LEFT JOIN LATERAL ( SELECT MAX(p2.time) time
FROM posts p2
WHERE p2.thread_id = p1.id ) p3 ON TRUE
WHERE p1.thread_id = 0
ORDER BY COALESCE(p3.time, p1.time);
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=fc1a21abf4b4987a591c5e0536369142

Related

Returning the Max value of a half the composite key MYSQL

I am trying to query a table that has a composite key made up of two integers. The relationship is
What I am trying to get as a result is for every conversation that has been started, I want the MAX(threadNum) from the messages table. Currently the query is
SELECT c.conversation_id
FROM conversation as c
INNER JOIN (
SELECT MAX(threadNum), user_from, user_to, message, dateTime, deleted,replied
FROM messages
GROUP BY conversation_id
) as m ON c.conversation_id = m.conversation_Id
WHERE (m.user_to ='$userId' OR m.user_from ='$userId') AND m.deleted = 0
The results that I am expecting for the conversation_Id and the threadNum would be :
35 5
34 4
33 55
one result for every conversation_Id and only the largest threadNum result. Currently I am getting a m.converation_Id is an unknown column. What is wrong with the query structure? And more importantly is there an easier way to do what I am attempting to do?
It looks like you want one row per conversation, along with the latest message in the conversation (that is, the message that has the greatest thread_id).
If so, that's a top-1-per-group problem. You can solve it by filtering with a correlated subquery:
select c.*, m.*
from conversation c
inner join messages m on m.conversation_id = c.conversation_id
where m.thread_num = (
select max(m1.thread_num)
from messages m1
where
m1.conversation_id = m.conversation_id
and m.deleted = 0
and :user_id in (m.user_from, m.user_to)
)
:user_id represents the query parameter for your query (you should use a parameterized query instead of munging the variable into the query string).
Alternatively, if you are running MySQL 8.0, you can use row_number():
select *
from (
select
c.*,
m.*,
row_number() over(partition by c.conversation order by m.thread_num desc) rn
from conversation c
inner join messages m on m.conversation_id = c.conversation_id
where m.deleted = 0 and :user_id in (m.user_from, m.user_to)
) t
where rn = 1

msyql query to return number of inner joins restricted by a WHERE clause

I have the following query to try to count the number of comments made within 30 minutes of a post getting made. There are two tables, posts and comments which are INNER JOINed on posts.id and comments.post_id like so:
SELECT id, count(post_id) as num_comm from posts
INNER JOIN comments on id = post_id
WHERE (UNIX_TIMESTAMP(posts.time_posted) - UNIX_TIMESTAMP(comments.time_posted)) < (30 * 60)
AND comments.reply_to_id = 0
GROUP BY id
ORDER BY num_comm ASC;
The problem I'm having is that the query is returning the the total number of num_comm results and not just the count of those comments that were made within 30 minutes of the original post and that has comments.reply_to_id set to 0.
How do I change the query to return only the number of comments that meet the criteria in the WHERE clause?
Presumably, the post is posted before the comment. So, your value is always negative. Try:
SELECT id, count(*) as num_comm
FROM posts p INNER JOIN
comments c
ON p.id = c.post_id
WHERE (UNIX_TIMESTAMP(c.time_posted) - UNIX_TIMESTAMP(p.time_posted)) < (30 * 60) AND
c.reply_to_id = 0
GROUP BY p.id
ORDER BY num_comm ASC;
I don't really like the conversion to Unix timestamps. I think the code is much clearer as:
WHERE c.time_posted < p.time_posted + interval 30 minute AND
c.reply_to_id = 0

How can I count total selected row before apply LIMIT?

I have an almost complex query like this:
SELECT qa.id,
qa.subject,
qa.category cat,
qa.keywords tags,
qa.body_html,
qa.amount,
qa.visibility,
qa.date_time,
COALESCE(u.reputation, 'N') reputation,
COALESCE(Concat(u.user_fname, ' ', u.user_lname), 'unknown') NAME,
COALESCE(u.avatar, 'anonymous.png') avatar,
(
SELECT COALESCE(Sum(vv.value),0)
FROM votes vv
WHERE qa.id = vv.post_id
AND 15 = vv.table_code) AS total_votes,
(
SELECT COALESCE(Sum(vt.total_viewed),0)
FROM viewed_total vt
WHERE qa.id = vt.post_id
AND 15 = vt.table_code limit 1) AS total_viewed
FROM qanda qa
LEFT JOIN users u
ON qa.author_id = u.id
AND qa.visibility = 1
WHERE qa.type = 0 $query_where
ORDER BY $query_order
LIMIT :j, 11;
Noted that $query_where variable contains some other conditions which will be created dynamically. Anyway, as you see, maximum it returns 10 posts.
Currently, to count total matched rows, I use another query like this:
SELECT COUNT(amount) paid_qs,
COUNT(*) all_qs
FROM qanda qa
WHERE type = 0 $query_where
I guess there is some waste processing. I mean two separated queries (with complex conditions on the where clause) will be too much.
Is there any approach to use one query instead of them?
You can query the found rows after the query with the FOUND_ROWS() function.
Reference: MySQL Reference Manual
You have to include the SELECT SQL_CALC_FOUND_ROWS ... clause into your query.

Generating user post count and thread count from different tables

So I don't know much about MySQL but I heard about views and I'm trying to wrath my head around it.
Basically what I want to do is
check the table forum_posts, count the number of posts made by each user
query forum_users for each user to get every column and add to the view
query forum_threads to get the number of threads made by that user.
I don't know if that order is correct performance-wise but the final view should in theory look like either
1.
UId (user id from forum_users)
UName (user name from forum_users)
UThreads (user thread count)
UPosts (user post count)
UFakePosts (named UPosts in forum_users, I'll rename that later to UFakePosts)
ULastPost (this one is not that important but I'm just throwing it here in case anyone knows how to do it, I imagine it would be possible by selecting the post with the biggest PDate column)
2.
All of forum_users but renaming the forum_users.UPosts and forum_users.UThreads to UFakePosts and UFakeThreads
ULastPost
UThreads (user thread count)
UPosts (user post count)
I managed to get the post count working by using the following code
SELECT
IFNULL(a.UId,-1) AS UId,
IFNULL(a.UName,'Unknown') AS UName,
postsquery.Posts AS UPosts,
IFNULL(a.UPosts,-1) AS UFakePosts
FROM
(
SELECT p.PId, p.PAuthorId, COUNT(p1.PAuthorId) as Posts
FROM forum_posts AS p
LEFT JOIN forum_posts AS p1 ON p1.PId = p.PId
GROUP BY p.PAuthorId
)
AS postsquery
LEFT JOIN forum_users AS a ON postsquery.PAuthorId = a.UId
ORDER BY postsquery.Posts DESC
which generates the following result
but no success with getting threads, I can get one of the other but not both at the same time.
I've also tried this
SELECT IFNULL(a.UId,-1) AS UId,
IFNULL(a.UName,'Unknown') AS UName,
postsquery.Posts AS UPosts,
threadsquery.Threads AS UThreads,
IFNULL(a.UPosts,-1) AS UFakePosts
FROM
(
SELECT p.PId, p.PAuthorId, COUNT(p.PAuthorId) as Posts
FROM forum_posts AS p
)
AS postsquery
LEFT JOIN forum_users AS a1 ON postsquery.PAuthorId = a1.UId,
(
SELECT t.TId, t.TAuthorId, COUNT(t.TAuthorId) as Threads
FROM forum_threads AS t
GROUP BY t.TAuthorId
)
AS threadsquery
LEFT JOIN forum_users AS a ON threadsquery.TAuthorId = a.UId
ORDER BY
postsquery.Posts DESC
.....but the results are wrong:
What's supposed to happen:
Unknown (user that I haven't scraped yet): 1 post / 0 threads
User1: 2 posts / 0 threads
User2: 1 post / 2 threads
User3: 0 posts / 0 threads
If I could do another view but for threads, getting number of unique posters and number of posts that would be cool as well but one thing at a time.
Fiddle with database structure
http://sqlfiddle.com/#!9/c93d9/1
Structure should be pretty easy to understand, U stands for user, T for thread, P for post, D for date and so on.
Create a subselect to get the thread count:
LEFT JOIN
(
SELECT
TAuthorId,
COUNT(1) as thread_count
FROM
forum_threads
GROUP BY
TAuthorId
) threads ON
threads.TAuthorId = postsquery.PAuthorId
And then select that column:
IFNULL(threads.thread_count, 0) as thread_count
Putting it all together:
SELECT IFNULL(a.UId,-1) AS UId, IFNULL(a.UName,'Unknown') AS UName, IFNULL(postsquery.Posts, 0) AS UPosts, IFNULL(threads.thread_count, 0) as thread_count, IFNULL(a.UPosts,-1) AS UFakePosts
FROM
(
SELECT p.PId, p.PAuthorId, COUNT(p1.PAuthorId) as Posts
FROM forum_posts AS p
LEFT JOIN forum_posts AS p1 ON p1.PId = p.PId
GROUP BY p.PAuthorId
)
AS postsquery
LEFT JOIN forum_users AS a ON postsquery.PAuthorId = a.UId
LEFT JOIN
(
SELECT
TAuthorId,
COUNT(1) as thread_count
FROM
forum_threads
GROUP BY
TAuthorId
) threads ON
threads.TAuthorId = postsquery.PAuthorId
ORDER BY
postsquery.Posts DESC

LEFT JOIN even if empty SQL

I have in my DB two tables :
Advices and Votes
an advice can have 0 or many votes
I'd like to get all advices validated order by the notoriety :
SELECT advices.*, COUNT(upvotes.id) - COUNT(downvotes.id) AS notoriety
FROM `advices`
LEFT JOIN votes AS upvotes ON upvotes.is_good=1 AND upvotes.advice_id=advices.id
LEFT JOIN votes AS downvotes ON downvotes.is_good=0 AND downvotes.advice_id=advices.id
WHERE `advices`.`subject_id` = 1
AND `advices`.`state` = 'validated'
ORDER BY notoriety ASC
But, the result only show advices with votes ! What should I change to have advices without vote too ?
Thanks
Use conditional aggregation instead of two joins:
SELECT a.*,
(SUM(downvotes.is_good = 1) - SUM(downvotes.is_good = 0) ) AS notoriety
FROM advices a LEFT JOIN
votes v
ON a.id = v.advice_id
WHERE a.`subject_id` = 1 AND a.`state` = 'validated'
GROUP BY a.id
ORDER BY notoriety ASC;
You can get your version to work using count(distinct) rather than count(). However, the above version is simpler and should perform better.
The reason you're having a problem is that the count is returning null where no votes exist. You can use the NVL function to replace those null's with 0.
SELECT advices.*, nvl(COUNT(upvotes.id),0) - nvl(COUNT(downvotes.id),0) AS notoriety
FROM `advices`
LEFT JOIN votes AS upvotes ON upvotes.is_good=1 AND upvotes.advice_id=advices.id
LEFT JOIN votes AS downvotes ON downvotes.is_good=0 AND downvotes.advice_id=advices.id
WHERE `advices`.`subject_id` = 1
AND `advices`.`state` = 'validated'
ORDER BY notoriety ASC