在过去的几天里,我一直在尝试按自然顺序对字母数字文本列表进行排序。我发现使用 NLS_SORT 选项可以正确对列表进行排序( see this answer )。但当我尝试这个解决方案时,我发现它没有什么区别。该列表仍然显示为正常的 ORDER BY 查询。请注意 solution involving regex对我来说不是一个选择。
出于测试目的,我制作了一个表格并填充了一些数据。运行 SELECT name FROM test ORDER BY name ASC 时,我得到以下结果:
如您所见,排序不自然。它应该更像 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
之前。您无法更改排序行为的这一方面。在您链接到的前面的问题中,看起来只有第一个字符是数字,这是一个略有不同的情况。
When character values are compared linguistically for the
ORDER BY
clause, they are first transformed to collation keys and then compared likeRAW
values. The collation keys are generated either explicitly as specified inNLSSORT
or implicitly using the same method thatNLSSORT
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/