Oracle:如何在 SQL 查询中实现“自然”排序?
Posted
技术标签:
【中文标题】Oracle:如何在 SQL 查询中实现“自然”排序?【英文标题】:Oracle: How can I implement a "natural" order-by in a SQL query? 【发布时间】:2008-10-20 21:20:56 【问题描述】:例如,
foo1
foo2
foo10
foo100
而不是
foo1
foo10
foo100
foo2
更新:对自己编码排序不感兴趣(尽管这本身很有趣),但让数据库为我进行排序。
【问题讨论】:
Jeff also has a post 关于该主题,还有更多其他语言的资源。 【参考方案1】:您可以在 order-by 子句中使用函数。在这种情况下, 您可以拆分的非数字和数字部分 字段并将它们用作两个排序标准。
select * from t
order by to_number(regexp_substr(a,'^[0-9]+')),
to_number(regexp_substr(a,'[0-9]+$')),
a;
您还可以创建基于函数的索引来支持这一点:
create index t_ix1
on t (to_number(regexp_substr(a, '^[0-9]+')),
to_number(regexp_substr(a, '[0-9]+$')),
a);
【讨论】:
如果您想要 SQL 答案,您可能应该澄清您的问题以指定它。还是想收集各种分拣技巧? 他同时发布了问题和答案。他要么希望分享他发现的这一点知识,要么收集代表点数,要么两者兼而有之。 不要失礼,但它被标记为SQL。 尽管如此,问题中的更多描述不会有什么坏处 $[0-9]+ 不匹配行尾之后的数字,所以总是为空?此外,如果有一组以上的数字,这也不起作用。【参考方案2】:我使用以下函数对值中可以找到的所有小于 10 的数字序列进行 0 填充,使每个数字的总长度变为 10 位。它甚至与包含一个、多个或没有数字序列的混合值集兼容。
CREATE OR replace function NATURAL_ORDER(
P_STR varchar2
) return varchar2
IS
/** --------------------------------------------------------------------
Replaces all sequences of numbers shorter than 10 digits by 0-padded
numbers that exactly 10 digits in length. Usefull for ordering-by
using NATURAL ORDER algorithm.
*/
l_result varchar2( 32700 );
l_len integer;
l_ix integer;
l_end integer;
begin
l_result := P_STR;
l_len := LENGTH( l_result );
l_ix := 1;
while l_len > 0 loop
l_ix := REGEXP_INSTR( l_result, '[0-9]1,9', l_ix, 1, 0 );
EXIT when l_ix = 0;
l_end := REGEXP_INSTR( l_result, '[^0-9]|$', l_ix, 1, 0 );
if ( l_end - l_ix >= 10 ) then
l_ix := l_end;
else
l_result := substr( l_result, 1, l_ix - 1 )
|| LPAD( SUBSTR( l_result, l_ix, l_end-l_ix ), 10, '0' )
|| substr( l_result, l_end )
;
l_ix := l_ix + 10;
end if;
end loop;
return l_result;
end;
/
例如:
select 'ABC' || LVL || 'DEF' as STR
from (
select LEVEL as LVL
from DUAL
start with 1=1
connect by LEVEL <= 35
)
order by NATURAL_ORDER( STR )
【讨论】:
聪明!感谢分享。虽然我怀疑不是特别有效。并且显然会分解为超过 10 位的较大整数。不过,很有趣。【参考方案3】:对于短字符串,少量数字
如果“数字”的数量和最大长度受到限制,则有一个基于正则表达式的解决方案。
想法是:
用 20 个零填充所有数字 使用另一个正则表达式删除过多的零。由于regexp backtracking,这可能会很慢。假设:
预先知道数字的最大长度(例如 20) 所有数字都可以填充(换句话说,lpad('1 ', 3000, '1 ')
将失败,因为无法将填充的数字放入 varchar2(4000)
)
以下查询针对“短数字”情况进行了优化(请参阅*?
),它需要 0.4 秒。但是,在使用这种方法时,您需要预先定义填充长度。
select * from (
select dbms_random.string('X', 30) val from xmltable('1 to 1000')
)
order by regexp_replace(regexp_replace(val, '(\d+)', lpad('0', 20, '0')||'\1')
, '0*?(\d21(\D|$))', '\1');
“聪明”的方法
尽管单独的 natural_sort
函数可以很方便,但在纯 SQL 中有一个鲜为人知的技巧。
关键思想:
去除所有数字的前导零,因此02
在1
和3
之间排序:regexp_replace(val, '(^|\D)0+(\d+)', '\1\2')
。注意:这可能会导致10.02
> 10.1
的“意外”排序(因为02
转换为2
),但是没有单一的答案应该如何排序10.02.03
之类的东西
将 "
转换为 ""
以便带引号的文本正常工作
将输入字符串转换为逗号分隔格式:'"'||regexp_replace(..., '([^0-9]+)', '","\1","')||'"'
通过xmltable
将csv转换为项目列表
增加类似数字的项目,以便字符串排序正常工作
使用length(length(num))||length(num)||num
代替lpad(num, 10, '0')
,因为后者不太紧凑并且不支持11 位以上的数字。
注意:
对于 1000 个长度为 30 的随机字符串的排序列表,响应时间大约为 3-4 秒(随机字符串的生成本身需要 0.2 秒)。
主要时间消费者是xmltable
,它将文本拆分为行。
如果使用 PL/SQL 而不是 xmltable
将字符串拆分为行,对于相同的 1000 行,响应时间将减少到 0.4 秒。
以下查询对 100 个随机字母数字字符串执行自然排序(注意:它在 Oracle 11.2.0.4 中产生错误结果,在 12.1.0.2 中有效):
select *
from (
select (select listagg(case when regexp_like(w, '^[0-9]')
then length(length(w))||length(w)||w else w
end
) within group (order by ord)
from xmltable(t.csv columns w varchar2(4000) path '.'
, ord for ordinality) q
) order_by
, t.*
from (
select '"'||regexp_replace(replace(
regexp_replace(val, '(^|\D)0+(\d+)', '\1\2')
, '"', '""')
, '([^0-9]+)', '","\1","')||'"' csv
, t.*
from (
select dbms_random.string('X', 30) val from xmltable('1 to 100')
) t
) t
) t
order by order_by;
有趣的是order by
可以在没有子查询的情况下表达,所以它是一个让你的审阅者疯狂的方便工具(它适用于 11.2.0.4 和 12.1.0.2):
select *
from (select dbms_random.string('X', 30) val from xmltable('1 to 100')) t
order by (
select listagg(case when regexp_like(w, '^[0-9]')
then length(length(w))||length(w)||w else w
end
) within group (order by ord)
from xmltable('$X'
passing xmlquery(('"'||regexp_replace(replace(
regexp_replace(t.val, '(^|\D)0+(\d+)', '\1\2')
, '"', '""')
, '([^0-9]+)', '","\1","')||'"')
returning sequence
) as X
columns w varchar2(4000) path '.', ord for ordinality) q
);
【讨论】:
那一定表现得非常好... :-)以上是关于Oracle:如何在 SQL 查询中实现“自然”排序?的主要内容,如果未能解决你的问题,请参考以下文章
java中数据库中实现分页的sql语句要求每页十条要查询的是第二页
我们如何在 Oracle SQL 或 PL/SQL 中实现 Standard Normal CDF?