oracle - 字母数字文本的二进制排序不表现得像自然排序

标签 oracle natural-sort varchar2

在过去的几天里,我一直在尝试按自然顺序对字母数字文本列表进行排序。我发现使用 NLS_SORT 选项可以正确对列表进行排序( see this answer )。但当我尝试这个解决方案时,我发现它没有什么区别。该列表仍然显示为正常的 ORDER BY 查询。请注意 solution involving regex对我来说不是一个选择。

出于测试目的,我制作了一个表格并填充了一些数据。运行 SELECT name FROM test ORDER BY name ASC 时,我得到以下结果:

enter image description here

如您所见,排序不自然。它应该更像 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

我尝试的解决方案涉及设置 nls_sort 选项。

ALTER SESSION SET nls_sort='BINARY'; -- or BINARY_AI
SELECT name FROM test ORDER BY NLSSORT(name,'NLS_SORT=BINARY') -- or BINARY_AI

它应该根据 ASCII table 中所述的每个字符的十进制代码对列表中的文本进行排序。 。所以我预计它会以正确的方式出现(因为该表中的顺序是“空格”,“点”,数字,字母),但它没有改变任何东西。顺序仍然与图像中的相同。

If it is BINARY then the sort order is based on the numeric value of each character, so it's dependant on the database character set

这可能与我使用的字符集有关,但我不确定它有什么问题。运行 SELECT value$ FROM sys.props$ WHERE name = 'NLS_CHARACTERSET'; 给出值 AL32UTF8。这看起来像是 UTF8 的稍微扩展版本(如果我错了,请纠正我)。我正在 Oracle 数据库版本 11.2.0.4.0 上运行。

那么谁能告诉我我做错了什么或者我错过了什么?

提前致谢。

最佳答案

您似乎期望二进制排序能够同时查看多个字符。事实并非如此。它有效地按第一个字符排序(因此以 1 开头的所有内容都位于以 2 开头的任何内容之前);然后是第二个字符(因此句点出现在 0 之前) - 这意味着 1. 出现在 10 之前是正确的,而且 10 也是正确的>(或 100000)位于 2 之前。您无法更改排序行为的这一方面。在您链接到的前面的问题中,看起来只有第一个字符是数字,这是一个略有不同的情况。

From the documentation :

When character values are compared linguistically for the ORDER BY clause, they are first transformed to collation keys and then compared like RAW values. The collation keys are generated either explicitly as specified in NLSSORT or implicitly using the same method that NLSSORT uses.

您可以看到用于排序的字节顺序:

with t (name) as (
  select level - 1 || '. test' from dual connect by level < 13
  union all select '20. test' from dual
  union all select '100. test' from dual
)
select name, nlssort(name, 'NLS_SORT=BINARY') as sort_bytes
from t
order by name;

NAME       SORT_BYTES         
---------- --------------------
0. test    302E207465737400    
1. test    312E207465737400    
10. test   31302E207465737400  
100. test  3130302E207465737400
11. test   31312E207465737400  
2. test    322E207465737400    
20. test   32302E207465737400  
3. test    332E207465737400    
4. test    342E207465737400    
5. test    352E207465737400    
6. test    362E207465737400    
7. test    372E207465737400    
8. test    382E207465737400    
9. test    392E207465737400  

您可以看到原始 NLSRORT 结果(排序规则键)按逻辑顺序排列。

如果您不想使用正则表达式,可以使用 substr()instr() 获取句点/空格之前的部分并将其转换到一个号码;尽管假设格式是固定的:

with t (name) as (
  select level - 1 || '. test' from dual connect by level < 13
  union all select '20. test' from dual
  union all select '100. test' from dual
)
select name
from t
order by to_number(substr(name, 1, instr(name, '. ') - 1)),
  substr(name, instr(name, '. '));

NAME     
----------
0. test   
1. test   
2. test   
3. test   
4. test   
5. test   
6. test   
7. test   
8. test   
9. test   
10. test  
11. test  
20. test  
100. test 

如果可能没有句点/空格,您可以检查一下:

select name
from t
order by case when instr(name, '. ') > 0 then to_number(substr(name, 1, instr(name, '. ') - 1)) else 0 end,
  case when instr(name, '. ') > 0 then substr(name, instr(name, '. ')) else name end;

...但是,如果名称中有两个句子,但第一个句子无法转换为数字,那么您仍然会遇到问题。如果发生这种情况,您可以实现一个“安全”to_number() 函数来压缩 ORA-01722。

使用正则表达式会更简单、更安全,例如:

select name
from t
order by to_number(regexp_substr(name, '^\d+', 1)), name;

关于oracle - 字母数字文本的二进制排序不表现得像自然排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39466527/

相关文章:

sql - 没有连接的 Oracle IN 子句对性能有何影响?

sql - ORACLE SQL - 如何找到每位教师在教师辞职前 2 个月每天的减免次数?

groovy - Groovy 中按自然顺序对列表进行排序

oracle - Oracle NLS_NCHAR_CHARACTERSET 和 NLS_CHARACTERSET 之间的区别

oracle - 删除标点符号

oracle - JSF/ADF selectOneChoice shortDesc 大小

sql - Oracle 触发器而不是更新和 ORA-22816 错误

c# - 使用自然排序对对象列表进行排序

mysql - 如何对 “X-Y” 字符串数据进行自然排序,先按 X,然后按 Y?

oracle utl_mail消息正文大小限制