MySQL get entry after certain element in an ordering - mysql

I have a posts table, where the ids are not necessarily in sequence. The posts should be sorted by their created timestamp value, but it is possible, that two posts have the same created timestamp, in which case I would like to sort them by their id.
Given this ordering, I was trying to find the entry that immediately precedes a specified entry.
Example: (descending order)
id | created
------|------------
4 | 2017-01-05
15 | 2017-01-04
12 | 2017-01-04
2 | 2017-01-04
8 | 2017-01-02
11 | 2017-01-01
(I simplified the timestamp to only the date, but you get the idea.)
In this example, given the id 2, I would like to return the id 8. After some experimentation I have come up with the following:
SELECT id
FROM posts
WHERE created < ? OR id < ?
AND created = ?
ORDER BY created DESC, id DESC
LIMIT 1
This works, given the timestamp and id in the correct places in the query, but is very tedious.
My question is now:
Is there, given an ordering, an easier way to find the preceding element?
Clarification:
The above code does return the correct results, but I was wondering whether there was some more general way to achieve the same, one that will be more easily adapted if the sorting specification changes..

You can use tuples:
SELECT id
FROM posts
WHERE (created, id) < (?,?)
ORDER BY created DESC, id DESC
LIMIT 1

what you have is fine ... otherwise the only way i can think of is create an order out of that using concat and lpad but that's even more confusing to read than what you have.
the more confusing method using concat and lpad
SELECT id,created FROM POSTS
WHERE CONCAT(created,LPAD(id, 8, '0')) < CONCAT('2017-01-04',LPAD(2, 8, '0'))
ORDER BY created desc, id desc
LIMIT 1

Related

Get next / previous record from MySQL query ordered by multiple columns

I'm running a query through my MySQL database (MariaDB 10.3) which goes like this:
SELECT * FROM my_table ORDER BY priority DESC, expiration_date ASC, id ASC
A sample of this table with given ordering would look like this:
id
...
priority
expiration_date
3
...
2
2022-07-01 12:00:00
7
...
2
2022-07-03 12:00:00
6
...
2
2022-07-04 12:00:00
9
...
1
2022-07-02 12:00:00
4
...
1
2022-07-05 12:00:00
11
...
1
2022-07-05 12:00:00
Now I already have the ID of a specific record and I'm trying to retrieve the record which would precede / succeed said record in the query result by the given ordering through SQL as well. Say I have the record ID 6, and I want to have the records with ID 9 and 7 respectively returned.
With an ordering by a single, unique column this would be quite easy to get in a single query, but I'm not sure how to handle multiple non-unique columns. Can someone tell me how to achieve this.
Following Paul Maxwell's hint about LEAD and LAG, I was able to compose a working query like this:
SELECT t.next_id
FROM (
SELECT id,
LEAD(id, 1) OVER (ORDER BY priority DESC, expiration_date ASC, id ASC) AS next_id
FROM my_table
) t
WHERE t.id = ?
Retrieving the previous record works accordingly with LAG.

how to achieve this in mysql/mariadb - Order and Sorting

---------------------------
|Status |Application Date|
|New |2019-01-02 |
|New |2019-01-01 |
|Updated |2019-01-15 |
|Deleted |2019-01-20 |
|Updated |2019-01-16 |
---------------------------
1. Sort in-order from New,Updated, and Deleted
a. Sort by New ASC - to see the first entry for first come first serve
b. Sort by Updated Desc - to see the latest update first
c. Sort by Deleted Desc - see the latest deleted
I have tried 3 query and union but you can order them together not per query.
Try this logic:
SELECT *
FROM yourTable
ORDER BY
FIELD(Status, 'New', 'Updated', 'Deleted'),
CASE WHEN Status = 'New' THEN UNIX_TIMESTAMP(app_date) ELSE -1.0*UNIX_TIMESTAMP(app_date) END;
The first level of sorting places new records before updated records, and updated records before deleted records. The second level sorts ascending on the date for new records, and descending for all others.

MYSQL pagination performance

I have the following sample MYSQL table:
id | count_likes
-----------
1 | 30
2 | 95
3 | 60
4 | 60
5 | 22
I want to order the table by column count_likes descending and display 5 rows at a time (this is a sample table so assume thousands of rows).
To achieve this I run the following command:
SELECT * FROM table ORDER BY count_likes DESC, id DESC LIMIT 5
I want to give the option for users to load more rows like loading facebook comments for example (5 rows at a time).
To achieve this I run the following command:
SELECT * FROM table WHERE id NOT IN(values already loaded)
ORDER BY count_likes DESC, id DESC LIMIT 5
This could work well for few pages but I think it's not recommended to have like hundred values in the WHERE NOT IN clause.
If I make the command like this:
SELECT * FROM table WHERE count_likes < 'the last displayed count number'
I could miss some rows which have the same count like the last loaded row.
If I make the command like this:
SELECT * FROM table WHERE count_likes <= 'the last displayed count number'
I could get duplicate values that are already loaded.
If I make the command like this:
SELECT * FROM table ORDER BY count_likes DESC LIMIT offset,5
I may get disorganized or duplicate rows as the count_likes for any row may increase or decrease while other users are manipulating the same page.
What is the best way to load more rows in my case above?
The most accurate one would be the WHERE NOT IN but I don't know if it causes performance issues on large number of members like hundred or even thousand.

MySql order by specific ID values

Is it possible to sort in MySQL by "order by" using a predefined set of column values (ID) like order by (ID=1,5,4,3) so I would get records 1, 5, 4, 3 in that order out?
UPDATE: Why I need this...
I want my records to change sort randomly every 5 minutes. I have a cron task to update the table to put different, random sort order in it.
There is just one problem! PAGINATION.
I will have visitors who come to my page, and I will give them the first 20 results. They will wait 6 minutes, go to page 2 and have the wrong results as the sort order has already changed.
So I thought that if I put all the IDs into a session on page 2, we get the correct records even if the sorting had already changed.
Is there any other better way to do this?
You can use ORDER BY and FIELD function.
See http://lists.mysql.com/mysql/209784
SELECT * FROM table ORDER BY FIELD(ID,1,5,4,3)
It uses Field() function, Which "Returns the index (position) of str in the str1, str2, str3, ... list. Returns 0 if str is not found" according to the documentation. So actually you sort the result set by the return value of this function which is the index of the field value in the given set.
You should be able to use CASE for this:
ORDER BY CASE id
WHEN 1 THEN 1
WHEN 5 THEN 2
WHEN 4 THEN 3
WHEN 3 THEN 4
ELSE 5
END
On the official documentation for mysql about ORDER BY, someone has posted that you can use FIELD for this matter, like this:
SELECT * FROM table ORDER BY FIELD(id,1,5,4,3)
This is untested code that in theory should work.
SELECT * FROM table ORDER BY id='8' DESC, id='5' DESC, id='4' DESC, id='3' DESC
If I had 10 registries for example, this way the ID 1, 5, 4 and 3 will appears first, the others registries will appears next.
Normal exibition
1
2
3
4
5
6
7
8
9
10
With this way
8
5
4
3
1
2
6
7
9
10
There's another way to solve this. Add a separate table, something like this:
CREATE TABLE `new_order` (
`my_order` BIGINT(20) UNSIGNED NOT NULL,
`my_number` BIGINT(20) NOT NULL,
PRIMARY KEY (`my_order`),
UNIQUE KEY `my_number` (`my_number`)
) ENGINE=INNODB;
This table will now be used to define your own order mechanism.
Add your values in there:
my_order | my_number
---------+----------
1 | 1
2 | 5
3 | 4
4 | 3
...and then modify your SQL statement while joining this new table.
SELECT *
FROM your_table AS T1
INNER JOIN new_order AS T2 on T1.id = T2.my_number
WHERE ....whatever...
ORDER BY T2.my_order;
This solution is slightly more complex than other solutions, but using this you don't have to change your SELECT-statement whenever your order criteriums change - just change the data in the order table.
If you need to order a single id first in the result, use the id.
select id,name
from products
order by case when id=5 then -1 else id end
If you need to start with a sequence of multiple ids, specify a collection, similar to what you would use with an IN statement.
select id,name
from products
order by case when id in (30,20,10) then -1 else id end,id
If you want to order a single id last in the result, use the order by the case. (Eg: you want "other" option in last and all city list show in alphabetical order.)
select id,city
from city
order by case
when id = 2 then city else -1
end, city ASC
If i had 5 city for example, i want to show the city in alphabetical order with "other" option display last in the dropdown then we can use this query.
see example other are showing in my table at second id(id:2) so i am using "when id = 2" in above query.
record in DB table:
Bangalore - id:1
Other - id:2
Mumbai - id:3
Pune - id:4
Ambala - id:5
my output:
Ambala
Bangalore
Mumbai
Pune
Other
SELECT * FROM TABLE ORDER BY (columnname,1,2) ASC OR DESC

Returning query results in predefined order

Is it possible to do a SELECT statement with a predetermined order, ie. selecting IDs 7,2,5,9 and 8 and returning them in that order, based on nothing more than the ID field?
Both these statements return them in the same order:
SELECT id FROM table WHERE id in (7,2,5,9,8)
SELECT id FROM table WHERE id in (8,2,5,9,7)
I didn't think this was possible, but found a blog entry here that seems to do the type of thing you're after:
SELECT id FROM table WHERE id in (7,2,5,9,8)
ORDER BY FIND_IN_SET(id,"7,2,5,9,8");
will give different results to
SELECT id FROM table WHERE id in (7,2,5,9,8)
ORDER BY FIND_IN_SET(id,"8,2,5,9,7");
FIND_IN_SET returns the position of id in the second argument given to it, so for the first case above, id of 7 is at position 1 in the set, 2 at 2 and so on - mysql internally works out something like
id | FIND_IN_SET
---|-----------
7 | 1
2 | 2
5 | 3
then orders by the results of FIND_IN_SET.
Your best bet is:
ORDER BY FIELD(ID,7,2,4,5,8)
...but it's still ugly.
Could you include a case expression that maps your IDs 7,2,5,... to the ordinals 1,2,3,... and then order by that expression?
All ordering is done by the ORDER BY keywords, you can only however sort ascending and descending. If you are using a language such as PHP you can then sort them accordingly using some code but I do not believe it is possible with MySQL alone.
This works in Oracle. Can you do something similar in MySql?
SELECT ID_FIELD
FROM SOME_TABLE
WHERE ID_FIELD IN(11,10,14,12,13)
ORDER BY
CASE WHEN ID_FIELD = 11 THEN 0
WHEN ID_FIELD = 10 THEN 1
WHEN ID_FIELD = 14 THEN 2
WHEN ID_FIELD = 12 THEN 3
WHEN ID_FIELD = 13 THEN 4
END
You may need to create a temp table with an autonumber field and insert into it in the desired order. Then sort on the new autonumber field.
Erm, not really. Closest you can get is probably:
SELECT * FROM table WHERE id IN (3, 2, 1, 4) ORDER BY id=4, id=1, id=2, id=3
But you probably don't want that :)
It's hard to give you any more specific advice without more information about what's in the tables.
It's hacky (and probably slow), but you can get the effect with UNION ALL:
SELECT id FROM table WHERE id = 7
UNION ALL SELECT id FROM table WHERE id = 2
UNION ALL SELECT id FROM table WHERE id = 5
UNION ALL SELECT id FROM table WHERE id = 9
UNION ALL SELECT id FROM table WHERE id = 8;
Edit: Other people mentioned the find_in_set function which is documented here.
You get answers fast around here, don't you…
The reason I'm asking this is that it's the only way I can think of to avoid sorting a complex multidimensional array. I'm not saying it would be difficult to sort, but if there were a simpler way to do it with straight sql, then why not.
One Oracle solution is:
SELECT id FROM table WHERE id in (7,2,5,9,8)
ORDER BY DECODE(id,7,1,2,2,5,3,9,4,8,5,6);
This assigns an order number to each ID. Works OK for a small set of values.
Best I can think of is adding a second Column orderColumn:
7 1
2 2
5 3
9 4
8 5
And then just do a ORDER BY orderColumn