I am having the below index defined on one of the tables in Oracle.
CASE WHEN C IS NOT NULL THEN A||','||B||','||C END
Initially, I thought the combination of A,B,C separated by commas should be unique when C is not null, but seems like I'm wrong. I did a bit of a research on this as well but could not find a good explanation.
Could someone kindly help me with this?
Thanks in Advance!!
Yes, it can be unique.
But don't forget - uniqueness is an additional cost.
There will be little benefit from such an index, since there will be duplicates of rows for fields A & B when C is NULL.
I suppose it might be worth changing the index function as :
a||','||b||case when c is not null then ','||c end
This will control duplicates if only columns A and B are used.
Of course, unless your table has other unique indexes using columns A and B.
For demonstration - a short example, with a unique functional index.
N.B. Please, when describing the problem /task, provide a description of the relevant objects (tables,indexes, procedures...etc.).)
For a test,try:
--ddl
drop table test_tab;
create table test_tab
( a varchar2(3) not null,
b varchar2(3) not null,
c varchar2(3),
idx varchar2(12) generated always as (case when c is not null then a||','||b||','||c end) virtual
);
--
create unique index test_tab_i1 on test_tab(idx);
--dml
insert into test_tab(a,b,c) values('a1','b1','c1');
insert into test_tab(a,b,c) values('a1','b2','c2');
insert into test_tab(a,b,c) values('a1','b1',null);
commit;
select * from test_tab;
ANALYZE TABLE test_tab COMPUTE STATISTICS FOR ALL INDEXES;
--get data of idx
select INDEX_NAME,NUM_ROWS,DISTINCT_KEYS from user_indexes i where i.table_name=upper('test_tab');
--dubt
insert into test_tab(a,b,c) values('a1','b1',null);
commit;
--
ANALYZE TABLE test_tab COMPUTE STATISTICS FOR ALL INDEXES;
--pass2
select * from test_tab;
--get data of idx
select INDEX_NAME,NUM_ROWS,DISTINCT_KEYS from user_indexes i where i.table_name=upper('test_tab');
--pass3
--dubt
insert into test_tab(a,b,c) values('a1','b1','c1');
Result:
Table created.
Index created.
1 row(s) inserted.
1 row(s) inserted.
1 row(s) inserted.
Statement processed.
Result Set 31
A B C IDX
a1 b1 c1 a1,b1,c1
a1 b2 c2 a1,b2,c2
a1 b1 - -
Download CSV
3 rows selected.
Statement processed.
Result Set 32
INDEX_NAME NUM_ROWS DISTINCT_KEYS
TEST_TAB_I1 2 2
Download CSV
1 row(s) inserted.
Statement processed.
Statement processed.
Result Set 33
A B C IDX
a1 b1 c1 a1,b1,c1
a1 b2 c2 a1,b2,c2
a1 b1 - -
a1 b1 - -
Download CSV
4 rows selected.
Result Set 34
INDEX_NAME NUM_ROWS DISTINCT_KEYS
TEST_TAB_I1 2 2
Download CSV
ORA-00001: unique constraint (SQL_XKMHFTSRXNSKMGVEDOYSVXZGL.TEST_TAB_I1) violated ORA-06512: at "SYS.DBMS_SQL", line 1721
Even if your question is vague, it is interesting. I will try to provide you a possible explanation.
A||','||B is unique if you can guarantee that for 2 distinct values of (A,B), the function A||','||B is not returning the same value.
You need to guarantee that TO_CHAR(A) ||','|| TO_CHAR(B) is unique.
I am considering that A and B are NOT NULL.
A||','||B is unique if A and B are VARCHAR but it is not the case if you have DATE or TIMESTAMP datatypes. This is not TRUE because TO_CHAR function has default parameters that depend on nls_parameters from your session.
Testcase - executing the same queries but changing the nls_date_format and try to create a unique index
drop table test;
create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10));
alter session set nls_date_format= 'dd/mm/yyyy hh24:mi:ss';
drop index test;
truncate table test;
insert into test(a,b,c) values(sysdate+(1/60),'1','c1');
insert into test(a,b,c) values(sysdate+(1/160),'1','c1');
insert into test(a,b,c) values(sysdate+(1/80),'2','c2');
insert into test(a,b,c) values(sysdate+(1/180),'2','c2');
commit;
create unique index test on test (case when c is not null then a||','||b||','||c end);
select (case when c is not null then a||','||b||','||c end) valfn from test order by B;
alter session set nls_date_format= 'dd/mm/yyyy';
truncate table test;
drop index test;
insert into test(a,b,c) values(sysdate+(1/60),'1','c1');
insert into test(a,b,c) values(sysdate+(1/160),'1','c1');
insert into test(a,b,c) values(sysdate+(1/80),'2','c2');
insert into test(a,b,c) values(sysdate+(1/180),'2','c2');
create unique index test on test (case when c is not null then a||','||b||','||c end);
select (case when c is not null then a||','||b||','||c end) valfn from test order by B;
output with nls_date_format= 'dd/mm/yyyy hh24:mi:ss'
Index TEST created.
VALFN
------------------------
15/05/2021 08:40:35,1,c1
15/05/2021 08:25:35,1,c1
15/05/2021 08:34:35,2,c2
15/05/2021 08:24:35,2,c2
output with nls_date_format= 'dd/mm/yyyy'
ORA-01452: cannot CREATE UNIQUE INDEX; duplicate keys found
VALFN
---------------
15/05/2021,1,c1
15/05/2021,1,c1
15/05/2021,2,c2
15/05/2021,2,c2
Then we can see what happened when the indexes were created. We will query the index definition to explain this behavior.
output with nls_date_format= 'dd/mm/yyyy hh24:mi:ss'
alter session set nls_date_format= 'dd/mm/yyyy hh24:mi:ss';
drop table test;
create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10));
create unique index test on test (case when c is not null then a||','||b||','||c end);
select index_name, column_expression from all_ind_expressions where table_name = 'TEST'
INDEX_NAME COLUMN_EXPRESSION
---------- ---------------------------------------------------------------------------------------------
TEST CASE WHEN "C" IS NOT NULL THEN TO_CHAR("A",'dd/mm/yyyy hh24:mi:ss')||','||"B"||','||"C" END
output with nls_date_format= 'dd/mm/yyyy'
alter session set nls_date_format= 'dd/mm/yyyy';
drop table test;
create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10));
create unique index test on test (case when c is not null then a||','||b||','||c end);
select index_name, column_expression from all_ind_expressions where table_name = 'TEST'
INDEX_NAME COLUMN_EXPRESSION
---------- ----------------------------------------------------------------------------------
TEST CASE WHEN "C" IS NOT NULL THEN TO_CHAR("A",'dd/mm/yyyy')||','||"B"||','||"C" END
As you can see, the index is not the same because it depends on your nls_date_format when the index was created. I always suggest to validate what was the expression used by Oracle when creating the object.
This is the same when creating virtual columns
alter session set nls_date_format= 'dd/mm/yyyy hh24:mi:ss';
drop table test;
create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10)
,idx varchar2(50) generated always as (case when c is not null then a||','||b||','||c end) virtual
);
select DATA_DEFAULT from all_tab_cols where table_name = 'TEST' and column_name = 'IDX';
DATA_DEFAULT
---------------------------------------------------------------------------------------------
CASE WHEN "C" IS NOT NULL THEN TO_CHAR("A",'dd/mm/yyyy hh24:mi:ss')||','||"B"||','||"C" END
alter session set nls_date_format= 'dd/mm/yyyy';
drop table test;
create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10)
,idx varchar2(50) generated always as (case when c is not null then a||','||b||','||c end) virtual
);
select DATA_DEFAULT from all_tab_cols where table_name = 'TEST' and column_name = 'IDX';
DATA_DEFAULT
----------------------------------------------------------------------------------
CASE WHEN "C" IS NOT NULL THEN TO_CHAR("A",'dd/mm/yyyy')||','||"B"||','||"C" END
Now, you can add C to this equation and validate why in your case : CASE WHEN C IS NOT NULL THEN A||','||B||','||C END is not unique
Most of the time, by default the nls_date_format is not including the time.
Related
I would like to insert data in to two tables. Will be one-to-many connection. For this, I have to use Foreign Key, of course.
I think, table1 - ID column is an ideal for this a Primary Key. But I generate it always with a trigger, automatically, every line. SO,
How can I put Table1.ID (auto generated, Primary Key) column in to table2.Fkey column in the same insert query?
INSERT ALL INTO table1 ( --here (before this) generated the table1.id column automatically with a trigger.
table1.food,
table1.drink,
table1.shoe
) VALUES (
'apple',
'water',
'slippers'
)
INTO table2 (
fkey,
color
) VALUES (
table1.id, -- I would like table2.fkey == table1.id this gave me error
'blue'
) SELECT
*
FROM
table1
INNER JOIN table2 ON table1.id = table2.fkey;
The error message:
"00904. 00000 - "%s: invalid identifier""
As suggested by #OldProgrammer, use sequence
INSERT ALL INTO table1 ( --here (before this) generated the table1.id column automatically with a trigger.
table1_id,
table1.food,
table1.drink,
table1.shoe
) VALUES (
<sequecename_table1>.nextval,
'apple',
'water',
'slippers'
)
INTO table2 (
fkey,
color
) VALUES (
<sequecename_table2>.nextval,
<sequecename_table1>.currval, -- returns the current value of a sequence.
'blue'
) SELECT
*
FROM
table1
INNER JOIN table2 ON table1.id = table2.fkey;
Since you're using Oracle DB's 12c version, then might use Identity Column Property. Then easily return the value of first table's (table1) to a local variable by charging of returning clause just after an insert statement for table1, and use inside the next insert statement which is for table2 as stated below :
SQL> create table table1(
2 ID integer generated always as identity primary key,
3 food varchar2(50), drink varchar2(50), shoe varchar2(50)
4 );
SQL> create table table2(
2 fkey integer references table1(ID),
3 color varchar2(50)
4 );
SQL> declare
2 cl_tab table1.id%type;
3 begin
4 insert into table1(food,drink,shoe) values('apple','water','slippers' )
5 returning id into cl_tab;
6 insert into table2 values(cl_tab,'blue');
7 end;
8 /
SQL> select * from table1;
ID FOOD DRINK SHOE
-- ------- ------- -------
1 apple water slippers
SQL> select * from table2;
FKEY COLOR
---- --------------------------------------------------
1 blue
Anytime you issue the above statement for insertions between begin and end, both table1.ID and table2.fkey columns will be populated by the same integer values. By the way do not forget to commit the changes by insertions, if you need these values throughout the DB(i.e.from other sessions also).
I have two tables Table1 and Table2 both with the same columns TestResult and Testcounts. Table1 has testresult as varchar and Table2 has testresult as number.
I have a string .for eg "Oracle" as value for testresult of varchar type for Table1 which needs to be inserted to testresult of number type of Table2 as null.How can i do this? Any suggestions will be highly appreciated :)
EDIT
I have table1 with columns as TestResult varchar2(50) and Testcount number with values as "0.5","0.6","0.8","Oracle" for TestResult and 1,2,3,4 for Testcount.
Now i have another table Table2 as TestResult number and Testcount number with no values, in other words its empty.. I would like to insert all data from table1 to table2 with "Oracle" being inserted as "null"
The following will do what you've asked for:
INSERT INTO TABLE2 (TESTRESULT, TESTCOUNTS)
SELECT CASE
WHEN LENGTH(REGEXP_SUBSTR(TESTRESULT, '[0-9.]*')) = LENGTH(TESTRESULT) THEN TESTRESULT
ELSE NULL
END,
TESTCOUNTS
FROM TABLE1
SQLFiddle here
If you only have a single string value that you can't convert to a number, and you want to set that to null, you can just use a case expression to supply the null:
insert into table2 (testresult, testcounts)
select case when testresult = 'Oracle' then null else to_number(testresult) end,
testcounts
from table1;
Demo:
create table table1 (testresult varchar2(10), testcounts number);
insert into table1
select '0.5', 1 from dual
union all select '0.6', 2 from dual
union all select '0.8', 3 from dual
union all select 'Oracle', 4 from dual;
create table table2 (testresult number, testcounts number);
insert into table2 (testresult, testcounts)
select case when testresult = 'Oracle' then null else to_number(testresult) end,
testcounts
from table1;
select * from table2;
TESTRESULT TESTCOUNTS
---------- ----------
.5 1
.6 2
.8 3
4
db<>fiddle
If you are using Oracle 12c Release 2 (or above) you could also just try to convert the string to a number and use the default ... on conversion error clause to substitute null for that, or any other, non-numeric value:
insert into table2 (testresult, testcounts)
select to_number(testresult default null on conversion error), testcounts
from table1;
select * from table2;
TESTRESULT TESTCOUNTS
---------- ----------
.5 1
.6 2
.8 3
4
In earlier versions you could do the same thing with a user-defined function that wraps the real to_number() call and returns null on error. Or a regex/translate check similar to what #BobJarvis has shown.
Having multiple rows with null would make the data hard to interpret though, so hopefully you only have this one fixed value...
I have a table in oracle which has column recid which is of number Datar type. The table is partition table and it has partition index on it.
When I query the partition like
select * from table partition (abc)
I am able to see value for rec id =50. But when I query
select * from table partition(abc) where rec id =50,
It doesn’t give any record .
If I do type casting as
select * from table partition(abc) where cast (recid as number ) =50
I am getting records.
Please let me know what might be the issue .?
The issue exist only for one partition and rest of the partition working normal.
If it's not behaving as a number, then it's not stored as a number.
Run a DESCRIBE (DESC) on your table in either SQL Developer, SQLcl, or SQL*Plus. It will show how the REC_ID column is defined.
If it's stored as a VARCHAR2, you wil get an error on your WHERE CLAUSE predicate for REC_ID, if not every REC_ID could be treated as also a number:
ORA-01722: invalid number
Like so:
SQL> DESC employees
Name Null? Type
EMPLOYEE_ID NOT NULL NUMBER(6)
FIRST_NAME VARCHAR2(20)
LAST_NAME NOT NULL VARCHAR2(25)
EMAIL NOT NULL VARCHAR2(25)
PHONE_NUMBER VARCHAR2(20)
HIRE_DATE NOT NULL DATE
JOB_ID NOT NULL VARCHAR2(10)
SALARY NUMBER(8,2)
COMMISSION_PCT NUMBER(2,2)
MANAGER_ID NUMBER(6)
DEPARTMENT_ID NUMBER(4)
SQL>
SQL> SELECT * FROM employees WHERE first_name = 50;
Error starting at line : 4 in command -
SELECT * FROM employees WHERE first_name = 50
Error report -
ORA-01722: invalid number
SQL> select * from emps_copy_num where first_name = 50;
EMPLOYEE_ID FIRST_NAME LAST_NAME EMAIL PHONE_NUMBER HIRE_DATE JOB_ID SALARY COMMISSION_PCT MANAGER_ID DEPARTMENT_ID
100 50 King SKING 515.123.4567 17-JUN-87 AD_PRES 24000 90
The first query fails - because not every value in that column can be simplicity cast as a number by the database.
The second query works, because I created a copy of the table where all of the first_name strings were values that COULD be cast as a number.
You probably have spaces in there somewhere, eg
SQL> create table t ( should_have_been_numeric varchar2(30));
Table created.
SQL>
SQL> insert into t values ('50 ');
1 row created.
SQL> insert into t values (' 50 ');
1 row created.
SQL> insert into t values (' 50');
1 row created.
SQL>
SQL> select * from t where should_have_been_numeric = '50';
no rows selected
SQL> select * from t where cast(should_have_been_numeric as number) = 50;
SHOULD_HAVE_BEEN_NUMERIC
------------------------------
50
50
50
3 rows selected.
but as already mentioned, if you are treating strings as numbers, then there is problems ahead in terms of spurious errors, not to mention potential performance issues because the optimizer also doesn't know that these are really numbers.
I am using an oracle 11 table with interval partitioning and list subpartitioning like this (simplified):
CREATE TABLE LOG
(
ID NUMBER(15, 0) NOT NULL PRIMARY KEY
, MSG_TIME DATE NOT NULL
, MSG_NR VARCHAR2(16 BYTE)
) PARTITION BY RANGE (MSG_TIME) INTERVAL (NUMTOYMINTERVAL (1,'MONTH'))
SUBPARTITION BY LIST (MSG_NR)
SUBPARTITION TEMPLATE (
SUBPARTITION login VALUES ('FOO')
, SUBPARTITION others VALUES (DEFAULT)
)
(PARTITION oldvalues VALUES LESS THAN (TO_DATE('01-01-2010','DD-MM-YYYY')));
How do I drop a specific subpartitition for a specific month without knowing the (system generated) name of the subpartition? There is a syntax "alter table ... drop subpartition for (subpartition_key_value , ...)" but I don't see a way to specify the month for which I am deleting the subpartition. The partition administration guide does not give any examples, either. 8-}
You can use the metadata tables to get the specific subpartition name:
SQL> insert into log values (1, sysdate, 'FOO');
1 row(s) inserted.
SQL> SELECT p.partition_name, s.subpartition_name, p.high_value, s.high_value
2 FROM user_tab_partitions p
3 JOIN
4 user_tab_subpartitions s
5 ON s.table_name = p.table_name
6 AND s.partition_name = p.partition_name
7 AND p.table_name = 'LOG';
PARTITION_NAME SUBPARTITION_NAME HIGH_VALUE HIGH_VALUE
--------------- ------------------ ------------ ----------
OLDVALUES OLDVALUES_OTHERS 2010-01-01 DEFAULT
OLDVALUES OLDVALUES_LOGIN 2010-01-01 'FOO'
SYS_P469754 SYS_SUBP469753 2012-10-01 DEFAULT
SYS_P469754 SYS_SUBP469752 2012-10-01 'FOO'
SQL> alter table log drop subpartition SYS_SUBP469752;
Table altered.
If you want to drop a partition dynamically, it can be tricky to find it with the ALL_TAB_SUBPARTITIONS view because the HIGH_VALUE column may not be simple to query. In that case you could use DBMS_ROWID to find the subpartition object_id of a given row:
SQL> insert into log values (4, sysdate, 'FOO');
1 row(s) inserted.
SQL> DECLARE
2 l_rowid_in ROWID;
3 l_rowid_type NUMBER;
4 l_object_number NUMBER;
5 l_relative_fno NUMBER;
6 l_block_number NUMBER;
7 l_row_number NUMBER;
8 BEGIN
9 SELECT rowid INTO l_rowid_in FROM log WHERE id = 4;
10 dbms_rowid.rowid_info(rowid_in =>l_rowid_in ,
11 rowid_type =>l_rowid_type ,
12 object_number =>l_object_number,
13 relative_fno =>l_relative_fno ,
14 block_number =>l_block_number ,
15 row_number =>l_row_number );
16 dbms_output.put_line('object_number ='||l_object_number);
17 END;
18 /
object_number =15838049
SQL> select object_name, subobject_name, object_type
2 from all_objects where object_id = '15838049';
OBJECT_NAME SUBOBJECT_NAME OBJECT_TYPE
--------------- --------------- ------------------
LOG SYS_SUBP469757 TABLE SUBPARTITION
As it turns out, the "subpartition for" syntax does indeed work, though that seems to be a secret Oracle does not want to tell you about. :-)
ALTER TABLE TB_LOG_MESSAGE DROP SUBPARTITION FOR
(TO_DATE('01.02.2010','DD.MM.YYYY'), 'FOO')
This deletes the subpartition that would contain MSG_TIME 2010/02/01 and MSG_NR FOO. (It is not necessary that there is an actual row with this exact MSG_TIME and MSG_NR. It throws an error if there is no such subpartition, though.)
Thanks for the post - it was very useful for me.
One observation though on the above script to identify the partition and delete it:
The object_id returned by dbms_rowid.rowid_info is not the object_id of the all_objects table. It is actually the data_object_id. It is observed that usually these ids match. However, after truncating the partitioned table several times, these ids diverged in my database. Hence it might be reasonable to instead use the data_object_id to find out the name of the partition:
select object_name, subobject_name, object_type
from all_objects where data_object_id = '15838049';
From the table description of ALL_OBJECTS:
OBJECT_ID Object number of the object
DATA_OBJECT_ID Object number of the segment which contains the object
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_rowid.htm
In the sample code provided in the above link, DBMS_ROWID.ROWID_OBJECT(row_id) is used instead to derive the same information that is given by dbms_rowid.rowid_info. However, the documentation around this sample mentions that it is a data object number from the ROWID.
Examples
This example returns the ROWID for a row in the EMP table, extracts
the data object number from the ROWID, using the ROWID_OBJECT function
in the DBMS_ROWID package, then displays the object number:
DECLARE object_no INTEGER; row_id ROWID; ... BEGIN
SELECT ROWID INTO row_id FROM emp
WHERE empno = 7499; object_no := DBMS_ROWID.ROWID_OBJECT(row_id); DBMS_OUTPUT.PUT_LINE('The obj. # is
'|| object_no); ...
What is the easiest way to INSERT a row if it doesn't exist, in PL/SQL (oracle)?
I want something like:
IF NOT EXISTS (SELECT * FROM table WHERE name = 'jonny') THEN
INSERT INTO table VALUES ("jonny", null);
END IF;
But it's not working.
Note: this table has 2 fields, say, name and age. But only name is PK.
INSERT INTO table
SELECT 'jonny', NULL
FROM dual -- Not Oracle? No need for dual, drop that line
WHERE NOT EXISTS (SELECT NULL -- canonical way, but you can select
-- anything as EXISTS only checks existence
FROM table
WHERE name = 'jonny'
)
Assuming you are on 10g, you can also use the MERGE statement. This allows you to insert the row if it doesn't exist and ignore the row if it does exist. People tend to think of MERGE when they want to do an "upsert" (INSERT if the row doesn't exist and UPDATE if the row does exist) but the UPDATE part is optional now so it can also be used here.
SQL> create table foo (
2 name varchar2(10) primary key,
3 age number
4 );
Table created.
SQL> ed
Wrote file afiedt.buf
1 merge into foo a
2 using (select 'johnny' name, null age from dual) b
3 on (a.name = b.name)
4 when not matched then
5 insert( name, age)
6* values( b.name, b.age)
SQL> /
1 row merged.
SQL> /
0 rows merged.
SQL> select * from foo;
NAME AGE
---------- ----------
johnny
If name is a PK, then just insert and catch the error. The reason to do this rather than any check is that it will work even with multiple clients inserting at the same time. If you check and then insert, you have to hold a lock during that time, or expect the error anyway.
The code for this would be something like
BEGIN
INSERT INTO table( name, age )
VALUES( 'johnny', null );
EXCEPTION
WHEN dup_val_on_index
THEN
NULL; -- Intentionally ignore duplicates
END;
I found the examples a bit tricky to follow for the situation where you want to ensure a row exists in the destination table (especially when you have two columns as the primary key), but the primary key might not exist there at all so there's nothing to select.
This is what worked for me:
MERGE INTO table1 D
USING (
-- These are the row(s) you want to insert.
SELECT
'val1' AS FIELD_A,
'val2' AS FIELD_B
FROM DUAL
) S ON (
-- This is the criteria to find the above row(s) in the
-- destination table. S refers to the rows in the SELECT
-- statement above, D refers to the destination table.
D.FIELD_A = S.FIELD_A
AND D.FIELD_B = S.FIELD_B
)
-- This is the INSERT statement to run for each row that
-- doesn't exist in the destination table.
WHEN NOT MATCHED THEN INSERT (
FIELD_A,
FIELD_B,
FIELD_C
) VALUES (
S.FIELD_A,
S.FIELD_B,
'val3'
)
The key points are:
The SELECT statement inside the USING block must always return rows. If there are no rows returned from this query, no rows will be inserted or updated. Here I select from DUAL so there will always be exactly one row.
The ON condition is what sets the criteria for matching rows. If ON does not have a match then the INSERT statement is run.
You can also add a WHEN MATCHED THEN UPDATE clause if you want more control over the updates too.
Using parts of #benoit answer, I will use this:
DECLARE
varTmp NUMBER:=0;
BEGIN
-- checks
SELECT nvl((SELECT 1 FROM table WHERE name = 'john'), 0) INTO varTmp FROM dual;
-- insert
IF (varTmp = 1) THEN
INSERT INTO table (john, null)
END IF;
END;
Sorry for I don't use any full given answer, but I need IF check because my code is much more complex than this example table with name and age fields. I need a very clear code. Well thanks, I learned a lot! I'll accept #benoit answer.
In addition to the perfect and valid answers given so far, there is also the ignore_row_on_dupkey_index hint you might want to use:
create table tq84_a (
name varchar2 (20) primary key,
age number
);
insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Johnny', 77);
insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Pete' , 28);
insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Sue' , 35);
insert /*+ ignore_row_on_dupkey_index(tq84_a(name)) */ into tq84_a values ('Johnny', null);
select * from tq84_a;
The hint is described on Tahiti.
you can use this syntax:
INSERT INTO table_name ( name, age )
select 'jonny', 18 from dual
where not exists(select 1 from table_name where name = 'jonny');
if its open an pop for asking as "enter substitution variable" then use this before the above queries:
set define off;
INSERT INTO table_name ( name, age )
select 'jonny', 18 from dual
where not exists(select 1 from table_name where name = 'jonny');
You should use Merge:
For example:
MERGE INTO employees e
USING (SELECT * FROM hr_records WHERE start_date > ADD_MONTHS(SYSDATE, -1)) h
ON (e.id = h.emp_id)
WHEN MATCHED THEN
UPDATE SET e.address = h.address
WHEN NOT MATCHED THEN
INSERT (id, address)
VALUES (h.emp_id, h.address);
or
MERGE INTO employees e
USING hr_records h
ON (e.id = h.emp_id)
WHEN MATCHED THEN
UPDATE SET e.address = h.address
WHEN NOT MATCHED THEN
INSERT (id, address)
VALUES (h.emp_id, h.address);
https://oracle-base.com/articles/9i/merge-statement
CTE and only CTE :-)
just throw out extra stuff. Here is almost complete and verbose form for all cases of life. And you can use any concise form.
INSERT INTO reports r
(r.id, r.name, r.key, r.param)
--
-- Invoke this script from "WITH" to the end (";")
-- to debug and see prepared values.
WITH
-- Some new data to add.
newData AS(
SELECT 'Name 1' name, 'key_new_1' key FROM DUAL
UNION SELECT 'Name 2' NAME, 'key_new_2' key FROM DUAL
UNION SELECT 'Name 3' NAME, 'key_new_3' key FROM DUAL
),
-- Any single row for copying with each new row from "newData",
-- if you will of course.
copyData AS(
SELECT r.*
FROM reports r
WHERE r.key = 'key_existing'
-- ! Prevent more than one row to return.
AND FALSE -- do something here for than!
),
-- Last used ID from the "reports" table (it depends on your case).
-- (not going to work with concurrent transactions)
maxId AS (SELECT MAX(id) AS id FROM reports),
--
-- Some construction of all data for insertion.
SELECT maxId.id + ROWNUM, newData.name, newData.key, copyData.param
FROM copyData
-- matrix multiplication :)
-- (or a recursion if you're imperative coder)
CROSS JOIN newData
CROSS JOIN maxId
--
-- Let's prevent re-insertion.
WHERE NOT EXISTS (
SELECT 1 FROM reports rs
WHERE rs.name IN(
SELECT name FROM newData
));
I call it "IF NOT EXISTS" on steroids. So, this helps me and I mostly do so.