SQL to insert only if a certain value is NOT already present in the table? - sql

How to insert a number into the table, only if the table does not already have that number in it?
I am looking for specific SQL code, not really sure how to approach this. Tried several things, nothing's working.
EDIT
Table looks like this:
PK ID Value
1 4 500
2 9 3
So if I am trying to INSERT (ID, Value) VALUES (4,100) it should not try to do it!

If ID is supposed to be unique, there should be a unique constraint defined on the table. That will throw an error if you try to insert a value that already exists
ALTER TABLE table_name
ADD CONSTRAINT uk_id UNIQUE( id );
You can catch the error and do whatever you'd like if an attempt is made to insert a duplicate key-- anything from ignoring the error to logging and re-raising the exception to raising a custom exception
BEGIN
INSERT INTO table_name( id, value )
VALUES( 4, 100 );
EXCEPTION
WHEN dup_val_on_index
THEN
<<do something>>
END;
You can also code the INSERT so that it inserts 0 rows (you would still want the unique constraint in place both from a data model standpoint and because it gives the optimizer more information and may make future queries more efficient)
INSERT INTO table_name( id, value )
SELECT 4, 100
FROM dual
WHERE NOT EXISTS(
SELECT 1
FROM table_name
WHERE id = 4 )
Or you could code a MERGE instead so that you update the VALUE column from 500 to 100 rather than inserting a new row.

Try MERGE statement:
MERGE INTO tbl USING
(SELECT 4 id, 100 value FROM dual) data
ON (data.id = tbl.id)
WHEN NOT MATCHED THEN
INSERT (id, value) VALUES (data.id, data.value)

INSERT INTO YOUR_TABLE (YOUR_FIELD)
SELECT '1' FROM YOUR_TABLE YT WHERE YT.YOUR_FIELD <> '1' LIMIT 1
Of course, that '1' will be your number or your variable.
You can use INSERT + SELECT to solve this problem.

Related

Cant figure out this SQL Trigger

so I'm suppose to be making a trigger for my database that will limit how many classes a faculty member can be assigned. If QUALIFIED = 'Y', then they can teach up to three classes. the trouble i'm running into is that I dont know what is wrong with my SQL statement that wont let it be run.
CREATE OR REPLACE trigger "ASSIGN_T1"
BEFORE
INSERT ON "ASSIGN"
FOR EACH ROW
BEGIN
DECLARE
A_COUNT NUMBER;
A_QUALIFY CHAR(2);
SET(SELECT QUALIFY FROM QUALIFY WHERE (FID = :NEW.FID)) AS A_QUALIFY
SET(SELECT COUNT(FID) FROM QUALIFY WHERE (FID = :NEW.FID)) AS A_COUNT
IF (A_QUALIFY = 'Y' AND A_COUNT < 3) THEN
INSERT INTO ASSIGN (FID, CID) VALUES (:NEW.FID, :NEW.CID);
END IF;
END;
The two errors i'm getting are
line 8, position 8 PLS-00103: Encountered the symbol "(" when expecting one of the following: constant exception table long double ref char time timestamp
line 8, position 61 PLS-00103: Encountered the symbol "AS" when expecting one of the following: set
The 1st problem here is that the BEGIN needs to move down below the DECLARE and the variable declarations.
The 2nd problem is the way you're attempting to set those variables. Get rid of those SETs and AS's. In PL/SQL, one valid way to set a variable with the result of a SQL statement is with a SELECT INTO. Like so....
SELECT QUALIFY
INTO A_QUALIFY
FROM QUALIFY
WHERE FID = :NEW.FID;
...and you can do the same for A_COUNT
I won't guarantee everything will work right after you do that, but that's the bare minimum to fix here.
Also, even if doing the above works, watch out for SELECT INTO because you'll get a "no data found" error if there's ever a scenario where you don't already have a FID = :NEW.FID being passed in OR an "exact fetch returns more than requested number of rows" error if you have more than 1 existing record with that FID in your table. You then either have to handle those exceptions, or use a different method of assigning values to your variable (such as declaring the SQL in a cursor and then OPEN cursor, FETCH cursor INTO variable, CLOSE cursor.)
Lastly, I think there may be a problem in your logic. You're asking for the value in QUALIFY for a FID, but then you're asking for the number of records that have that FID. That implies that the FID isn't the primary key on your table, which means there could be different records with the same FID but different values in the QUALIFY field. So if you're going to be using that variable in your logic later on, then that may be a problem, since the same FID can have one record with QUALIFY = 'Y', and another record with QUALIFY = 'N'
You have used the BEGIN after DECLARE part. And am not sure why you are using SET .. AS. We can combine both selects into one and use it in IF condition.
I don't think you can trigger on the same table and do insert at the same time. You will end up with ORA-04088 error.
Instead, you can restrict the insertion by throwing an error.
(my option would be a Foreign Key Constraint over the ASSIGN table)
--Creating Tables
create table ASSIGN (FID number, CID number);
create table QUALIFY (FID number, QUALIFY char);
-- Loading sample data
insert into QUALIFY values (1, 'Y');
insert into QUALIFY values (1, 'Y');
insert into QUALIFY values (1, 'Y');
insert into QUALIFY values (2, 'Y');
insert into QUALIFY values (2, 'Y');
insert into QUALIFY values (3, 'N');
insert into QUALIFY values (4, 'Y');
CREATE OR REPLACE trigger "ASSIGN_T1"
BEFORE
INSERT ON "ASSIGNEE" --< change table name to yours
FOR EACH ROW
DECLARE
A_COUNT NUMBER;
BEGIN
SELECT COUNT(QUALIFY) into A_COUNT FROM QUALIFY WHERE QUALIFY='Y' AND FID = :NEW.FID;
-- If they are qualified and already has 3 classes. They are not allowed/record is not inserted.
IF A_COUNT = 3 THEN
Raise_Application_Error (-20343, 'FID is not Qualified or already has 3 Classes.');
END IF;
END;
/
Test by inserting data into the ASSIGNEE table
-- FID 1 already assigned to 3 classes, should not be allowed any more.
insert into ASSIGNEE values (1,3);
-- See error below
Error report -
ORA-20343: FID is not Qualified or already has 3 Classes.
-- FID 2 has only 2 classes, so allowed to insert.
insert into ASSIGNEE values (2,3);
1 row inserted.
One way to accomplish your goal is to do something like this:
CREATE OR REPLACE TRIGGER ASSIGN_T1
BEFORE INSERT ON ASSIGN
FOR EACH ROW
BEGIN
FOR aRow IN (SELECT q.QUALIFY,
COUNT(*) OVER (PARTITION BY q.FID) AS FID_COUNT
FROM QUALIFY q
WHERE q.FID = :NEW.FID)
LOOP
IF aRow.QUALIFY = 'Y' AND aRow.FID_COUNT < 3 THEN
INSERT INTO ASSIGN (FID, CID) VALUES (:NEW.FID, :NEW.CID);
END IF;
END LOOP;
END ASSIGN_T1;

How to add a unique constraint which complex conditions in Oracle?

There is a table t1. It has a uuid field (which cannot be made primary key) and a deleted_ts field.
Whenever a new record is going to be added, we should check if "select count(1) from t1 where uuid = [recourd.uuid] and deleted_ts is not null" is 0 or not. If it is not 0, the record should not be added.
The same thing should be done when updating an record.
I think I should use constraint or trigger... but I have investigated it for a while but still don't know how to do it. Any suggestion?
You probably want a unique function-based index
create unique index idx_uuid_uniq_or_del
on t1( case when deleted_ts is not null
then uuid
else null
end );
Note that your description seems odd to me. You seem to want to allow multiple "live" rows with the same uuid value but only one "deleted" row with that value. Normally, it would be the reverse-- you want to ensure that there is only one "live" row per uuid while allowing multiple deleted rows. If that's really what you want
create unique index idx_one_live_uuid
on t1( case when deleted_ts is null
then uuid
else null
end );
If you try to use a single trigger like #Littlefoot shows, that will work so long as you only ever do single-row insert ... values statements. As soon as someone comes along and does a multi-row insert (i.e. an insert ... select)
insert into test( uuid, deleted_ts, name )
select 2, null, 'a' from dual union all
select 2, null, 'b' from dual union all
select 2, 100, 'c' from dual;
the trigger will throw a mutating table exception. You could have a compound trigger with row- and statement-level sections (or a combination of row- and statement-level triggers) to get around the mutating table exception but you'd still have transaction control issues where two sessions are making changes that together violate your rule but neither session can see the violation because they can't see the other session's uncommitted changes. You could work around those transaction control issues by adding some additional locking but now we're getting into a pretty complicated bit of code that is going to be relatively easy for you (or some future developer) to make a mistake in and/or create support or scalability issues. A function-based unique index is much simpler and takes care of these issues automatically.
Would this be OK?
SQL> create table test (uuid number, deleted_ts number, name varchar2(10));
Table created.
SQL> create or replace trigger trg_biu_test
2 before insert or update on test
3 for each row
4 declare
5 l_cnt number;
6 begin
7 select count(*) into l_cnt
8 from test
9 where uuid = :new.uuid
10 and deleted_ts is not null;
11 if l_cnt > 0 then
12 raise_application_error(-20000, 'Error - uniqueness violated');
13 end if;
14 end;
15 /
Trigger created.
Testing (read comments):
SQL> -- OK, first row ever
SQL> insert into test values (1, null, 'a');
1 row created.
SQL> -- OK, as there's no row with a non-empty DELETED_TS
SQL> insert into test values (1, null, 'b');
1 row created.
SQL> -- OK, as this is the first non-empty DELETED_TS for UUID = 1
SQL> insert into test values (1, 100, 'c');
1 row created.
SQL> -- Error, as this is the 2nd non-empty DELETED_TS for UUID = 1
SQL> insert into test values (1, 200, 'd');
insert into test values (1, 200, 'd')
*
ERROR at line 1:
ORA-20000: Error - uniqueness violated
ORA-06512: at "SCOTT.TRG_BIU_TEST", line 9
ORA-04088: error during execution of trigger 'SCOTT.TRG_BIU_TEST'
SQL> -- OK, as this is the first non-empty DELETED_TS for UUID = 2
SQL> insert into test values (2, 300, 'e');
1 row created.
SQL>

How to handle Oracle Error [ Unique Constraint ] error

I have a table named TABLE_1 which has 3 columns
row_id row_name row_descr
1 check1 checks here
2 check2 checks there
These rows are created through a front end application. Now suppose I delete the entry with row_name check2 from the front end and create another entry from front end with row_name check3, in database my entries will be as follows.
row_id row_name row_descr
1 check1 checks here
3 check3 checks
Now row_id if you observe is not a normal one time increment, Now my problem is i'm writing an insert statement to automate something and i don't know what i should insert in the row_id column. Previously i thought it is just new row_id = old row_id +1. But this is not the case here. Please help
EDIT :
Currently im inserting like this which is Wrong :
insert into TABLE1 (row_id, row_name, row_descr
) values ( (select max (row_id) + 1 from TABLE1),'check1','checks here');
row_id is not a normal one time increment.
Never ever calculate ids by max(id)+1 unless you can absolutly exclude simultaneous actions ( which is almost never ever the case). In oracle (pre version 12 see Kumars answer) create a sequence once and insert the values from that sequences afterwards.
create sequence my_sequence;
Either by a trigger which means you don't have to care about the ids during the insert at all:
CREATE OR REPLACE TRIGGER myTrigger
BEFORE INSERT ON TABLE1 FOR EACH ROW
BEGIN
SELECT my_sequence.NEXTVAL INTO :NEW.row_id FROM DUAL;
END;
/
Or directly with the insert
insert into TABLE1 (row_id, row_name, row_descr
) values ( my_sequence.nextval,'check1','checks here');
Besides using row_id as column name in oracle might be a little confusing, because of the pseudocolumn rowid which has a special meaning.
To anwser your quetstion though: If you really need to catch oracle errors as excpetions you can do this with PRAGMA EXCEPTION INIT by using a procedure for your inserts. It might look somehow like this:
CREATE OR REPLACE PROCEDURE myInsert( [...] )
IS
value_allready_exists EXCEPTION;
PRAGMA EXCEPTION_INIT ( value_allready_exists, -00001 );
--ORA-00001: unique constraint violated
BEGIN
/*
* Do your Insert here
*/
EXCEPTION
WHEN value_allready_exists THEN
/*
* Do what you think is necessary on your ORA-00001 here
*/
END myInsert;
Oracle 12c introduced IDENTITY columns. Precisely, Release 12.1. It is very handy with situations where you need to have a sequence for your primary key column.
For example,
SQL> DROP TABLE identity_tab PURGE;
Table dropped.
SQL>
SQL> CREATE TABLE identity_tab (
2 ID NUMBER GENERATED ALWAYS AS IDENTITY,
3 text VARCHAR2(10)
4 );
Table created.
SQL>
SQL> INSERT INTO identity_tab (text) VALUES ('Text');
1 row created.
SQL> DELETE FROM identity_tab WHERE ID = 1;
1 row deleted.
SQL> INSERT INTO identity_tab (text) VALUES ('Text');
1 row created.
SQL> INSERT INTO identity_tab (text) VALUES ('Text');
1 row created.
SQL> INSERT INTO identity_tab (text) VALUES ('Text');
1 row created.
SQL> DELETE FROM identity_tab WHERE ID = 2;
1 row deleted.
SQL> SELECT * FROM identity_tab;
ID TEXT
---------- ----------
3 Text
4 Text
SQL>
Now let's see what's under the hood -
SQL> SELECT table_name,
2 column_name,
3 generation_type,
4 identity_options
5 FROM all_tab_identity_cols
6 WHERE owner = 'LALIT'
7 /
TABLE_NAME COLUMN_NAME GENERATION IDENTITY_OPTIONS
-------------------- --------------- ---------- --------------------------------------------------
IDENTITY_TAB ID ALWAYS START WITH: 1, INCREMENT BY: 1, MAX_VALUE: 9999999
999999999999999999999, MIN_VALUE: 1, CYCLE_FLAG: N
, CACHE_SIZE: 20, ORDER_FLAG: N
SQL>
So, there you go. A sequence implicitly created by Oracle.
And don't forget, you can get rid off the sequence only with the purge option with table drop.
If you are not worried about which values are causing the error, then you could handle it by including a /*+ hint */ in the insert statement.
Here is an example where we would be selecting from another table, or perhaps an inner query, and inserting the results into a table called TABLE_NAME which has a unique constraint on a column called IDX_COL_NAME.
INSERT /*+ ignore_row_on_dupkey_index(TABLE_NAME(IDX_COL_NAME)) */
INTO TABLE_NAME(
INDEX_COL_NAME
, col_1
, col_2
, col_3
, ...
, col_n)
SELECT
INDEX_COL_NAME
, col_1
, col_2
, col_3
, ...
, col_n);
Oracle will blow past the redundant row. This is not a great solution if you care about know WHICH row is causing the issue, or anything else. But if you don't care about that and are fine just keeping the first value that was inserted, then this should do the job.
You can use an exception build in which will raise whenever there will be duplication on unique key
DECLARE
emp_count number;
BEGIN
select count(*) into emp_count from emp;
if emp_count < 1 then
insert into emp
values(1, 'First', 'CLERK', '7839', SYSDATE, 1200, null, 30);
dbms_output.put_line('Clerk added');
else
dbms_output.put_line('No data added');
end if;
EXCEPTION
when dup_val_on_index then
dbms_output.put_line('Tried to add row with duplicated index');
END;

Unique constraint on one column with excluding row with same values in other

I'd like to add a unique key to column value but I must ignore rows that have the same values in columns value and header_id. For example, consider this table:
id | header_id | value
1 | 1 | a
2 | 1 | a
3 | 2 | a
So rows 1 and 2 point to same object and the unique key should accept them, but row 3 has a different header_id (pointing to another object) and, because it has the same value as object 1, it should violate unique constraint and raise an error.
Edit 16.2:1327:
I'm using a core framework that generates columns to handle history so I cannot normalize the table. My class has lots of columns but for this example I'm only considering the value column.
You could do it if you can change your table structure slightly:
your_table
id header_value
1 1
2 1
3 2
header_value
id header_id value
1 1 a
2 2 a
Add a foreign key constraint from your_table.header_value to header_value.id.
Now you can add a unique constraint on header_value.value.
You could use a trigger to simulate a unique constraint with your desired properties. Something like this would do the trick:
create or replace function sort_of_unique() returns trigger as $$
declare
got_one boolean;
begin
select exists(
select 1
from your_table
where header_id != new.header_id
and value = new.value
) into got_one;
if got_one then
raise exception 'Uniqueness violation in your_table';
end if;
return new;
end;
$$ language plpgsql;
create trigger sort_of_unique_trigger
before insert or update on your_table
for each row execute procedure sort_of_unique();
Then you'd get things like this happening:
=> insert into your_table (id, header_id, value) values (1, 1, 'a');
=> insert into your_table (id, header_id, value) values (2, 1, 'a');
=> insert into your_table (id, header_id, value) values (3, 2, 'a');
ERROR: Uniqueness violation in your_table
=> insert into your_table (id, header_id, value) values (3, 2, 'b');
=> update your_table set value = 'a' where id = 3;
ERROR: Uniqueness violation in your_table
You can create partial unique indexes by attaching a WHERE clause to the index. This allows you to apply your uniqueness constraint to slices of the table; however, I can't think of a way to get the WHERE clause to specify an "anti-slice" so I don't see a way to make this work with a partial index. I could be missing something obvious though.
After a while I found something. Using constrain CHECK with function to determine if exist (Cannot use SELECT in CHECK statement but you can use function with desired select)
CREATE OR REPLACE FUNCTION is_value_free(_header_id integer, _value varchar) RETURNS BOOLEAN AS
$$
BEGIN
RETURN NOT EXISTS (SELECT header_id,value FROM myschema.mytalbe WHERE value LIKE _value AND header_id != _header_id LIMIT 1);
END;
$$ LANGUAGE plpgsql;
ALTER TABLE mytable ADD CONSTRAINT uniq_value CHECK (is_value_free(header_id,value))

Oracle: how to UPSERT (update or insert into a table?)

The UPSERT operation either updates or inserts a row in a table, depending if the table already has a row that matches the data:
if table t has a row exists that has key X:
update t set mystuff... where mykey=X
else
insert into t mystuff...
Since Oracle doesn't have a specific UPSERT statement, what's the best way to do this?
The MERGE statement merges data between two tables. Using DUAL
allows us to use this command. Note that this is not protected against concurrent access.
create or replace
procedure ups(xa number)
as
begin
merge into mergetest m using dual on (a = xa)
when not matched then insert (a,b) values (xa,1)
when matched then update set b = b+1;
end ups;
/
drop table mergetest;
create table mergetest(a number, b number);
call ups(10);
call ups(10);
call ups(20);
select * from mergetest;
A B
---------------------- ----------------------
10 2
20 1
The dual example above which is in PL/SQL was great becuase I wanted to do something similar, but I wanted it client side...so here is the SQL I used to send a similar statement direct from some C#
MERGE INTO Employee USING dual ON ( "id"=2097153 )
WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
WHEN NOT MATCHED THEN INSERT ("id","last","name")
VALUES ( 2097153,"smith", "john" )
However from a C# perspective this provide to be slower than doing the update and seeing if the rows affected was 0 and doing the insert if it was.
An alternative to MERGE (the "old fashioned way"):
begin
insert into t (mykey, mystuff)
values ('X', 123);
exception
when dup_val_on_index then
update t
set mystuff = 123
where mykey = 'X';
end;
Another alternative without the exception check:
UPDATE tablename
SET val1 = in_val1,
val2 = in_val2
WHERE val3 = in_val3;
IF ( sql%rowcount = 0 )
THEN
INSERT INTO tablename
VALUES (in_val1, in_val2, in_val3);
END IF;
insert if not exists
update:
INSERT INTO mytable (id1, t1)
SELECT 11, 'x1' FROM DUAL
WHERE NOT EXISTS (SELECT id1 FROM mytble WHERE id1 = 11);
UPDATE mytable SET t1 = 'x1' WHERE id1 = 11;
None of the answers given so far is safe in the face of concurrent accesses, as pointed out in Tim Sylvester's comment, and will raise exceptions in case of races. To fix that, the insert/update combo must be wrapped in some kind of loop statement, so that in case of an exception the whole thing is retried.
As an example, here's how Grommit's code can be wrapped in a loop to make it safe when run concurrently:
PROCEDURE MyProc (
...
) IS
BEGIN
LOOP
BEGIN
MERGE INTO Employee USING dual ON ( "id"=2097153 )
WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
WHEN NOT MATCHED THEN INSERT ("id","last","name")
VALUES ( 2097153,"smith", "john" );
EXIT; -- success? -> exit loop
EXCEPTION
WHEN NO_DATA_FOUND THEN -- the entry was concurrently deleted
NULL; -- exception? -> no op, i.e. continue looping
WHEN DUP_VAL_ON_INDEX THEN -- an entry was concurrently inserted
NULL; -- exception? -> no op, i.e. continue looping
END;
END LOOP;
END;
N.B. In transaction mode SERIALIZABLE, which I don't recommend btw, you might run into
ORA-08177: can't serialize access for this transaction exceptions instead.
I'd like Grommit answer, except it require dupe values. I found solution where it may appear once: http://forums.devshed.com/showpost.php?p=1182653&postcount=2
MERGE INTO KBS.NUFUS_MUHTARLIK B
USING (
SELECT '028-01' CILT, '25' SAYFA, '6' KUTUK, '46603404838' MERNIS_NO
FROM DUAL
) E
ON (B.MERNIS_NO = E.MERNIS_NO)
WHEN MATCHED THEN
UPDATE SET B.CILT = E.CILT, B.SAYFA = E.SAYFA, B.KUTUK = E.KUTUK
WHEN NOT MATCHED THEN
INSERT ( CILT, SAYFA, KUTUK, MERNIS_NO)
VALUES (E.CILT, E.SAYFA, E.KUTUK, E.MERNIS_NO);
I've been using the first code sample for years. Notice notfound rather than count.
UPDATE tablename SET val1 = in_val1, val2 = in_val2
WHERE val3 = in_val3;
IF ( sql%notfound ) THEN
INSERT INTO tablename
VALUES (in_val1, in_val2, in_val3);
END IF;
The code below is the possibly new and improved code
MERGE INTO tablename USING dual ON ( val3 = in_val3 )
WHEN MATCHED THEN UPDATE SET val1 = in_val1, val2 = in_val2
WHEN NOT MATCHED THEN INSERT
VALUES (in_val1, in_val2, in_val3)
In the first example the update does an index lookup. It has to, in order to update the right row. Oracle opens an implicit cursor, and we use it to wrap a corresponding insert so we know that the insert will only happen when the key does not exist. But the insert is an independent command and it has to do a second lookup. I don't know the inner workings of the merge command but since the command is a single unit, Oracle could execute the correct insert or update with a single index lookup.
I think merge is better when you do have some processing to be done that means taking data from some tables and updating a table, possibly inserting or deleting rows. But for the single row case, you may consider the first case since the syntax is more common.
A note regarding the two solutions that suggest:
1) Insert, if exception then update,
or
2) Update, if sql%rowcount = 0 then insert
The question of whether to insert or update first is also application dependent. Are you expecting more inserts or more updates? The one that is most likely to succeed should go first.
If you pick the wrong one you will get a bunch of unnecessary index reads. Not a huge deal but still something to consider.
Try this,
insert into b_building_property (
select
'AREA_IN_COMMON_USE_DOUBLE','Area in Common Use','DOUBLE', null, 9000, 9
from dual
)
minus
(
select * from b_building_property where id = 9
)
;
From http://www.praetoriate.com/oracle_tips_upserts.htm:
"In Oracle9i, an UPSERT can accomplish this task in a single statement:"
INSERT
FIRST WHEN
credit_limit >=100000
THEN INTO
rich_customers
VALUES(cust_id,cust_credit_limit)
INTO customers
ELSE
INTO customers SELECT * FROM new_customers;