Oracle:如何在SQL查询中实现“自然”order by?

Oracle:如何在SQL查询中实现“自然”order by?,sql,oracle,Sql,Oracle,e、 g, 而不是 foo1 foo2 foo10 foo100 更新:我对自己编写排序代码不感兴趣,虽然这本身就很有趣,但是有数据库为我进行排序。您可以在order by子句中使用函数。在这种情况下,, 您可以拆分的非数字部分和数字部分 字段,并将其用作两个排序标准 foo1 foo10 foo100 foo2 您还可以创建基于函数的索引以支持此操作: select * from t order by to_number(regexp_substr(a,'^[0-9]+')),

e、 g,

而不是

foo1
foo2
foo10
foo100

更新:我对自己编写排序代码不感兴趣,虽然这本身就很有趣,但是有数据库为我进行排序。

您可以在order by子句中使用函数。在这种情况下,, 您可以拆分的非数字部分和数字部分 字段,并将其用作两个排序标准

foo1
foo10
foo100
foo2
您还可以创建基于函数的索引以支持此操作:

select * from t
 order by to_number(regexp_substr(a,'^[0-9]+')),
          to_number(regexp_substr(a,'[0-9]+$')),
          a;

我使用以下函数0-pad值中所有小于10的数字序列,使每个数字的总长度变为10位。它甚至与具有一个、多个或无数字序列的混合值集兼容

create index t_ix1
    on t (to_number(regexp_substr(a, '^[0-9]+')),
          to_number(regexp_substr(a, '[0-9]+$')), 
          a);
例如:

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;
/
对于短字符串,使用少量数字 如果数字的数量和最大长度受到限制,则存在基于regexp的解决方案

这个想法是:

用20个零填充所有数字 使用另一个regexp删除过多的零。这可能是缓慢的,因为。 假设:

预先知道数字的最大长度,例如20 所有的数字都可以填充,换句话说,lpad'1',3000',1'将因do无法将填充的数字放入varchar24000而失败 以下查询针对简短的数字案例进行了优化,请参见*?需要0.4秒。但是,在使用这种方法时,需要预定义填充长度

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 )
巧妙的方法 尽管单独的自然排序函数很方便,但在纯SQL中有一个鲜为人知的技巧可以做到这一点

关键思想:

所有数字的带前导零so 02的顺序介于1和3之间:regexp_replaceval,“^ |\D0+\d+”,“\1\2”。注意:这可能会导致10.02>10.1的意外排序,因为02被转换为2,但是对于10.02.03之类的东西应该如何排序,没有一个单一的答案 转换为带引号的文本可以正常工作 将输入字符串转换为逗号分隔的格式:“| | regexp|u replace…”、“[^0-9]+”、“、\1”、“| |” 通过xmltable将csv转换为项目列表 增加类似数字的项,以便字符串排序正常工作 使用lengthlengthnum | | lengthnum | | num代替lpadnum,10,'0',因为后者不太紧凑,不支持11位以上的数字。 注: 对于长度为30的1000个随机字符串的排序列表,响应时间大约为3-4秒。生成随机字符串本身需要0.2秒。 主要的时间消费者是将文本拆分为行的xmltable。 如果使用PL/SQL而不是xmltable将字符串拆分为行,那么对于相同的1000行,响应时间将减少到0.4秒

以下查询对100个随机字母数字字符串执行自然排序注意:它在Oracle 11.2.0.4中生成错误结果,在12.1.0.2中有效:

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*?(\d{21}(\D|$))', '\1');
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 1000')
)
order by regexp_replace(regexp_replace(val, '(\d+)', lpad('0', 20, '0')||'\1')
                      , '0*?(\d{21}(\D|$))', '\1');
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;

如果您想要SQL答案,您可能应该澄清您的问题以指定答案。或者你想收集各种分类技术?他同时发布了问题和答案。他要么是想分享他发现的这一点知识,要么收集代表点,要么两者兼而有之。这不是不礼貌的,但它被标记为SQL。尽管如此,问题中的更多描述不会有什么影响,$[0-9]+不是匹配行尾后的数字,所以总是为空吗?此外,如果有多组数字,这也不起作用。这必须表现得非常好…:-关于这个话题,有更多其他语言的资源。聪明!谢谢分享。虽然我怀疑不是特别有效率。很明显,用大于10位数的大整数分解。还是很有趣。