2017-04-27 10 views
1

인쇄 대화 상자의 페이지 선택 순서와 유사하게 0과 1의 문자열을 1을 나타내는 정수 시퀀스로 변환해야합니다.0과 1의 순서를 인쇄 스타일 페이지 목록으로 변환하십시오.

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

하나의 SQL select (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 Setup :

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

오라클이 특정 적이 지 않은 것은 좋지만, 나에게 그렇게 중요하지는 않습니다. 쿼리의 기본 부분 인 varchar2를 Oracle 버그 13876895를 해결하기 위해 캐스팅해야했습니다. – Barney

1

는 (계층 적 쿼리를 사용하여) Isalamon의 솔루션 약간 더 효율적인 버전입니다. (correlated subqueries에서) 다중 쿼리 대신 단일 계층 쿼리를 사용하기 때문에 약간 더 효율적입니다. 내부 쿼리에서 1의 각 시퀀스의 길이를 한 번 계산합니다. (실제로는 어쨌든 한 번만 계산되지만 함수 호출 자체에는 약간의 오버 헤드가 있습니다.)

이 버전은 '00000'NULL과 같은 입력도 올바르게 처리합니다. Isalamon의 솔루션은 그렇지 않으며 MT0의 솔루션은 입력 값이 NULL 일 때 행을 반환하지 않습니다. 입력 데이터에서 NULL이 가능한지 여부는 확실하지 않으며, 그렇다면 원하는 결과가 무엇인지; 나는 page_list NULL과 함께 행을 반환해야한다고 생각했습니다.

이 버전의 최적화 비용은 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 솔루션을 모두 실행했으며 실제 데이터에서 약 20 % 더 빠르게 실행되는 것으로 나타났습니다. 어떠한 방법으로도 철저한 테스트가 아닙니다. 내 데이터는 null이거나 모두 0 일 수는 없지만 좋은 지적입니다. 그리고 이전에 sys_guid()가 null이 아닌 것을 찾으러와 주셔서 감사합니다 :-) – Barney

+0

갱단이'NULL' 행을 보여주기 위해 업데이트되었습니다 - 필요한 모든 것은'OR val IS NULL'을 최종 쿼리. – MT0