2017-04-27 50 views
1

我需要将0和1的字符串转换为表示1的整数序列,类似于打印对话框中的页面选择序列。将0和1的序列转换为打印样式的页面列表

例如'0011001110101' - >'3-4,7-9,11,13'

是否可以在单个SQL选择(在Oracle 11g中)执行此操作?

我可以得到页码的个人列表下列要求:

with data as (
    select 'K1' KEY, '0011001110101' VAL from dual 
    union select 'K2', '0101000110' from dual 
    union select 'K3', '011100011010' from dual 
) 
select 
    KEY, 
    listagg(ords.column_value, ',') within group (
     order by ords.column_value 
    ) PAGES 
from 
    data 
cross join (
    table(cast(multiset(
     select level 
     from dual 
     connect by level <= length(VAL) 
    ) as sys.OdciNumberList)) ords 
) 
where 
    substr(VAL, ords.column_value, 1) = '1' 
group by 
    KEY 

但是,这并不做分组(如返回“3,4,7,8,9,11,13 “为第一个值)。

如果我可以在每次更改值时分配组号,那么我可以使用分析函数来获取每个组的最小值和最大值。即如果我能生成以下,然后我会设置:

Key  Page Val  Group 
K1  1  0  1 
K1  2  0  1 
K1  3  1  2 
K1  4  1  2 
K1  5  0  3 
K1  6  0  3 
K1  7  1  4 
K1  8  1  4 
K1  9  1  4 
K1  10  0  5 
K1  11  1  6 
K1  12  0  7 
K1  13  1  8 

但我坚持这一点。

任何人有任何想法,或另一种方法来得到这个?

回答

3

首先让我们级别的吧:

select regexp_instr('0011001110101', '1+', 1, LEVEL) istr, 
     regexp_substr('0011001110101', '1+', 1, LEVEL) strlen 
FROM dual 
CONNECT BY regexp_substr('0011001110101', '1+', 1, LEVEL) is not null 

那么接下来就容易LISTAGG:

with data as 
(
    select 'K1' KEY, '0011001110101' VAL from dual 
    union select 'K2', '0101000110' from dual 
    union select 'K3', '011100011010' from dual 
) 
SELECT key, 
     (SELECT listagg(CASE 
         WHEN length(regexp_substr(val, '1+', 1, LEVEL)) = 1 THEN 
          to_char(regexp_instr(val, '1+', 1, LEVEL)) 
         ELSE 
          regexp_instr(val, '1+', 1, LEVEL) || '-' || 
          to_char(regexp_instr(val, '1+', 1, LEVEL) + 
            length(regexp_substr(val, '1+', 1, LEVEL)) - 1) 
         END, 
         ' ,') within GROUP(ORDER BY regexp_instr(val, '1+', 1, LEVEL)) 
      from dual 
     CONNECT BY regexp_substr(data.val, '1+', 1, LEVEL) IS NOT NULL) val 
    FROM data 
+0

这是真棒!通过使用基于扩展数据的递归查询,我得到了一些工作,但是你的优雅更加优雅,更不用说提速几个数量级。 – Barney

3

使用递归子查询保条款没有正则表达式:

Oracle安装程序

CREATE TABLE data (key, val) AS 
    SELECT 'K1', '0011001110101' FROM DUAL UNION ALL 
    SELECT 'K2', '0101000110' FROM DUAL UNION ALL 
    SELECT 'K3', '011100011010' FROM DUAL UNION ALL 
    SELECT 'K4', '000000000000' FROM DUAL UNION ALL 
    SELECT 'K5', '000000000001' FROM DUAL; 

查询

WITH ranges (key, val, pos, rng) AS (
    SELECT key, 
     val, 
     INSTR(val, '1', 1), -- Position of the first 1 
     NULL 
    FROM data 
UNION ALL 
    SELECT key, 
     val, 
     INSTR(val, '1', INSTR(val, '0', pos)), -- Position of the next 1 

     rng || ',' || CASE 
      WHEN pos = LENGTH(val)     -- Single 1 at end-of-string 
      OR pos = INSTR(val, '0', pos) - 1 -- 1 immediately followed by 0 
      THEN TO_CHAR(pos) 
      WHEN INSTR(val, '0', pos) = 0   -- Multiple 1s until end-of-string 
      THEN pos || '-' || LENGTH(val) 
      ELSE pos || '-' || (INSTR(val, '0', pos) - 1) -- Normal range 
     END 
    FROM ranges 
    WHERE pos > 0 
) 
SELECT KEY, 
     VAL, 
     SUBSTR(rng, 2) AS rng -- Strip the leading comma 
FROM ranges 
WHERE pos = 0 OR val IS NULL 
ORDER BY KEY; 

输出

KEY VAL   RNG 
--- ------------- ------------- 
K1 0011001110101 3-4,7-9,11,13 
K2 0101000110 2,4,8-9 
K3 011100011010 2-4,8-9,11 
K4 000000000000 
K5 000000000001 12 
+0

好,它不是特定于oracle的,尽管这对我来说并不重要。我只需将null转换为查询基本部分中的varchar2即可解决Oracle错误13876895. – Barney

1

这里是Isalamon的解决方案的效率更高一点的版本(采用分层查询)。它的效率稍高一些,因为我使用了一个分层查询而不是多个分层查询(在相关的子查询中),并且我在内部查询中计算每个1序列的长度只有一次。 (实际上它只计算一次,但函数调用本身有一些开销。)

此版本也正确处理输入如'00000'NULL。 Isalamon的解决方案没有,当输入值为NULL时,MT0的解决方案不会返回一行。目前还不清楚NULL甚至可能在输入数据中,如果是,那么期望的结果是什么;我认为应该返回一行,并且page_list NULL也是如此。

此版本的优化器成本为17,而Isalamon的解决方案为18,MT0为33。但是,与标准字符串函数相比,优化器成本没有考虑到正则表达式的处理速度明显较慢;如果执行速度很重要,MT0的解决方案肯定会被尝试,因为它可能会更快。

with data (key, val) as (
     select 'K1', '0011001110101' from dual union all 
     select 'K2', '0101000110' from dual union all 
     select 'K3', '011100011010' from dual union all 
     select 'K4', '000000000000' from dual union all 
     select 'K5', '000000000001' from dual union all 
     select 'K6', null   from dual union all 
     select 'K7', '1111111'  from dual union all 
     select 'K8', '1'    from dual 
    ) 
-- End of test data (not part of the solution); SQL query begins below this line. 
select key, val, 
     listagg(case when len = 1 then to_char(s_pos) 
        when len > 1 then to_char(s_pos) || '-' || to_char(s_pos + len - 1) 
       end, ',') within group (order by lvl) as page_list 
from (select key, level as lvl, val, 
       regexp_instr(val, '1+', 1, level)   as s_pos, 
       length(regexp_substr(val, '1+', 1, level)) as len 
     from data 
     connect by regexp_substr(val, '1+', 1, level) is not null 
       and prior key = key 
       and prior sys_guid() is not null 
     ) 
group by key, val 
order by key 
; 

输出

KEY VAL   PAGE_LIST 
--- ------------- ------------- 
K1 0011001110101 3-4,7-9,11,13 
K2 0101000110  2,4,8-9 
K3 011100011010 2-4,8-9,11 
K4 000000000000 
K5 000000000001 12 
K6 
K7 1111111  1-7 
K8 1    1 
+0

我从SQL查询中运行Isalamon和MTO解决方案,而ISalamon在我的实际数据上似乎运行速度快大约20%。不以任何方式进行详尽的测试。我的数据不能为零或全为零,但这是一个很好的观点。并感谢让我去查找什么'连接通过...之前sys_guid()不为空'确实:-) – Barney

+0

更新我的显示'NULL'行 - 所需要的是添加'OR VAL IS NULL'最后的查询。 – MT0

相关问题