On a website, I have got a list of Items I am struggling to sort with MySQL.
Some of the items are "expired" (their end_at is in the past), and I'd like to retrieve them, but after the "active" items (their end_at can be either NULL or in the future).
I would like to be able to further sort items according to another field, let's say "views" (to get the most popular ones first)
Basically, this is what I'd like to get :
item 1 (active - most popular)
item 2 (active - 2nd most popular)
item 3 (active - 3rd most popular)
item 4 (expired)
item 5 (expired)
(and so on)
So far, this is what I have tried:
SELECT name, end_at
FROM items
ORDER BY (end_at IS NULL OR end_at > CURDATE()), views DESC;
Doesn't work : even the first returned item is expired.
You almost got it. A condition in MySQL returns 0 or 1. And since 0 comes before 1 you have to either sort desc
SELECT name,end_at FROM items
ORDER BY (end_at IS NULL OR end_at > CURDATE()) DESC,
views DESC;
or negate the condition to get the desired result
SELECT name,end_at FROM items
ORDER BY end_at IS NOT NULL AND end_at < CURDATE() ASC,
views DESC;
I just found a way :
SELECT *
FROM items
ORDER BY CASE
WHEN (end_at IS NULL OR end_at > CURDATE())
THEN 0
ELSE 1
END, views DESC;
The CASE WHEN affects a value on each row, according to an expression. The sort is done as usual, using this computed value.
Related
I'm building a custom news feed component and am trying to implement the concept of highlighted posts. An example of the table is as follows:
id
title
content
post_date
highlight_date
1
Test 1
...
2021-10-01 06:12:00
null
2
Test 2
...
2021-10-02 08:54:00
null
3
Test 3
...
2021-10-03 13:52:00
2021-11-30 00:00:00
4
Test 4
...
2021-10-04 15:32:00
null
Naturally, when pulling data I'll be ordering by post_date DESC, however highlighted posts need to appear at the top, but only if today's date is before the highlighted date. If today's date is after the highlighted date, it it should be ignored and appear naturally within the post_date sort.
This query below for example won't work, as highlighted posts will always appear first, even after the highlight_date has lapsed.
select * from post
order by post_date desc, highlight_date asc
limit 20
While I can separate this into two queries (one for highlighted only and one for regular only), and compound the two in the application, implementing it for use with pagination/infinite scroll will now be difficult as I don't have the benefit of using LIMIT and OFFSET in my query.
Using the above example data, I'm trying to build a query where the resulting order will be [3, 4, 2, 1] for dates before 2021-11-31, but [4, 3, 2, 1] for dates after.
I'm not sure how to go about this in an efficient way, as I'm expecting this table to grow over the years. Any direction or assistance would be greatly appreciated! TIA!
SELECT * FROM post
ORDER BY CASE WHEN highlight_date > NOW() THEN 0 ELSE 1 END, post_date DESC
LIMIT 20
db<>fiddle
SQL:
SELECT *,
COALESCE(highlight_date > NOW(),0) highlight
FROM post
ORDER
BY highlight DESC , id DESC
My date NOW is 2021-10-01 23:06:00
Result when NOW() is lower than highlight_date
Result when NOW() is greather than highlight_date
Explaination:
COALESCE is required for comparing NULL data.
Because when NULL compared by date, the result is NULL.
And 0 is Greater than NULL
Wrong Result Without Coalesce:
SELECT *,
highlight_date > NOW() highlight
FROM post
ORDER
BY highlight DESC , id DESC
Resulted:
I have got a table like the following. The select order needs to show closest launch date from now first(it is more urgent), then launch date desc. Say current date is 17-11-2017
tblProduct
01-02-2016
09-05-2015
03-11-2017
30-11-2017
02-01-2018
09-06-2018
The output order should be
30-11-2017
02-01-2018
09-06-2018
03-11-2017
09-05-2015
01-02-2016
The top 2 are within 3 months from now, so are shown first(between the 2 results, 30-11-2017 is closer from now so shown first).
What's the mysql query?
I'm sure not the most elegant way, but this could help you
http://sqlfiddle.com/#!9/2a6eca/1
SELECT *, IF(mydate BETWEEN NOW() AND DATE_ADD( NOW() , INTERVAL +3 MONTH) , 1, 0) as `dateInRange`
FROM `demo`
ORDER BY `dateInRange` DESC,
CASE WHEN `dateInRange` = 1 THEN `mydate` END ASC,
CASE WHEN `dateInRange` = 0 THEN `mydate` END DESC
What this does is adds another column dateInRange to the selection (and sets it to 1 if date is between now and 3 monts from now.
Then in the order by part we first sort by this column (making sure all the dates that are in the 3 months range are on the top), and when this column is equal to 0, then we sort by the actual date ascending. If this column is 0 we sort by dates descending.
This code does what you would like, but there must be a more elegant way to go about this.
I'm trying to get a list of 20 events grouped by their Ids and sorted by whether they are in progress, pending, or already finished. The problem is that there are events with the same id that include finished, pending, and in progress events and I want to have 20 distinct Ids in the end. What I want to do is group these events together but if one of them is in progress then sort that group by that event. So basically I want to sort by the latest end time that is also before now().
What I have so far is something like this where end and start are end/start times. I'm not sure if what is inside max() is behaving how I should expect.
select * from event_schedule as t1
JOIN (
SELECT DISTINCT(event_id) as e
from event_schedule
GROUP BY event_id
order by MAX(end < unix_timestamp(now())) asc,
MIN(start >= unix_timestamp(now())) asc,
MAX(start) desc
limit 0, 20
)
as t2 on (t1.event_id = t2.e)
This results in some running / pending events to be mixed around in order when I want them to be in the order running -> pending -> Ended.
I would suggest to first create a view in order to not get an overcomplicated SELECT statement:
CREATE VIEW v_event_schedule AS
SELECT *,
CASE
WHEN end < unix_timestamp(now())
THEN 1
WHEN start > unix_timestamp(now())
THEN 2
ELSE 3
END AS category
FROM event_schedule;
This view v_event_schedule returns an extra column, in addition to the columns of event_schedule, which represents the priority of the category (running, pending, past):
running (in progress)
pending (future)
past
Then the following will do what you want:
SELECT a.*
FROM v_event_schedule a
INNER JOIN (
SELECT id,
MIN(category) category
FROM v_event_schedule b
GROUP BY id
) b
ON a.id = b.id
AND a.category = b.category
ORDER BY category,
start DESC
LIMIT 20;
The ORDER BY can be further adapted to your needs as to how you want to sort within the same category. I added start DESC as that seemed what you were doing in your attempt.
About the original ORDER BY
You had this:
order by MAX(end < unix_timestamp(now())) asc,
MIN(start >= unix_timestamp(now())) asc,
The expressions you have there evaluate to boolean values, and both elements in the ORDER BY each divide the groups into two sections, one for false and one for true, so in total 4 groups.
The first of the two will order IDs first that have no record with an end value in the past, because only then the boolean expression is always false which is the only way to make the MAX of them false as well.
Now let's say for the same ID you have both records that have an end date in the future as well as records with an end date in the past. In that case the MAX aggregates to true, and so the id will be sorted secondary. This is not intended, as this ID might have a "running" record.
I did not look into making your query work based on such aggregates on boolean expressions. It requires some time to understand what they are doing. A CASE WHEN to determine the category with a number really makes the SQL a lot easier to understand, at least to me.
I have an table with projects in them:
id (int)
ordering (int)
content (string)
My projects-page shows the current project and a preview of the next three projects. For fetching the next three projects, I would like to use a clean MySQL query. Of course, if the person is on one of the last projects, there are not 3 more projects left to show.
So it then needs to show the first one again.
Basically Im trying to combine these two statements:
SELECT *
FROM projects
WHERE ordering > {currentProjectOrdering}
ORDER BY ordering ASC
and
SELECT *
FROM projects
WHERE ordering > 0
ORDER BY ordering ASC
and also
LIMIT 0,3
In a nutshell: get the next three records with higher ordering than the current project, if (some of) these do not exist, start from ordering = 1.
Assuming there are 10 projects:
Project 1 shows 2,3 and 4
Project 2 shows 3,4 and 5
...
Project 9 shows 10, 1 and 2
If you want data from first condition AND second condition use
SELECT *
FROM projects
WHERE ordering > {currentProjectOrdering} AND ordering > 0
ORDER BY ordering ASC
LIMIT 0,3
If you want data from first condition OR second condition use
SELECT *
FROM projects
WHERE ordering > {currentProjectOrdering} OR ordering > 0
ORDER BY ordering ASC
LIMIT 0,3
I'm curious about this. I don't have mysql and can't test, but I don't think you are going to get the 3 records you want in these examples - if you are at the end of the list and need to grab 1 or 2 from the beginning. I'm playing with TSQL - so there is no LIMIT Hence the TOP.
I'm also curious if anyone can do it better.
SELECT TOP 3
CASE
WHEN ORDERING >= {currentProjectOrdering} THEN ORDERING
ELSE {currentProjectOrdering} - ORDERING END AS DISPLAYORDER,*
FROM SO_projects
WHERE (ordering >= {currentProjectOrdering} AND ordering < {currentProjectOrdering}+ 3 ) OR ordering < 3
ORDER BY DISPLAYORDER DESC
Can't promise as I have no idea about MYSQL, but here is my try. In T-SQL TOP returns the specified number of recs (3 in this case) depending on the ORDER BY. There is no equivocation to the 0 in the 0,3 part (I wish) of LIMIT. I googled and it looks like the CASE stuff should be pretty similar. You may be able to get rid of the > and < because of how LIMIT works. The logic works on SqlServer:
SELECT
CASE
WHEN ORDERING >= {currentProjectOrdering} THEN ORDERING
ELSE {currentProjectOrdering} - ORDERING END AS DISPLAYORDER,*
FROM SO_projects
WHERE (ordering >= {currentProjectOrdering} AND ordering < {currentProjectOrdering} + 3 ) OR ordering < 3
ORDER BY DISPLAYORDER DESC
LIMIT 0,3
Something Like this should work (assuming you limiting by just projects that person is working on):
SELECT * FROM projects WHERE person = person_id && (ordering > 0 && COUNT(id) < 4)
|| (ordering >= currentProjectOrdering && COUNT(id) >= 4)) ORDER BY ordering LIMIT 0,3;
Question title might be a bit vague, so feel free to change it into something that makes more sense, I just couldn't find the right words.
So here is my problem:
In my table there are 2 important columns
date_added (a datetime value)
special_price (numeric)
special_price is either NULL or a numeric value depending on what the special price is.
I'll describe the way I want to order my result now:
First I want to see all the items with no special_price (NULL) ordered by date (newest first), Then I want to see the items that do have a special_price (so anything other than NULL) ordered by date.
At first I tried this:
SELECT * FROM products ORDER BY special_price, date_added DESC
This worked great for the first part where all the special_price values where NULL, but when it started with the the items that did have a special_price ordered on the value that was in the special_price column. This makes sense of course because in the query I wanted to order by special_price first.
So how can I let the query ignore the value of special_price and just see whether it's NULL or not? So I get all NULL's ordered by date and then I get all the NOT NULL's ordered by date.
ORDER BY IF( ISNULL( special_price ), 0, 1 ), date_added DESC
I'd use
SELECT *, IF(special_price IS NULL, 0, 1) AS orderer FROM products ORDER BY orderer, date_added DESC
that works fine for what you want to accomplish