복붙노트

[SQL] 어떻게 변환 쉼표는 오라클의 행에 값을 분리?

SQL

어떻게 변환 쉼표는 오라클의 행에 값을 분리?

여기에 DDL입니다 -

create table tbl1 (
   id number,
   value varchar2(50)
);

insert into tbl1 values (1, 'AA, UT, BT, SK, SX');
insert into tbl1 values (2, 'AA, UT, SX');
insert into tbl1 values (3, 'UT, SK, SX, ZF');

공지 사항, 여기에 값은 쉼표로 분리 된 문자열입니다.

그러나, 우리는 다음과 같은 -과 같은 결과를 필요

ID VALUE
-------------
1  AA
1  UT
1  BT
1  SK
1  SX
2  AA
2  UT
2  SX
3  UT
3  SK
3  SX
3  ZF

어떻게 우리는이에 대한 SQL을 작성합니까?

해결법

  1. ==============================

    1.나는 이것이 정말 나쁜 디자인에 동의합니다. 당신이 디자인을 변경할 수없는 경우이 시도 :

    나는 이것이 정말 나쁜 디자인에 동의합니다. 당신이 디자인을 변경할 수없는 경우이 시도 :

    select distinct id, trim(regexp_substr(value,'[^,]+', 1, level) ) value, level
      from tbl1
       connect by regexp_substr(value, '[^,]+', 1, level) is not null
       order by id, level;
    

    산출

    id value level
    1   AA  1
    1   UT  2
    1   BT  3
    1   SK  4
    1   SX  5
    2   AA  1
    2   UT  2
    2   SX  3
    3   UT  1
    3   SK  2
    3   SX  3
    3   ZF  4
    

    이 크레딧

    더 우아하고 효율적인 방법으로 중복을 제거하려면 (크레딧 @mathguy합니다)

    select id, trim(regexp_substr(value,'[^,]+', 1, level) ) value, level
      from tbl1
       connect by regexp_substr(value, '[^,]+', 1, level) is not null
          and PRIOR id =  id 
          and PRIOR SYS_GUID() is not null  
       order by id, level;
    

    당신은 CTE를 가진 "ANSIer"접근 방식의 이동을 원하는 경우 :

    with t (id,res,val,lev) as (
               select id, trim(regexp_substr(value,'[^,]+', 1, 1 )) res, value as val, 1 as lev
                 from tbl1
                where regexp_substr(value, '[^,]+', 1, 1) is not null
                union all           
                select id, trim(regexp_substr(val,'[^,]+', 1, lev+1) ) res, val, lev+1 as lev
                  from t
                  where regexp_substr(val, '[^,]+', 1, lev+1) is not null
                  )
    select id, res,lev
      from t
    order by id, lev;
    

    산출

    id  val lev
    1   AA  1
    1   UT  2
    1   BT  3
    1   SK  4
    1   SX  5
    2   AA  1
    2   UT  2
    2   SX  3
    3   UT  1
    3   SK  2
    3   SX  3
    3   ZF  4
    

    MT0에 의해하지만 정규식없이 또 다른 재귀 방법 :

    WITH t ( id, value, start_pos, end_pos ) AS
      ( SELECT id, value, 1, INSTR( value, ',' ) FROM tbl1
      UNION ALL
      SELECT id,
        value,
        end_pos                    + 1,
        INSTR( value, ',', end_pos + 1 )
      FROM t
      WHERE end_pos > 0
      )
    SELECT id,
      SUBSTR( value, start_pos, DECODE( end_pos, 0, LENGTH( value ) + 1, end_pos ) - start_pos ) AS value
    FROM t
    ORDER BY id,
      start_pos;
    

    30000 개 행이 데이터 집합 나는 3 개 방법을 시도하고 118,104 행이 반환 다음과 같은 평균 결과를 얻었다 :

    @Mathguy는 더 큰 데이터 세트로 테스트했습니다 :

  2. ==============================

    2.이것은 중복을 제거 할 필요하거나 CONNECT BY의 SYS_GUID () 또는 DBMS_RANDOM.VALUE ()를 포함하는 해킹을 사용하지 않고 값을 얻을 것이다 :

    이것은 중복을 제거 할 필요하거나 CONNECT BY의 SYS_GUID () 또는 DBMS_RANDOM.VALUE ()를 포함하는 해킹을 사용하지 않고 값을 얻을 것이다 :

    SELECT t.id,
           v.COLUMN_VALUE AS value
    FROM   TBL1 t,
           TABLE(
             CAST(
               MULTISET(
                 SELECT TRIM( REGEXP_SUBSTR( t.value, '[^,]+', 1, LEVEL ) )
                 FROM   DUAL
                 CONNECT BY LEVEL <= REGEXP_COUNT( t.value, '[^,]+' )
               )
               AS SYS.ODCIVARCHAR2LIST
             )
           ) v
    

    최신 정보:

    목록에서 요소의 인덱스를 반환 :

    옵션 1 - 돌려 UDT :

    CREATE TYPE string_pair IS OBJECT( lvl INT, value VARCHAR2(4000) );
    /
    
    CREATE TYPE string_pair_table IS TABLE OF string_pair;
    /
    
    SELECT t.id,
           v.*
    FROM   TBL1 t,
           TABLE(
             CAST(
               MULTISET(
                 SELECT string_pair( level, TRIM( REGEXP_SUBSTR( t.value, '[^,]+', 1, LEVEL ) ) )
                 FROM   DUAL
                 CONNECT BY LEVEL <= REGEXP_COUNT( t.value, '[^,]+' )
               )
               AS string_pair_table
             )
           ) v;
    

    옵션 2 - 사용 ROW_NUMBER () :

    SELECT t.id,
           v.COLUMN_VALUE AS value,
           ROW_NUMBER() OVER ( PARTITION BY id ORDER BY ROWNUM ) AS lvl
    FROM   TBL1 t,
           TABLE(
             CAST(
               MULTISET(
                 SELECT TRIM( REGEXP_SUBSTR( t.value, '[^,]+', 1, LEVEL ) )
                 FROM   DUAL
                 CONNECT BY LEVEL <= REGEXP_COUNT( t.value, '[^,]+' )
               )
               AS SYS.ODCIVARCHAR2LIST
             )
           ) v;
    
  3. ==============================

    3.베르 첼리는 정답을 기록했다. 그러나 분할에 하나 이상의 문자열로, 많은, 많은 중복이있는 행의 기하 급수적으로 증가하는 번호를 생성하여 연결. (그냥 구분없이 쿼리를 시도하십시오.)이 아닌 사소한 크기의 데이터에 대한 성능을 파괴 할 것이다.

    베르 첼리는 정답을 기록했다. 그러나 분할에 하나 이상의 문자열로, 많은, 많은 중복이있는 행의 기하 급수적으로 증가하는 번호를 생성하여 연결. (그냥 구분없이 쿼리를 시도하십시오.)이 아닌 사소한 크기의 데이터에 대한 성능을 파괴 할 것이다.

    이 문제를 해결하는 한 가지 일반적인 방법은 이전 상태와 계층 구조에서 피하기 사이클에 대한 추가 검사를 사용하는 것입니다. 그래서 같이 :

    select id, trim(regexp_substr(value,'[^,]+', 1, level) ) value, level
      from tbl1
       connect by regexp_substr(value, '[^,]+', 1, level) is not null
              and prior id = id
              and prior sys_guid() is not null
       order by id, level;
    

    참조, 예를 들어, OTN에서이 토론 : https://community.oracle.com/thread/2526535

  4. ==============================

    4.또 다른 방법은 간단한 PL / SQL 함수를 정의하는 것입니다 :

    또 다른 방법은 간단한 PL / SQL 함수를 정의하는 것입니다 :

    CREATE OR REPLACE FUNCTION split_String(
      i_str    IN  VARCHAR2,
      i_delim  IN  VARCHAR2 DEFAULT ','
    ) RETURN SYS.ODCIVARCHAR2LIST DETERMINISTIC
    AS
      p_result       SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST();
      p_start        NUMBER(5) := 1;
      p_end          NUMBER(5);
      c_len CONSTANT NUMBER(5) := LENGTH( i_str );
      c_ld  CONSTANT NUMBER(5) := LENGTH( i_delim );
    BEGIN
      IF c_len > 0 THEN
        p_end := INSTR( i_str, i_delim, p_start );
        WHILE p_end > 0 LOOP
          p_result.EXTEND;
          p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, p_end - p_start );
          p_start := p_end + c_ld;
          p_end := INSTR( i_str, i_delim, p_start );
        END LOOP;
        IF p_start <= c_len + 1 THEN
          p_result.EXTEND;
          p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, c_len - p_start + 1 );
        END IF;
      END IF;
      RETURN p_result;
    END;
    /
    

    그런 다음 SQL은 매우 간단하게 :

    SELECT t.id,
           v.column_value AS value
    FROM   TBL1 t,
           TABLE( split_String( t.value ) ) v
    
  5. from https://stackoverflow.com/questions/38371989/how-to-convert-comma-separated-values-to-rows-in-oracle by cc-by-sa and MIT license