In SQL Server, you can do things like this:
INSERT INTO some_table (...) OUTPUT INSERTED.*
VALUES (...)
So that you can insert arbitrary sets of columns/values and get those results back. Is there any way to do this in Oracle?
The best I can come up with is this:
INSERT INTO some_table (...)
VALUES (...)
RETURNING ROWID INTO :out_rowid
...using :out_rowid as a bind variable. And then using a second query like this:
SELECT *
FROM some_table
WHERE ROWID = :rowid
...but this isn't quite the same as it returns everything within the column, not just the columns I inserted.
Is there any better way to do this without using a lot of PL/SQL and preferably with only one query?
Maybe I don't understand the question, but wouldn't this do it? (you must know what you want back)
INSERT INTO some_table (...)
VALUES (...)
RETURNING some_column_a, some_column_b, some_column_c, ... INTO :out_a, :out_b, :out_c, ...
#Vincent returning bulk collect into for multi-row insert works only in conjunction with forall (in another words if you insert from collection you can retrieve "results" into another)
The RETURNING clause supports the BULK COLLECT INTO synthax. Consider (10g):
SQL> CREATE TABLE t (ID NUMBER);
Table created
SQL> INSERT INTO t (SELECT ROWNUM FROM dual CONNECT BY LEVEL <= 5);
5 rows inserted
SQL> DECLARE
2 TYPE tab_rowid IS TABLE OF ROWID;
3 l_r tab_rowid;
4 BEGIN
5 UPDATE t SET ID = ID * 2
6 RETURNING ROWID BULK COLLECT INTO l_r;
7 FOR i IN 1 .. l_r.count LOOP
8 dbms_output.put_line(l_r(i));
9 END LOOP;
10 END;
11 /
AADcriAALAAAAdgAAA
AADcriAALAAAAdgAAB
AADcriAALAAAAdgAAC
AADcriAALAAAAdgAAD
AADcriAALAAAAdgAAE
It works with multi-row UPDATE and DELETE with my version (10.2.0.3.0) but NOT with INSERT:
SQL> DECLARE
2 TYPE tab_rowid IS TABLE OF ROWID;
3 l_r tab_rowid;
4 BEGIN
5 INSERT INTO t (SELECT ROWNUM FROM dual CONNECT BY LEVEL <= 5)
6 RETURNING ROWID BULK COLLECT INTO l_r;
7 FOR i IN 1 .. l_r.count LOOP
8 dbms_output.put_line(l_r(i));
9 END LOOP;
10 END;
11 /
ORA-06550: line 7, column 5:
PL/SQL: ORA-00933: SQL command not properly ended
Maybe you have a more recent version (11g?) and the BULK COLLECT INTO is supported for multi-row INSERTs ?
Related
Hi i am using the below PLSQL script to insert rows in new table new_table.
set serveroutput on SIZE 1000000;
DECLARE
CURSOR get_record IS
SELECT * from cycle_table ;
BEGIN
FOR rec IN get_record
LOOP
DBMS_OUTPUT.put_line('Inserting Record into new_table..');
EXECUTE IMMEDIATE('insert into new_table
select cycle_code,cycle_instance,cycle_start_date,cycle_end_date
from cycle_table');
END LOOP;
COMMIT;
END;
/
Now the table cycle_table consist only 4 rows. The loop runs only four times beacuse its printing 'Inserting Record into new_table..' 4 times only.
But when i see the new_table it consist 16 rows. Which means everytime the loop iterates it insert all the 4 rows and thus total 16 rows.
What i want is that it insert single row at a time.
So that i can perform other actions on that row also. Like if the row already exist, insert in some other table or anything.
Please suggest what can I do here? I am using SQL developer on oracle 10g
Thanks in advance
It is very simple:
set serveroutput on SIZE 1000000;
DECLARE
BEGIN
FOR rec in (select * from cycle_table)
LOOP
DBMS_OUTPUT.put_line('Inserting Record into new_table..');
insert into new_table (cycle_code,
cycle_instance,
cycle_start_date,
cycle_end_date)
values (rec.cycle_code,
rec.cycle_instance,
rec.cycle_start_date,
rec.cycle_end_date);
END LOOP;
COMMIT;
END;
/
I would discourage this approach, though, as you could run into a performance issue if there is a large number of records. You have only four, so it's fine.
The reason I'm against this is that there is context switching involved between Oracle's PL/SQL engine and SQL engine. I'd suggest you do an insert into .... select... or use a forall instead, as these are the least resource-consuming approaches.
A more efficient way is to eliminate all of the looping, and allow the SQL to handle everything. Here is my suggestion:
BEGIN
-- Handle matches first, because after you handle non-matches, everything matches
INSERT INTO match_table (cycle_code, cycle_instance, cycle_start_date
, cycle_end_date)
SELECT cycle_table.cycle_code, cycle_table.cycle_instance, cycle_table.cycle_start_date
, cycle_table.cycle_end_date
FROM cycle_table INNER JOIN new_table ON (new_table.cycle_code = cycle_table.cycle_code);
-- Single insert to insert all non matching records
INSERT INTO new_table (cycle_code, cycle_instance, cycle_start_date
, cycle_end_date)
SELECT cycle_code, cycle_instance, cycle_start_date
, cycle_end_date
FROM cycle_table
WHERE NOT EXISTS
(SELECT NULL
FROM new_table
WHERE new_table.cycle_code = cycle_table.cycle_code);
COMMIT;
END;
I can't figure out the correct syntax for the following pseudo-sql:
INSERT INTO some_table
(column1,
column2)
SELECT col1_value,
col2_value
FROM other_table
WHERE ...
RETURNING id
INTO local_var;
I would like to insert something with the values of a subquery.
After inserting I need the new generated id.
Heres what oracle doc says:
Insert Statement
Returning Into
OK i think it is not possible only with the values clause...
Is there an alternative?
You cannot use the RETURNING BULK COLLECT from an INSERT.
This methodology can work with updates and deletes howeveer:
create table test2(aa number)
/
insert into test2(aa)
select level
from dual
connect by level<100
/
set serveroutput on
declare
TYPE t_Numbers IS TABLE OF test2.aa%TYPE
INDEX BY BINARY_INTEGER;
v_Numbers t_Numbers;
v_count number;
begin
update test2
set aa = aa+1
returning aa bulk collect into v_Numbers;
for v_count in 1..v_Numbers.count loop
dbms_output.put_line('v_Numbers := ' || v_Numbers(v_count));
end loop;
end;
You can get it to work with a few extra steps (doing a FORALL INSERT utilizing TREAT)
as described in this article:
returning with insert..select
T
to utilize the example they create and apply it to test2 test table
CREATE or replace TYPE ot AS OBJECT
( aa number);
/
CREATE TYPE ntt AS TABLE OF ot;
/
set serveroutput on
DECLARE
nt_passed_in ntt;
nt_to_return ntt;
FUNCTION pretend_parameter RETURN ntt IS
nt ntt;
BEGIN
SELECT ot(level) BULK COLLECT INTO nt
FROM dual
CONNECT BY level <= 5;
RETURN nt;
END pretend_parameter;
BEGIN
nt_passed_in := pretend_parameter();
FORALL i IN 1 .. nt_passed_in.COUNT
INSERT INTO test2(aa)
VALUES
( TREAT(nt_passed_in(i) AS ot).aa
)
RETURNING ot(aa)
BULK COLLECT INTO nt_to_return;
FOR i IN 1 .. nt_to_return.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(
'Sequence value = [' || TO_CHAR(nt_to_return(i).aa) || ']'
);
END LOOP;
END;
/
Unfortunately that's not possible. RETURNING is only available for INSERT...VALUES statements. See this Oracle forum thread for a discussion of this subject.
You can't, BUT at least in Oracle 19c, you can specify a SELECT subquery inside the VALUES clause and so use RETURNING! This can be a good workaround, even if you may have to repeat the WHERE clause for every field:
INSERT INTO some_table
(column1,
column2)
VALUES((SELECT col1_value FROM other_table WHERE ...),
(SELECT col2_value FROM other_table WHERE ...))
RETURNING id
INTO local_var;
Because the insert is based on a select, Oracle is assuming that you are permitting a multiple-row insert with that syntax. In that case, look at the multiple row version of the returning clause document as it demonstrates that you need to use BULK COLLECT to retrieve the value from all inserted rows into a collection of results.
After all, if your insert query creates two rows - which returned value would it put into an single variable?
EDIT - Turns out this doesn't work as I had thought.... darn it!
This isn't as easy as you may think, and certainly not as easy as it is using MySQL. Oracle doesn't keep track of the last inserts, in a way that you can ping back the result.
You will need to work out some other way of doing this, you can do it using ROWID - but this has its pitfalls.
This link discussed the issue: http://forums.oracle.com/forums/thread.jspa?threadID=352627
The following Oracle SQL code generates the error "ORA-02287: sequence number not allowed here":
INSERT INTO Customer (CustomerID,Name) VALUES (Customer_Seq.nextval,'AAA');
SELECT * FROM Customer where CustomerID=Customer_Seq.currval;
The error occurs on the second line (SELECT statement). I don't really understand the problem, because this does work:
INSERT INTO Customer (CustomerID,Name) VALUES (Customer_Seq.nextval,'AAA');
SELECT Customer_Seq.currval from dual;
You have posted some sample code, so it is not clear what you are trying to achieve. If you want to know the assigned value, say for passing to some other procedure you could do something like this:
SQL> var dno number
SQL> insert into dept (deptno, dname, loc)
2 values (deptno_seq.nextval, 'IT', 'LONDON')
3 returning deptno into :dno
4 /
1 row created.
SQL> select * from dept
2 where deptno = :dno
3 /
DEPTNO DNAME LOC
---------- -------------- -------------
55 IT LONDON
SQL>
Edit
We can use the RETURNING clause to get the values of any column, including those which have been set with default values or by trigger code.
You don't say what version of Oracle you are using. There have in the past been limitations on where sequences can be used in PL/SQL - mostly if not all gone in 11G. Also, there are restrictions in SQL - see this list.
In this case you may need to write:
SELECT Customer_Seq.currval INTO v_id FROM DUAL;
SELECT * FROM Customer where CustomerID=v_id;
(Edited after comments).
This doesn't really directly answer your question, but maybe what you want to do can be resolved using a the INSERT's RETURNING clause?
DECLARE
-- ...
last_rowid rowid;
-- ...
BEGIN
-- ...
INSERT INTO Customer (CustomerID,Name) VALUES (Customer_Seq.nextval,'AAA') RETURNING rowid INTO last_rowid;
SELECT * FROM Customer where rowid = last_rowid;
-- ...
END;
/
You may not use a sequence in a WHERE clause - it does look natural in your context, but Oracle does not allow the reference in a comparison expression.
[Edit]
This would be a PL/SQL implementation:
declare
v_custID number;
cursor custCur is
select customerid, name from customer
where customerid = v_custID;
begin
select customer_seq.nextval into v_custID from dual;
insert into customer (customerid, name) values (v_custID, 'AAA');
commit;
for custRow in custCur loop
dbms_output.put_line(custRow.customerID||' '|| custRow.name);
end loop;
end;
You have not created any
sequence
First create any sequence its cycle and cache. This is some basic example
Create Sequence seqtest1
Start With 0 -- This Is Hirarchy Starts With 0
Increment by 1 --Increments by 1
Minvalue 0 --With Minimum value 0
Maxvalue 5 --Maximum Value 5. So The Cycle Of Creation Is Between 0-5
Nocycle -- No Cycle Means After 0-5 the Insertion Stopes
Nocache --The cache Option Specifies How Many Sequence Values Will Be Stored In Memory For Faster Access
You cannot do Where Clause on Sequence in SQL beacuse you cannot filter a sequence . Use procedures like #APC said
When i ran the below queries it's failing in the second query becuase prev_test_ref1 variable is not defined. If i remove the insert statement in the first query ,run again then it's working and using the prev_test_ref1 value from the first sql query in second query. Is it because of variable scope? How can i resolve this with the insert statement.
QUERY1
column prev_test_ref1 new_value prev_test_ref1 ;
insert into testing.test_ref_details(TEST_TYPE,TEST_REF_NO)
select '1',max(test_ref_no) as prev_test_ref1
from testing.test_runs_status
where test_type = 1
and run_status = 1
and test_end_dt = (select last_day(add_months(trunc(sysdate),-6))+2 from dual)
group by test_end_dt
;
QUERY2
column last_test_end_dt new_value last_test_end_dt;
select to_char(test_completion_dt,'DD-MON-YYYY HH24:MI:SS') as last_test_end_dt
from testing.test_runs_status
where test_ref_no = '&prev_test_ref1';
In SQLPlus substitution variables will only be defined with SELECT statements. Your first insert doesn't return rows so it won't work (think about it: it only returns 1 row inserted., SQLPlus has no way to know the value inserted.)
I suggest you add a step to save the value into the variable (or use a PL/SQL block):
column prev_test_ref1 new_value prev_test_ref1 ;
SELECT MAX(test_ref_no) AS prev_test_ref1
FROM testing.test_runs_status
WHERE test_type = 1
AND run_status = 1
AND test_end_dt = (SELECT last_day(add_months(trunc(SYSDATE), -6)) + 2
FROM dual)
GROUP BY test_end_dt;
INSERT INTO testing.test_ref_details(TEST_TYPE,TEST_REF_NO)
VALUES ('1', &prev_test_ref1);
SELECT ...
declare
prev_test_ref1 number(10);
begin
insert into ...select ...;
select ... into prev_test_ref1 from ...;
end;
/
The INSERT statement has a RETURNING clause. We can use this to get access to "unknown" values from the table. The following examples uses RETURNING to get the assigned nextval from a sequence, but we could return any column from the row:
SQL> var prev_id number
SQL> insert into t23 (id, name) values (my_seq.nextval, 'MAISIE')
2 returning id into :prev_id
3 /
1 row created.
SQL> select * from t23
2 where id = :prev_id
3 /
NAME ID
---------- ----------
MAISIE 122
SQL>
Unfortunately the RETURNING clause only works with single-row SQL.
It isn't really clear what the purpose of the whole script is, especially in light of the comment "i have similar sql query which returns multiple rows. In that case i cant have separate insert statement."
If you want to use the results of a select, see if Multi-Table Inserts fit the bill. Your select statement can insert into both the primary table and also a second table (eg a global temporary table). You can then query the global temporary table to see what rows were inserted.
I don't know how I can return all attributes with the RETURNING clause
I want something like this:
DECLARE
v_user USER%ROWTYPE
BEGIN
INSERT INTO User
VALUES (1,'Bill','QWERTY')
RETURNING * INTO v_user;
END;
RETURNING * INTO gets an error , how can I replace * ?
It would be neat if we could do something like that but alas:
SQL> declare
2 v_row t23%rowtype;
3 begin
4 insert into t23
5 values (my_seq.nextval, 'Daisy Head Maisy')
6 returning * into v_row;
7 end;
8 /
returning * into v_row;
*
ERROR at line 6:
ORA-06550: line 6, column 19:
PL/SQL: ORA-00936: missing expression
ORA-06550: line 4, column 5:
PL/SQL: SQL Statement ignored
SQL>
I believe there may be a logged change request for this feature, because I know lots of people want it. But for the moment all we can do is the long-winded specification of every column:
SQL> declare
2 v_row t23%rowtype;
3 begin
4 insert into t23
5 values (my_seq.nextval, 'Daisy Head Maisy')
6 returning id, person_name into v_row;
7 end;
8 /
PL/SQL procedure successfully completed.
SQL>
Bad news if you have a lot of columns!
I suspect the rationale is, most tables have relatively few derived columns (sequence assigned to an ID, sysdate assigned to a CREATED_DATE, etc) so most values should already be known (or at least knowable) to the inserting process.
edit
I was care how returning all
attributes without long-winded
specification of every column ;) Maybe
it's impossible.
I thought I had made it clear, but anyway: yes currently it is impossible to use * or some similar unspecific mechanism in a RETURNING clause.
So far the best solution I believe could be:
DECLARE
v_user USER%ROWTYPE;
rowid_v rowid;
BEGIN
INSERT INTO User
VALUES (1,'Bill','QWERTY')
RETURNING ROWID INTO rowid_v;
SELECT * INTO v_user WHERE rowid = rowid_v ;
END;
https://oracle-base.com/articles/misc/dml-returning-into-clause
Unfortunately, still there is no such functionality as returning * or returning row :) but will be cool.