by 마농 [마농] 행복제 ROW-GENERATION CONNECT BY LEVEL [2012.04.03 17:47:39]
단일행을 여러행으로 만들기(행복제, Row-Generation)
○ 행복제 기본 유형
0. 행복제 기본 유형 SELECT LEVEL lv FROM dual CONNECT BY LEVEL <= 10;
○ 행복제 기본 유형 사용시 유의사항
- dual 테이블을 이용하여 간단하게 원하는 수 만큼의 행을 생성 할 수 있습니다. 참~ 쉽죠잉~
- 유의 할 점은 dual 과 같이 1행 집합에만 적용해야 한다는 것입니다.
- 예를들어 10행의 자료를 각각 10건씩으로 복제 한다고 가정하고 위의 방법을 적용하면...
- 10 * 10 = 100개 행이 조회될꺼라 생각한다면? 아니아니 아니되오~
- 10행의 집합에 Connect By LEVEL <= 10 조건을 주었을 때 결과는?
- 놀라지 마세요~ 11,111,111,110 건이 발생됩니다.
- 수식까지 알 필요는 없지만 굳이 알고자 하신다면? (10^1 + 10^2 + 10^3 + ... + 10^10)
- 암튼 결론은 이렇게 쓰지 말아라 하는것입니다.
○ 행복제 응용 문제 1
레코드에 저장되어 있는 건수만큼 복제하기
-- 테스트용 데이터 -- WITH t AS ( SELECT 1 pk, 2 cnt FROM dual UNION ALL SELECT 2, 4 FROM dual UNION ALL SELECT 3, 6 FROM dual ) SELECT * FROM t;
1. 행복제 기본 유형과 조인 SELECT pk, cnt, lv FROM t , (SELECT LEVEL lv FROM dual CONNECT BY LEVEL <= 99) WHERE lv <= cnt ORDER BY pk, lv ; ==> 쓸데 없이 많은 수(99) 만큼 복제하여 조인하는 비효율 발생 ==> MAX(cnt) 만큼만 복제 하면 어떨까?
2. MAX(cnt) 만큼만 복제 하는 방법 SELECT pk, cnt, lv FROM t , (SELECT LEVEL lv FROM (SELECT MAX(cnt) cnt FROM t) CONNECT BY LEVEL <= cnt) WHERE lv <= cnt ORDER BY pk, lv ; ==> 테이블을 두번 읽는 비효율 발생 ==> 테이블에 직접 Connect By Level 조건을 걸면 한번만 읽고 가능하긴 한데... ==> 위에 언급한 유의 사항 때문에 안되겠군... 어떻게 안될까?
3. Connect_By_Root 조건 삽입. SELECT pk, cnt, LEVEL lv FROM t CONNECT BY LEVEL <= cnt AND CONNECT_BY_ROOT pk = pk ; ==> 어라? 잘 되네? 이게 왜 되는거야? 뭔가 불안한데... ==> 11G 에서는 안됩니다. 고로 사용 불가... ==> ORA-30007: CONNECT BY ROOT 연산자는 START WITH 또는 CONNECT BY 조건에서 지원되지 않습니다.
4. dbms_random.value 조건 삽입. SELECT pk, cnt, LEVEL lv FROM t CONNECT BY LEVEL <= cnt AND PRIOR pk = pk AND PRIOR dbms_random.value IS NOT NULL ; ==> 어라? 잘 되네? 이건 또 왜 되는거야? 불안한데... ==> 이것 역시 11G 에서는 안됩니다. 고로 사용 불가...라고 쓸려고 했는데. ==> 으미.. 잘 되네..참고 싸이트에서는 안된다고 쓰여 있던데... ==> 참고로 내 11g 버전은 11.2.0.1.0 ==> 이걸 써야 되는 거야? 쓰지 말아야 하는거야? 고민되네...
5. Model 절 활용 SELECT pk, cnt, lv FROM t MODEL PARTITION BY (pk) DIMENSION BY (1 lv) MEASURES(cnt) RULES(cnt[FOR lv FROM 1 TO cnt[1] INCREMENT 1] = cnt[1]) ;
6. Multiset 을 이용한 방법 SELECT pk, cnt , TO_NUMBER(column_value) lv FROM t , TABLE(CAST(MULTISET(SELECT LEVEL FROM dual CONNECT BY LEVEL <= cnt) AS sys.dbms_debug_vc2coll)) ;
7. 11G Recursive SQL WITH t AS ( SELECT 1 pk, 2 cnt FROM dual UNION ALL SELECT 2, 4 FROM dual UNION ALL SELECT 3, 6 FROM dual ) , t1(pk, cnt, lv) AS ( SELECT pk, cnt , 1 lv FROM t UNION ALL SELECT pk, cnt , lv + 1 lv FROM t1 WHERE lv + 1 <= cnt ) SELECT * FROM t1 ORDER BY pk, lv ;
별 별 방법이 다 있군요....
참고 : http://rwijk.blogspot.com/2007/11/interval-based-row-generation.html
○ 행복제 응용 문제 2
한 필드에 여러 값이 구분자로 저장되어 있을 때 구분자로 구별하여 행 나누기
WITH t AS ( SELECT 1 pk, '1,3,5' v FROM dual UNION ALL SELECT 2, '2,4,6,8' FROM dual UNION ALL SELECT 3, '9,10,11' FROM dual ) SELECT pk, lv , REGEXP_SUBSTR(v, '[^,]+', 1, lv) v FROM t , (SELECT LEVEL lv FROM dual CONNECT BY LEVEL <= 99) WHERE lv <= LENGTH(v) - LENGTH(REPLACE(v, ',')) + 1 ORDER BY pk, lv ;
-- ○ XMLTABLE 기능 활용 -- -- 1. 특정 구간만큼 행 생성 -- SELECT * FROM XMLTABLE('3 to 5' COLUMNS c1 NUMBER PATH '.') ; -- 2. 레코드에 저장되어 있는 건수만큼 복제 WITH t AS ( SELECT 1 pk, 2 cnt FROM dual UNION ALL SELECT 2, 4 FROM dual UNION ALL SELECT 3, 6 FROM dual ) SELECT pk, cnt, lv FROM (SELECT pk, cnt , '1 to ' || cnt x FROM t ) , XMLTABLE(x COLUMNS lv NUMBER PATH '.') ; WITH t AS ( SELECT 1 pk, 2 cnt FROM dual UNION ALL SELECT 2, 4 FROM dual UNION ALL SELECT 3, 6 FROM dual ) SELECT * FROM t , XMLTABLE('1 to xs:int(.)' PASSING cnt COLUMNS lv NUMBER PATH '.') -- , XMLTABLE('1 to xs:int(V)' PASSING XMLELEMENT(V, cnt) COLUMNS lv INT PATH '.') ; -- 3. 구분자로 구별하여 행 나누기 WITH t AS ( SELECT 1 pk, '1,3,5' v FROM dual UNION ALL SELECT 2, '2,4,6,8' FROM dual UNION ALL SELECT 3, '9,10,11' FROM dual ) SELECT * FROM t , XMLTABLE(v COLUMNS x NUMBER PATH '.') ;
-- 구분자로 구별하여 행 나누기 -- MSSQL 2016 OPENJSON -- WITH t AS ( SELECT 1 pk, '1,3,5' v UNION ALL SELECT 2, '2,4,6,8' UNION ALL SELECT 3, '9,10,11' ) SELECT * FROM t CROSS APPLY OPENJSON('[' + v + ']') -- 숫자만 -- CROSS APPLY OPENJSON('["' + REPLACE(v, ',', '","') + '"]') -- 문자포함 ; -- MSSQL 2016 STRING_SPLIT -- WITH t AS ( SELECT 1 pk, '1,3,5' v UNION ALL SELECT 2, '2,4,6,8' UNION ALL SELECT 3, '9,10,11' ) SELECT * FROM t CROSS APPLY STRING_SPLIT(v, ',') ;