Oracle doesn't have easy support for multi-row inserts. However, I did find few workarounds (1, 2) to this. However, I didn't find an option where I can do multiple inserts and get the ID values back.
For e.g. here is a statement in postgresql:
insert into student (name, age) values ('monica', 23), ('saroop', 34) returning id
Assuming here the student
table has some auto-generating id
field, the above query will return as list of id values. (Two, in this case).
Does oracle pl/sql have something to this same effect?
Note: oracle pl/sql has something for single row inserts that return into a variable as shown here. However, this will fail for multi-row inserts.
CodePudding user response:
This is a sample table:
SQL> CREATE TABLE student
2 (
3 id NUMBER GENERATED ALWAYS AS IDENTITY,
4 name VARCHAR2 (20),
5 age NUMBER
6 );
Table created.
If you try to use the RETURNING
clause with a multi-row insert
, it won't work (that's just how it is in Oracle; it would work with update
or delete
, though):
SQL> DECLARE
2 l_ids SYS.odcinumberlist;
3 BEGIN
4 INSERT INTO student (name, age)
5 SELECT 'monica', 23 FROM DUAL
6 UNION ALL
7 SELECT 'saroop', 34 FROM DUAL
8 RETURNING id
9 BULK COLLECT INTO l_ids;
10 END;
11 /
RETURNING id
*
ERROR at line 8:
ORA-06550: line 8, column 16:
PL/SQL: ORA-00933: SQL command not properly ended
ORA-06550: line 4, column 4:
PL/SQL: SQL Statement ignored
SQL>
There's a workaround: first "prepare" rows to be inserted, use forall
to insert them into the target table (now you can use returning
), do whatever you want to do with returned values.
Types first:
SQL> CREATE TYPE r_stu AS OBJECT (name VARCHAR2 (20), age NUMBER);
2 /
Type created.
SQL> CREATE TYPE t_stu AS TABLE OF r_stu;
2 /
Type created.
SQL> SET SERVEROUTPUT ON
Procedure (read comments within the code):
SQL> DECLARE
2 l_ids SYS.odcinumberlist;
3 l_tab t_stu;
4 BEGIN
5 -- prepare rows to be inserted into the target STUDENT table
6 WITH
7 temp (name, age)
8 AS
9 (SELECT 'monica', 23 FROM DUAL
10 UNION ALL
11 SELECT 'saroop', 34 FROM DUAL)
12 SELECT r_stu (name, age)
13 BULK COLLECT INTO l_tab
14 FROM temp;
15
16 -- L_TAB now contains all rows we need; insert them and use the RETURNING clause
17 FORALL i IN l_tab.FIRST .. l_tab.LAST
18 INSERT INTO student (name, age)
19 VALUES (l_tab (i).name, l_tab (i).age)
20 RETURNING id
21 BULK COLLECT INTO l_ids;
22
23 -- these values have been inserted into the ID column
24 FOR i IN l_ids.FIRST .. l_ids.LAST
25 LOOP
26 DBMS_OUTPUT.put_line ('Inserted ID value: ' || l_ids (i));
27 END LOOP;
28 END;
29 /
Inserted ID value: 1
Inserted ID value: 2
PL/SQL procedure successfully completed.
Table contents:
SQL> SELECT * FROM student;
ID NAME AGE
---------- -------------------- ----------
1 monica 23
2 saroop 34
SQL>
CodePudding user response:
Basically a variation of the @Littelfodds proposal, ommiting the PL/SQL stuff.
TLDR - use a temporary table, fill it first with your data enriched with the ID fetched from the sequence and publish the data in the real table in the second step.
Table Setup
CREATE TABLE student
(
id NUMBER GENERATED BY DEFAULT AS IDENTITY,
name VARCHAR2 (20),
age NUMBER
);
SELECT sequence_name FROM ALL_TAB_IDENTITY_COLS
WHERE table_name = 'STUDENT';
-- TMP Table
CREATE TABLE student_tmp
(
id NUMBER,
name VARCHAR2 (20),
age NUMBER
);
The important part is GENERATED BY DEFAULT
AS IDENTITY, so you can both let the DB to assign the key or you may pass it in the insert
statement.
Under to hood of the identoty is a plain sequence, the name you get with the following query:
SELECT sequence_name FROM ALL_TAB_IDENTITY_COLS
WHERE table_name = 'STUDENT';
Prepare Data
INSERT INTO student_tmp (id, name, age)
WITH dt(name, age) AS (
SELECT 'monica', 23 FROM DUAL
UNION ALL
SELECT 'saroop', 34 FROM DUAL
)
SELECT
ISEQ$$_85678.nextval, -- use here the IDENTITY sequence
name, age
FROM dt;
Now you have all data in the TMP table, you may check and process the new ID's with
SELECT id, name, age FROM student_tmp;
Publish Data
This is a plain INSERT
INSERT INTO student(id, name, age)
SELECT id, name, age
FROM student_tmp;
I'm leaving out details as exceptions, transactions and hint for direct insert - set it on your requirements.