如何将逗号分隔值转换为oracle中的行?

这是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'); 

注意,这里的值是用逗号分隔的string。

但是,我们需要如下的结果 –

 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?

我同意这是一个非常糟糕的devise。 如果你不能改变这个devise,试试这个:

 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; 

OUPUT

 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; 

如果你想要一个“ANSIer”方法去CTE:

 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; 

OUTPUT

 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的另一个recursion方法,但没有正则expression式:

 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种方法,并返回了118104行,得到以下平均结果:

  • 我的recursion方法:5秒
  • MT0方法:4秒
  • Mathguy的方法:16秒
  • MT0recursion方法no-regex:3.45秒

@Mathguy也testing了一个更大的数据集:

在所有情况下,recursion查询(我只testing了一个常规substr和instr)比2到5更好。下面是每个string的string/标记的数量和分层与recursion的CTAS执行时间的组合,先分层。 所有时间在几秒钟内

  • 30,000×4:5/1。
  • 30,000×10:15/3。
  • 30,000 x 25:56/37。
  • 5,000 x 50:33/14。
  • 5,000 x 100:160/81。
  • 10,000 x 200:1,924 / 772

这将得到的值,而不要求你删除重复或不得不使用包括SYS_GUID()DBMS_RANDOM.VALUE()CONNECT BY

 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; 

韦尔切利贴出了正确的答案。 但是,如果要分割多个string,则connect by将会生成数量呈指数级增长的行,并具有许多重复的行。 (只要尝试没有distinct的查询。)这将破坏非平凡大小的数据的性能。

克服这个问题的一个常用方法是使用prior条件和附加的检查来避免层次结构中的循环。 像这样:

 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

另一种方法是定义一个简单的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