自然sorting在MySQL中

有没有一个优雅的方式来在MySQL数据库中进行自然sorting?

例如,如果我有这个数据集:

  • 最终幻想
  • 最终幻想4
  • 最终幻想10
  • 最终幻想12
  • 最终幻想12:Promathia的链条
  • 最终幻想冒险
  • 最终幻想起源
  • 最终幻想战术

任何其他优雅的解决scheme,而不是把游戏的名字分解成他们的组件

  • 标题 :“最终幻想”
  • 号码 :“12”
  • 副标题 :“Promathia链子”

确保它们以正确的顺序出来? (10之后4,而不是2之前)。

这样做是在a **中的一个痛苦,因为时不时的另一个游戏打破了parsing游戏标题(例如“战锤4万”,“詹姆斯·邦德007”)的机制

我想这就是为什么很多东西都是按发布datesorting的原因。

解决scheme可能是在表中为“SortKey”创build另一列。 这可能是标题的消毒版本,符合您创build的易于sorting或计数器的模式。

这是一个快速解决scheme:

SELECT alphanumeric, integer FROM sorting_test ORDER BY LENGTH(alphanumeric), alphanumeric 

刚刚发现这个:

 SELECT names FROM your_table ORDER BY games + 0 ASC 

当数字在前面的时候自然sorting,可能也适用于中间。

与@plalx发布的function相同,但重写为MySQL:

 DROP FUNCTION IF EXISTS `udf_FirstNumberPos`; DELIMITER ;; CREATE FUNCTION `udf_FirstNumberPos` (`instring` varchar(4000)) RETURNS int LANGUAGE SQL DETERMINISTIC NO SQL SQL SECURITY INVOKER BEGIN DECLARE position int; DECLARE tmp_position int; SET position = 5000; SET tmp_position = LOCATE('0', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF; SET tmp_position = LOCATE('1', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF; SET tmp_position = LOCATE('2', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF; SET tmp_position = LOCATE('3', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF; SET tmp_position = LOCATE('4', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF; SET tmp_position = LOCATE('5', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF; SET tmp_position = LOCATE('6', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF; SET tmp_position = LOCATE('7', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF; SET tmp_position = LOCATE('8', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF; SET tmp_position = LOCATE('9', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF; IF (position = 5000) THEN RETURN 0; END IF; RETURN position; END ;; DROP FUNCTION IF EXISTS `udf_NaturalSortFormat`; DELIMITER ;; CREATE FUNCTION `udf_NaturalSortFormat` (`instring` varchar(4000), `numberLength` int, `sameOrderChars` char(50)) RETURNS varchar(4000) LANGUAGE SQL DETERMINISTIC NO SQL SQL SECURITY INVOKER BEGIN DECLARE sortString varchar(4000); DECLARE numStartIndex int; DECLARE numEndIndex int; DECLARE padLength int; DECLARE totalPadLength int; DECLARE i int; DECLARE sameOrderCharsLen int; SET totalPadLength = 0; SET instring = TRIM(instring); SET sortString = instring; SET numStartIndex = udf_FirstNumberPos(instring); SET numEndIndex = 0; SET i = 1; SET sameOrderCharsLen = CHAR_LENGTH(sameOrderChars); WHILE (i <= sameOrderCharsLen) DO SET sortString = REPLACE(sortString, SUBSTRING(sameOrderChars, i, 1), ' '); SET i = i + 1; END WHILE; WHILE (numStartIndex <> 0) DO SET numStartIndex = numStartIndex + numEndIndex; SET numEndIndex = numStartIndex; WHILE (udf_FirstNumberPos(SUBSTRING(instring, numEndIndex, 1)) = 1) DO SET numEndIndex = numEndIndex + 1; END WHILE; SET numEndIndex = numEndIndex - 1; SET padLength = numberLength - (numEndIndex + 1 - numStartIndex); IF padLength < 0 THEN SET padLength = 0; END IF; SET sortString = INSERT(sortString, numStartIndex + totalPadLength, 0, REPEAT('0', padLength)); SET totalPadLength = totalPadLength + padLength; SET numStartIndex = udf_FirstNumberPos(RIGHT(instring, CHAR_LENGTH(instring) - numEndIndex)); END WHILE; RETURN sortString; END ;; 

用法:

 SELECT name FROM products ORDER BY udf_NaturalSortFormat(name, 10, ".") 

MySQL不允许这种“自然sorting”,所以它看起来像是最好的方式来得到你正在分裂你的数据设置,如上所述(单独的ID字段等),或失败根据你的数据库中的非标题元素,索引元素(date,db中插入的id等)进行sorting。

让db对你进行sorting几乎总是比将大型数据集读入你select的编程语言并对其进行sorting要快,所以如果你在这里对db模式有任何控制,那么看看添加如上所述的容易sorting的字段,从长远来看它将为您节省大量的麻烦和维护。

在MySQL错误和讨论论坛上不时出现要求添加“自然sorting”的请求,许多解决scheme围绕删除数据的特定部分并将其转换为查询的ORDER BY部分,例如

 SELECT * FROM table ORDER BY CAST(mid(name, 6, LENGTH(c) -5) AS unsigned) 

这种解决scheme可能只是在上面的最终幻想的例子上工作,但不是特别灵活,并且不太可能完全扩展到一个数据集,比如说“Warhammer 40000”和“James Bond 007” 。

前段时间我为MSSQL 2000写了这个函数:

 /** * Returns a string formatted for natural sorting. This function is very useful when having to sort alpha-numeric strings. * * @author Alexandre Potvin Latreille (plalx) * @param {nvarchar(4000)} string The formatted string. * @param {int} numberLength The length each number should have (including padding). This should be the length of the longest number. Defaults to 10. * @param {char(50)} sameOrderChars A list of characters that should have the same order. Ex: '.-/'. Defaults to empty string. * * @return {nvarchar(4000)} A string for natural sorting. * Example of use: * * SELECT Name FROM TableA ORDER BY Name * TableA (unordered) TableA (ordered) * ------------ ------------ * ID Name ID Name * 1. A1. 1. A1-1. * 2. A1-1. 2. A1. * 3. R1 --> 3. R1 * 4. R11 4. R11 * 5. R2 5. R2 * * * As we can see, humans would expect A1., A1-1., R1, R2, R11 but that's not how SQL is sorting it. * We can use this function to fix this. * * SELECT Name FROM TableA ORDER BY dbo.udf_NaturalSortFormat(Name, default, '.-') * TableA (unordered) TableA (ordered) * ------------ ------------ * ID Name ID Name * 1. A1. 1. A1. * 2. A1-1. 2. A1-1. * 3. R1 --> 3. R1 * 4. R11 4. R2 * 5. R2 5. R11 */ CREATE FUNCTION dbo.udf_NaturalSortFormat( @string nvarchar(4000), @numberLength int = 10, @sameOrderChars char(50) = '' ) RETURNS varchar(4000) AS BEGIN DECLARE @sortString varchar(4000), @numStartIndex int, @numEndIndex int, @padLength int, @totalPadLength int, @i int, @sameOrderCharsLen int; SELECT @totalPadLength = 0, @string = RTRIM(LTRIM(@string)), @sortString = @string, @numStartIndex = PATINDEX('%[0-9]%', @string), @numEndIndex = 0, @i = 1, @sameOrderCharsLen = LEN(@sameOrderChars); -- Replace all char that has to have the same order by a space. WHILE (@i <= @sameOrderCharsLen) BEGIN SET @sortString = REPLACE(@sortString, SUBSTRING(@sameOrderChars, @i, 1), ' '); SET @i = @i + 1; END -- Pad numbers with zeros. WHILE (@numStartIndex <> 0) BEGIN SET @numStartIndex = @numStartIndex + @numEndIndex; SET @numEndIndex = @numStartIndex; WHILE(PATINDEX('[0-9]', SUBSTRING(@string, @numEndIndex, 1)) = 1) BEGIN SET @numEndIndex = @numEndIndex + 1; END SET @numEndIndex = @numEndIndex - 1; SET @padLength = @numberLength - (@numEndIndex + 1 - @numStartIndex); IF @padLength < 0 BEGIN SET @padLength = 0; END SET @sortString = STUFF( @sortString, @numStartIndex + @totalPadLength, 0, REPLICATE('0', @padLength) ); SET @totalPadLength = @totalPadLength + @padLength; SET @numStartIndex = PATINDEX('%[0-9]%', RIGHT(@string, LEN(@string) - @numEndIndex)); END RETURN @sortString; END GO 

所以,虽然我知道你已经find了一个令人满意的答案,但我一直在努力解决这个问题,而且之前我们已经确定在SQL中不能很好地完成任务,而且我们将不得不在JavaScript上使用JSONarrays。

以下是我如何使用SQL解决它。 希望这对其他人有帮助:

我有这样的数据:

场景1
场景1A
场景1B
场景2A
场景3
 ...
场景101
场景XXA1
场景XXA2

我其实并没有“投”的东西,但我想这也可能奏效了。

我首先replace了数据中不变的部分,在这个例子中是“Scene”,然后做了一个LPAD来排列。 这似乎很好地允许stringsorting正确以及编号的。

我的ORDER BY子句如下所示:

 ORDER BY LPAD(REPLACE(`table`.`column`,'Scene ',''),10,'0') 

显然,这并不能解决最初的问题,因为这个问题并不那么统一 – 但我想这可能会适用于其他许多相关的问题,所以把它放在那里。

  1. 在表格中添加一个sorting键(排名)。 ORDER BY rank

  2. 利用“发布date”栏。 ORDER BY release_date

  3. 当从SQL中提取数据时,让你的对象做sorting,例如,如果解压到一个Set中,使它成为一个TreeSet,并使你的数据模型实现Comparable,并在这里执行自然sortingalgorithm(插入sorting就足够了,如果你正在使用一种没有集合的语言),因为当你创build模型并将其插入到集合中时,你将逐个从SQL中读取行)

关于Richard Toth的最佳答复https://stackoverflow.com/a/12257917/4052357

注意包含2字节(或更多)字符和数字的UTF8编码string,例如

 12 南新宿 

udf_NaturalSortFormat函数中使用MySQL的LENGTH()会返回string的字节长度,并且不正确,而是使用CHAR_LENGTH()来返回正确的字符长度。

在我的情况下使用LENGTH()导致查询永远不会完成,并导致100%的CPU使用MySQL

 DROP FUNCTION IF EXISTS `udf_NaturalSortFormat`; DELIMITER ;; CREATE FUNCTION `udf_NaturalSortFormat` (`instring` varchar(4000), `numberLength` int, `sameOrderChars` char(50)) RETURNS varchar(4000) LANGUAGE SQL DETERMINISTIC NO SQL SQL SECURITY INVOKER BEGIN DECLARE sortString varchar(4000); DECLARE numStartIndex int; DECLARE numEndIndex int; DECLARE padLength int; DECLARE totalPadLength int; DECLARE i int; DECLARE sameOrderCharsLen int; SET totalPadLength = 0; SET instring = TRIM(instring); SET sortString = instring; SET numStartIndex = udf_FirstNumberPos(instring); SET numEndIndex = 0; SET i = 1; SET sameOrderCharsLen = CHAR_LENGTH(sameOrderChars); WHILE (i <= sameOrderCharsLen) DO SET sortString = REPLACE(sortString, SUBSTRING(sameOrderChars, i, 1), ' '); SET i = i + 1; END WHILE; WHILE (numStartIndex <> 0) DO SET numStartIndex = numStartIndex + numEndIndex; SET numEndIndex = numStartIndex; WHILE (udf_FirstNumberPos(SUBSTRING(instring, numEndIndex, 1)) = 1) DO SET numEndIndex = numEndIndex + 1; END WHILE; SET numEndIndex = numEndIndex - 1; SET padLength = numberLength - (numEndIndex + 1 - numStartIndex); IF padLength < 0 THEN SET padLength = 0; END IF; SET sortString = INSERT(sortString, numStartIndex + totalPadLength, 0, REPEAT('0', padLength)); SET totalPadLength = totalPadLength + padLength; SET numStartIndex = udf_FirstNumberPos(RIGHT(instring, CHAR_LENGTH(instring) - numEndIndex)); END WHILE; RETURN sortString; END ;; 

PS我会添加这个作为评论原来,但我没有足够的声誉(还)

另一个select是在从MySQL中提取数据后在内存中进行sorting。 虽然从性能的angular度来看它不是最好的select,但是如果你不sorting巨大的列表,你应该没问题。

如果你看看Jeff的post,你可以find很多algorithm来处理你可能使用的语言。 http://www.codinghorror.com/blog/archives/001018.html

为“sorting键”添加一个字段,该字段的所有string都是零填充到固定长度,然后对该字段进行sorting。

如果你可能有很长的一串数字,另一种方法是在每个数字串上加上数字的数量(固定宽度,零填充)。 例如,如果连续不超过99个数字,那么对于“Super Blast 10 Ultra”,sorting键将是“Super Blast 0210 Ultra”。

订购:
0
1
2
10
23
101
205
1000
一个
AAC
b
casdsadsa
CSS

使用这个查询:

select 
    列名 
从 
     TABLE_NAME 
 ORDER BY
     column_name REGEXP'^ \ d * [^ \ da-z&\。\'\  -  \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \'\'\ | \ _ \  - ]'DESC, 
     column_name + 0, 
    列名;

我尝试了几个解决scheme,但实际上它非常简单:

 SELECT test_column FROM test_table ORDER BY LENGTH(test_column) DESC, test_column DESC /* Result -------- value_1 value_2 value_3 value_4 value_5 value_6 value_7 value_8 value_9 value_10 value_11 value_12 value_13 value_14 value_15 ... */ 

您还可以dynamic地创build“sorting列”:

 SELECT name, (name = '-') boolDash, (name = '0') boolZero, (name+0 > 0) boolNum FROM table ORDER BY boolDash DESC, boolZero DESC, boolNum DESC, (name+0), name 

这样,您可以创build组进行sorting。

在我的查询中,我想要“ – ”在所有内容之前,然后是数字,然后是文本。 这可能会导致类似的情况:

 - 0 1 2 3 4 5 10 13 19 99 102 Chair Dog Table Windows 

这样,您不必在添加数据时按照正确的顺序维护sorting列。 您也可以根据需要更改sorting顺序。

如果你使用PHP,你可以在PHP中进行自然sorting。

 $keys = array(); $values = array(); foreach ($results as $index => $row) { $key = $row['name'].'__'.$index; // Add the index to create an unique key. $keys[] = $key; $values[$key] = $row; } natsort($keys); $sortedValues = array(); foreach($keys as $index) { $sortedValues[] = $values[$index]; } 

我希望MySQL能够在未来的版本中实现自然sorting,但是function要求(#1588)从2003年开始,所以我不会屏住呼吸。

@ plaix / Richard Toth / Luke Hoggett最佳答案的简化非udf版本,仅适用于该领域的第一个整数

 SELECT name, LEAST( IFNULL(NULLIF(LOCATE('0', name), 0), ~0), IFNULL(NULLIF(LOCATE('1', name), 0), ~0), IFNULL(NULLIF(LOCATE('2', name), 0), ~0), IFNULL(NULLIF(LOCATE('3', name), 0), ~0), IFNULL(NULLIF(LOCATE('4', name), 0), ~0), IFNULL(NULLIF(LOCATE('5', name), 0), ~0), IFNULL(NULLIF(LOCATE('6', name), 0), ~0), IFNULL(NULLIF(LOCATE('7', name), 0), ~0), IFNULL(NULLIF(LOCATE('8', name), 0), ~0), IFNULL(NULLIF(LOCATE('9', name), 0), ~0) ) AS first_int FROM table ORDER BY IF(first_int = ~0, name, CONCAT( SUBSTR(name, 1, first_int - 1), LPAD(CAST(SUBSTR(name, first_int) AS UNSIGNED), LENGTH(~0), '0'), SUBSTR(name, first_int + LENGTH(CAST(SUBSTR(name, first_int) AS UNSIGNED))) )) ASC 

如果你不想重新发明轮子,或者有许多代码不起作用的头痛,只需使用Drupal Natural Sort …只需运行压缩的SQL(MySQL或Postgre)即可。 在进行查询时,只需使用以下命令:

 ... ORDER BY natsort_canon(column_name, 'natural') 

也有natsort 。 它的目的是成为一个Drupal插件的一部分,但它独立工作。

我知道这个话题是古老的,但我想我已经find了一个办法来做到这一点:

 SELECT * FROM `table` ORDER BY CONCAT( GREATEST( LOCATE('1', name), LOCATE('2', name), LOCATE('3', name), LOCATE('4', name), LOCATE('5', name), LOCATE('6', name), LOCATE('7', name), LOCATE('8', name), LOCATE('9', name) ), name ) ASC 

废话,它不正确地sorting下列设置(这是无用的笑):

最终幻想1最终幻想2最终幻想5最终幻想7最终幻想7:来临儿童最终幻想12最终幻想112 FF1 FF2