MERGE WITH ROW_NUMBER - oracle

I am trying to perform a merge to insert and update as the case may be in a table. However in table 1, I only want it to insert the unique values ​​of the tbl2.rfc field regardless if my other fields change, I only want to show the unique values ​​of said field. I am occupying the ROW_NUMBER function to bring only unique values, but I have not been able to add this function to my merge.
MERGE INTO B69_TBL1 tbl1
USING (SELECT T1.*, ROW_NUMBER() OVER (PARTITION BY T1.rfc ORDER BY T1.rfc DESC) ENUMERADO
FROM B69_TBL2 tbl2
WHEN MATCHED THEN
UPDATE SET
tbl1.id_tbl1 = tbl2.id_con,
tbl1.rfc = tbl2.rfc,
tbl1.rfc = tbl2.name_cont
WHEN NOT MATCHED THEN
INSERT (tbl1.id_tbl1,tbl1.tbl1tipo,tbl1.id_concentrado,tbl1.rfc,
tbl1.name_cont,tbl1.baja_logica,tbl1.last_update)
VALUES (id_tbl1autt.nextval,'1','1',tbl2.id_concentrado,tbl2.rfc,
tbl2.name_cont,'0', '11/05/2021')
) T1
WHERE ENUMERADO=1
AND RFC IS NOT NULL
The error it marks is 00907. 00000 - "missing right parenthesis"
because I had it like this and it fails precisely in the on.
MERGE INTO B69_TBL1 tbl1
USING (SELECT T1.*, ROW_NUMBER() OVER (PARTITION BY T1.rfc ORDER BY T1.rfc DESC) ENUMERADO
ON tbl1.rfc = tbl2.rfc
FROM B69_TBL2 tbl2 )
WHEN MATCHED THEN
UPDATE SET
tbl1.id_alert = tbl2.id_con,
tbl1.rfc = tbl2.rfc,
tbl1.rfc = tbl2.name_cont
WHEN NOT MATCHED THEN
INSERT (tbl1.id_alert,tbl1.alertype,tbl1.id_con,tbl1.rfc,
tbl1.name_cont,tbl1.baja_logica,tbl1.last_update)
VALUES (id_tbl1autt.nextval,'1','1',tbl2.id_con,tbl2.rfc,
tbl2.name_cont,'0', '11/05/2021')
) T1
WHERE ENUMERADO=1
AND RFC IS NOT NULL

Your ON clause is in the wrong place;
You had the tbl2 alias inside the USING query instead of following it;
You do not need the tbl1 aliases before the destination column names in the UPDATE or INSERT;
You are updating the rfc column twice (I removed the second instance);
There was a strange T1 alias after the insert that should be inside the USING query; and
You can use a DATE literal rather than a string.
There may be other errors, as I do not have your tables to test the query.
MERGE INTO B69_TBL1 tbl1
USING (
SELECT T1.*,
ROW_NUMBER() OVER (PARTITION BY T1.rfc ORDER BY T1.rfc DESC) AS ENUMERADO
FROM B69_TBL2 t1
) tbl2
ON tbl1.rfc = tbl2.rfc
WHEN MATCHED THEN
UPDATE SET
id_alert = tbl2.id_con,
rfc = tbl2.rfc
WHEN NOT MATCHED THEN
INSERT (
id_alert,
alertype,
id_con,
rfc,
name_cont,
baja_logica,
last_update
) VALUES (
id_tbl1autt.nextval,
'1',
'1',
tbl2.id_con,
tbl2.rfc,
tbl2.name_cont,
'0',
DATE '2021-05-11'
)
WHERE ENUMERADO=1
AND RFC IS NOT NULL

Related

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 -

Appropriate Join/Connection For This Scenario?

I want to join two tables together which I have done
I also want to join them based on a condition, where a particular column has a specific value, and I also have done this successfully. I used an inner join and a where clause so far.
However, for this result set, I want to further filter it by selecting ONLY the columns where a particular string appears more than once for a set of columns, eg;
employee_ID and CERTIFICATE
I'd like to group where employee_id has CERTIFICATE count > 2. This is after I have joined the tables together using a where clause.
I am perhaps thinking of using a subquery in my WHERE clause (which is the 3rd line that is also last)
For further clarification, I want to display only employees who have a certificate count greater than 2. By certificate, I am referencing a table with a string 'Certificate' under a column 'Skill'. In other words, select only columns where the string 'Certificate' appears TWICE for a particular employee ID.
To get just the employee ids:
SELECT t1.employee_id
FROM table1 t1
INNER JOIN
table2 t2
ON ( t1.col1 = t2.col1 )
GROUP BY t1.employee_id
HAVING COUNT( CASE t2.skill WHEN 'CERTIFICATE' THEN 1 END ) > 1
Or, to get all the columns:
SELECT *
FROM (
SELECT t1.*,
t2.*,
COUNT( CASE t2.skill WHEN 'CERTIFICATE' THEN 1 END )
OVER ( PARTITION BY t1.employee_id )
AS num_certificate
FROM table1 t1
INNER JOIN
table2 t2
ON ( t1.col1 = t2.col1 )
)
WHERE num_certificate > 1

How to overcome IN clause issue in oracle

i am updating a table called table2 from table1 on column name Active_status.But the problem the result contains more than 1000.How to overcome this issue.I am using this query in a c# application.I heard we can use temp table ,if so how can i use or is there any better way.followingis my query
UPDATE table2
SET Active_status = 'N',
MODIFIED_DATE = SYSDATE
WHERE t2_SLNO IN
(SELECT t2_SLNO
FROM table2
LEFT JOIN table1
ON table2.t2_NAME = table1.t1_NAME
WHERE table1.t1_NAME IS NULL
)
AND Active_status <> 'N';
EDIT
I am doing this modification everday.By using a merge.But there are situations like table2 contain some dead data which i want to make status 'N' since the source of table1 and table2 is a remote database which is exposed by some other team.So thought of to do this work after my existing MERGE operation
As #aleksej suggested, you can use MERGE:
MERGE INTO table1 t
USING table2 s
ON(t.t1_NAME = s.t2_NAME)
WHEN NOT MATCHED UPDATE SET Active_status = 'N',
MODIFIED_DATE = sysdate
WHERE t.Active_status <> 'N'
And you can change you update to work like this:
UPDATE table1 t
SET t.Active_status = 'N',
t.MODIFIED_DATE = sysdate
WHERE NOT EXISTS(SELECT 1 FROM table2 s
WHERE t.t1_NAME = s.t2_NAME)
AND t.Active_status <> 'N'

Oracle join rows in order where order is defined differently on each table

I have two tables
TABLE_A with columns project_id, id and load_date
and TABLE_B with columns project_id, delete_flag and delete_date
where TABLE_A.load_date is a new column and I want to populate it based on TABLE_B.delete_date for historic data. Basically, a file has been repeatedly loaded into the system and historically we didn't keep track of when it was loaded. However, each time the file is re-loaded, the previous version of it is updated in TABLE_B with a delete_date (i.e. a soft delete). The previous version just stays in TABLE_A without any changes.
I would like to populate TABLE_A.load_date based on matching projects in TABLE_B. The oldest row in TABLE_A (smallest TABLE_A.id) matches the oldest row in TABLE_B (oldest delete_date), etc. So the rows should match up if you keep picking the next one in order from each table. But I don't know how to turn that into an Oracle statement. What I've got so far is this which doesn't deal with matching on row order:
MERGE INTO TABLE_A a
USING
(
SELECT PROJECT_ID, DELETE_DATE
FROM TABLE_B
WHERE DELETE_FLAG = 'Y'
ORDER BY DELETE_DATE ASC
) b ON (a.PROJECT_ID = b.PROJECT_ID)
WHEN MATCHED THEN UPDATE
SET a.LOAD_DATE = p.DELETE_DATE;
This merge should do the work, as far as I properly understood your criteria:
merge into table_a ta
using (
select pid project_id, id, delete_date
from (
select project_id pid, id,
row_number() over (partition by project_id order by id) rn
from table_a) a
join (
select project_id pid, delete_date,
row_number() over (partition by project_id order by delete_date ) rn
from table_b
where delete_flag='Y') b using (pid, rn) ) tb
on (ta.project_id = tb.project_id and ta.id = tb.id)
when matched then update
set ta.load_date = tb.delete_date

Error on Partition over row number

I am creating a sub-query to select distinct entries on a certain column, DIS_COL, then return all other columns for those distinct entries, arbitrarily selecting the first row.
To do this I'm creating a sub-query that selects only first rows using over - partition by, then selecting from that sub-query.
There is an error with my code however; "ORA-00923: FROM keyword not found where expected".
My code is below:
select *
from (
select *,
row_number() over (partition by DIS_COL order by COL_2) as row_number --ORDER BY FIELD DETERMINES WHICH ROW IS THE FIRST ROW AND THUS WHICH ONE IS SELECTED.
from MY_TABLE
) as rows
where row_number = 1
AND CRITERIA_COL = 'CRIT_1'
OR CRITERIA_COL_2 = 'CRIT_2';
How can I correct my code to achieve the desired result?
I am working on an Oracle database.
Remove as rows. It is not proper syntax for the table/query alias. It is syntax for column alias.
select *
from (
select T.*,
row_number() over (partition by DIS_COL order by COL_2) as row_number --ORDER BY FIELD DETERMINES WHICH ROW IS THE FIRST ROW AND THUS WHICH ONE IS SELECTED.
from MY_TABLE t
)
where row_number = 1
AND (CRITERIA_COL = 'CRIT_1'
OR CRITERIA_COL_2 = 'CRIT_2');
It's not the ROW_NUMBER, it's the *, Add an alias to the subquery:
select *
from (
select T.*, -- here
row_number() over (partition by DIS_COL order by COL_2) as row_number --ORDER BY FIELD DETERMINES WHICH ROW IS THE FIRST ROW AND THUS WHICH ONE IS SELECTED.
from MY_TABLE
)T as rows -- and here
where row_number = 1
AND CRITERIA_COL = 'CRIT_1'
OR CRITERIA_COL_2 = 'CRIT_2';

Resources