프린트 하기

OS 환경 : Oracle Linux 8.4 (64bit)

 

DB 환경 : Oracle Database 23.4.0.24.05 ai Free

 

방법 : 오라클 23ai 신기능 Table Value Constructor(TVC)

오라클 23ai 부터 Table Value Constructor(TVC) 를 사용할수 있음
이 방식은 insert, select, merge 시 여러건의 데이터를 한번에 처리할 수 있음
예를들어 insert into values 절 사용시 1건이 아닌 여러건을 넣을수 있고
select 절에서 union all로 데이터를 합치던 방식을 union all 없이 values 절로 간결하게 작성할수 있어 가독성도 높아짐
그리고 with 절이나 merge 문에도 효과적으로 사용할 수 있음
참고로 한번의 values 절에서 최대 65535건까지 넣을수 있음

 

 

본문에서는 insert values 절, select values 절, with 문 사용시, merge 문 사용시 각각 이전 방식과 새로추가된 방식을 사용하는법을 설명하고 추가로 실행계획도 확인해봄

 

 

테스트
1. insert into values 절
2. select values 절
3. select values 절 + with절
4. merge 문
5. TVC 실행계획(autotrace + xplan + 10046 + 10053)

 

 

1. insert into values 절
한번의 insert 구문 실행시 여러건 insert 가능
샘플 테이블 생성

1
2
3
SQL> 
drop table t1 purge;
create table t1 (col1 number, col2 varchar(10));

 

 

샘플 데이터 3건 삽입(기존 방식)

1
2
3
4
5
6
7
8
9
10
11
SQL> insert into t1 values (1, 'test1');
 
1 row created.
 
SQL> insert into t1 values (2, 'test2');
 
1 row created.
 
SQL> insert into t1 values (3, 'test3');
 
1 row created.

 

 

샘플 데이터 3건 삽입(새로 추가된 방식)

1
2
3
SQL> insert into t1 values (4, 'test4'), (5, 'test5'), (6, 'test6');
 
3 rows created.

 

 

입력된 데이터 확인

1
2
3
4
5
6
7
8
9
10
11
12
SQL> select * from t1;
 
      COL1 COL2
---------- ----------
         1 test1
         2 test2
         3 test3
         4 test4
         5 test5
         6 test6
 
6 rows selected.

두 방식 모두 정상적으로 삽입됨

 

 

참고용
insert values 절 최대 갯수만큼 삽입

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SQL>
DECLARE
    v_sql clob;
BEGIN
    v_sql := 'INSERT INTO t1 VALUES ';
    
    FOR i IN 1..65534 LOOP
        IF i > 1 THEN
            v_sql := v_sql || ',';
        END IF;
        v_sql := v_sql || '(1)';
    END LOOP;
    
    EXECUTE IMMEDIATE v_sql;
    
    DBMS_OUTPUT.PUT_LINE('Insertion complete');
END;
/

최대 갯수 초과 입력시 ORA-63805 에러 발생함

참고 : ORA-63805: Maximum number of tuples in Table Value Constructor exceeded ( https://positivemh.tistory.com/1119 )

 

 

2. select values 절
샘플 데이터 생성시 사용가능
샘플 데이터 생성(기존 방식)

1
2
3
4
5
6
7
8
9
10
11
12
SQL>
select 1 as id, 'max' as name, '2024-01-01' as date1 from dual
union all
select 2 as id, 'bob' as name, '2024-02-01' as date1 from dual
union all
select 3 as id, 'tim' as name, '2024-03-01' as date1 from dual;
 
        ID NAM DATE1
---------- --- ----------
         1 max 2024-01-01
         2 bob 2024-02-01
         3 tim 2024-03-01

 

 

샘플 데이터 생성(새로 추가된 방식)

1
2
3
4
5
6
7
8
9
10
11
12
13
SQL>
select * from (
values 
      (1, 'max', '2024-01-01'),
      (2, 'bob', '2024-02-01'),
      (3, 'tim', '2024-03-01')
) as temp_tbl (id, name, date1);
 
        ID NAM DATE1
---------- --- ----------
         1 max 2024-01-01
         2 bob 2024-02-01
         3 tim 2024-03-01

union all 없이 select 절에 정상적으로 values 절 사용이 가능함

 

 

3. select values 절 + with절
샘플 데이터 생성시 사용가능
샘플 데이터 생성(기존 방식)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SQL>
with temp_tbl (id, name, date1) as (
select 1 as id, 'max' as name, '2024-01-01' as date1 from dual
union all
select 2 as id, 'bob' as name, '2024-02-01' as date1 from dual
union all
select 3 as id, 'tim' as name, '2024-03-01' as date1 from dual
)
select * from temp_tbl;
 
        ID NAM DATE1
---------- --- ----------
         1 max 2024-01-01
         2 bob 2024-02-01
         3 tim 2024-03-01

 

 

샘플 데이터 생성(새로 추가된 방식)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SQL>
with temp_tbl (id, name, date1) as (
values 
      (1, 'max', '2024-01-01'),
      (2, 'bob', '2024-02-01'),
      (3, 'tim', '2024-03-01')
)
select * from temp_tbl;
 
        ID NAM DATE1
---------- --- ----------
         1 max 2024-01-01
         2 bob 2024-02-01
         3 tim 2024-03-01

union all 없이 with 절에 정상적으로 values 절 사용이 가능함

 

 

4. merge 문
merge 문 사용시 values 절 사용가능
emp2 테이블 생성

1
2
3
SQL> create table emp2 as select empno, ename, hiredate, job from emp;
 
Table created.

 

 

기존 emp2 테이블 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SQL> select * from emp2;
 
     EMPNO ENAME      HIREDATE  JOB
---------- ---------- --------- ---------
      7839 KING       17-NOV-81 PRESIDENT
      7698 BLAKE      01-MAY-81 MANAGER
      7782 CLARK      09-JUN-81 MANAGER
      7566 JONES      02-APR-81 MANAGER
      7788 SCOTT      13-JUL-87 ANALYST
      7902 FORD       03-DEC-81 ANALYST
      7369 SMITH      17-DEC-80 CLERK
      7499 ALLEN      20-FEB-81 SALESMAN
      7521 WARD       22-FEB-81 SALESMAN
      7654 MARTIN     28-SEP-81 SALESMAN
      7844 TURNER     08-SEP-81 SALESMAN
      7876 ADAMS      13-JUL-87 CLERK
      7900 JAMES      03-DEC-81 CLERK
      7934 MILLER     23-JAN-82 CLERK
 
14 rows selected.

14건이 존재함

 

 

merge 문 실행
(empno를 기준으로 매칭한뒤, empno가 일치하는 경우 해당 행을 업데이트하고, 일치하지 않는 경우 새로운 행을 삽입)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SQL>
merge into emp2
using (
  values
    (1, 'MAX', to_date('2024-01-01', 'yyyy-mm-dd'), 'SALESMAN'),
    (2, 'BOB', to_date('2024-02-01', 'yyyy-mm-dd'), 'CLERK'),
    (3, 'TIM', to_date('2024-03-01', 'yyyy-mm-dd'), 'ANALYST')
) src (empno, ename, hiredate, job)
on (emp2.empno = src.empno)
when matched then
  update set
    emp2.ename = src.ename,
    emp2.hiredate = src.hiredate,
    emp2.job = src.job
when not matched then
  insert (empno, ename, hiredate, job)
  values (src.empno, src.ename, src.hiredate, src.job);
  
3 rows merged.

merge 구문 설명
merge into emp2 : emp2 테이블을 대상으로 merge 작업을 실행
using (values ...) src (empno, ename ...) : values 절을 사용해 소스 데이터를 정의하고 src라는 별칭 부여
on (emp2.empno = src.empno) : 대상 테이블(emp2)과 소스 데이터(src) 간의 매칭 조건을 정의
when matched then ... : 매칭 조건이 충족될 때 update 실행
when not matched then ... : 매칭 조건이 충족되지 않을 때 insert 실행

 

 

merge 후 emp2 테이블 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SQL> select * from emp2;
 
     EMPNO ENAME      HIREDATE  JOB
---------- ---------- --------- ---------
      7839 KING       17-NOV-81 PRESIDENT
      7698 BLAKE      01-MAY-81 MANAGER
      7782 CLARK      09-JUN-81 MANAGER
      7566 JONES      02-APR-81 MANAGER
      7788 SCOTT      13-JUL-87 ANALYST
      7902 FORD       03-DEC-81 ANALYST
      7369 SMITH      17-DEC-80 CLERK
      7499 ALLEN      20-FEB-81 SALESMAN
      7521 WARD       22-FEB-81 SALESMAN
      7654 MARTIN     28-SEP-81 SALESMAN
      7844 TURNER     08-SEP-81 SALESMAN
      7876 ADAMS      13-JUL-87 CLERK
      7900 JAMES      03-DEC-81 CLERK
      7934 MILLER     23-JAN-82 CLERK
         1 MAX        01-JAN-24 SALESMAN
         2 BOB        01-FEB-24 CLERK
         3 TIM        01-MAR-24 ANALYST
 
17 rows selected.

기존에 empno가 1,2,3인 row가 없었기 때문에 신규로 empno가 1,2,3 인 row가 추가됨

 

 

5. TVC 실행계획(autotrace + xplan + 10046 + 10053)
실행계획은 insert 구문만 확인해봄
5_1. autotrace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
SQL> 
set autotrace on
insert into t1 values (4, 'test4'), (5, 'test5'), (6, 'test6');
 
3 rows created.
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 1
 
-------------------------------------------------------------------------
| Id  | Operation                | Name | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------------
|   0 | INSERT STATEMENT         |      |     3 |     6   (0)| 00:00:01 |
|   1 |  LOAD TABLE CONVENTIONAL | T1   |       |            |          |
|   2 |   VALUES SCAN            |      |     3 |     6   (0)| 00:00:01 |
-------------------------------------------------------------------------
 
Table Value Constructor
------------------------------------------------------
 
   2 - #tuples:3, #elems:2 values:(4, 'test4'), (5, 'test5'), (6, 'test6')
 
 
Statistics
----------------------------------------------------------
          0  recursive calls
          3  db block gets
          1  consistent gets
          0  physical reads
          0  redo size
        204  bytes sent via SQL*Net to client
         83  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          3  rows processed

처음보는 VALUES SCAN 라는 실행계획이 표시되고
Table Value Constructor 라는 탭도 같이 표시됨
2 - #tuples:3, #elems:2 values:(4, 'test4'), (5, 'test5'), (6, 'test6')
row 3개(tuples:3) 라고 나오고 값까지 각각 표시됨(values:(4, 'test4'), (5, 'test5'), (6, 'test6'))

 

 

5_2. xplan display_cursor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
SQL> 
alter session set statistics_level = all;
insert into t1 values (4, 'test4'), (5, 'test5'), (6, 'test6');
SELECT * FROM DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ADVANCED ALLSTATS LAST');
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  31uu8vmn4twsr, child number 1
-------------------------------------
insert into t1 values (4, 'test4'), (5, 'test5'), (6, 'test6')
 
Plan hash value: 1
 
-------------------------------------------------------------------------------------------------------------------
| Id  | Operation                | Name | Starts | E-Rows | Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT         |      |      1 |        |     6 (100)|          |      0 |00:00:00.01 |       4 |
|   1 |  LOAD TABLE CONVENTIONAL | T1   |      1 |        |            |          |      0 |00:00:00.01 |       4 |
|   2 |   VALUES SCAN            |      |      1 |      3 |     6   (0)| 00:00:01 |      3 |00:00:00.01 |       0 |
-------------------------------------------------------------------------------------------------------------------
 
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
 
   1 - SET$1
 
Outline Data
-------------
 
  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('23.1.0')
      DB_VERSION('23.1.0')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$1")
      OUTLINE_LEAF(@"SEL$2")
      OUTLINE_LEAF(@"SEL$3")
      OUTLINE_LEAF(@"SET$1")
      OUTLINE_LEAF(@"INS$1")
      LOAD_METHOD(@"INS$1" "T1"@"INS$1" CONVENTIONAL)
      LOAD_TYPE(@"INS$1" "T1"@"INS$1" SERIAL)
      FULL(@"INS$1" "T1"@"INS$1")
      END_OUTLINE_DATA
  */
 
Column Projection Information (identified by operation id):
-----------------------------------------------------------
 
   2 - STRDEF[22], STRDEF[10]
 
Query Block Registry:
---------------------
 
  INS$1 (PARSER) [FINAL]
  SEL$1 (PARSER) [FINAL]
  SEL$2 (PARSER) [FINAL]
  SEL$3 (PARSER) [FINAL]
  SET$1 (PARSER) [FINAL]
 
54 rows selected.

autotrace 처럼 처음보는 VALUES SCAN 라는 실행계획이 표시되고
Column Projection Information에 STRDEF라고 표시됨
Query Block Name 에는 쿼리블록이 SET$1만 표시되는데
Query Block Registry에는 총 5개가 표시됨
(INS$1 (PARSER) [FINAL], SEL$1 (PARSER) [FINAL], SEL$2 (PARSER) [FINAL], SEL$3 (PARSER) [FINAL], SET$1 (PARSER) [FINAL])
insert 1건(INS$1 (PARSER)), select 3건(SEL$1~3 (PARSER) [FINAL]), 집합 연산자 사용 1건(SET$1 (PARSER) [FINAL])
이렇게 표시됨 내부적으로는 union all을 한것으로 보임

 

 

5_3. 10046 trace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
SQL>
alter session set events '10046 trace name context forever, level 12';
insert into t1 values (4, 'test4'), (5, 'test5'), (6, 'test6');
alter session set events '10046 trace name context off';
 
$ cd /opt/oracle/diag/rdbms/free/FREE/trace
$ tkprof FREE_ora_79831.trc FREE_ora_79831.txt sys=no
$ cat FREE_ora_79831.txt
********************************************************************************
 
SQL ID: 31uu8vmn4twsr Plan Hash: 1
 
insert into t1
values
 (4, 'test4'), (5, 'test5'), (6, 'test6')
 
 
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          1         15          0           0
Execute      1      0.00       0.00          0         30         49           3
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01       0.01          1         45         49           3
 
Misses in library cache during parse: 1
Misses in library cache during execute: 1
Optimizer mode: ALL_ROWS
Parsing user id: 139
Number of plan statistics captured: 1
 
Rows (1st) Rows (avg) Rows (max)  Row Source Operation
---------- ---------- ----------  ---------------------------------------------------
         0          0          0  LOAD TABLE CONVENTIONAL  T1 (cr=5 pr=0 pw=0 time=163 us starts=1 direct read=0 direct write=0)
         3          3          3   VALUES SCAN  (cr=0 pr=0 pw=0 time=4 us starts=1 direct read=0 direct write=0 cost=6 size=0 card=3)
 
 
Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  log file sync                                   1        0.00          0.00
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.00          0.00
********************************************************************************

10046 트레이스에서는 특별한 내용은 보이지 않음
다만 10046 트레이스에 Row Source Operation 세부항목에 direct read와 direct write 항목도 새로 추가된듯함

 

 

5_4. 10053 trace

1
2
3
4
5
6
7
8
SQL> 
alter session set events '10053 trace name context forever, level 12';
insert into t1 values (4, 'test4'), (5, 'test5'), (6, 'test6');
alter session set events '10053 trace name context off';
$ cd /opt/oracle/diag/rdbms/free/FREE/trace
$ vi FREE_ora_79377.trc
Final query after transformations: qb INS$1 (#0):******* UNPARSED QUERY IS *******
SELECT 0 FROM "SYS"."T1" "T1"

Final query를 검색해서 보면 insert 가 아닌 select 구문이 존재함

 

 

트레이스 전체를 다시 확인하여 UNION ALL 구문을 찾아냄

1
2
3
$ vi FREE_ora_79377.trc
 qb SET$1 (#0):******* UNPARSED QUERY IS *******
 (SELECT 4 "4",'test4' "'test4'" FROM "SYS"."DUAL" "DUAL") UNION ALL  (SELECT 5,'test5' FROM "SYS"."DUAL" "DUAL") UNION ALL  (SELECT 6,'test6' FROM "SYS"."DUAL" "DUAL")

 

 

데이터 삽입시 insert values (다중 values) 를 사용했지만
내부적으로는 아래와 같이 union all 을 사용한 방식으로 insert가 동작하는듯함

1
2
3
4
5
6
7
8
9
SQL>
insert into t1
(select 4, 'test4' from dual)
union all
(select 5, 'test5' from dual)
union all
(select 6, 'test6' from dual);
 
3 rows created.

 

 

결론 :
오라클 23ai의 Table Value Constructor 기능 덕분에 insert, select, merge 작업을 더욱 간편하고 효율적으로 처리할 수 있게됨
이 기능을 잘 활용하면 SQL을 좀더 직관적으로 작성할수 있기 때문에 코드 생산성과 간결성, 가독성이 크게 올라갈것으로 보임
트레이스 확인결과 내부적으로는 기존 방식처럼 select from dual 구문을 union all 방식을 사용해 묶어서 데이터를 처리하는 방식을 이용하는듯함
그렇기 때문에 한번에 많은값을 넣을 경우 다중 union all로 인한 파싱 부하를 고려해서 사용해야 할듯함

 

 

참조 : 

https://apex.oracle.com/pls/apex/features/r/dbfeatures/features?feature_id=1711
https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/IN-Condition.html#SQLRF-GUID-C7961CB3-8F60-47E0-96EB-BDCF5DB1317C
https://www.youtube.com/watch?v=LIwm_UDAHBE
https://medium.com/@vbalebai/oracle-23c-sql-syntax-for-efficient-data-manipulation-value-constructor-8a776e49f4d
https://oracle-base.com/articles/23/table-values-constructor-23
https://positivemh.tistory.com/1108
https://positivemh.tistory.com/1109