使用多个小数点(。)对Oracle中的记录进行排序

时间:2014-01-09 12:22:00

标签: sql oracle sorting oracle10g decimal-point

  

更新

     

ORACLE VERSION 10G

我在Oracle中有一个记录列表,如下所示,这些记录实际上是各种书籍的一部分 记录以下列格式生成

[主题]。[子主题]。[第一级] ....... [最后一级]

Sections
--------
1
7.1
6.2 
7.1
7.4
6.8.3
6.8.2
10
1.1
7.6
6.1
11
8.3
8.5
1.1.2
6.4
6.6
8.4
1.1.6
6.8.1
7.7.1
7.5
7.3

我想订购如下

 1
 1.1
 1.1.2
 1.1.6
 6.2    
 6.4    
 6.5    
 6.6    
 6.7    
 6.8.1    
 6.8.2    
 6.8.3    
 7.2    
 7.3    
 7.4    
 7.5    
 7.6    
 7.7.1    
 7.7.2    
 8.3    
 8.4    
 8.5
 10

但由于该字段不是numeric datatype,因此排序会产生类似的结果

1
10
1.1
1.1.2
1.1.6
....
.....
8.5

我如何对它们进行排序。由于多个小数点,我无法将它们转换为数字。

oracle中是否有支持这种排序技术的函数

6 个答案:

答案 0 :(得分:5)

当已知最大深度时,您可以在子部分中拆分该部分:

SQL> SELECT SECTION FROM DATA
  2   ORDER BY to_number(regexp_substr(SECTION, '[^.]+', 1, 1)) NULLS FIRST,
  3            to_number(regexp_substr(SECTION, '[^.]+', 1, 2)) NULLS FIRST,
  4            to_number(regexp_substr(SECTION, '[^.]+', 1, 3)) NULLS FIRST;

SECTION
-------
1
1.1
1.1.2
1.1.6
6.1
6.2
[...]
8.5
10
11

如果子节的最大深度未知(但可能在8位字符数据库中少于几百或在ANSI字符数据库中少于几千),您可以定义一个转换不可分类数字的函数分为可排序的字符:

SQL> CREATE OR REPLACE FUNCTION order_section (p_section VARCHAR2)
  2     RETURN VARCHAR2 IS
  3     l_result VARCHAR2(4000);
  4  BEGIN
  5     FOR i IN 1..regexp_count(p_section, '[^.]+') LOOP
  6        l_result := l_result
  7                    || CASE WHEN i > 1 THEN '.' END
  8                    || CHR(64+regexp_substr(p_section, '[^.]+', 1, i));
  9     END LOOP;
 10     RETURN l_result;
 11  END;
 12  /

Function created

SQL> SELECT SECTION, order_section(SECTION)
  2    FROM DATA
  3   ORDER BY 2;

SECTION ORDER_SECTION(SECTION)
------- -------------------------
1       A
1.1     A.A
1.1.2   A.A.B
1.1.6   A.A.F
6.1     F.A
6.2     F.B
[...]
8.5     H.E
10      J
11      K

答案 1 :(得分:3)

没有正则表达式和函数的解决方案(假设t是包含源数据的表):

select * from t
order by
    (
      select 
        sum(
          to_number(substr(
                   sections,
                   decode(level,
                     1,1,
                     instr(sections, '.', 1, level-1)+1
                   ),
                   decode(instr(sections, '.', 1, level),
                     0, length(sections),
                     instr(sections, '.', 1, level) 
                     - 
                     decode(level,
                       1,1,
                       instr(sections, '.', 1, level-1)+1
                     )
                   )  
          )) 
          * power(1000, 10-level)
        )
      from dual
        connect by instr(sections,'.',1,level-1) > 0
    ) 

SQLFiddle example

主要思想是计算数字,它表示每行的优先级。假设我们有33.17.21.2值。该字符串可以被视为数字系统中的数字,基数为Q,如十六进制数字表示IPv4地址,然后转换为数字表示:
33*(Q^3) + 17*(Q^2) + 21*(Q^1) + 2*(Q^0)

例如,如果Q=100,那么来自exmple的数字是

33*1000000 + 17*10000 + 21*100 + 2*1 = 33172102

这种方法的第一个问题是每个级别数要求低于选择Q值。这是设计上的,不能被扼杀。

接下来就是我们根本不知道有多少级别,我们有7.12.2.2.2.2.2,而最短的级别是第一个。因此,在计算值时,它从某个固定功率N开始,然后降低Q的功效,因此,如果Q=100N=3多路复用器序列以这些数字开头: 1000000, 10000, 100, 1, 1/100, 1/10000, 1/1000000, ...

Q=1000N=10之上的代码中,但这可能会根据所需参数进行更改。 由选择的Q值和Oracle number类型的精度限制的级别数。从理论上讲,通过将字符串拆分成部分,可以为更长的字符串构建表达式。

其余代码只是用于将字符串拆分为数字序列的分层查询。

更新

同样的方法可以很容易地用于字符串:'20'出现在'8'之前,因为缺少关于第二个数字的信息。如果我们将这两个值填充到某个固定长度,它会按预期排序:'008' < '020',因此只能处理字符串:

select * from t order by 
  (
    select
      listagg(
        lpad(
          substr(
            sections,
            decode( level,
              1,1,
              instr(sections, '.', 1, level-1)+1
            ),
            decode(instr(sections, '.', 1, level),
              0, length(sections),
              instr(sections, '.', 1, level)
              -
              decode(level,
                1, 1,
                instr(sections, '.', 1, level-1)+1
              )
            )
          ),
          8,'0'
        ),
        '-'
      ) within group (order by level)
    from dual
    connect by instr(sections,'.',1,level-1) > 0
  )

字符串长度限制为4000个字符,每个级别有9个数字,单个分隔符号(上例中为'-'),可以处理400级别的层次结构。

这种方法的主要缺点是内存消耗和比较速度。 另一方面,如果没有转换为数字,即使使用混合章节编号也是如此(例如'13.3.a.vii''III.A.13.2'(Ooops ... roman numerals处理不当)

在十进制数的情况下,只有带字符串的编号变量可以通过将数字转换为十六进制表示来压缩。使用4个十六进制符号,可以在每个级别处理16535个数字,并且可以使用8个符号 - 完整的32位数字,这对于大多数应用来说已经足够了。

select * from t order by 
  (
    select
      listagg(
        lpad(
          trim(to_char(
            to_number(substr(
              sections,
              decode( level,
                1,1,
                instr(sections, '.', 1, level-1)+1
              ),
              decode(instr(sections, '.', 1, level),
                0, length(sections),
                instr(sections, '.', 1, level)
                -
                decode(level,
                  1, 1,
                  instr(sections, '.', 1, level-1)+1
                )
              )
            )),
            'XXXXXXXX'
          )),
          4,'0'
        ),
        '-'
      ) within group (order by level)
    from dual
    connect by instr(sections,'.',1,level-1) > 0
  ) 

P.S。当然,可以使用选择列表中的所有表达式来检查计算值,而不是在order by中使用它。

答案 2 :(得分:2)

如果级别的数量已修复(例如最多4个),您可以使用此级别:

ORDER BY 
    TO_NUMBER(REGEXP_SUBSTR(Sections, '\d+', 1, 1)) NULLS FIRST, 
    TO_NUMBER(REGEXP_SUBSTR(Sections, '\d+', 1, 2)) NULLS FIRST, 
    TO_NUMBER(REGEXP_SUBSTR(Sections, '\d+', 1, 3)) NULLS FIRST, 
    TO_NUMBER(REGEXP_SUBSTR(Sections, '\d+', 1, 4)) NULLS FIRST

答案 3 :(得分:0)

以下是我最终解决的一般情况下的解决方案(当点数未知时) - 保留第一个点,并用零替换所有其他点,因此您将只有一个浮点数您可以通过以下方式申请订购:

SELECT SECTIONS FROM T_TABLE
ORDER BY TO_NUMBER(SUBSTR(SECTIONS,0,DECODE(INSTR(SECTIONS,'.'),0,LENGTH(SECTIONS)+1,INSTR(SECTIONS,'.'))) ||
REPLACE(SUBSTR(SECTIONS,DECODE(INSTR(SECTIONS,'.'),0,LENGTH(SECTIONS)+1,INSTR(SECTIONS,'.'))),'.','0'))

使用常规表达式可以更加优雅地重写它,但我并不熟悉它们,所以只使用了基本的Oracle函数:)

答案 4 :(得分:0)

你可以尝试一下 -

最好的部分 - 不必担心级别的深度

SELECT
Section
FROM SectionData
ORDER BY
CAST (CASE WHEN CHARINDEX('.',Section) > 0
THEN SUBSTRING(Section,0,CHARINDEX('.',Section))
ELSE Section END AS INT)
,REPLACE(Section,'0',':')

工作原理: -

  

首先根据第一个DOT之前的整数进行排序。

由于您的Sections是字符串类型,因此排序是基于ASCII代码完成的。此外,您的部分中最重要的部分是第一个DOT之前的第一组挖掘者。

  

这是您的第二个排序标准。

现在,它会产生所有问题的0 - 所以用任何ASCII值高于9的东西替换'0'。

我已经使用一些基本组合(包括更高的深度)对其进行了测试 - 在使用之前继续进行测试。

答案 5 :(得分:0)

最简单我认为......复制并运行以查看输出:

SELECT val FROM  --,to_number(trim(BOTH '.' FROM substr(val, 1, 2))) num_val,
(
 SELECT '1' val FROM dual
 UNION ALL
 SELECT '7.1' FROM dual
 UNION ALL
 SELECT '6.2' FROM dual
 UNION ALL
 SELECT '7.1' FROM dual
 UNION ALL
 SELECT '7.4' FROM dual
 UNION ALL
 SELECT '6.8.3' FROM dual
 UNION ALL
 SELECT '6.8.2' FROM dual
 UNION ALL
 SELECT '10' FROM dual
 UNION ALL
 SELECT '1.1' FROM dual
 UNION ALL
 SELECT '7.6' FROM dual
 UNION ALL
 SELECT '6.1' FROM dual
 UNION ALL
 SELECT '11' FROM dual
 UNION ALL
 SELECT '8.3' FROM dual
 UNION ALL
 SELECT '8.5' FROM dual
 UNION ALL
 SELECT '1.1.2' FROM dual
 UNION ALL
 SELECT '6.4' FROM dual
 UNION ALL
 SELECT '6.6' FROM dual
 UNION ALL
 SELECT '8.4' FROM dual
 UNION ALL
 SELECT '1.1.6' FROM dual
 UNION ALL
 SELECT '6.8.1' FROM dual
 UNION ALL
 SELECT '7.7.1' FROM dual
 UNION ALL
 SELECT '7.5' FROM dual
 UNION ALL
 SELECT '7.3' FROM dual
)
ORDER BY to_number(trim(BOTH '.' FROM substr(val, 1, 2)))