Skip to main content

Multi table insert

 

multi-table inserts in oracle

 

 

Multi-table insert is a new feature of Oracle 9i Release 1 (9.0). An extension to INSERT..SELECT, this feature enables us to define multiple insert targets for a source dataset. Until the introduction of this feature, only SQL*Loader had a similar capability.

This article provides an overview of multi-table inserts and how they are used.

syntax overview

There are two types of multi-table insert as follows:

  • INSERT FIRST; and
  • INSERT ALL.

Multi-table inserts are an extension to INSERT..SELECT. Syntax is of the following form:

INSERT ALL|FIRST

   [WHEN condition THEN] INTO target [VALUES]

   [WHEN condition THEN] INTO target [VALUES]

   ...

   [ELSE] INTO target [VALUES]

SELECT ...

FROM   source_query;

We define multiple INTO targets between the INSERT ALL/FIRST and the SELECT. The inserts can be conditional or unconditional and if the record structure of the datasource matches the target table, the VALUES clause can be omitted. We will describe the various permutations in this article.

setup

For the examples in this article, we shall use the ALL_OBJECTS view as our source data. For simplicity, we will create four tables with the same structure as follows.

SQL> CREATE TABLE t1

  2  ( owner       VARCHAR2(30)

  3  , object_name VARCHAR2(30)

  4  , object_type VARCHAR2(30)

  5  , object_id   NUMBER

  6  , created     DATE

  7  );

 

Table created.

 

SQL> CREATE TABLE t2 AS SELECT * FROM t1;

 

Table created.

 

SQL> CREATE TABLE t3 AS SELECT * FROM t1;

 

Table created.

 

SQL> CREATE TABLE t4 AS SELECT * FROM t1;

 

Table created.

These tables will be our targets for the ALL_OBJECTS view data.

simple multi-table insert

To begin, we will unconditionally INSERT ALL the source data into every target table. The source records and target tables are all of the same structure so we will omit the VALUES clause from each INSERT.

SQL> SELECT COUNT(*) FROM all_objects;

 

  COUNT(*)

----------

     28981

 

1 row selected.

 

SQL> INSERT ALL

  2     INTO t1

  3     INTO t2

  4     INTO t3

  5     INTO t4

  6  SELECT owner

  7  ,      object_type

  8  ,      object_name

  9  ,      object_id

 10  ,      created

 11  FROM   all_objects;

 

115924 rows created.

 

SQL> SELECT COUNT(*) FROM t1;

 

  COUNT(*)

----------

     28981

 

1 row selected.

Note the feedback from sqlplus and compare this to the count of ALL_OBJECTS. We get the total number of records inserted and this is evenly distributed between our target tables (although in practice, this will usually be distributed unevenly between the target tables).

Before we continue with extended syntax, note that multi-table inserts can turn single source records into multiple target records (i.e. to re-direct portions of records to different tables). We can see this in the previous example where we insert four times the number of source records. We can also generate multiple records for a single table (i.e. the same table is repeatedly used as a target) whereby each record picks a different set of attributes from the source record (similar to pivotting).

conditional multi-table insert

Multi-table inserts can also be conditional (i.e. we do not need to insert every record into every table in the list). There are some key points to note about conditional multi-table inserts as follows.

  • we cannot mix conditional with unconditional inserts. This means that in situations where we need a conditional insert on a subset of target tables, we will often need to "pad out" the unconditional inserts with a dummy condition such as "WHEN 1=1";
  • we can optionally include an ELSE clause in our INSERT ALL|FIRST target list for when none of the explicit conditions are satisfied;
  • an INSERT ALL conditional statement will evaluate every insert condition for each record. With INSERT FIRST, each record will stop being evaluated on the first condition it satisfies;
  • the conditions in an INSERT FIRST statement will be evaluated in order from top to bottom. Oracle makes no such guarantees with an INSERT ALL statement.

With these restrictions in mind, we can now see an example of a conditional INSERT FIRST statement. Each source record will be directed to one target table at most. Note that for demonstration purposes, the following example includes varying column lists and an ELSE clause.

SQL> INSERT FIRST

  2     --<>--

  3     WHEN owner = 'SYSTEM'

  4     THEN

  5        INTO t1 (owner, object_name)

  6        VALUES  (owner, object_name)

  7     --<>--

  8     WHEN object_type = 'TABLE'

  9     THEN

 10        INTO t2 (owner, object_name, object_type)

 11        VALUES  (owner, object_name, object_type)

 12     --<>--

 13     WHEN object_name LIKE 'DBMS%'

 14     THEN

 15        INTO t3 (owner, object_name, object_type)

 16        VALUES  (owner, object_name, object_type)

 17     --<>--

 18     ELSE

 19        INTO t4 (owner, object_type, object_name, created, object_id)

 20        VALUES  (owner, object_type, object_name, created, object_id)

 21     --<>--

 22  SELECT owner

 23  ,      object_type

 24  ,      object_name

 25  ,      object_id

 26  ,      created

 27  FROM   all_objects;

 

28981 rows created.

 

SQL> SELECT COUNT(*) FROM t1;

 

  COUNT(*)

----------

       362

 

1 row selected.

 

SQL> SELECT COUNT(*) FROM t2;

 

  COUNT(*)

----------

       844

 

1 row selected.

 

SQL> SELECT COUNT(*) FROM t3;

 

  COUNT(*)

----------

       266

 

1 row selected.

 

SQL> SELECT COUNT(*) FROM t4;

 

  COUNT(*)

----------

     27509

 

1 row selected.

We can see that each source record was inserted into one table only. INSERT FIRST is a good choice for performance when each source record is intended for one target only, but in practice, INSERT ALL is much more common.

Remember that we cannot mix conditional with unconditional inserts. The following example shows the unintuitive error message we receive if we try.

SQL> INSERT ALL

  2     --<>--

  3     INTO t1 (owner, object_name)  --<-- unconditional

  4     VALUES  (owner, object_name)

  5     --<>--

  6     WHEN object_type = 'TABLE'    --<-- conditional

  7     THEN

  8        INTO t2 (owner, object_name, object_type)

  9        VALUES  (owner, object_name, object_type)

 10     --<>--

 11  SELECT owner

 12  ,      object_type

 13  ,      object_name

 14  ,      object_id

 15  ,      created

 16  FROM   all_objects;

   INTO t1 (owner, object_name)  --<-- unconditional

   *

ERROR at line 3:

ORA-00905: missing keyword

The workaround to this, as stated earlier, is to include a dummy TRUE condition as follows.

SQL> INSERT ALL

  2     --<>--

  3     WHEN 1 = 1                    --<-- dummy TRUE condition

  4     THEN

  5        INTO t1 (owner, object_name)

  6        VALUES  (owner, object_name)

  7     --<>--

  8     WHEN object_type = 'TABLE'    --<-- conditional

  9     THEN

 10        INTO t2 (owner, object_name, object_type)

 11        VALUES  (owner, object_name, object_type)

 12     --<>--

 13  SELECT owner

 14  ,      object_type

 15  ,      object_name

 16  ,      object_id

 17  ,      created

 18  FROM   all_objects;

 

29958 rows created.

Counter-intuitive to this is the fact that in a conditional multi-table insert, each INTO clause inherits the current condition until it changes. We can see this below by loading T1, T2 and T3 from a single condition in an INSERT ALL statement. The T4 table will be loaded from the ELSE clause.

SQL> INSERT ALL

  2     WHEN owner = 'SYSTEM'

  3     THEN

  4        INTO t1 (owner, object_name)

  5        VALUES  (owner, object_name)

  6        --<>--

  7        INTO t2 (owner, object_name, object_type)  --<-- owner = 'SYSTEM

  8        VALUES  (owner, object_name, object_type)

  9        --<>--

 10        INTO t3 (owner, object_name, object_type)  --<-- owner = 'SYSTEM

 11        VALUES  (owner, object_name, object_type)

 12     ELSE

 13        INTO t4 (owner, object_type, object_name, created, object_id)

 14        VALUES  (owner, object_type, object_name, created, object_id)

 15  SELECT owner

 16  ,      object_type

 17  ,      object_name

 18  ,      object_id

 19  ,      created

 20  FROM   all_objects;

 

29705 rows created.

 

SQL> SELECT COUNT(*) FROM all_objects WHERE owner = 'SYSTEM';

 

  COUNT(*)

----------

       362

 

1 row selected.

 

SQL> SELECT COUNT(*) FROM t1;

 

  COUNT(*)

----------

       362

 

1 row selected.

 

SQL> SELECT COUNT(*) FROM t2;

 

  COUNT(*)

----------

       362

 

1 row selected.

 

SQL> SELECT COUNT(*) FROM t3;

 

  COUNT(*)

----------

       362

 

1 row selected.

multi-table inserts and triggers

As we might expect, multi-table inserts will cause insert-event triggers to fire. We can see this quite simply with the following example. We create two insert triggers (one for T1 and one for T2) and run a conditional INSERT ALL statement. Each trigger will output a message to the screen on firing.

SQL> CREATE OR REPLACE TRIGGER t1_insert_trigger

  2  AFTER INSERT ON t1

  3  BEGIN

  4     DBMS_OUTPUT.PUT_LINE('T1 trigger fired...');

  5  END insert_trigger;

  6  /

 

Trigger created.

 

SQL> CREATE OR REPLACE TRIGGER t2_insert_trigger

  2  AFTER INSERT ON t2

  3  BEGIN

  4     DBMS_OUTPUT.PUT_LINE('T2 trigger fired...');

  5  END insert_trigger;

  6  /

 

Trigger created.

 

SQL> INSERT ALL

  2     --<>--

  3     WHEN owner = 'SYSTEM'

  4     THEN

  5        INTO t1 (owner, object_name)

  6        VALUES  (owner, object_name)

  7     --<>--

  8     WHEN object_type = 'TABLE'

  9     THEN

 10        INTO t2 (owner, object_name, object_type)

 11        VALUES  (owner, object_name, object_type)

 12     --<>--

 13  SELECT owner

 14  ,      object_type

 15  ,      object_name

 16  FROM   all_objects;

T1 trigger fired...

T2 trigger fired...

 

1339 rows created.

multi-table inserts and sequences

Sequences can be used directly in multi-table inserts but their placement can be counter-intuitive. They are referenced in the relevant VALUES clause(s) and not in the source query (as we might expect). Further to this, when referencing a single sequence in multiple VALUES clauses, we might consider it necessary to be "smart" with our use of the NEXTVAL and CURRVAL pseudo-columns. This is not the case, as the following example demonstrates. We will create a sequence and use it in multiple INTO..VALUES clauses.

SQL> CREATE SEQUENCE multi_table_seq;

 

Sequence created.

 

SQL> INSERT ALL

  2     --<>--

  3     INTO t1 (owner, object_id)

  4     VALUES  (owner, multi_table_seq.NEXTVAL)

  5     --<>--

  6     INTO t1 (owner, object_id)

  7     VALUES  (owner, multi_table_seq.NEXTVAL)

  8     --<>--

  9     INTO t1 (owner, object_id)

 10     VALUES  (owner, multi_table_seq.NEXTVAL)

 11     --<>--

 12     INTO t1 (owner, object_id)

 13     VALUES  (owner, multi_table_seq.NEXTVAL)

 14     --<>--

 15  SELECT owner

 16  ,      object_type

 17  ,      object_name

 18  ,      object_id

 19  ,      created

 20  FROM   all_objects

 21  WHERE  ROWNUM <= 50;

 

200 rows created.

 

SQL> SELECT COUNT(*)                   AS record_count

  2  ,      COUNT(DISTINCT(object_id)) AS sequence_numbers_assigned

  3  FROM   t1;

 

RECORD_COUNT SEQUENCE_NUMBERS_ASSIGNED

------------ -------------------------

         200                        50

 

1 row selected.

We can see from this example that the sequence.NEXTVAL expression is used in each VALUES clause but each increment is constant for the entire INTO list. Given the fact that Oracle doesn't guarantee the execution order of an INSERT ALL statement, this sequence behaviour actually makes sense (because we couldn't guarantee a NEXTVAL before a CURRVAL, for example).

multi-table inserts and referential constraints

It has been stated that Oracle does not guarantee the insert order of an INSERT ALL statement, despite the fact that we will usually observe an ordered behaviour. This fact is critical when we have a set of INTO target tables that have parent-child relationships between them. We might consider that simply ordering the INTO clauses in a way such that the parent is inserted before the child is sufficient. Fortunately, most of the time this will be the case but Oracle cannot guarantee it. This author has had to use deferrable constraints to workaround this issue in a large, six-table parallel insert where the INTO ordering was not maintained. Reproducing this problem for this article, however, has not been possible but it is important that we are aware of the potential issue.

performance considerations

The performance of multi-table inserts can be improved in several ways.

First, we can use INSERT FIRST if it makes sense to do so and if the insert volumes are high (though in practice this will make only a small difference).

Second, we can use hints such as PARALLEL or APPEND for "brute-force" loading in parallel or direct-path. Hints are added between the INSERT and the ALL/FIRST as follows:

INSERT /*+ hint */ ALL|FIRST

With regard to parallel insert, the documentation states that the entire statement will be parallelised if we use a PARALLEL hint for any of the target tables (even if the target tables haven't been created with PARALLEL). If no hint is supplied, then the insert will not be performed in parallel unless every table in the statement has its PARALLEL attribute set.

Third, we can tune the source query as this is likely to be the most "expensive" part of the operation. The benefit of multi-table inserts over pre-9i solutions is that we need only generate the source dataset once. Of course, large SQL statements can often provide numerous opportunities for tuning, so we can benefit in two ways: once to reduce the work to a single statement; and twice to tune the single statement itself.

We can compare a multi-table insert with a pre-9i solution of loading each table separately. We will load the ALL_OBJECTS source data into our four target tables, first with multi-table insert (INSERT ALL) and second as four separate statements. We will use a variation on Tom Kyte's RUNSTATS utility to measure the time and resource differences between the two methods.

We will begin with the multi-table solution.

SQL> exec runstats_pkg.rs_start;

 

PL/SQL procedure successfully completed.

 

SQL> INSERT ALL

  2     INTO t1

  3     INTO t2

  4     INTO t3

  5     INTO t4

  6  SELECT owner

  7  ,      object_type

  8  ,      object_name

  9  ,      object_id

 10  ,      created

 11  FROM   all_objects;

 

115924 rows created.

Now we can run the pre-9i solution by executing four separate statements.

SQL> exec runstats_pkg.rs_middle;

 

PL/SQL procedure successfully completed.

 

SQL> INSERT INTO t1

  2  SELECT owner, object_name, object_type, object_id, created

  3  FROM   all_objects;

 

28981 rows created.

 

SQL> INSERT INTO t2

  2  SELECT owner, object_name, object_type, object_id, created

  3  FROM   all_objects;

 

28981 rows created.

 

SQL> INSERT INTO t3

  2  SELECT owner, object_name, object_type, object_id, created

  3  FROM   all_objects;

 

28981 rows created.

 

SQL> INSERT INTO t4

  2  SELECT owner, object_name, object_type, object_id, created

  3  FROM   all_objects;

 

28981 rows created.

Finally we can report the time and resource differences as follows.

SQL> exec runstats_pkg.rs_stop(1000);

Run1 ran in 233 hsecs

Run2 ran in 663 hsecs

Run1 ran in 35.14% of the time

 

 

Name                                  Run1        Run2        Diff

STAT..bytes received via SQL*N       1,010       2,453       1,443

STAT..dirty buffers inspected            0       1,536       1,536

STAT..free buffer inspected              0       1,537       1,537

STAT..hot buffers moved to hea           0       2,194       2,194

LATCH.checkpoint queue latch            73       4,047       3,974

STAT..index fetch by key             1,496       5,952       4,456

STAT..rows fetched via callbac       1,488       5,952       4,464

LATCH.cache buffers lru chain           89       4,731       4,642

STAT..consistent gets - examin       3,899      12,816       8,917

LATCH.simulator hash latch           6,396      25,049      18,653

STAT..redo size                  7,444,524   7,421,848     -22,676

STAT..index scans kdiixs1           44,658     178,600     133,942

STAT..table fetch by rowid          47,796     191,184     143,388

STAT..buffer is not pinned cou      70,673     282,660     211,987

STAT..buffer is pinned count        71,067     284,268     213,201

STAT..no work - consistent rea      98,650     394,552     295,902

STAT..session logical reads        107,016     411,832     304,816

STAT..consistent gets              103,457     408,276     304,819

LATCH.cache buffers chains         220,008     826,083     606,075

 

 

Run1 latches total versus run2 -- difference and pct

Run1        Run2        Diff        Pct

234,021     868,611     634,590     26.94%

 

PL/SQL procedure successfully completed.

We can see that the multi-table insert is considerably quicker in our example. This is because the cost of generating the source dataset is borne only once with this solution. The overall level of resources used by the inserts are very similar (i.e. we write the same volume of data regardless of the approach).

multi-table insert restrictions

There are several restrictions with multi-table inserts. The online documentation lists the following:

  • we cannot have views or materialized views as targets;
  • we cannot use remote tables as targets;
  • we cannot load more than 999 columns (all INTO clause combined);
  • we cannot parallel insert in RAC environments;
  • we cannot parallel insert into an IOT or a table with a bitmap index;
  • we cannot use plan stability (outlines) for multi-table insert statements;
  • we cannot use TABLE collection expressions; and
  • we cannot use a sequence in the source query.

 

Comments

G Karibasava said…
Nice content to understand.
rishav said…
Please keep sharing these very useful explanation..it helps us very much
saptarshi said…
nice example , very helpful
Unknown said…
Easy to understand.
Very well informed and easy to understand.
Sadananda said…
Great content, Easy to understand
D said…
Great content!
Manish said…
Self explanatory becoz of examples for every case!!! Great content please add interview related questions and tips. Thank you
Arunangshu Sen said…
Great content. Easy to understand.
Guruprasad p said…
That was very informative ajay, thank you for sharing
nice and clearly explained with examples
amir said…
Simple steps. Clear and precise. Thank you Ajay
Sowmya Reddy said…
Content is so so good and understandable.. please keep doing this so that many of learners like us feel comfortable in learning...
Unknown said…
great content ,simple to understand.

ANKIT ANAND said…
nice content...makes the topic easy to understand
Karthik gowda k said…
great content,easy to understand
Unknown said…
Nice Content .easy to understand
Pranav said…
the content provided is nice and easy to understand.
SURAJ S said…
Ajay, Your blog is very helpful to learn and understand SQL concepts for better understanding.
Kudos to you.
~ Suraj
Ankur kumar said…
Nice Content.Clearly explained and easy to understand.
Thank you.
AJAY said…
Thanks for those clear explanation and for the Blog to understand those explanation.keep on sharing blogs like this.....once again thankyou .
Unknown said…
It made me understand easily and it was very helpful.
Shiva Surabhi said…
Thanks for the blog. very useful!!
-Surabhi Sada Shiva Sharma
Great reasearch on the topic and projecting it in a simple way
and easy to understand for a beginner using these,
looking forward for more.
Ashwin said…
Easy to understand. Great content.
nikhil said…
Very informative and useful.
Pavankumar.e said…
Thanks for this blog sir, very nice and clear

- E Pavan kumar
thanks for information. It really helpful.
Joy said…
This blog is very good for understanding the concept
Tapas Mahato said…
very useful!......Tapas Mahato
Unknown said…
It was really easy to understand.
thanks for it.
Tejus said…
Very helpful and easy to understand.
Unknown said…
Thanks sir ..
Great content. Easy to understand.

Prateek Kumar said…
very nice explanation with example, very much helpful..
Unknown said…
Ease to understand
Akshay patil said…
understanding of concept is excellent.
Akshay patil said…
This comment has been removed by the author.
Unknown said…
great content,easy to understand.
Ankit Gupta said…
great explanation very much easy to understand.
thanks for this.
Hari Charan said…
Great explanation with perfect example! Keep sharing lot more stuff.
Hari Charan said…
This comment has been removed by the author.
Unknown said…
Great content sir with perfect example
Mukul Mahajan said…
Easy to understand.
SURAJ S said…
Ajay, Your blog is very helpful to learn and understand SQL concepts,
It made complex things easier to understand.
Kudos to you.
Unknown said…
Great content with perfect example
Akshay patil said…
excellent content with easy understanding.
Mukul Mahajan said…
Useful information and easy to understand.
Unknown said…
extremely helpful blog sir.
very useful for me.
Sourav Nandy said…
It's a great piece of work very help full.

Popular posts from this blog

PL / SQL practice programs

  PL / SQL  practice programs 1. Write a program to print the following format WELCOME TO PL/SQL PROGRAMMING   BEGIN   DBMS_OUTPUT.PUT_LINE('WELCOME   TO   PL/SQL   PROGRAMMING'); END; /   2. Write a program to print the numbers from 1 to 100   DECLARE N NUMBER(3):=1; V VARCHAR2(1000); BEGIN WHILE N <=1000 LOOP V:=V||''||N; N:=N+1; END LOOP; DBMS_OUTPUT.PUT_LINE(V); END; / 3. write a program to print the even numbers from 1 to 100 DECLARE N NUMBER(3):=0; BEGIN WHILE N <=100 LOOP N:=N+2; DBMS_OUTPUT.PUT_LINE(N); END LOOP; END; / 4. Write a program to print the odd numbers from 1 to 100 DECLARE N NUMBER(3):=1; BEGIN WHILE N <=100 LOOP N:=N+2; DBMS_OUTPUT.PUT_LINE(N); END LOOP; END; / 5. write a program for multiplication table DECLARE A NUMBER(2):=&A; B   NUMBER(2):=1; C   NUMBER(3); BEGIN WHILE B <

RDBMS MINI PROJECT

  Capgemini class room training.   RDBMS MINI PROJECT ( SPRINT ) LIBRARY MANAGEMENT SYSTEM   Table of Contents Serial No. Topic Name Content Page No. 1.   Introduction 1.1 Setup checklist for mini project 3     1.2 Instructions 3 2.   Problem statement   2.1 Objective 4     2.2 Abstract of the project 4     2.3 Functional components of the project 4     2.4 Technology used 5 3.   Implementation in RDBMS LOT 3.1 Guidelines on the functionality to be built 6 4.   Evaluation 4.1 Evaluation 7     1.      Introduction This document outlines a mini project for the RDBMS LOT

sample tables

  --sample tables DROP TABLE EMP; DROP TABLE DEPT; DROP TABLE BONUS; DROP TABLE SALGRADE; DROP TABLE DUMMY;   CREATE TABLE EMP        (EMPNO NUMBER(4) NOT NULL,         ENAME VARCHAR2(10),         JOB VARCHAR2(9),         MGR NUMBER(4),         HIREDATE DATE,         SAL NUMBER(7, 2),         COMM NUMBER(7, 2),         DEPTNO NUMBER(2));   INSERT INTO EMP VALUES         (7369, 'SMITH',   'CLERK',      7902,         TO_DATE('17-DEC-1980', 'DD-MON-YYYY'),   800, NULL, 20); INSERT INTO EMP VALUES         (7499, 'ALLEN',   'SALESMAN',   7698,         TO_DATE('20-FEB-1981', 'DD-MON-YYYY'), 1600,   300, 30); INSERT INTO EMP VALUES         (7521, 'WARD',    'SALESMAN',   7698,         TO_DATE('22-FEB-1981', 'DD-MON-YYYY'), 1250,   500, 30); INSERT INTO EMP VALUES         (7566, 'JONES',   'MANAGER',    7839,         TO_DATE('2-APR-1981', 'DD-MON-YYYY'),   2975, NULL,