Any efficient solution for that - oracle

declare
cursor cur1 is select * from address where aid in
(select Min(aid) from address group by
country,state,city,street_name,locality,house_no);
cursor cur2 is select * from address;
cur1_aid address.aid%type;
cur1_country address.country%type;
cur1_city address.city%type;
cur1_state address.state%type;
cur1_streetAddress address.street_name%type;
cur1_locality address.locality%type;
cur1_houseNo address.house_no%type;
cur2_aid address.aid%type;
cur2_country address.country%type;
cur2_city address.city%type;
cur2_state address.state%type;
cur2_streetAddress address.street_name%type;
cur2_locality address.locality%type;
cur2_houseNo address.house_no%type;
begin
open cur1;
loop
fetch cur1 into cur1_aid,cur1_country,cur1_state,cur1_city,cur1_streetAddress,cur1_locality,cur1_houseNo;
exit when cur1%NOTFOUND;
open cur2;
loop
fetch cur2 into cur2_aid,cur2_country,cur2_state,cur2_city,cur2_streetAddress,cur2_locality,cur2_houseNo;
exit when cur2%NOTFOUND;
if(cur1_country=cur2_country) and (cur1_state=cur2_state) and (cur1_city=cur2_city) and (cur1_streetAddress=cur2_streetAddress) and (cur1_locality=cur2_locality) and (cur1_houseNo=cur2_houseNo) then
if (cur1_aid!=cur2_aid) then
update employee_add set aid=cur1_aid where aid=cur2_aid;
delete address where aid=cur2_aid;
end if;
end if;
end loop;
close cur2;
end loop;
close cur1;
DELETE FROM employee_add a
WHERE ROWID > (SELECT MIN(ROWID) FROM employee_add b
WHERE b.eid=a.eid and b.aid=a.aid
);
end;
/
I have three table Employee(eid,ename) ,Address(aid,country,state,city,streetaddress,locality,houseNo) and a relationship table (M2M) MANY TO MANY TABLE employee_add(eid,aid),
I want to remove duplicates from address table and employee_add table without data loss

Assuming this is a one time de-duplication you could:
Create a new temporary set of eid <-> aid relationships based on the current address attached to an employee and always pick the min address record with matching data (this is what you are doing above)
Delete existing eid <-> aid relationships
Insert new relationships from step 1, drop step 1 data
Delete addresses that no longer have any employee attached
Something like this (untested as you did not provide any DDL or DML to create a working example from):
-- Step 1
CREATE TABLE employee_add_new AS
SELECT ea.eid,
(SELECT MIN(a2.aid)
FROM address a2
WHERE a2.country = a.country
AND a2.state = a.state
AND a2.city = a.city
AND a2.street_name = a.street_name
AND a2.locality = a.locality
AND a2.house_no = a.house_no) AS aid
FROM employee_add ea
INNER JOIN address a
ON a.aid = ea.aid;
-- Step 2
TRUNCATE TABLE employee_add;
-- Step 3
INSERT INTO employee_add
(eid,
aid)
SELECT eid,
aid
FROM employee_add_new;
DROP TABLE employee_add_new;
-- Step 4
DELETE FROM address a
WHERE NOT EXISTS (SELECT NULL
FROM employee_add ea
WHERE ea.aid = a.aid);
You could also change step 2 and 3 to drop the existing employee_add table and rename employee_add_new to employee_add, but I have no idea what your table structure looks like (columns, FKs, indexes, etc).

Related

ora-01422 Fetch Size

I am getting the ORA-1422 error. Here is the error:
Connecting to the database Quantum Train.
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "TRAIN.UPDATE_MASTER_TO_NULL", line 26
ORA-06512: at line 2
Process exited.
Disconnecting from the database Quantum Train.
PROCEDURE UPDATE_MASTER_TO_NULL
is
-- This gets the pnm_auto_keys for the records in the warehouse and location with the specified manufacturer
Cursor Csr is
Select pnm.loc_auto_key
from parts_master pnm join warehouse whs on pnm.whs_auto_key = whs.whs_auto_key
where whs.warehouse_code = 'SHOP' and
pnm.loc_auto_key <> '39';
-- pnm.loc_auto_key <> '39' and
-- pnm.loc_auto_key <> '26' and
-- pnm.loc_auto_key <> '14';
loc_key integer;
Begin
For i in Csr Loop
-- Now get the loc_auto_key for your new location
Select loc2.loc_auto_key
into loc_key
From PARTS_MASTER loc2
Where loc2.loc_auto_key is null;
-- Assigne the new loc_auto_key to the selected record.
Update parts_master pnm2
Set pnm2.loc_auto_key = loc_key
where pnm2.loc_auto_key = i.loc_auto_key;
End Loop;
Commit;
End UPDATE_MASTER_TO_NULL;
Thanks,
Jeff
The problem is that this select statement is returning more than one row
Select loc2.loc_auto_key
into loc_key
From PARTS_MASTER loc2
Where loc2.loc_auto_key is null;
If you modify that query to return only one row, then your code should work.
Also, the entire procedure can be replaced and improved in efficiency by using a single update statement similar to this:
UPDATE parts_master pm
SET pm.loc_auto_key =
(SELECT *
FROM (SELECT pm2.loc_auto_key
FROM PARTS_MASTER pm2
WHERE pm2.loc_auto_key IS NULL)
WHERE ROWNUM = 1)
WHERE pm.whs_auto_key IN (SELECT w.whs_auto_key
FROM warehouse w
WHERE w.warehouse_code = 'SHOP')
AND pm.loc_auto_key <> '39';

After Insert Trigger Oracle - How to ensure only unique record insertion?

I've a question on an after Insert trigger in Oracle 12c.
I have a Dimension table in which DML operations occur via a UI. Whenever there is an Insert or Update, I want to perform Insert or Update in another table, which is called as Rating table.
But the Dimension table is at a lower grain and Rating table is at a higher grain, so I want to insert only unique records in the Rating table.
Is that possible? How?
Thanks for your help in advance.
Existing Trigger Code is as below:-
create or replace TRIGGER RFJVBASE.KPI_COCKPIT_ATTR_AFTER_INS2
AFTER INSERT ON RFJVBASE.DIM_KPI_COCKPIT_ATTRS FOR EACH ROW
DECLARE
v_fiscal_week_start varchar2(50);
v_fiscal_week_month_end varchar2(50);
v_fiscal_week_qtr_end varchar2(50);
v_fiscal_week_semi_end varchar2(50);
v_fiscal_week_year_end varchar2(50);
BEGIN
select rfjvbase.run_dates('1-Jan-1900') into v_fiscal_week_start from dual;
select fiscal_month_end_week_name into v_fiscal_week_month_end from rfjvstg.stg_cockpit_business_dt;
select fiscal_qtr_end_week_name into v_fiscal_week_qtr_end from rfjvstg.stg_cockpit_business_dt;
select fiscal_semi_end_week_name into v_fiscal_week_semi_end from rfjvstg.stg_cockpit_business_dt;
select fiscal_year_end_week_name into v_fiscal_week_year_end from rfjvstg.stg_cockpit_business_dt;
/*Insert record into FCT table*/
IF (:new.KPI_TYPE = 'Quantitative') THEN
INSERT INTO RFJVBASE.FCT_KPI_COCKPIT_RATING
(
PROCESS_KEY_FCT,CURRENT_PROCESS_RATING_KEY,PROCESS_RATING_KEY,CREATED_FISCAL_WEEK,VALID_THROUGH_FISCAL_WEEK,PROCESS_NAME,PROCESS_GROUP,PROCESS,PROCESS_INDICATOR_CLASS,PROCESS_INDICATOR_SEQUENCE,
PERFORMANCE_INDICATOR_NAME,PERF_IND_SUB_LEVEL,UNIT,KPI_TYPE,ORG_UNIT,TOLERANCE_DIRECTION, TOLERANCE,TARGET,TARGET_ENABLE_FLAG,CREATED_DT,CREATED_BY,LAST_UPDATE_DT,LAST_UPDATED_BY,AUDIT_KEY
)
VALUES
(
FCT_KPI_COCKPIT_RATING_SEQ.NEXTVAL,
:new.PROCESS_KEY,
:new.PROCESS_KEY,
v_fiscal_week_start,
case when :new.ANNUAL_FREQUENCY = 1 then v_fiscal_week_year_end
when :new.ANNUAL_FREQUENCY = 2 then v_fiscal_week_semi_end
when :new.ANNUAL_FREQUENCY = 4 then v_fiscal_week_qtr_end
when :new.ANNUAL_FREQUENCY = 12 then v_fiscal_week_month_end
end,
:new.PROCESS_NAME,
:new.PROCESS_GROUP,
:new.PROCESS,
:new.PROCESS_INDICATOR_CLASS,
:new.PROCESS_INDICATOR_SEQUENCE,
:new.PERFORMANCE_INDICATOR_NAME,
:new.PERF_IND_SUB_LEVEL,
:new.UNIT,
:new.KPI_TYPE,
:new.ORG_UNIT,
:new.TOLERANCE_DIRECTION,
:new.TOLERANCE,
:new.TARGET,
:new.TARGET_ENABLE_FLAG,
SYSDATE,
:new.USERNAME,
SYSDATE,
:new.USERNAME,
:new.AUDIT_KEY);
ELSE IF (:new.KPI_TYPE = 'Qualitative') THEN
INSERT INTO RFJVBASE.FCT_KPI_COCKPIT_RATING
(
PROCESS_KEY_FCT,CURRENT_PROCESS_RATING_KEY,PROCESS_RATING_KEY,CREATED_FISCAL_WEEK,VALID_THROUGH_FISCAL_WEEK,PROCESS_NAME,PROCESS_GROUP,PROCESS,PROCESS_INDICATOR_CLASS,PROCESS_INDICATOR_SEQUENCE,
PERFORMANCE_INDICATOR_NAME,PERF_IND_SUB_LEVEL,UNIT,KPI_TYPE,ORG_UNIT,TOLERANCE_DIRECTION, TOLERANCE,TARGET_ENABLE_FLAG,CREATED_DT,CREATED_BY,LAST_UPDATE_DT,LAST_UPDATED_BY,AUDIT_KEY
)
VALUES
(
FCT_KPI_COCKPIT_RATING_SEQ.NEXTVAL,
:new.PROCESS_KEY,
:new.PROCESS_KEY,
v_fiscal_week_start,
case when :new.ANNUAL_FREQUENCY = 1 then v_fiscal_week_year_end
when :new.ANNUAL_FREQUENCY = 2 then v_fiscal_week_semi_end
when :new.ANNUAL_FREQUENCY = 4 then v_fiscal_week_qtr_end
when :new.ANNUAL_FREQUENCY = 12 then v_fiscal_week_month_end
end,
:new.PROCESS_NAME,
:new.PROCESS_GROUP,
:new.PROCESS,
:new.PROCESS_INDICATOR_CLASS,
:new.PROCESS_INDICATOR_SEQUENCE,
:new.PERFORMANCE_INDICATOR_NAME,
:new.PERF_IND_SUB_LEVEL,
:new.UNIT,
:new.KPI_TYPE,
:new.ORG_UNIT,
:new.TOLERANCE_DIRECTION,
:new.TOLERANCE,
:new.TARGET_ENABLE_FLAG,
SYSDATE,
:new.USERNAME,
SYSDATE,
:new.USERNAME,
:new.AUDIT_KEY);
END IF;
END IF;
END;
/
I want to insert only unique records in the Rating table.
Create unique index on the Rating table which won't allow duplicates to be inserted. Let the database do the dirty job, you just sit & relax.

Oracle, ROWNUM=1 with FOR UPDATE clause?

My statement:
SELECT ROW_ID DATA_T WHERE CITY_ID=2000 AND IS_FREE=0 AND ROWNUM = 1
is used to retrieve the first row for a db table that has many entries with CITY_ID equal to 2000.
The ROW_ID that is returned is then used in an UPDATE statement in order to use this row and set IS_FREE=1.
That worked very well until two threads called the SELECT statement and the got the same ROW_ID obviously... That is my problem in a few words.
I am using ORACLE DB (12.x)
How do I resolve the problem? Can I use FOR UPDATE in this case?
I want every "client" somehow to get a different row or at least lock on of them
Something like this
function get_row_id return number
as
cursor cur_upd is
SELECT ROW_ID FROM TB WHERE CITY_ID=2000 AND IS_FREE=0 AND ROWNUM = 1
FOR UPDATE SKIP LOCKED;
begin
for get_cur_upd in cur_upd
loop
update TB
set IS_FREE = 1
where ROW_ID = get_cur_upd.ROW_ID;
commit work;
return get_cur_upd.ROW_ID;
end loop;
return null;
end;
commit or not after update depends on your logic.
Also you can return row_id without update&commit and do it later outside func.

PL/SQL Trigger Variable Problems

I am relatively new to PL/SQL and i am trying to create a trigger that will alert me after an UPDATE on a table Review. When it is updated I want to ge the username(User table), score(Review Table), and product name (Product Table) and print them out:
This is what I have so far:
three tables:
Review: score, userid,pid, rid
Users: userid,uname
Product: pid,pname
So Review can reference the other tables with forigen keys.
create or replace trigger userNameTrigger
after insert on review
for each row
declare
x varchar(256);
y varchar(256);
z varchar(256);
begin
select uname into x , pname into y , score into z
from review r , product p , users u
where r.pid = p.pid and r.userid = u.userid and r.rid =new.rid;
dbms_output.put_line('user: '|| X||'entered a new review for Product: '|| Y || 'with a review score of: '|| Z);
end;
The problem I am having is I cannot seem to figure out how to store the selected fields into the variables and output it correctly.
DDL:
Create Table Review
(
score varchar2(100)
, userid varchar2(100)
, pid varchar2(100)
, rid varchar2(100)
);
Create Table Users
(
userid varchar2(100)
, uname varchar2(100)
);
Create Table Product
(
pid varchar2(100)
, pname varchar2(100)
);
The first problem I can see is that you're missing a colon when you refer to new.rid. The second is that you're accessing the review table inside a row-level trigger on that same table, which will give you a mutating table error at some point; but you don't need to as all the data from the inserted row is in the new pseudorow.
create or replace trigger userNameTrigger
after insert on review
for each row
declare
l_uname users.uname%type;
l_pname product.pname%type;
begin
select u.uname into l_uname
from users u
where u.userid = :new.userid;
select p.pname
into l_pname
from product
where p.pid = :new.pid;
dbms_output.put_line('user '|| l_uname
|| ' entered a new review for product ' || l_pname
|| ' with a review score of '|| :new.score);
end;
The bigger problem is that the only person who could see the message is the user inserting tow row, which seems a bit pointless; and they would have to have output enabled in their session to see it.
If you're trying to log that so someone else can see it then store it in a table or write it to a file. As the review table can be queried anyway it seems a bit redundant though.
Having all your table columns as strings is also not good - don't store numeric values (e.g. scores, and probably the ID fields) or dates as strings, use the correct data types. It will save you a lot of pain later. You also don't seem to have any referential integrity (primary/foreign key) constraints - so you can review a product that doesn't exist, for instance, which will cause a no-data-found exception in the trigger.
It makes really no sense to use a trigger to notify themselves about changed rows. If you insert new rows into the table, then you have all info about them. Why not something like the block below instead a trigger:
create table reviews as select 0 as rid, 0 as userid, 0 as score, 0 as pid from dual where 1=0;
create table users as select 101 as userid, cast('nobody' as varchar2(100)) as uname from dual;
create table products as select 1001 as pid, cast('prod 1001' as varchar2(100)) as pname from dual;
<<my>>declare newreview reviews%rowtype; uname users.uname%type; pname products.pname%type; begin
insert into reviews values(1,101,10,1001) returning rid,userid,score,pid into newreview;
select uname, pname into my.uname, my.pname
from users u natural join products p
where u.userid = newreview.userid and p.pid = newreview.pid
;
dbms_output.put_line('user: '||my.uname||' entered a new review for Product: '||my.pname||' with a review score of: '||newreview.score);
end;
/
output: user: nobody entered a new review for Product: prod 1001 with a review score of: 10
In order to inform another session about an event you should use dbms_alert (transactional) or dbms_pipe (non transactional) packages. An example of dbms_alert:
create or replace trigger new_review_trig after insert on reviews for each row
begin
dbms_alert.signal('new_review_alert', 'signal on last rid='||:new.rid);
end;
/
Run the following block in another session (new window, worksheet, sqlplus or whatever else). It will be blocked until the registered signal is arrived:
<<observer>>declare message varchar2(400); status integer; uname users.uname%type; pname products.pname%type; score reviews.score%type;
begin
dbms_alert.register('new_review_alert');
dbms_alert.waitone('new_review_alert', observer.message, observer.status);
if status != 0 then raise_application_error(-20001, 'observer: wait on new_review_alert error'); end if;
select uname, pname, score into observer.uname, observer.pname, observer.score
from reviews join users using(userid) join products using (pid)
where rid = regexp_substr(observer.message, '\w+\s?rid=(\d+)', 1,1,null,1)
;
dbms_output.put_line('observer: new_review_alert for user='||observer.uname||',product='||observer.pname||': score='||observer.score);
end;
/
Now in your session:
insert into reviews values(2, 101,7,1001);
commit; --no alerting before commit
The another (observer) session will be finished with the output:
observer: new_review_alert for user=nobody,product=prod 1001: score=7
P.S. There was no RID in the Table REVIEW, so i'll just assume it was supposed to be PID.
create or replace trigger userNameTrigger
after insert on review
for each row
declare
x varchar2(256);
y varchar2(256);
z varchar2(256);
BEGIN
select uname
, pname
, score
INTO x
, y
, z
from review r
, product p
, users u
where r.pid = p.pid
and r.userid = u.userid
and r.PID = :new.pid;
dbms_output.put_line('user: '|| X ||'entered a new review for Product: '|| Y || 'with a review score of: '|| Z);
end userNameTrigger;
You just made a mistake on the INTO statement, you can just clump them together in one INTO.

Fastest way of doing field comparisons in the same table with large amounts of data in oracle

I am recieving information from a csv file from one department to compare with the same inforation in a different department to check for discrepencies (About 3/4 of a million rows of data with 44 columns in each row). After I have the data in a table, I have a program that will take the data and send reports based on a HQ. I feel like the way I am going about this is not the most efficient. I am using oracle for this comparison.
Here is what I have:
I have a vb.net program that parses the data and inserts it into an extract table
I run a procedure to do a full outer join on the two tables into a new table with the fields in one department prefixed with '_c'
I run another procedure to compare the old/new data and update 2 different tables with detail and summary information. Here is code from inside the procedure:
DECLARE
CURSOR Cur_Comp IS SELECT * FROM T.AEC_CIS_COMP;
BEGIN
FOR compRow in Cur_Comp LOOP
--If service pipe exists in CIS but not in FM and the service pipe has status of retired in CIS, ignore the variance
If(compRow.pipe_num = '' AND cis_status_c = 'R')
continue
END IF
--If there is not a summary record for this HQ in the table for this run, create one
INSERT INTO t.AEC_CIS_SUM (HQ, RUN_DATE)
SELECT compRow.HQ, to_date(sysdate, 'DD/MM/YYYY') from dual WHERE NOT EXISTS
(SELECT null FROM t.AEC_CIS_SUM WHERE HQ = compRow.HQ AND RUN_DATE = to_date(sysdate, 'DD/MM/YYYY'))
-- Check fields and update the tables accordingly
If (compRow.cis_loop <> compRow.cis_loop_c) Then
--Insert information into the details table
INSERT INTO T.AEC_CIS_DET( Fac_id, Pipe_Num, Hq, Address, AutoUpdatedFl,
DateTime, Changed_Field, CIS_Value, FM_Value)
VALUES(compRow.Fac_ID, compRow.Pipe_Num, compRow.Hq, compRow.Street_Num || ' ' || compRow.Street_Name,
'Y', sysdate, 'Cis_Loop', compRow.cis_loop, compRow.cis_loop_c);
-- Update information into the summary table
UPDATE AEC_CIS_SUM
SET cis_loop = cis_loop + 1
WHERE Hq = compRow.Hq
AND Run_Date = to_date(sysdate, 'DD/MM/YYYY')
End If;
END LOOP;
END;
Any suggestions of an easier way of doing this rather than an if statement for all 44 columns of the table? (This is run once a week if it matters)
Update: Just to clarify, there are 88 columns of data (44 of duplicates to compare with one suffixed with _c). One table lists each field in a row that is different so one row can mean 30+ records written in that table. The other table keeps tally of the number of discrepencies for each week.
First of all I believe that your task can be implemented (and should be actually) with staight SQL. No fancy cursors, no loops, just selects, inserts and updates. I would start with unpivotting your source data (it is not clear if you have primary key to join two sets, I guess you do):
Col0_PK Col1 Col2 Col3 Col4
----------------------------------------
Row1_val A B C D
Row2_val E F G H
Above is your source data. Using UNPIVOT clause we convert it to:
Col0_PK Col_Name Col_Value
------------------------------
Row1_val Col1 A
Row1_val Col2 B
Row1_val Col3 C
Row1_val Col4 D
Row2_val Col1 E
Row2_val Col2 F
Row2_val Col3 G
Row2_val Col4 H
I think you get the idea. Say we have table1 with one set of data and the same structured table2 with the second set of data. It is good idea to use index-organized tables.
Next step is comparing rows to each other and storing difference details. Something like:
insert into diff_details(some_service_info_columns_here)
select some_service_info_columns_here_along_with_data_difference
from table1 t1 inner join table2 t2
on t1.Col0_PK = t2.Col0_PK
and t1.Col_name = t2.Col_name
and nvl(t1.Col_value, 'Dummy1') <> nvl(t2.Col_value, 'Dummy2');
And on the last step we update difference summary table:
insert into diff_summary(summary_columns_here)
select diff_row_id, count(*) as diff_count
from diff_details
group by diff_row_id;
It's just rough draft to show my approach, I'm sure there is much more details should be taken into account. To summarize I suggest two things:
UNPIVOT data
Use SQL statements instead of cursors
You have several issues in your code:
If(compRow.pipe_num = '' AND cis_status_c = 'R')
continue
END IF
"cis_status_c" is not declared. Is it a variable or a column in AEC_CIS_COMP?
In case it is a column, just put the condition into the cursor, i.e. SELECT * FROM T.AEC_CIS_COMP WHERE not (compRow.pipe_num = '' AND cis_status_c = 'R')
to_date(sysdate, 'DD/MM/YYYY')
That's nonsense, you convert a date into a date, simply use TRUNC(SYSDATE)
Anyway, I think you can use three single statements instead of a cursor:
INSERT INTO t.AEC_CIS_SUM (HQ, RUN_DATE)
SELECT comp.HQ, trunc(sysdate)
from AEC_CIS_COMP comp
WHERE NOT EXISTS
(SELECT null FROM t.AEC_CIS_SUM WHERE HQ = comp.HQ AND RUN_DATE = trunc(sysdate));
INSERT INTO T.AEC_CIS_DET( Fac_id, Pipe_Num, Hq, Address, AutoUpdatedFl, DateTime, Changed_Field, CIS_Value, FM_Value)
select comp.Fac_ID, comp.Pipe_Num, comp.Hq, comp.Street_Num || ' ' || comp.Street_Name, 'Y', sysdate, 'Cis_Loop', comp.cis_loop, comp.cis_loop_c
from T.AEC_CIS_COMP comp
where comp.cis_loop <> comp.cis_loop_c;
UPDATE AEC_CIS_SUM
SET cis_loop = cis_loop + 1
WHERE Hq IN (Select Hq from T.AEC_CIS_COMP)
AND trunc(Run_Date) = trunc(sysdate);
They are not tested but they should give you a hint how to do it.

Resources