Insert or update if already exists - oracle

merge into bonuses
using( select * from bonuses)s ON s.employee_id = '111'
WHEN MATCHED THEN update set bonus='555'
WHEN NOT MATCHED THEN insert insert into BONUSES (employee_id) values(115)
Table`s insert queries are
insert into BONUSES (employee_id) values(111)
insert into BONUSES (employee_id) values(112)
insert into BONUSES (employee_id) values(113)
insert into BONUSES (employee_id) values(114)
insert into BONUSES (employee_id) values(115)
If employee_id=111 already exists it should update else
it should insert.
Kindly help if someone know

Something like:
MERGE INTO bonuses dst
USING ( SELECT '111' AS employee_id, '555' AS bonus FROM DUAL ) src
ON ( dst.employee_id = src.employee_id )
WHEN MATCHED THEN
UPDATE SET bonus = src.bonus
WHEN NOT MATCHED THEN
INSERT ( employee_id, bonus )
VALUES ( src.employee_id, src.bonus );

Your statement has two syntax errors.
You have repeated the insert keyword.
You have missed the brackets around the on clause conditions. These are mandatory, unlike the join conditions in a normal from clause.
So your code should look like this:
merge into bonuses b
using( select * from bonuses) s
ON (s.employee_id = 115)
WHEN MATCHED THEN update set bonus='555'
WHEN NOT MATCHED THEN insert(employee_id) values(115)
/
However, it doesn't make sense to have the target table in the using clause. It doesn't produce the results you think it's going to...
SQL> select * from bonuses;
EMPLOYEE_ID BONUS
----------- ----------
111
112
113
114
115
5 rows selected.
SQL> merge into bonuses b
2 using( select * from bonuses) s
3 ON (s.employee_id = 115)
4 WHEN MATCHED THEN update set bonus='555'
5 WHEN NOT MATCHED THEN insert (employee_id) values(115)
6 /
9 rows merged.
SQL> select * from bonuses;
EMPLOYEE_ID BONUS
----------- ----------
111 555
112 555
113 555
114 555
115 555
115
115
115
115
9 rows selected.
SQL>
Maybe something like this would suit you?
merge into bonuses b
using( select * from employees) e
ON ( b.employee_id = e.employee_id )
WHEN MATCHED THEN
update set bonus= 555
WHEN NOT MATCHED THEN
insert (employee_id) values (e.id)
If you don't have a source of employee IDs distinct from the BONUSES table you can use the DUAL table to fake it:
SQL> merge into bonuses b
2 using( select 115 as employee_id, 555 as bonus from dual union all
3 select 116 as employee_id, 555 as bonus from dual) e
4 ON ( b.employee_id = e.employee_id )
5 WHEN MATCHED THEN
6 update set bonus= e.bonus
7 WHEN NOT MATCHED THEN
8 insert (employee_id) values (e.employee_id)
9 /
2 rows merged.
SQL> select * from bonuses;
EMPLOYEE_ID BONUS
----------- ----------
111
112
113
114
115 555
116
6 rows selected.
SQL>

I think what you're after is something like:
merge into bonuses tgt
using (select '111' employee_id, '555' bonus from dual) src
on (tgt.employee_id = src.employee_id)
WHEN MATCHED THEN
update set tgt.bonus = src.bonus
WHEN NOT MATCHED THEN
insert (tgt.employee_id, tgt.bonus)
values (src.employee_id, src.bonus);
As an aside, why are you inserting strings into columns which usually have a datatype of some form of NUMBER? Do these columns really have string datatypes (e.g. VARCHAR2, CHAR, etc)?

Related

Insert record from one table to another table - Oracle

I have a table TABLE1 which has 5 columns (ROLL_NO, NAME, UNITS, CODE, AMOUNT);
CREATE TABLE TABLE1 (ROLL_NO VARCHAR2(3), NAME VARCHAR2(4), UNITS NUMBER, AMOUNT NUMBER, CODE VARCHAR2(3));
------------------------------------------------------------------------------------------
INSERT INTO TABLE1 VALUES ('101', 'JOHN', 1, 6, 'ABC');
INSERT INTO TABLE1 VALUES ('101', 'JOHN', 2, 6, 'ABC');
INSERT INTO TABLE1 VALUES ('102', 'TOMS', 1, 7, 'ABC');
INSERT INTO TABLE1 VALUES ('102', 'TOMS', 6, 7, 'ABC');
INSERT INTO TABLE1 VALUES ('103', 'FINN', 1, 1, 'BCD');
ROLL_NO NAME UNITS AMOUNT CODE
-------------------------------------------------------
101 JOHN 1 6 ABC
101 JOHN 2 6 ABC
-------------------------------------------
102 TOMS 1 7 ABC
102 TOMS 6 7 ABC
103 FINN 1 1 BCD
There is second table TABLE2 where we need to insert data from TABLE1
CREATE TABLE TABLE2 (ROLL_NO VARCHAR2(3), NAME VARCHAR2(4), RESULT VARCHAR2(3));
There are three conditions to insert data into TABLE2
1st case : If CODE is 'ABC' and SUM(UNITS) of particular ROLL_NO is equal to AMOUNT then don't insert data into TABLE2
2nd case : If CODE is 'ABC' and SUM(UNITS) of particular ROLL_NO is not equal to AMOUNT then insert data with RESULT column value as 'YES'
3rd case : If CODE is not 'ABC' then RESULT column will be 'YES'.
Note: NAME, CODE and AMOUNT will be same for particular ROLL_NO though ROLL_NO has multiple UNITS.
Example :
ROLL_NO 102 CODE 'ABC' and two lines with SUM(UNITS) as 7 and its equal to AMOUNT i.e. 7 and (1st case)
ROLL_NO 101 has CODE 'ABC' and two lines with SUM(UNITS) as 3 and its not equal to AMOUNT i.e. 6 (2nd case)
ROLL_NO 103 has CODE 'BCD' which is not equal to 'ABC'(3rd case)
At the end TABLE2 should have
ROLL_NO NAME RESULT
-----------------------------
101 JOHN YES
103 FINN YES
I have tried this oracle query but it is inserting data related to 102 ROLL_NO which I don't need
SELECT T1.ROLL_NO, T1.NAME,
CASE
WHEN T1.CODE <> 'ABC' THEN 'YES'
WHEN T1.CODE = 'ABC' AND T2.TOT_UNITS <> T1.AMOUNT THEN 'YES'
END RESULT
FROM (SELECT DISTINCT ROLL_NO, NAME, AMOUNT, CODE
FROM TABLE1 ) T1
JOIN (SELECT ROLL_NO, SUM(UNITS) AS TOT_UNITS
FROM TABLE1
GROUP BY ROLL_NO) T2 ON T1.ROLL_NO = T2.ROLL_NO
I am not able to figure out how to not insert ROLL_NO 102 record into TABLE2..Can anyone provide better query than this if possible? Thank you
A "better" option is to scan table1 only once.
SQL> insert into table2 (roll_no, name, result)
2 with temp as
3 (select roll_no, name, sum(units) sum_units, amount, code,
4 case when code = 'ABC' and sum(units) = amount then 'NO'
5 when code = 'ABC' and sum(units) <> amount then 'YES'
6 else 'YES'
7 end as result
8 from table1
9 group by roll_no, name, amount, code
10 )
11 select roll_no, name, result
12 from temp
13 where result = 'YES';
2 rows created.
SQL> select * from table2;
ROL NAME RES
--- ---- ---
101 JOHN YES
103 FINN YES
SQL>

I am trying to update a table on joining them in order to give 2% bonus to the person who hasn't received at all

2 tables employee_1 and bonus_1 joined and trying to update but gives me the error -
ERROR REPORT UNKNOWN COMMAND
UPDATE(
SELECT e.first_name,
sum(b.bonus_amount) as bon
from employee_1 e
LEFT join bonus_1 b
on e.employee_id=b.employee_id
group by e.first_name
)
set bon=0.2*e.salary
where bon IS NULL;
Please help me update this join query to give 2% bonus to employees whose bonus amount will be null in bonus_amount.
It would help if you posted sample data.
With tables I imagine you have:
SQL> select * from employee_1;
EMPLOYEE_ID SALARY
----------- ----------
1 100
2 200
SQL> select * From bonus_1;
EMPLOYEE_ID BONUS_AMOUNT
----------- ------------
1 20
2 --> no bonus for this employee, so their salary should be raised by 2%
One option might be merge:
SQL> merge into employee_1 e
2 using (select b.employee_id,
3 sum(b.bonus_amount) as bon
4 from bonus_1 b
5 group by b.employee_id
6 ) x
7 on (x.employee_id = e.employee_id)
8 when matched then update set
9 e.salary = e.salary * 1.02
10 where x.bon is null;
1 row merged.
SQL> select * from employee_1;
EMPLOYEE_ID SALARY
----------- ----------
1 100
2 204
Another might be update:
SQL> rollback;
Rollback complete.
SQL> merge into employee_1 e
2 using (select b.employee_id,
3 sum(b.bonus_amount) as bon
4 from bonus_1 b
5 group by b.employee_id
6 ) x
7 on (x.employee_id = e.employee_id)
8 when matched then update set
9 e.salary = e.salary * 1.02
10 where x.bon is null;
1 row merged.
SQL> select * From employee_1;
EMPLOYEE_ID SALARY
----------- ----------
1 100
2 204
SQL>
If you just want to display the amount then do not use UPDATE, just use SELECT and COALESCE:
SELECT MAX(e.first_name) AS first_name,
COALESCE(
SUM(b.bonus_amount),
0.2*MAX(e.salary)
) as bon
FROM employee_1 e
LEFT JOIN bonus_1 b
ON e.employee_id=b.employee_id
GROUP BY e.employee_id;
Note: do not GROUP BY e.first_name as you will aggregate together people with the same first name; which is almost certainly incorrect.
If you want to modify the bonus_1 table to add a bonus when there are no bonuses for an employee then you want an INSERT statement and not an UPDATE:
INSERT INTO bonus_1 (employee_id, bonus_amount)
SELECT employee_id, 0.2*salary
FROM employee_1 e
WHERE NOT EXISTS (SELECT 1 FROM bonus_1 b WHERE b.employee_id = e.employee_id);
Or a MERGE ... WHEN NOT MATCHED THEN INSERT ... statement:
MERGE INTO bonus_1 b
USING employee_1 e
ON (b.employee_id = e.employee_id)
WHEN NOT MATCHED THEN
INSERT (employee_id, bonus_amount)
VALUES (e.employee_id, 0.2*e.salary);
Note: your title states a 2% bonus, if that is the case then 0.02 = 2% whereas 0.2 = 20%.
Once you have performed the insert then:
SELECT MAX(e.first_name) AS first_name,
sum(b.bonus_amount) as bon
FROM employee_1 e
LEFT JOIN bonus_1 b
ON e.employee_id=b.employee_id
GROUP BY e.employee_id;
Will have a row for all employees to display the bonuses.
fiddle

(Oracle)Correlated subquery usage

Subquery query1 below works fine.
But when I put equi condition in -sort of- nested clause like query2, it shows error ORA-00904.
Is this wrong usage of correlated subquery or it is because of other reason?
--Query1: It shows expected result.
SELECT
O.ENAME
O,SAL
,(SELECT COUNT(*)
FROM SCOTT.EMP I
WHERE I.SAL>O.SAL --correlated to outer
) AS RESULT
from SCOTT.EMP O;
--Query2:ORA-00904: "O"."SAL": invalid identifier shows. How to modify to use correlated subquery?
SELECT
O.ENAME
O,SAL
,(
WITH TEMP AS
(
SELECT COUNT(*)
FROM SCOTT.EMP I
WHERE I.SAL>O.SAL --I have put equi condistion here
)
SELECT * FROM TEMP
) AS RESULT
from SCOTT.EMP O;
I believe the second option is a wrong use of correlated subqueries, not because of the comparison, but for the use of the with clause. I would like to remember that you should avoid correlated subqueries as much as possible.
The WITH clause, or subquery factoring clause, may be processed as an inline view or resolved as a temporary table. The advantage of the latter is that repeated references to the subquery may be more efficient as the data is easily retrieved from the temporary table, rather than being requeried by each reference.
In your third column of your second query you want to get the result from the inline view. The problem is that parsing of the inline view is done independently and therefore cannot have references to anything in the outer query.
SQL> create table emp ( ename varchar2(10) , sal number ) ;
Table created.
SQL> insert into emp values ( 'AAA' , 1000 ) ;
insert into emp values ( 'BBB' , 1000 ) ;
insert into emp values ( 'CCC' , 1000 ) ;
insert into emp values ( 'DDD' , 1000 ) ;
1 row created.
SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> select * from emp ;
ENAME SAL
---------- ----------
AAA 1000
BBB 1000
CCC 1000
DDD 1000
To write the query with inline view, the filter must be done in the outer query
SELECT
O.ENAME
O,SAL
,(
WITH TEMP AS
(
SELECT * FROM EMP
)
SELECT count(*) FROM TEMP t WHERE t.SAL>O.SAL
) AS RESULT
from EMP O;
O SAL RESULT
---------- ---------- ----------
AAA 1000 0
BBB 1000 0
CCC 1000 0
DDD 1000 0
It has been explained that the second query does not uses with correctly.
Let me suggest, however, that your query can be simpler and more efficiently phrased. For each employee, you want to count how many employees have a greater salary. Window functions are the way to go here:
select e.*, rank() over(order by salary desc) - 1 result
from scott.emp e

Oracle update from random on another table

I have some fields in table1 to update with random values from some fields in table2.
I have to random into rows of table2 and update each rows of table1 with the same rows values of table2.
Here is my SQL code, but it doesn't work.
update owner.table1 t1
set (t1.adress1, t1.zip_code, t1.town) = (select t2.adress, t2.zip_code, t2.town
from table1 t2
where id = trunc(dbms_random.value(1,20000)))
Result: all rows are updated with the same values, like no random on table 2 rows
How about switching to analytic ROW_NUMBER function? It doesn't really create a random value, but might be good enough.
Here's an example: first, create test tables and insert some data:
SQL> create table t1 (id number,address varchar2(20), town varchar2(10));
Table created.
SQL> create table t2 (id number, address varchar2(20), town varchar2(10));
Table created.
SQL> insert into t1
2 select 1, 'Ilica 20', 'Zagreb' from dual union all
3 select 2, 'Petrinjska 30', 'Sisak' from dual union all
4 select 3, 'Stradun 12', 'Dubrovnik' from dual;
3 rows created.
SQL> insert into t2
2 select 1, 'Pavelinska 15', 'Koprivnica' from dual union all
3 select 2, 'Baščaršija 11', 'Sarajevo' from dual union all
4 select 3, 'Riva 22', 'Split' from dual;
3 rows created.
SQL> select * From t1 order by id;
ID ADDRESS TOWN
---------- -------------------- ----------
1 Ilica 20 Zagreb
2 Petrinjska 30 Sisak
3 Stradun 12 Dubrovnik
SQL> select * From t2 order by id;
ID ADDRESS TOWN
---------- -------------------- ----------
1 Pavelinska 15 Koprivnica
2 Baščaršija 11 Sarajevo
3 Riva 22 Split
Update t1 with rows from t2:
SQL> update t1 set
2 (t1.address, t1.town) =
3 (select x.address, x.town
4 from (select row_number() over (order by address) id, t2.address, t2.town
5 from t2
6 ) x
7 where x.id = t1.id);
3 rows updated.
SQL> select * From t1 order by id;
ID ADDRESS TOWN
---------- -------------------- ----------
1 Baščaršija 11 Sarajevo
2 Pavelinska 15 Koprivnica
3 Riva 22 Split
SQL>

What is the Best way to Perform Bulk insert in oracle ?

With this, I mean Inserting millions of records in tables. I know how to insert data using loops but for inserting millions of data it won't be a good approach.
I have two tables
CREATE TABLE test1
(
col1 NUMBER,
valu VARCHAR2(30),
created_Date DATE,
CONSTRAINT pk_test1 PRIMARY KEY (col1)
)
/
CREATE TABLE test2
(
col2 NUMBER,
fk_col1 NUMBER,
valu VARCHAR2(30),
modified_Date DATE,
CONSTRAINT pk_test2 PRIMARY KEY (col2),
FOREIGN KEY (fk_col1) REFERENCES test1(col1)
)
/
Please suggest a way to insert some dummy records upto 1 million without loops.
As a fairly simplistic approach, which may be enough for you based on your comments, you can generate dummy data using a hierarchical query. Here I'm using bind variables to control how many are created, and to make some of the logic slightly clearer, but you could use literals instead.
First, parent rows:
var parent_rows number;
var avg_children_per_parent number;
exec :parent_rows := 5;
exec :avg_children_per_parent := 3;
-- create dummy parent rows
insert into test1 (col1, valu, created_date)
select level,
dbms_random.string('a', dbms_random.value(1, 30)),
trunc(sysdate) - dbms_random.value(1, 365)
from dual
connect by level <= :parent_rows;
which might generate rows like:
COL1 VALU CREATED_DA
---------- ------------------------------ ----------
1 rYzJBVI 2016-11-14
2 KmSWXfZJ 2017-01-20
3 dFSTvVsYrCqVm 2016-07-19
4 iaHNv 2016-11-08
5 AvAxDiWepPeONGNQYA 2017-01-20
Then child rows, which a random fk_col1 in the range generated for the parent:
-- create dummy child rows
insert into test2 (col2, fk_col1, valu, modified_date)
select level,
round(dbms_random.value(1, :parent_rows)),
dbms_random.string('a', dbms_random.value(1, 30)),
trunc(sysdate) - dbms_random.value(1, 365)
from dual
connect by level <= :parent_rows * :avg_children_per_parent;
which might generate:
select * from test2;
COL2 FK_COL1 VALU MODIFIED_D
---------- ---------- ------------------------------ ----------
1 2 AqRUtekaopFQdCWBSA 2016-06-30
2 4 QEczvejfTrwFw 2016-09-23
3 4 heWMjFshkPZNyNWVQG 2017-02-19
4 4 EYybXtlaFHkAYeknhCBTBMusGAkx 2016-03-18
5 4 ZNdJBQxKKARlnExluZWkHMgoKY 2016-06-21
6 3 meASktCpcuyi 2016-10-01
7 4 FKgmf 2016-09-13
8 3 JZhk 2016-06-01
9 2 VCcKdlLnchrjctJrMXNb 2016-05-01
10 5 ddL 2016-11-27
11 4 wbX 2016-04-20
12 1 bTfa 2016-06-11
13 4 QP 2016-08-25
14 3 RgmIahPL 2016-03-04
15 2 vhinLUmwLwZjczYdrPbQvJxU 2016-12-05
where the number of children varies for each parent:
select fk_col1, count(*) from test2 group by fk_col1 order by fk_col1;
FK_COL1 COUNT(*)
---------- ----------
1 1
2 3
3 3
4 7
5 1
To insert a million rows instead, just change the bind variables.
If you needed more of a relationship between the children and parents, e.g. so the modified date is always after the created date, you can modify the query; for example:
insert into test2 (col2, fk_col1, valu, modified_date)
select *
from (
select level,
round(dbms_random.value(1, :parent_rows)) as fk_col1,
dbms_random.string('a', dbms_random.value(1, 30)),
trunc(sysdate) - dbms_random.value(1, 365) as modified_date
from dual
connect by level <= :parent_rows * :avg_children_per_parent
) t2
where not exists (
select null from test1 t1
where t1.col1 = t2.fk_col1 and t1.created_date > t2.modified_date
);
You may also want non-midnight times (I set everything to midnight via the trunc() call, based on the column name being 'date' not 'datetime'), or some column values null; so this might just be a starting point for you.

Resources