Is there an elegant way to have performant, natural sorting in a MySQL database?
For example if I have this data set:
So, while I know that you have found a satisfactory answer, I was struggling with this problem for awhile, and we'd previously determined that it could not be done reasonably well in SQL and we were going to have to use javascript on a JSON array.
Here's how I solved it just using SQL. Hopefully this is helpful for others:
I had data such as:
Scene 1 Scene 1A Scene 1B Scene 2A Scene 3 ... Scene 101 Scene XXA1 Scene XXA2
I actually didn't "cast" things though I suppose that may also have worked.
I first replaced the parts that were unchanging in the data, in this case "Scene ", and then did a LPAD to line things up. This seems to allow pretty well for the alpha strings to sort properly as well as the numbered ones.
My ORDER BY
clause looks like:
ORDER BY LPAD(REPLACE(`table`.`column`,'Scene ',''),10,'0')
Obviously this doesn't help with the original problem which was not so uniform - but I imagine this would probably work for many other related problems, so putting it out there.
Add a field for "sort key" that has all strings of digits zero-padded to a fixed length and then sort on that field instead.
If you might have long strings of digits, another method is to prepend the number of digits (fixed-width, zero-padded) to each string of digits. For example, if you won't have more than 99 digits in a row, then for "Super Blast 10 Ultra" the sort key would be "Super Blast 0210 Ultra".
You can also create in a dynamic way the "sort column" :
SELECT name, (name = '-') boolDash, (name = '0') boolZero, (name+0 > 0) boolNum
FROM table
ORDER BY boolDash DESC, boolZero DESC, boolNum DESC, (name+0), name
That way, you can create groups to sort.
In my query, I wanted the '-' in front of everything, then the numbers, then the text. Which could result in something like :
-
0
1
2
3
4
5
10
13
19
99
102
Chair
Dog
Table
Windows
That way you don't have to maintain the sort column in the correct order as you add data. You can also change your sort order depending on what you need.
Also there is natsort. It is intended to be a part of a drupal plugin, but it works fine stand-alone.
Add a Sort Key (Rank) in your table. ORDER BY rank
Utilise the "Release Date" column. ORDER BY release_date
When extracting the data from SQL, make your object do the sorting, e.g., if extracting into a Set, make it a TreeSet, and make your data model implement Comparable and enact the natural sort algorithm here (insertion sort will suffice if you are using a language without collections) as you'll be reading the rows from SQL one by one as you create your model and insert it into the collection)
Here is a simple one if titles only have the version as a number:
ORDER BY CAST(REGEXP_REPLACE(title, "[a-zA-Z]+", "") AS INT)';
Otherwise you can use simple SQL if you use a pattern (this pattern uses a # before the version):
create table titles(title);
insert into titles (title) values
('Final Fantasy'),
('Final Fantasy #03'),
('Final Fantasy #11'),
('Final Fantasy #10'),
('Final Fantasy #2'),
('Bond 007 ##2'),
('Final Fantasy #01'),
('Bond 007'),
('Final Fantasy #11}');
select REGEXP_REPLACE(title, "#([0-9]+)", "\\1") as title from titles
ORDER BY REGEXP_REPLACE(title, "#[0-9]+", ""),
CAST(REGEXP_REPLACE(title, ".*#([0-9]+).*", "\\1") AS INT);
+-------------------+
| title |
+-------------------+
| Bond 007 |
| Bond 007 #2 |
| Final Fantasy |
| Final Fantasy 01 |
| Final Fantasy 2 |
| Final Fantasy 03 |
| Final Fantasy 10 |
| Final Fantasy 11 |
| Final Fantasy 11} |
+-------------------+
8 rows in set, 2 warnings (0.001 sec)
You can use other patterns if needed. For example if you have a movie "I'm #1" and "I'm #1 part 2" then maybe wrap the version e.g. "Final Fantasy {11}"