SQL Condition in ORDER BY clause - mysql

I have this query here:
SELECT posts.id, username, cover, audio, title, postDate, commentsDisabled,
MAX(postClicks.clickDate) as clickDate,
COUNT(*) as ClickCount
FROM postClicks INNER JOIN
posts
ON posts.id = postClicks.postid INNER JOIN
users
ON users.id = posts.user
WHERE posts.private = 0
GROUP BY postClicks.postid
ORDER BY ClickCount
LIMIT 5
This query gets me the top 5 results ORDER BY Count which is ClickCount. Each postClicks in my database has a clickDate what I am trying to do now is with the 5 results I get back, put them in order by ClickCount within the past 24 hours, I still need 5 results, but they need to be in order of ClickCount with 24 hour period.
I use to have this in the where clause:
postClicks.clickDate > DATE_SUB(CURDATE(), INTERVAL 1 DAY)
But after the 24 hour period I would not get 5 results, I need to get 5 results.
My question is, can I put a condition or case in my order by clause?

You cannot put a condition in the ORDER BY in this query, because that would affect the LIMIT. Instead, you can use a subquery:
SELECT pc5.*
FROM (SELECT posts.id, username, cover, audio, title, postDate, commentsDisabled,
MAX(postClicks.clickDate) as clickDate,
COUNT(*) as ClickCount,
SUM(postClicks.clickDate > DATE_SUB(CURDATE(), INTERVAL 1 DAY)) as clicks24hours
FROM postClicks INNER JOIN
posts
ON posts.id = postClicks.postid INNER JOIN
users
ON users.id = posts.user
WHERE posts.private = 0
GROUP BY postClicks.postid
ORDER BY ClickCount
LIMIT 5
) pc5
ORDER BY clicks24hours DESC;

Related

MySQL, DISTINCT in SUM operation

Currently I trying to calculate number of unique user visit in my application based on user gender. Here is the example query that calculate all the visits (not unique)
SELECT
DATE(v.visited_at) AS visit_date,
SUM(IF(u.gender = 'M', 1, 0)) AS male_visit,
SUM(IF(u.gender = 'F', 1, 0)) AS female_visit,
SUM(IF(u.gender = '' OR u.gender IS NULL, 1, 0)) AS unknown_visit
FROM
visits v
INNER JOIN users u ON v.user_id = u.id
WHERE
DATE(v.visited_at) >= DATE_SUB(SYSDATE(), INTERVAL 30 DAY)
AND v.duration > 30
GROUP BY
DATE(v.visited_at)
Tried using subquery and count distinct it's works, but it's 4 times slower.
SELECT
DATE(visited_at) as visit_date,
(SELECT COUNT(DISTINCT u.id) FROM visits v JOIN users u ON v.user_id = u.id WHERE u.gender = 'M' AND DATE(v.visited_at) = visit_date AND v.duration > 30) AS male_visit,
(SELECT COUNT(DISTINCT u.id) FROM visits v JOIN users u ON v.user_id = u.id WHERE u.gender = 'F' AND DATE(v.visited_at) = visit_date AND v.duration > 30) AS female_visit,
(SELECT COUNT(DISTINCT u.id) FROM visits v JOIN users u ON v.user_id = u.id WHERE u.gender = '' OR u.gender IS NULL AND DATE(v.visited_at) = visit_date AND v.duration > 30) AS unknown_visit
FROM
visits v
WHERE
DATE(visited_at) >= DATE_SUB(SYSDATE(), INTERVAL 30 DAY)
GROUP BY
DATE(visited_at)
Any suggestion on this?
COUNT(DISTINCT) is always going to be slower than COUNT(). You can try:
SELECT DATE(v.visited_at) AS visit_date,
COUNT(DISTINCT CASE WHEN u.gender = 'M' THEN u.id END) AS male_visit,
COUNT(DISTINCT CASE WHEN u.gender = 'F' THEN u.id END) AS female_visit,
COUNT(DISTINCT CASE WHEN u.gender = '' OR u.gender IS NULL THEN u.id END) AS unknown_visit
FROM visits v INNER JOIN
users u
ON v.user_id = u.id
WHERE DATE(v.visited_at) >= DATE_SUB(SYSDATE(), INTERVAL 30 DAY) AND
v.duration > 30
GROUP BY DATE(v.visited_at);
I don't know if it will be much faster, though.
There are 2 tables as per query (user and visit) with sample data.
Query
SELECT
DATE(v.visited_date) AS visit_date,
u.gender,
COUNT(DISTINCT v.user_id) AS total_count
FROM
visits v
INNER JOIN users u ON v.user_id = u.id
WHERE
DATE(v.visited_date) >= DATE_SUB(SYSDATE(), INTERVAL 30 DAY)
AND v.duration >= 30
GROUP BY u.gender,DATE(v.visited_date)
ORDER BY DATE(v.visited_date) ASC;
This query will give you unique count of users gender wise for particular date.
This type of query is likely to be slow, especially if you have a large number of entries in the table as when selecting rows based upon date and time values mysql has to perform a full table scan.
Optimising your database structure is likely to offer you performance gains much in excess of anything you will get trying to query it like this.
A couple of suggestions would be to partition the table by date ranges. Doing so can greatly reduce query execution as it means instead of a full table scan mysql can simply ignore any partitions outside the query date range. The bigger the table the more benefit you will see, but potentially anything from 2x to 10x faster I would expect.
If you were to replace your gender column with 3 columns male, female and unknown you would replace 3 queries containing the slow COUNT(DISTINCT... statements with a single query with less conditions, you can also add the user id to the group by statement to remove the need to count distinct as you can specify more than one column for grouping.
Finally you could add a database trigger and either have an extra column which it sets as 1 when logging the visits if the duration is over 30 and it's their first visit of the day, or you create a new calendar table for visits and have the trigger increment the value within that upon database write of each log which equates to a unique visit for the day.

Using mysql GROUP_CONCAT and WHERE

I have the following tables in my DB
EVENT
ID, TITLE, ...
VOTES
ID, TYPE, ID_EVENT
COMMENTS
ID, COMMENT, ID_EVENT
DATES
ID, DATE, ID_EVENT
One EVENT has many actions, has many comments and has many dates.
I'm using following query to retrieve info from EVENTS table, and for each event retrieve the number of votes, the number of comments and each one of the dates. For events with one of their date = tomorrow (2015-04-03)
SELECT events.id,
events.title,
GROUP_CONCAT(dates.date) AS dates,
COUNT(distinct votes.id) AS votes,
COUNT(distinct comments.id) AS comments
FROM events
LEFT JOIN dates on dates.post_id = events.id
LEFT JOIN votes on votes.post_id = events.id AND votes.type = 1
LEFT JOIN comments on comments.votes_id = votes.id
WHERE dates.date = CURDATE() + INTERVAL 1 DAY
GROUP BY events.id
Result looks like this
id title dates votes comment
33 Event33 2015-04-03,2015-04-03,2015-04-03 4 0
39 Event39 2015-04-03 9 1
Why the dates column repeats the same date (tomorrow)??? The dates of Event33 should be 2015-04-01, 2015-04-02, 2015-04-03.
What is wrong?
You want the events that have one of their dates tomorrow. Your query does that but it also cuts off all other dates.
You need an extra join or an EXISTS subquery.
You also need a DISTINCT on the GROUP_CONCAT(), the same way you used it at the COUNT() aggregate:
SELECT events.id,
events.title,
GROUP_CONCAT(DISTINCT dates.date) AS dates,
COUNT(DISTINCT votes.id) AS votes,
COUNT(DISTINCT comments.id) AS comments
FROM events
LEFT JOIN dates ON dates.post_id = events.id
LEFT JOIN votes ON votes.post_id = events.id AND votes.type = 1
LEFT JOIN comments ON comments.votes_id = votes.id
WHERE EXISTS
( SELECT 1
FROM dates AS dd
WHERE dd.date = CURDATE() + INTERVAL 1 DAY
AND dd.post_id = events.id
)
GROUP BY events.id ;
Another way would be using inline subqueries. No need for GROUP BY or DISTINCT in this. A minor disadvantage in your case, is that the join to comments is through votes, so one subquery has an extra join:
SELECT e.id,
e.title,
( SELECT GROUP_CONCAT(d.date)
FROM dates AS d
WHERE d.post_id = e.id
) AS dates,
( SELECT COUNT(v.id)
FROM votes AS v
WHERE v.post_id = e.id AND v.type = 1
) AS votes,
( SELECT COUNT(c.id)
FROM comments AS c
JOIN votes AS v ON c.votes_id = v.id
WHERE v.post_id = e.id AND v.type = 1
) AS comments
FROM events AS e
WHERE EXISTS
( SELECT 1
FROM dates AS dd
WHERE dd.date = CURDATE() + INTERVAL 1 DAY
AND dd.post_id = events.id
) ;

MySQL SELECT subquery

I have a calendar and user_result table and I need to join these two queries.
calendar query
SELECT `week`, `date`, `time`, COUNT(*) as count
FROM `calendar`
WHERE `week` = 1
GROUP BY `date`
ORDER BY `date` DESC
and the result is
{"week":"1","date":"2014-08-21","time":"15:30:00","count":"4"}, {"week":"1","date":"2014-08-20","time":"17:30:00","count":"12"}
user_result query
SELECT `date`, SUM(`point`) as score
FROM `user_result`
WHERE `user_id` = 1
AND `date` = '2014-08-20'
and the result is just score 3
My goal is to always show calendar even if the user isn't present in the user_result table, but if he is, SUM his points for that day where calendar.date = user_result.date. Result should be:
{"week":"1","date":"2014-08-21","time":"15:30:00","count":"4","score":"3"}, {"week":"1","date":"2014-08-20","time":"17:30:00","count":"12","score":"0"}
I have tried this query below, but the result is just one row and unexpected count
SELECT c.`week`, c.`date`, c.`time`, COUNT(*) as count, SUM(p.`point`) as score
FROM `calendar` c
INNER JOIN `user_result` p ON c.`date` = p.`date`
WHERE c.`week` = 1
AND p.`user_id` = 1
GROUP BY c.`date`
ORDER BY c.`date` DESC
{"week":"1","date":"2014-08-20","time":"17:30:00","count":"4","score":"9"}
SQL Fiddle
ow sorry, i was edited, and i was try at your sqlfiddle, if you want to show all date from calendar you can use LEFT JOIN, but if you want to show just the same date between calendar and result you can use INNER JOIN, note: in this case INNER JOIN just show 1 result, and LEFT JOIN show 2 results
SELECT c.`week`, p.user_id, c.`date`, c.`time`, COUNT(*) as count, p.score
FROM `calendar` c
LEFT JOIN
(
SELECT `date`, SUM(`point`) score, user_id
FROM `result`
group by `date`
) p ON c.`date` = p.`date`
WHERE c.`week` = 1
GROUP BY c.`date`
ORDER BY c.`date` DESC
I put a pre-aggreate query / group by date as a select for the one person you were interested in... then did a left-join to it. Also, your column names of week, date and time (IMO) are poor choice column names as they can appear to be too close to reserved keywords in MySQL. They are not, but could be confusing..
SELECT
c.week,
c.date,
c.time,
coalesce( OnePerson.PointEntries, 0 ) as count,
coalesce( OnePerson.totPoints, 0 ) as score
FROM
calendar c
LEFT JOIN ( select
r.week,
r.date,
COUNT(*) as PointEntries,
SUM( r.point ) as totPoints
from
result r
where
r.week = 1
AND r.user_id = 1
group by
r.week,
r.date ) OnePerson
ON c.week = OnePerson.week
AND c.date = OnePerson.date
WHERE
c.week = 1
GROUP BY
c.date
ORDER BY
c.date DESC
Posted code to SQLFiddle

MySQL JOIN + Subquery Query Optimization

I'm trying to fetch 100 posts and order them by the number of times they've been "remixed" in the last week. Here is my query thus far:
SELECT COUNT(remixes.post_id) AS count, posts.title
FROM posts
LEFT JOIN (
SELECT * FROM remixes WHERE created_at >= 1343053513
) AS remixes ON posts.id = remixes.post_id
GROUP BY posts.id
ORDER BY count DESC, posts.created_at DESC
LIMIT 100
This produces the correct result; however, after running DESCRIBE I get this:
And here are my indexes on posts:
And my indexes on remixes:
And here are my questions:
Can you explain what the terms used in the extra column are really trying to tell me?
Could you provide tips on how I can optimize this query so that it'll scale better.
Thanks in advance!
Update
Per Zane's solution, I've updated my query to:
SELECT COUNT(remixes.post_id) AS count, posts.title
FROM posts
LEFT JOIN remixes ON posts.id = remixes.post_id AND remixes.created_at >= 1343053513
GROUP BY posts.id
ORDER BY count DESC, posts.created_at DESC
LIMIT 100
And here's the latest DESCRIBE
I'm still worried about the filesort part. Any ideas?
Try not to wrap your JOIN in a sub-select as this will create an unindexed temporary table to store the result of the subselect in, where it then joins on that unindexed table.
Instead, put created_at as an additional join condition when joining the remixes table:
SELECT
a.title, COUNT(b.post_id) AS remixcnt
FROM
posts a
LEFT JOIN
remixes b ON a.id = b.post_id AND b.created_at >= 1343053513
GROUP BY
a.id, a.title
ORDER BY
remixcnt DESC, a.created_at DESC
LIMIT 100
It seems to me that
SELECT COUNT(remixes.post_id) AS count, posts.title
FROM posts
LEFT JOIN (
SELECT * FROM remixes WHERE created_at >= 1343053513
) AS remixes ON posts.id = remixes.post_id
GROUP BY posts.id
ORDER BY count DESC, posts.created_at DESC
LIMIT 100
could be rewritten as
SELECT COUNT(r.post_id) AS count, posts.title
FROM posts
LEFT JOIN remixes r ON posts.id = r.post_id
WHERE r.created_at >= 1343053513
GROUP BY posts.id
ORDER BY count DESC, posts.created_at DESC
LIMIT 100
which should give you a better EXPLAIN plan and run faster.

mysql problem left join and from_unixtime

I have this
SELECT COUNT(1) cnt, a.auther_id
FROM `posts` a
LEFT JOIN users u ON a.auther_id = u.id
GROUP BY a.auther_id
ORDER BY cnt DESC
LIMIT 20
It works fine, but now I want select posts from within the last day. I tried to use
WHERE from_unixtime(post_time) >= SUBDATE(NOW(),1)
but it didn't work. Any one have idea why?
This may work:
WHERE FROM_UNIXTIME(post_time) >= SUBDATE(NOW(), INTERVAL 1 DAY)