Oracle - Deleting or inserting rows via cursor - oracle

Apologies in advance, I am occasional Oracle user. I have put together a lookup table used by various functions/procedures and need to keep refresh this once a day with rows that either need removing or inserting. I have put together the following simply queries that return the columns against which I can determine the required action. Once I have returned my deletion data, I then need to delete from table A all records where the site_id and zone_ids match. I cant figure out the best way to achieve this, I have thought about running the select statements as cursors, but am not sure how I then delete the rows from table A using the site_id and zone_id from each record returned.
Query That returns records to be deleted from Table_A
SELECT site_id,zone_id,upper(ebts_switch_name)
FROM Table_A
minus
(SELECT site_id,zone_id, upper(ebts_switch_name)
FROM Table_B#remote_db
UNION
SELECT site_id,zone_id,upper(ebts_switch_name)
FROM Table_C);
Query That returns records to be Inserted into Table_A
SELECT cluster_id, site_id,zone_id, upper(trigram),upper(ebts_switch_name)
FROM Table_B#remote_db
WHERE site_id is NOT NULL
minus
SELECT cluster_name,site_id,zone_id,upper(trigram),upper(ebts_switch_name)
FROM Table_A

You can use your statements directly in the manner shown below:
DELETE FROM TABLE_A
WHERE (SITE_ID, ZONE_ID, UPPER(EBTS_SWITCH_NAME)) IN
(SELECT site_id, zone_id, upper(ebts_switch_name)
FROM Table_A
minus
(SELECT site_id, zone_id, upper(ebts_switch_name)
FROM Table_B#remote_db
UNION
SELECT site_id, zone_id, upper(ebts_switch_name)
FROM Table_C));
INSERT INTO TABLE_A (CLUSTER_NAME, SITE_ID, ZONE_ID, TRIGRAM, EBTS_SWITCH_NAME)
SELECT cluster_id, site_id, zone_id, upper(trigram), upper(ebts_switch_name)
FROM Table_B#remote_db
WHERE site_id is NOT NULL
minus
SELECT cluster_name, site_id, zone_id, upper(trigram), upper(ebts_switch_name)
FROM Table_A;
Best of luck.

I can't understand what do you mean by first query, cause it's almost same as
SELECT *
FROM table_a
MINUS
SELECT *
FROM table_a
means empty record set.
But generally, use DELETE syntax
DELETE
FROM table_a
WHERE (col1, col2) IN (SELECT col1, col2
FROM table_b);
And INSERT syntax
INSERT INTO table_a (col1, col2)
SELECT col1, col2
FROM table_b;

Related

SELECT * with UNION ALL in Oracle

I have a requirement wherein I intend to fetch duplicate records from Oracle DB so as to mimic scenarios wherein we have duplicate records.
So, I decided on using union_all.
select column1 from tbl where rowid in (select rowid from tbl where rownum<=5)
union all
select column1 from tbl where rowid in (select rowid from tbl where rownum<=5)
order by column1;
--works
However, when trying to fetch all the columns,
select * from tbl where rowid in (select rowid from tbl where rownum<=5)
union all
select * from tbl where rowid in (select rowid from tbl where rownum<=5)
order by column1;
-- Doesn't work. Invalid identifier
Any suggestion on what is wrong here would be great help.
You can try:
select * from (
select * from tbl where rowid in (select rowid from tbl where rownum<=5)
union all
select * from tbl where rowid in (select rowid from tbl where rownum<=5)
)
order by column1;
Or explicitly list the selected columns instead of using select *:
Sorting Query Results
For compound queries containing set operators UNION, INTERSECT,
MINUS, or UNION ALL, the ORDER BY clause must specify positions or
aliases rather than explicit expressions. Also, the ORDER BY clause
can appear only in the last component query. The ORDER BY clause
orders all rows returned by the entire compound query.

Reduce overload on pl/sql

I have a requirement to do matching of few attributes one by one. I'm looking to avoid multiple select statements. Below is the example.
Table1
Col1|Price|Brand|size
-----------------------
A|10$|BRAND1|SIZE1
B|10$|BRAND1|SIZE1
C|30$|BRAND2|SIZE2
D|40$|BRAND2|SIZE4
Table2
Col1|Col2|Col3
--------------
B|XYZ|PQR
C|ZZZ|YYY
Table3
Col1|COL2|COL3|LIKECOL1|Price|brand|size
-----------------------------------------
B|XYZ|PQR|A|10$|BRAND1|SIZE1
C|ZZZ|YYY|D|NULL|BRAND2|NULL
In table3, I need to insert data from table2 by checking below conditions.
Find a match for record in table2, if Brand and size, Price match
If no match found, then try just Brand, Size
still no match found, try brand only
In the above example, for the first record in table2, found match with all the 3 attributes and so inserted into table3 and second record, record 'D' is matching but only 'Brand'.
All I can think of is writing 3 different insert statements like below into an oracle pl/sql block.
insert into table3
select from tab2
where all 3 attributes are matching;
insert into table3
select from tab2
where brand and price are matching
and not exists in table3 (not exists is to avoid
inserting the same record which was already
inserted with all 3 attributes matched);
insert into table3
select from tab2
where Brand is matching and not exists in table3;
Can anyone please suggest a better way to achieve it in any better way avoiding multiple times selecting from table2.
This is a case for OUTER APPLY.
OUTER APPLY is a type of lateral join that allows you join on dynamic views that refer to tables appearing earlier in your FROM clause. With that ability, you can define a dynamic view that finds all the matches, sorts them by the pecking order you've specified, and then use FETCH FIRST 1 ROW ONLY to only include the 1st one in the results.
Using OUTER APPLY means that if there is no match, you will still get the table B record -- just with all the match columns null. If you don't want that, you can change OUTER APPLY to CROSS APPLY.
Here is a working example (with step by step comments), shamelessly stealing the table creation scripts from Michael Piankov's answer:
create table Table1 (Col1,Price,Brand,size1)
as select 'A','10','BRAND1','SIZE1' from dual union all
select 'B','10','BRAND1','SIZE1' from dual union all
select 'C','30','BRAND2','SIZE2' from dual union all
select 'D','40','BRAND2','SIZE4'from dual
create table Table2(Col1,Col2,Col3)
as select 'B','XYZ','PQR' from dual union all
select'C','ZZZ','YYY' from dual;
-- INSERT INTO table3
SELECT t2.col1, t2.col2, t2.col3,
t1.col1 likecol1,
decode(t1.price,t1_template.price,t1_template.price, null) price,
decode(t1.brand,t1_template.brand,t1_template.brand, null) brand,
decode(t1.size1,t1_template.size1,t1_template.size1, null) size1
FROM
-- Start with table2
table2 t2
-- Get the row from table1 matching on col1... this is our search template
inner join table1 t1_template on
t1_template.col1 = t2.col1
-- Get the best match from table1 for our search
-- template, excluding the search template itself
outer apply (
SELECT * FROM table1 t1
WHERE 1=1
-- Exclude search template itself
and t1.col1 != t2.col1
-- All matches include BRAND
and t1.brand = t1_template.brand
-- order by match strength based on price and size
order by case when t1.price = t1_template.price and t1.size1 = t1_template.size1 THEN 1
when t1.size1 = t1_template.size1 THEN 2
else 3 END
-- Only get the best match for each row in T2
FETCH FIRST 1 ROW ONLY) t1;
Unfortunately is not clear what do you mean when say match. What is you expectation if there is more then one match?
Should it be only first matching or it will generate all available pairs?
Regarding you question how to avoid multiple inserts there is more then one way:
You could use multitable insert with INSERT first and condition.
You could join table1 to self and get all pairs and filter results in where condition
You could use analytical function
I suppose there is another ways. But why you would like to avoid 3 simple inserts. Its easy to read and maintain. And may be
There is example with analytical function next:
create table Table1 (Col1,Price,Brand,size1)
as select 'A','10','BRAND1','SIZE1' from dual union all
select 'B','10','BRAND1','SIZE1' from dual union all
select 'C','30','BRAND2','SIZE2' from dual union all
select 'D','40','BRAND2','SIZE4'from dual
create table Table2(Col1,Col2,Col3)
as select 'B','XYZ','PQR' from dual union all
select'C','ZZZ','YYY' from dual
with s as (
select Col1,Price,Brand,size1,
count(*) over(partition by Price,Brand,size1 ) as match3,
count(*) over(partition by Price,Brand ) as match2,
count(*) over(partition by Brand ) as match1,
lead(Col1) over(partition by Price,Brand,size1 order by Col1) as like3,
lead(Col1) over(partition by Price,Brand order by Col1) as like2,
lead(Col1) over(partition by Brand order by Col1) as like1,
lag(Col1) over(partition by Price,Brand,size1 order by Col1) as like_desc3,
lag(Col1) over(partition by Price,Brand order by Col1) as like_desc2,
lag(Col1) over(partition by Brand order by Col1) as like_desc1
from Table1 t )
select t.Col1,t.Col2,t.Col3, coalesce(s.like3, like_desc3, s.like1, like_desc1, s.like1, like_desc1),
case when match3 > 1 then size1 end as size1,
case when match1 > 1 then Brand end as Brand,
case when match2 > 1 then Price end as Price
from table2 t
left join s on s.Col1 = t.Col1
COL1 COL2 COL3 LIKE_COL SIZE1 BRAND PRICE
B XYZ PQR A SIZE1 BRAND1 10
C ZZZ YYY D - BRAND2 -

Column name become invalid after referred as result of aggregate function MIN()

SELECT cust_detl.*,
MIN(CREATION_TIMESTAMP) OVER (PARTITION BY CUST_ID) AS MIN_TIMESTAMP
FROM CUST_DETAILS cust_detl
WHERE CREATION_TIMESTAMP=MIN_TIMESTAMP;
Above query select all columns from table CUST_DETAILS with oldest value inCREATION_TIMESTAMP column.
Any idea why MIN_TIMESTAMP encounter as an invalid identifier?
These are the columns that should display:
SELECT
CUSTOMER_DTL_SEQ.nextval,
CUST_ID
CUS_REF_ID
CUST_NAME
CUST_ADDRESS
CREATION_TIMESTAMP
FROM
(
CUSTOMER_DTL_SEQ.nextval,
cust_detl.CUST_ID,
cust_detl.CUST_REF_ID,
cust_detl.CUST_NAME,
cust_detl.CUST_ADDRESS,
cust_detl.CREATION_TIMESTAMP,
MIN(CREATION_TIMESTAMP) OVER (PARTITION BY CUST_ID) AS min_timestamp
FROM cust_details cust_detl
)
WHERE CREATION_TIMESTAMP = min_timestamp;
I would need to select CREATION_TIMESTAMP column as well, only those columns with minimum timestamp will be selected. The problem is the sequence with nextval is not allowed. I would need sequence in the query as this statment is going to use for INSERT later SELECT...INSERT INTO
The PK need to be incremented.
The column name is not yet valid, data is filtered first with the where condition and then on the filtered data, select statement works. You need to put it in sub query before you can use it.
SELECT * FROM
(SELECT cust_detl.*,
MIN(CREATION_TIMESTAMP) OVER (PARTITION BY CUST_ID) AS MIN_TIMESTAMP
FROM CUST_DETAILS cust_detl)
WHERE CREATION_TIMESTAMP=MIN_TIMESTAMP;
UPDATE: I don't know what list of columns you have in your table, but if you need only specific columns, then the query goes like this(assuming you need only columns cust_id, column1, column2 and column3 in select list)
SELECT cust_id,
column1,
column2,
column3
FROM (SELECT cust_detl.cust_id,
cust_detl.column1,
cust_detl.column2,
cust_detl.column3,
cust_detl.creation_timestamp,
MIN(creation_timestamp) over(PARTITION BY cust_id) AS min_timestamp
FROM cust_details cust_detl)
WHERE creation_timestamp = min_timestamp;
If you still don't get your solution, the post the list of columns from the table and the expected output.
Update2 : Fetch the cursor in the outer query, this query should work fine.
SELECT customer_dtl_seq.nextval,
cust_id,
cus_ref_id,
cust_name,
cust_address,
creation_timestamp
FROM (SELECT cust_detl.cust_id,
cust_detl.cust_ref_id,
cust_detl.cust_name,
cust_detl.cust_address,
cust_detl.creation_timestamp,
MIN(creation_timestamp) over(PARTITION BY cust_id) AS min_timestamp
FROM cust_details cust_detl)
WHERE creation_timestamp = min_timestamp;

Add the virtual column with table while select * from

Want to add column to Table A
Table_A
ID,
ZONE
I want to add ACCNT HAVING VALUE NEW
Just in SELECT statement:
SELECT A.*, B.* FROM TABLE_A A , (SELECT "NEW" AS ACCNT FROM DUAL) B
You dont need a select statement to add a column with constant name, just write query as below:
SELECT *, 'New' as Zone
FROM TABLE_A
If you need to rename a particular column, then you could do it as follows:
SELECT id, accnt as New
FROM TABLE_A
Using below Query i am able to do the above task .
SELECT A.*, B.* FROM TABLE_A A , (SELECT "NEW" AS ACCNT FROM DUAL) B
Very old thread, but giving answer to close it.
You can directly use it in your query as following:
SELECT A.*, 'NEW' as acct FROM TABLE_A A
Cheers!!

Oracle Equivalent to MySQL INSERT IGNORE?

I need to update a query so that it checks that a duplicate entry does not exist before insertion. In MySQL I can just use INSERT IGNORE so that if a duplicate record is found it just skips the insert, but I can't seem to find an equivalent option for Oracle. Any suggestions?
If you're on 11g you can use the hint IGNORE_ROW_ON_DUPKEY_INDEX:
SQL> create table my_table(a number, constraint my_table_pk primary key (a));
Table created.
SQL> insert /*+ ignore_row_on_dupkey_index(my_table, my_table_pk) */
2 into my_table
3 select 1 from dual
4 union all
5 select 1 from dual;
1 row created.
Check out the MERGE statement. This should do what you want - it's the WHEN NOT MATCHED clause that will do this.
Do to Oracle's lack of support for a true VALUES() clause the syntax for a single record with fixed values is pretty clumsy though:
MERGE INTO your_table yt
USING (
SELECT 42 as the_pk_value,
'some_value' as some_column
FROM dual
) t on (yt.pk = t.the_pke_value)
WHEN NOT MATCHED THEN
INSERT (pk, the_column)
VALUES (t.the_pk_value, t.some_column);
A different approach (if you are e.g. doing bulk loading from a different table) is to use the "Error logging" facility of Oracle. The statement would look like this:
INSERT INTO your_table (col1, col2, col3)
SELECT c1, c2, c3
FROM staging_table
LOG ERRORS INTO errlog ('some comment') REJECT LIMIT UNLIMITED;
Afterwards all rows that would have thrown an error are available in the table errlog. You need to create that errlog table (or whatever name you choose) manually before running the insert using DBMS_ERRLOG.CREATE_ERROR_LOG.
See the manual for details
I don't think there is but to save time you can attempt the insert and ignore the inevitable error:
begin
insert into table_a( col1, col2, col3 )
values ( 1, 2, 3 );
exception when dup_val_on_index then
null;
end;
/
This will only ignore exceptions raised specifically by duplicate primary key or unique key constraints; everything else will be raised as normal.
If you don't want to do this then you have to select from the table first, which isn't really that efficient.
Another variant
Insert into my_table (student_id, group_id)
select distinct p.studentid, g.groupid
from person p, group g
where NOT EXISTS (select 1
from my_table a
where a.student_id = p.studentid
and a.group_id = g.groupid)
or you could do
Insert into my_table (student_id, group_id)
select distinct p.studentid, g.groupid
from person p, group g
MINUS
select student_id, group_id
from my_table
A simple solution
insert into t1
select from t2
where not exists
(select 1 from t1 where t1.id= t2.id)
This one isn't mine, but came in really handy when using sqlloader:
create a view that points to your table:
CREATE OR REPLACE VIEW test_view
AS SELECT * FROM test_tab
create the trigger:
CREATE OR REPLACE TRIGGER test_trig
INSTEAD OF INSERT ON test_view
FOR EACH ROW
BEGIN
INSERT INTO test_tab VALUES
(:NEW.id, :NEW.name);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN NULL;
END test_trig;
and in the ctl file, insert into the view instead:
OPTIONS(ERRORS=0)
LOAD DATA
INFILE 'file_with_duplicates.csv'
INTO TABLE test_view
FIELDS TERMINATED BY ','
(id, field1)
How about simply adding an index with whatever fields you need to check for dupes on and say it must be unique? Saves a read check.
yet another "where not exists"-variant using dual...
insert into t1(id, unique_name)
select t1_seq.nextval, 'Franz-Xaver' from dual
where not exists (select 1 from t1 where unique_name = 'Franz-Xaver');

Resources