Creating test load for self-referencing table - performance

I have to do some SQL Server 2008 R2 performance testing and it would be very convenient to do it using only SSMS and SQL Server, without additional application support.
One of the tests I have to do is querying a self-referencing table (tree-like structure) with unknown content. So, for a start I would have to load something like 100K - 1M randomly parent-child-related rows into this table.
CREATE TABLE Test2 (
ID int IDENTITY(1,1) PRIMARY KEY CLUSTERED NOT NULL,
ParentID int NULL REFERENCES Test2 (ID))
I am currently trying with SSMS and this script to load 10K rows into the table:
SET NOCOUNT ON
INSERT INTO Test2 (ParentID)
VALUES (NULL)
DECLARE #n int = 0
;WHILE(1=1)
BEGIN
--PRINT #n
INSERT INTO Test2 (ParentID)
SELECT TOP 1 ID FROM Test2 ORDER BY NEWID()
SET #n = #n + 1
IF(#n >= 9999)
BREAK
END
SET NOCOUNT OFF
My problem is that it runs something like 2m 45s on my laptop. You can imagine how long it would take to load 100K or even 1M records this way.
I would like to have a faster way to load this random tree-like structure into database table using TSQL?
EDIT:
After Mitch Wheat's suggestion, I replaced
SELECT TOP 1 ID FROM Test2 ORDER BY NEWID()
with
SELECT TOP 1 ID FROM Test2
WHERE ID >= RAND(CHECKSUM(NEWID())) * (SELECT MAX(ID) FROM Test2)
Regarding random row selection, results really look uniformly distributed. Execution time falls from 160s to 5s (!) -> this enables me to insert 100K records in ~60s. However, inserting 1M records using my RBAR script is still very slow and I'm still searching for possible set-based expression to fill my table. If it exists.
Now, after ~10mins of filling random data I have 1M rows. It is slow but acceptable.
However, to copy this data to another table using batch insert it takes <10s.
SELECT *
INTO Test3
FROM Test2
So, I believe some form of batch insert could speed up the process.

You are not really measuring the INSERT performance with your posted code.
Picking a single random row using an ORDER BY clause like this:
SELECT TOP 1 * FROM table ORDER BY NEWID()
or even
SELECT TOP 1 * FROM table ORDER BY CHECKSUM(NEWID())
performs a table scan (because the random value associated with each row obviously needs to be calculated before the rows can be ordered), which can be slow for large tables. Using an indexed integer column (such as that commonly used for a primary key), and using:
SELECT TOP 1 * FROM table
WHERE rowid >= RAND(CHECKSUM(NEWID())) * (SELECT MAX(rowid) FROM table)
works in constant time, provided the rowid column is indexed. Note: this assumes that rowid is uniformly distributed in the range 0..MAX(rowid). If your dataset has some other distribution, your results will be skewed (i.e. some rows will be picked more often than others).

I ended up using my original aproach with some tweaks:
disabling reference constraint before insert and re-enabling afterwards
using batch inserts as Mitch Wheat suggested
This is the schema:
DROP TABLE Test2
GO
CREATE TABLE Test2 (
ID int IDENTITY(1,1) PRIMARY KEY CLUSTERED NOT NULL,
ParentID int NULL /*REFERENCES Test2 (ID)*/
)
GO
ALTER TABLE Test2
ADD CONSTRAINT FK_SelfRef
FOREIGN KEY(ParentID) REFERENCES Test2 (ID)
GO
And the script:
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
SET NOCOUNT ON
ALTER TABLE Test2 NOCHECK CONSTRAINT FK_SelfRef
INSERT INTO Test2 (ParentID)
VALUES (NULL)
DECLARE #n int = 1
;WHILE(1=1)
BEGIN
INSERT INTO Test2 (ParentID)
SELECT ID FROM Test2 ORDER BY NEWID()
SELECT #n = COUNT(*) FROM Test2
IF(#n >= 999999)
BREAK
END
ALTER TABLE dbo.Test2 WITH CHECK CHECK CONSTRAINT FK_SelfRef
SET NOCOUNT OFF
This executes in 10 secs, and I can't do it this fast with any other method.
NOTE: It inserts more records than needed. But the method can be arranged to insert exact no of records by limiting number of inserts in the last pass.

When parent is assigned randomly from the previously inserted rows, there is no control over the tree height (number of levels) and the way levels populated, which may not be desired in some scenarios.
It may be more convenient to populate tree with a data level by level.
Auxiliary table valued function is taken to generate numbers sequence using Itzik's cross joined CTE method (see e.g. here about it)
create function ftItziksCJCTE
(
#cnt int
)
returns table as
return
(
WITH
E00(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E00 a, E00 b),
E04(N) AS (SELECT 1 FROM E02 a, E02 b),
E08(N) AS (SELECT 1 FROM E04 a, E04 b),
E16(N) AS (SELECT 1 FROM E08 a, E08 b),
E32(N) AS (SELECT 1 FROM E16 a, E16 b),
E(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
select N from E where N <= #cnt
)
Simple table to control elements distribution in the tree:
create table #TreeLevels
(
LevelNo int identity(1, 1) not NULL,
MinElements int not NULL,
MaxElements int not NULL,
primary key clustered (LevelNo)
)
Sample distribution:
insert into #TreeLevels values (7, 10)
insert into #TreeLevels values (70, 100)
insert into #TreeLevels values (700, 1000)
Will give us something like 7 to 10 elements with ParentID = NULL, each of them having something like 70 to 100 elements, etc. With total number of elements 343000 to 1000000
Or other distribution:
insert into #TreeLevels values (1, 1)
insert into #TreeLevels values (9, 15)
insert into #TreeLevels values (10, 12)
insert into #TreeLevels values (9, 15)
insert into #TreeLevels values (10, 12)
insert into #TreeLevels values (9, 15)
insert into #TreeLevels values (10, 12)
Meaning there will be single root element with something between 9 and 15 child elements, each of them having something like 10 to 12 elements, etc.
Then tree can be populated level by level:
declare #levelNo int, #eMin int, #eMax int
create table #Inserted (ID int not NULL, primary key nonclustered (ID))
create table #Inserted2 (ID int not NULL, primary key nonclustered (ID))
set #levelNo = 1
while 1=1
begin
select #eMin = MinElements, #eMax = MaxElements from #TreeLevels where LevelNo = #levelNo
if ##ROWCOUNT = 0
break
if #levelNo = 1
begin
insert into TestTree (ParentID)
output inserted.ID into #Inserted (ID)
select NULL from ftItziksCJCTE(round(rand(checksum(newid())) * (#eMax - #eMin) + #eMin, 0))
end
else
begin
if exists (select 1 from #Inserted)
begin
insert into TestTree (ParentID)
output inserted.ID into #Inserted2 (ID)
select
I.ID
from
#Inserted I
cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (#eMax - #eMin) + #eMin, 0)) F
truncate table #Inserted
end
else
begin
insert into TestTree (ParentID)
output inserted.ID into #Inserted (ID)
select
I.ID
from
#Inserted2 I
cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (#eMax - #eMin) + #eMin, 0)) F
truncate table #Inserted2
end
end
set #levelNo = #levelNo + 1
end
However, there is no control on the exact number of elements the tree will contain and leaf nodes are on the last level only. It would be good to have additional parameter controlling level population (percent of nodes on the same level which will have children).
create table #TreeLevels
(
LevelNo int identity(1, 1) not NULL,
MinElements int not NULL,
MaxElements int not NULL,
PopulatedPct float NULL,
primary key clustered (LevelNo)
)
Sample distribution:
insert into #TreeLevels values (1, 1, NULL)
insert into #TreeLevels values (9, 15, NULL)
insert into #TreeLevels values (10, 12, NULL)
insert into #TreeLevels values (9, 15, 80)
insert into #TreeLevels values (10, 12, 65)
insert into #TreeLevels values (9, 15, 35)
insert into #TreeLevels values (10, 12, NULL)
NULL for a PopulatedPct percent is treated as 100%. PopulatedPct controls next level population and should be taken from previous level during cycle. Also it has no meaning for the last row in the #TreeLevels hence.
Now we can cycle trough levels taking PopulatedPct into account.
declare #levelNo int, #eMin int, #eMax int
create table #Inserted (ID int not NULL, primary key nonclustered (ID))
create table #Inserted2 (ID int not NULL, primary key nonclustered (ID))
set #levelNo = 1
while 1=1
begin
select #eMin = MinElements, #eMax = MaxElements from #TreeLevels where LevelNo = #levelNo
if ##ROWCOUNT = 0
break
if #levelNo = 1
begin
insert into TestTree (ParentID)
output inserted.ID into #Inserted (ID)
select NULL from ftItziksCJCTE(round(rand(checksum(newid())) * (#eMax - #eMin) + #eMin, 0))
end
else
begin
declare #pct float
select #pct = PopulatedPct from #TreeLevels where LevelNo = #levelNo - 1
if exists (select 1 from #Inserted)
begin
if (#pct is NULL)
insert into TestTree (ParentID)
output inserted.ID into #Inserted2 (ID)
select
I.ID
from
#Inserted I
cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (#eMax - #eMin) + #eMin, 0)) F
else
insert into TestTree (ParentID)
output inserted.ID into #Inserted2 (ID)
select
I.ID
from
(select top (#pct) PERCENT ID from #Inserted order by rand(checksum(newid()))) I
cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (#eMax - #eMin) + #eMin, 0)) F
truncate table #Inserted
end
else
begin
if (#pct is NULL)
insert into TestTree (ParentID)
output inserted.ID into #Inserted (ID)
select
I.ID
from
#Inserted2 I
cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (#eMax - #eMin) + #eMin, 0)) F
else
insert into TestTree (ParentID)
output inserted.ID into #Inserted (ID)
select
I.ID
from
(select top (#pct) PERCENT ID from #Inserted2 order by rand(checksum(newid()))) I
cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (#eMax - #eMin) + #eMin, 0)) F
truncate table #Inserted2
end
end
set #levelNo = #levelNo + 1
end
Still there is no control over the exact number of elements, but better control over the tree shape is gained.

Related

PLSQL code requirement for partition comparison in oracle

Is there any way to compare values for one partition with another partition of the same table? Requirement is like I have a table and suppose there are 5 partitions, table having two columns(not null). Suppose Col1 having all the distinct values and in col2 there can be a duplicate values. So while comparing one partition with other or we can say rest of the 4 partitions on the basis of distinct col2 values according to the partition name, if the value match between two partition then a new table will create with union of the two partition.
And if there is no match between the col2 values of one partition and with rest of the partition then new table will create of same structure(without any union).
Note:
I want to automate this process through PLSQL code.
Currently what I am doing manually:
I have one table having five partition, for example Table structure:
create table PART_TEST1
(col1 int not null,
col2 int not null)
partition by range (col2)
(partition part1 values less than (10),
partition part2 values less than (20),
partition part3 values less than (30),
partition part4 values less than (40),
partition part5 values less than (maxvalue));
Data distribution:
col1 having distinct values like- 1, 2, 3....so on.
col2 having values like- 1, 2, -1, 1, 2, 3, 4, 1...so on
col2 has duplicate values and my goal is to find the distinct values according to the name of the partition like:
select distinct col2 from PART_TEST1 partition (part1);
For example output of above query is:
Col2
1
2
Again I am querying for finding matching values in other partition:
select distinct col2 from PART_TEST1 partition (part2);
For example output of above query is:
Col2
2
3
So now part 1 and part2 has one same value '2' and two non common values 1 and 3.
so my final query is:
create table 'TABLE_NAME' as select * from part_test1 where col2 = 1;
create table 'TABLE_NAME' as select * from part_test1 where col2 = 3;
create table 'TABLE_NAME' as
(select * from part_test1 where col2 = 2
union
select * from part_test1 where col2 = 2);
Hopefully now you will get some clarity about my problem. I am new to PLSQL and not able to compare the partition values. Also if I am able to compare the values then how can I store the output of the comparison query and then finally create the table? And also I am thinking that I need to compare one partition with rest of the partition like some kind of loop operation.

Delete data returned from subquery in oracle

I have two tables. if the data in table1 is more than a predefined limit (say 2), i need to copy the remaining contents of table1 to table2 and delete those same contents from table1.
I used the below query to insert the excess data from table1 to table2.
insert into table2
SELECT * FROM table1 WHERE ROWNUM < ((select count(*) from table1)-2);
Now i need the delete query to delete the above contents from table1.
Thanks in advance.
A straightforward approach would be an interim storage in a temporary table. Its content can be used to determine the data to be deleted from table1 as well as the source to feed table 2.
Assume (slightly abusing notation) to be the PK column (or that of any candidate key) of table1 - usually there'll be some key that comprises only 1 column.
create global temporary table t_interim as
( SELECT <pk> pkc FROM table1 WHERE ROWNUM < ((select count(*) from table1)-2 )
;
insert into table2
select * from table1 where <pk> IN (
select pkc from t_interim
);
delete from table1 where <pk> IN (
select pkc from t_interim
);
Alternative
If any key of table1 spans more than 1 column, use an EXISTS clause instead as follows ( denoting the i-th component of a candidate key in table1):
create global temporary table t_interim as
( SELECT <ck_1> ck1, <ck_2> ck2, ..., <ck_n> ckn FROM table1 WHERE ROWNUM < ((select count(*) from table1)-2 )
;
insert into table2
select * from table1 t
where exists (
select 1
from t_interim t_i
where t.ck_1 = t_i.ck1
and t.ck_2 = t_i.ck2
...
and t.ck_n = t_i.ckn
)
;
delete from table1 t where
where exists (
select 1
from t_interim t_i
where t.ck_1 = t_i.ck1
and t.ck_2 = t_i.ck2
...
and t.ck_n = t_i.ckn
)
;
(Technically you could try to adjust the first scheme by synthesizing a key from the components of any CK, eg. by concatenating. You run the risk of introducing ambiguities ( (a bc, ab c) -> (abc, abc) ) or run into implementation limits ( max. varchar length ) using the first method)
Note
In case the table doesn't have a PK, you can apply the technique using any candidate key of table1. There will always be one, in the extreme case it's the set of all columns.
This situation may be the right time to improve the db design and add a (synthetic) pk column to table1 ( and any other tables in the system that lack it).

SQL delete rows not in another table

I'm looking for a good SQL approach (Oracle database) to fulfill the next requirements:
Delete rows from Table A that are not present in Table B.
Both tables have identical structure
Some fields are nullable
Amount of columns and rows is huge (more 100k rows and 20-30 columns to compare)
Every single field of every single row needs to be compared from Table A against table B.
Such requirement is owing to a process that must run every day as changes will come from Table B.
In other words: Table A Minus Table B => Delete the records from the Table A
delete from Table A
where (field1, field2, field3) in
(select field1, field2, field3
from Table A
minus
select field1, field2, field3
from Table B);
It's very important to mention that a normal MINUS within DELETE clause fails as does not take the nulls on nullable fields into consideration (unknown result for oracle, then no match).
I also tried EXISTS with success, but I have to use NVL function to replace the nulls with dummy values, which I don't want it as I cannot guarantee that the value replaced in NVL will not come as a valid value in the field.
Does anybody know a way to accomplish such thing? Please remember performance and nullable fields as "a must".
Thanks ever
decode finds sameness (even if both values are null):
decode( field1, field2, 1, 0 ) = 1
To delete rows in table1 not found in table2:
delete table1 t
where t.rowid in (select t1.rowid
from table1 t1
left outer join table2 t2
on decode(t1.field1, t2.field1, 1, 0) = 1
and decode(t1.field2, t2.field2, 1, 0) = 1
and decode(t1.field3, t2.field3, 1, 0) = 1
/* ... */
where t2.rowid is null /* no matching row found */
)
to use existing indexes
...
left outer join table2 t2
on (t1.index_field1=t2.index_field1 or
t1.index_field1 is null and t2.index_field1 is null)
and ...
Use a left outer join and test for null in your where clause
delete a
from a
left outer join b on a.x = b.x
where b.x is null
Have you considered ORALCE SQL MERGE statement?
Use Bulk operation for huge number of records. Performance wise it will be faster.
And use join between two table to get rows to be delete. Nullable columns can be compared with some default value.
Also, if you want Table A to be similar as Table B, why don't you truncate table A and then insert data from table b
Assuming you the same PK field available on each table...(Having a PK or some other unique key is critical for this.)
create table table_a (id number, name varchar2(25), dob date);
insert into table_a values (1, 'bob', to_date('01-01-1978','MM-DD-YYYY'));
insert into table_a values (2, 'steve', null);
insert into table_a values (3, 'joe', to_date('05-22-1989','MM-DD-YYYY'));
insert into table_a values (4, null, null);
insert into table_a values (5, 'susan', to_date('08-08-2005','MM-DD-YYYY'));
insert into table_a values (6, 'juan', to_date('11-17-2001', 'MM-DD-YYYY'));
create table table_b (id number, name varchar2(25), dob date);
insert into table_b values (1, 'bob', to_date('01-01-1978','MM-DD-YYYY'));
insert into table_b values (2, 'steve',to_date('10-14-1992','MM-DD-YYYY'));
insert into table_b values (3, null, to_date('05-22-1989','MM-DD-YYYY'));
insert into table_b values (4, 'mary', to_date('12-08-2012','MM-DD-YYYY'));
insert into table_b values (5, null, null);
commit;
-- confirm minus is working
select id, name, dob
from table_a
minus
select id, name, dob
from table_b;
-- from the minus, re-query to just get the key, then delete by key
delete table_a where id in (
select id from (
select id, name, dob
from table_a
minus
select id, name, dob
from table_b)
);
commit;
select * from table_a;
But, if at some point in time, tableA is to be reset to the same as tableB, why not, as another answer suggested, truncate tableA and select all from tableB.
100K is not huge. I can do ~100K truncate and insert on my laptop instance in less than 1 second.
> DELETE FROM purchase WHERE clientcode NOT IN (
> SELECT clientcode FROM client );
This deletes the rows from the purchase table whose clientcode are not in the client table. The clientcode of purchase table references the clientcode of client table.
DELETE FROM TABLE1 WHERE FIELD1 NOT IN (SELECT CLIENT1 FROM TABLE2);

how to create a temporary table or to select only distinct values from a column in a loop

I have a table with 2 columns, first column has repetitive values, now in a while loop i want to select each distinct value at a time, i created a temporary table in sql, but in oracle sql developer how do i write the code?
CREATE TABLE look_up_table
(row_id INT NOT NULL,
attribute VARCHAR(500),
VARCHAR(700)
)
/* now manually populating this table */
INSERT INTO look_up_table
VALUES
(1, grmacolor_frame_access, black);
(2, grmacolor_frame_access, blue);
(3, grmacolor_frame_access, red);
(4, grmamaterial_frame_access, acetate);
(5, grmamaterial_frame_access, metal);
(6, grmamaterial_frame_access, nylon);
(7, grmamaterial_frame_access, plastic);
DECLARE #temp_col_val NVARCHAR (700), #counter1 INT,
SET #counter1 = 0;
SET #column_count = (SELECT COUNT (DISTINCT attribute) FROM look_up_table);
CREATE TABLE #temp1 AS
SELECT DISTINCT attribute AS attrib,
ROW_NUMBER() OVER (ORDER BY attribute) AS seqno1,
FROM look_up_table;
WHILE (#counter1 < #column_count)
BEGIN;
SET #temp_col_val = (SELECT attrib FROM #temp1 WHERE seqno1 = #counter1;
please help
The following code will loop over each attribute and print it :
declare
curField varchar2(100);
resultCnt number ;
begin
select count(distinct attribute) into resultCnt from look_up_table;
for ind in 1..resultCnt loop
select attribute into curField from (
select attribute, rownum rwn
from
(
select distinct attribute
from look_up_table
)
) where rwn = ind;
dbms_output.put_line (curField);
end loop;
end;
/

Oracle: how to INSERT if a row doesn't exist

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.

Resources