Insert row into SQL table based on existing row - sql

I need to add a new row to a table in Oracle. The problem is that the table has 50 columns and I really don't want to write them all out for an INSERT statement. I tried to do a SELECT INTO statement to duplicate the row and then change the fields I care about individually, but this results in a UNIQUE violation on the primary key. So what I really want to do is declare a variable that holds one row without naming all the columns, change the primary key field, and then insert that variable. How do?

You can use %ROWTYPE in an anonymous PL/SQL block to declare a record representing a row from a table and then select a row into that record and change the primary key and insert the updated record. You can even re-use it for multiple inserts:
DECLARE
rec SOME_TABLE%ROWTYPE;
BEGIN
SELECT *
INTO rec
FROM SOME_TABLE
WHERE A = 1; -- Primary Key
rec.A := 2; -- Change the primary key value.
INSERT INTO SOME_TABLE VALUES rec;
rec.A := 3; -- Change the primary key again.
INSERT INTO SOME_TABLE VALUES rec;
FOR i IN 4 .. 9 LOOP
rec.A := i; -- Change it repeatedly...
INSERT INTO SOME_TABLE VALUES rec;
END LOOP;
FOR i IN 1 .. 3 LOOP
rec.A := SOME_SEQUENCE.NEXTVAL; -- Or you can manage the primary key's value using a sequence.
INSERT INTO SOME_TABLE VALUES rec;
END LOOP;
END;
/
SQLFIDDLE

I have often wanted to do something similar to this, but it's just not possible in any SQL variant I know of. You cannot ask for only some of the columns in a table without explicitly naming them (or perhaps defining a view on them in advance).
The only shortcut I can suggest is to dump the list of column names into a convenient location and then just copy it into an insert statement, changing only the value you need:
insert into foo (select 'newC1' as c1, c2, c3, c4, ..., c50 from foo where bar='baz');
::edit:: In fact, I do this so often that I wrote a Python script to help me. I tell it what table I'm editing, some where clause that matches exactly 1 row, the list of column(s) I want to change, and the list of new value(s) I want in those columns. Then it does the rest.

Related

Trigger that checks if the same tuple exists in two different tables

I wrote the following code:
CREATE OR REPLACE TRIGGER CHECK_tuple
BEFORE INSERT ON tableB
FOR EACH ROW
DECLARE IS_JOIN BOOLEAN:=FALSE
BEGIN
SELECT tableB.column1, tableB.column2,
CASE
WHEN IS_JOIN:= FALSE THEN raise_application_error(-20101, 'ERROR.');
ELSE IS_JOIN:= TRUE
END AS CHCK_JOIN
FROM tableB
JOIN tableA
ON tableB.column1=tableA.column1 AND tableB.column2=tableA.column2;
END;
I have to check if a tuple (t1) exits in table A (with "tuple", i mean the entire row of the table with multiple columns). If exists, it has to match with t2 in table B. Before one inserts tuple t2 in table B, the trigger must activate. If t1 doesn't match with t2, the flag IS_JOIN will remain FALSE and Oracle SQL will give an error. Else, if t1 is equal to t2, IS_JOIN will be TRUE and no action will be take. I want this "check" to take place for each row that one will insert in table B. Is this the proper way to do it? If the task isn't clear, please ask for further info.
The proper way to do something like this:
CREATE OR REPLACE TRIGGER test_test_CHECK_tuple
BEFORE INSERT ON tableB
FOR EACH ROW
declare
v_cnt number(10);
BEGIN
SELECT count(*)
into v_cnt
FROM tableA
where column1=:new.column1
and column2=:new.column2;
if v_cnt= 0 then
raise_application_error(-20101, 'ERROR.');
end if;
END;
:new means, that are the values, you want to insert. There is no other way to use that values.
btw. that is not really how a foreign key works, since a foreign key is assigned to a primary key or unique key

Oracle - Insert New Row with Auto Incremental ID

I have a workqueue table that has a workid column. The workID column has values that increment automatically. Is there a way I can run a query in the backend to insert a new row and have the workID column increment automatically?
When I try to insert a null, it throws error ORA01400 - Cannot insert null into workid.
insert into WORKQUEUE (facilitycode,workaction,description) values ('J', 'II', 'TESTVALUES')
What I have tried so far - I tried to look at the table details and didn't see any auto-increment. The table script is as follow
"WORKID" NUMBER NOT NULL ENABLE,
Database: Oracle 10g
Screenshot of some existing data.
ANSWER:
I have to thank each and everyone for the help. Today was a great learning experience and without your support, I couldn't have done. Bottom line is, I was trying to insert a row into a table that already has sequences and triggers. All I had to do was find the right sequence, for my question, and call that sequence into my query.
The links you all provided me helped me look these sequences up and find the one that is for this workid column. Thanks to you all, I gave everyone a thumbs up, I am able to tackle another dragon today and help patient care take a step forward!"
This is a simple way to do it without any triggers or sequences:
insert into WORKQUEUE (ID, facilitycode, workaction, description)
values ((select max(ID)+1 from WORKQUEUE), 'J', 'II', 'TESTVALUES')
It worked for me but would not work with an empty table, I guess.
To get an auto increment number you need to use a sequence in Oracle.
(See here and here).
CREATE SEQUENCE my_seq;
SELECT my_seq.NEXTVAL FROM DUAL; -- to get the next value
-- use in a trigger for your table demo
CREATE OR REPLACE TRIGGER demo_increment
BEFORE INSERT ON demo
FOR EACH ROW
BEGIN
SELECT my_seq.NEXTVAL
INTO :new.id
FROM dual;
END;
/
There is no built-in auto_increment in Oracle.
You need to use sequences and triggers.
Read here how to do it right. (Step-by-step how-to for "Creating auto-increment columns in Oracle")
ELXAN#DB1> create table cedvel(id integer,ad varchar2(15));
Table created.
ELXAN#DB1> alter table cedvel add constraint pk_ad primary key(id);
Table altered.
ELXAN#DB1> create sequence test_seq start with 1 increment by 1;
Sequence created.
ELXAN#DB1> create or replace trigger ad_insert
before insert on cedvel
REFERENCING NEW AS NEW OLD AS OLD
for each row
begin
select test_seq.nextval into :new.id from dual;
end;
/ 2 3 4 5 6 7 8
Trigger created.
ELXAN#DB1> insert into cedvel (ad) values ('nese');
1 row created.
You can use either SEQUENCE or TRIGGER to increment automatically the value of a given column in your database table however the use of TRIGGERS would be more appropriate. See the following documentation of Oracle that contains major clauses used with triggers with suitable examples.
Use the CREATE TRIGGER statement to create and enable a database trigger, which is:
A stored PL/SQL block associated with a table, a schema, or the
database or
An anonymous PL/SQL block or a call to a procedure implemented in
PL/SQL or Java
Oracle Database automatically executes a trigger when specified conditions occur. See.
Following is a simple TRIGGER just as an example for you that inserts the primary key value in a specified table based on the maximum value of that column. You can modify the schema name, table name etc and use it. Just give it a try.
/*Create a database trigger that generates automatically primary key values on the CITY table using the max function.*/
CREATE OR REPLACE TRIGGER PROJECT.PK_MAX_TRIGGER_CITY
BEFORE INSERT ON PROJECT.CITY
FOR EACH ROW
DECLARE
CNT NUMBER;
PKV CITY.CITY_ID%TYPE;
NO NUMBER;
BEGIN
SELECT COUNT(*)INTO CNT FROM CITY;
IF CNT=0 THEN
PKV:='CT0001';
ELSE
SELECT 'CT'||LPAD(MAX(TO_NUMBER(SUBSTR(CITY_ID,3,LENGTH(CITY_ID)))+1),4,'0') INTO PKV
FROM CITY;
END IF;
:NEW.CITY_ID:=PKV;
END;
Would automatically generates values such as CT0001, CT0002, CT0002 and so on and inserts into the given column of the specified table.
SQL trigger for automatic date generation in oracle table:
CREATE OR REPLACE TRIGGER name_of_trigger
BEFORE INSERT
ON table_name
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT sysdate INTO :NEW.column_name FROM dual;
END;
/
the complete know how, i have included a example of the triggers and sequence
create table temasforo(
idtemasforo NUMBER(5) PRIMARY KEY,
autor VARCHAR2(50) NOT NULL,
fecha DATE DEFAULT (sysdate),
asunto LONG );
create sequence temasforo_seq
start with 1
increment by 1
nomaxvalue;
create or replace
trigger temasforo_trigger
before insert on temasforo
referencing OLD as old NEW as new
for each row
begin
:new.idtemasforo:=temasforo_seq.nextval;
end;
reference:
http://thenullpointerexceptionx.blogspot.mx/2013/06/llaves-primarias-auto-incrementales-en.html
For completeness, I'll mention that Oracle 12c does support this feature. Also it's supposedly faster than the triggers approach. For example:
CREATE TABLE foo
(
id NUMBER GENERATED BY DEFAULT AS IDENTITY (
START WITH 1 NOCACHE ORDER ) NOT NULL ,
name VARCHAR2 (50)
)
LOGGING ;
ALTER TABLE foo ADD CONSTRAINT foo_PK PRIMARY KEY ( id ) ;
Best approach: Get the next value from sequence
The nicest approach is getting the NEXTVAL from the SEQUENCE "associated" with the table. Since the sequence is not directly associated to any specific table,
we will need to manually refer the corresponding table from the sequence name convention.
The sequence name used on a table, if follow the sequence naming convention, will mention the table name inside its name. Something likes <table_name>_SEQ. You will immediately recognize it the moment you see it.
First, check within Oracle system if there is any sequence "associated" to the table
SELECT * FROM all_sequences
WHERE SEQUENCE_OWNER = '<schema_name>';
will present something like this
Grab that SEQUENCE_NAME and evaluate the NEXTVAL of it in your INSERT query
INSERT INTO workqueue(id, value) VALUES (workqueue_seq.NEXTVAL, 'A new value...')
Additional tip
In case you're unsure if this sequence is actually associated with the table, just quickly compare the LAST_NUMBER of the sequence (meaning the current value) with the maximum id of
that table. It's expected that the LAST_NUMBER is greater than or equals to the current maximum id value in the table, as long as the gap is not too suspiciously large.
SELECT LAST_NUMBER
FROM all_sequences
WHERE SEQUENCE_OWNER = '<schema_name>' AND SEQUENCE_NAME = 'workqueue_seq';
SELECT MAX(ID)
FROM workqueue;
Reference: Oracle CURRVAL and NEXTVAL
Alternative approach: Get the current max id from the table
The alternative approach is getting the max value from the table, please refer to Zsolt Sky answer in this same question
This is a simple way to do it without any triggers or sequences:
insert into WORKQUEUE (ID, facilitycode, workaction, description)
values ((select count(1)+1 from WORKQUEUE), 'J', 'II', 'TESTVALUES');
Note : here need to use count(1) in place of max(id) column
It perfectly works for an empty table also.

INSERT ALL INTO and Sequence.nextval for a Surrogate Key

I'm trying to insert 40 rows using an INSERT ALL INTO and I'm not certain on how to insert the surrogate key. Here's what I have
BEGIN
INSERT ALL
INTO question(question_id)
VALUES (question_seq.nextval)
END
Now if I add another INTO VALUES then I get a unique constraint violation.
BEGIN
INSERT ALL
INTO question(question_id)
VALUES (question_seq.nextval)
INTO question(question_id)
VALUES (question_seq.nextval)
END
How can I update the sequences nextval value for each INTO VALUES so that I can avoid the unique constraint violation? I assumed that nextval would automatically update itself.
UPDATE: I don't know if this is the best way to handle this but here's the solution I came up with:
first I created a function that returns a value
then I called that function in the id field of the VALUES clause
create or replace
FUNCTION GET_QUESTION_ID RETURN NUMBER AS
num NUMBER;
BEGIN
SELECT UHCL_QUESTIONS_SEQ.nextval
INTO num
FROM dual;
return num;
END GET_QUESTION_ID;
INSERT ALL
INTO question(question_id)
VALUES (GET_QUESTION_ID())
INTO question(question_id)
VALUES (GET_QUESTION_ID())
Being from a SQL Server background, I've always created a trigger on the table to basically emulate IDENTITY functionality. Once the trigger is on, the SK is automatically generated from the sequence just like identity and you don't have to worry about it.
You can use something like this:
insert into question(question_id)
select question_seq.nextval from
(
select level from dual connect by level <= 40
);
Although it's not a very convenient format, especially if there are other columns you want to add. You'd probably need to create another UNION ALL query, and join it by the LEVEL or ROWNUM.
My first thought was to do something like this:
insert into question(question_id)
select question_seq.nextval value from dual
union all
select question_seq.nextval from dual;
But it generates ORA-02287: sequence number not allowed here, due to the restrictions on sequence values.
By the way, are you sure your INSERT ALL works without a subquery? I get the error ORA-00928: missing SELECT keyword, and the diagram from the 11.2 manual implies there must be a subquery:
I don't know if this is the best way to handle this but here's the solution I came up with:
first I created a function that returns a value
then I called that function in the id field of the VALUES clause
create or replace
FUNCTION GET_QUESTION_ID RETURN NUMBER AS
num NUMBER;
BEGIN
SELECT UHCL_QUESTIONS_SEQ.nextval
INTO num
FROM dual;
return num;
END GET_QUESTION_ID;
INSERT ALL
INTO question(question_id)
VALUES (GET_QUESTION_ID())
INTO question(question_id)
VALUES (GET_QUESTION_ID())

Oracle - Modify an existing table to auto-increment a column

I have a table with the following column:
NOTEID NUMBER NOT NULL,
For all intents and purposes, this column is the primary key. This table has a few thousand rows, each with a unique ID. Before, the application would SELECT the MAX() value from the table, add one, then use that as the next value. This is a horrible solution, and is not transaction or thread safe (in fact, before they didn't even have a UNIQUE constraint on the column and I could see the same NOTEID was duplicated in 9 different occasions)..
I'm rather new to Oracle, so I'd like to know the best syntax to ALTER this table and make this column auto-increment instead. If possible, I'd like to make the next value in the sequence be the MAX(NOTEID) + 1 in the table, or just make it 800 or something to start out. Thanks!
You can't alter the table. Oracle doesn't support declarative auto-incrementing columns. You can create a sequence
CREATE SEQUENCE note_seq
START WITH 800
INCREMENT BY 1
CACHE 100;
Then, you can create a trigger
CREATE OR REPLACE TRIGGER populate_note_id
BEFORE INSERT ON note
FOR EACH ROW
BEGIN
:new.note_id := note_seq.nextval;
END;
or, if you want to allow callers to specify a non-default NOTE_ID
CREATE OR REPLACE TRIGGER populate_note_id
BEFORE INSERT ON note
FOR EACH ROW
BEGIN
IF( :new.note_id is null )
THEN
:new.note_id := note_seq.nextval;
END IF;
END;
If your MAX(noteid) is 799, then try:
CREATE SEQUENCE noteseq
START WITH 800
INCREMENT BY 1
Then when inserting a new record, for the NOTEID column, you would do:
noteseq.nextval

How to merge rows + retrieve new and existing keys

In an Oracle table (e.g. MYTABLE, with a numeric sequenced field as primary key), I have to insert several thousand of rows, but some of them are supposed to already exist in the table.
Naturally, I should try to use MERGE but I need, as well, to retrieve all created (when inserting) and existing (when updating) primary keys.
As well, it should be as fast as possible.
Is the following attempt (pseudo code) the only way to go? Thanks.
keys_list = empty array
for each row to merge
do query 'SELECT PK_MYTABLE FROM MYTABLE WHERE PK_MYTABLE = '+row.pk_mytable
==> retrieve key
if found then:
add key to keys_list
else:
do query 'INSERT INTO MYTABLE (PK_MYTABLE, ...) VALUES (SEQ_MYTABLE.NEXTVAL, ...)'
do query 'SELECT SEQ_MYTABLE.CURRVAL FROM DUAL' ==> retrieve key
add key to keys_list
Add a MODIFICATION_DATE column to the table
Grab and save the sysdate.
When you merge update/insert the value of the sysdate as well.
When the merge is complete, select the rows where the MODIFICATION_DATE = SYSDATE and you
have the set you are interested in.
Why can't you use a MERGE statement for this? This is exactly what a MERGE is for. Here is a rough idea of how it would look...
merge into mytable mt
using
(
select key_field, value_field from sourcetable
) st
on
( mt.key_field = st.key_field )
when matched then update
set mt.value_field = st.value_field
when not matched then insert
( key_field, value_field )
values
( st.key_field, st.value_field )
;
Using a MERGE statement is fast because it is a single statement and the Oracle optimizer can utilize indexes and choose a better explain path than iterating through a cursor using PL/SQL.
If the keys are being generated from a sequence, then the normal way to get the key generated by that insert is to use the returning clause:
declare
v_insert_seq integer;
begin
insert into t1 (pk, c1)
values (myseq.nextval, 'value') returning pk into v_insert_seq;
end;
/
However, as best as I can tell, the merge statement doesn't support that returning feature.
Depending on the source of your new rows, there are different ways you could do this. If you are inserting one row at a time, then the approach above will work pretty well.
To detect the duplicate records, just catch the exceptions when you are inserting (when dup_val_on_index) and then handle them with updates.
If your source of rows is another table, you probably want to look at bulk inserts, and allowing Oracle to return you an array of new PK values. I tried this, but couldn't get it working, so perhaps it's not supported (or I'm missing something today - it gives a syntax error):
declare
type t_type is table of t1.pk%type;
v_insert_seqs t_type;
begin
insert into t1 (pk, c1)
select level newpk, 'value' c1value
from dual
connect by level <= 10 returning pk bulk collect into v_insert_seqs;
exception
when dup_val_on_index then
raise;
end;
/
The next best thing is to select the rows into arrays and then use bulk binds with the returning clause to capture the new PK IDs and also use Save Exceptions to catch all the rows that failed to inserted. Then you can process any of the failed inserted afterwards:
set serveroutput on
declare
type t_pk is table of t1.pk%type;
type t_c1 is table of t1.c1%type;
v_pks t_pk;
v_c1s t_c1;
v_new_pks t_pk;
ex_dml_errors EXCEPTION;
PRAGMA EXCEPTION_INIT(ex_dml_errors, -24381);
begin
-- get the batch of rows you want to insert
select level newpk, 'value' c1
bulk collect into v_pks, v_c1s
from dual connect by level <= 10;
-- bulk bind insert, saving exceptions and capturing the newly inserted
-- records
forall i in v_pks.first .. v_pks.last save exceptions
insert into t1 (pk, c1)
values (v_pks(i), v_c1s(i)) returning pk bulk collect into v_new_pks;
exception
-- Process the exceptions
when ex_dml_errors then
for i in 1..SQL%BULK_EXCEPTIONS.count loop
DBMS_OUTPUT.put_line('Error: ' || i ||
' Array Index: ' || SQL%BULK_EXCEPTIONS(i).error_index ||
' Message: ' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
end loop;
end;
/
If you are running Oracle 10 or better, you might be able to do much the same thing, for nearly free by issuing a commit before the merge to update the SCN, then after the merge,
use the ORA_ROWSCN to detect which rows have changed.