Update or Insert on a Table based on a column value - insert

I have two tables BASE and DAILY as shown below:
BASE
Cust ID IP address
1 10.5.5.5
2 10.5.5.50
3 10.5.5.6
DAILY
Cust ID IP address
1 10.5.5.5
2 10.5.5.70
4 10.5.5.67
The table DAILY is periodically refreshed every 24 hours. Now for every Cust Id in BASE I have to check if the IP address is modified in DAILY. If yes then update the row in BASE.
All the new entries in DAILY have to be inserted into BASE.
I have tried this using a Cursor comparing and then updating and then another cursor for insertion.
But it is taking lot of time.
What is the best possible way to do this?

You could also use MERGE depending on your database system.
SQL Server syntax would be
MERGE INTO BASE B
USING DAILY D
ON D.CustId = B.CustId
WHEN NOT MATCHED THEN
INSERT (CustId, Ip) VALUES (D.CustId, D.Ip)
WHEN MATCHED AND D.Ip <> B.Ip THEN
UPDATE SET B.Ip = D.Ip;
Oracle PL/SQL syntax seems to be much the same, take a look here

If you just want to update all your BASE table, use an UPDATE to update all the rows in your BASE table.
UPDATE `BASE`
SET `IP address` = (SELECT `IP address`
FROM DAILY
WHERE DAILY.`Cust ID` = `BASE`.`Cust ID`);
Then, use this INSERT INTO query to insert new values that not exists in your table BASE.
INSERT INTO `BASE`
SELECT `Cust ID`, `IP address`
FROM DAILY
WHERE DAILY.`Cust ID` NOT IN (SELECT `Cust ID` FROM BASE);

SQL>
declare
begin
for i in (select * from daily where ip_add not in (select ip_add from base))
loop
update base set ip_add=i.ip_add where custid=i.custid;
end loop;
end;
PL/SQL procedure successfully completed.
SQL> select * from base;
CUSTID IP_ADD
---------- ----------
1 10..5.5.5
2 10..5.5.20 -- updated value from base where ip_add is different
3 10..5.5.6
SQL> select * from base ;
CUSTID IP_ADD
---------- ----------
1 10..5.5.5
2 10..5.5.20
4 10..5.5.62
SQL>

Related

Using EXECUTE IMMEDIATE based on entries of table

I (using Oracle 12c, PL/SQL) need to update an existing table TABLE1 based on information stored in a table MAP. In a simplified version, MAP looks like this:
COLUMN_NAME
MODIFY
COLUMN1
N
COLUMN2
Y
COLUMN3
N
...
...
COLUMNn
Y
COLUMN1 to COLUMNn are column names in TABLE1 (but there are more columns, not just these). Now I need to update a column in TABLE1 if MODIFY in table MAP contains a 'Y' for that columns' name. There are other row conditions, so what I would need would be UPDATE statements of the form
UPDATE TABLE1
SET COLUMNi = value_i
WHERE OTHER_COLUMN = 'xyz_i';
where COLUMNi runs through all the columns of TABLE1 which are marked with MODIFY = 'Y' in MAP. value_i and xyz_i also depend on information stored in MAP (not displayed in the example).
The table MAP is not static but changes, so I do not know in advance which columns to update. What I did so far is to generate the UPDATE-statements I need in a query from MAP, i.e.
SELECT <Text of UPDATE-STATEMENT using row information from MAP> AS SQL_STMT
FROM MAP
WHERE MODIFY = 'Y';
Now I would like to execute these statements (possibly hundreds of rows). Of course I could just copy the contents of the query into code and execute, but is there a way to do this automatically, e.g. using EXECUTE IMMEDIATE? It could be something like
BEGIN
EXECUTE IMMEDIATE SQL_STMT USING 'xyz_i';
END;
only that SQL_STMT should run through all the rows of the previous query (and 'xyz_i' varies with the row as well). Any hints how to achieve this or how one should approach the task in general?
EDIT: As response to the comments, a bit more background how this problem emerges. I receive an empty n x m Matrix (empty except row and column names, think of them as first row and first column) quarterly and need to populate the empty fields from another process.
The structure of the initial matrix changes, i.e. there may be new/deleted columns/rows and existing columns/rows may change their position in the matrix. What I need to do is to take the old version of the matrix, where I already have filled the empty spaces, and translate this into the new version. Then, the populating process merely looks if entries have changed and if so, alters them.
The situation from the question arises after I have translated the old version into the new one, before doing the delta. The new matrix, populated with the old information, is TABLE1. The delta process, over which I have no control, gives me column names and information to be entered into the cells of the matrix (this is table MAP). So I need to find the column in the matrix labeled by the delta process and then to change values in rows (which ones is specified via other information provided by the delta process)
Dynamic SQL it is; here's an example, see if it helps.
This is a table whose contents should be modified:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Little 100
2 200
3 Foot 0
4 0
This is the map table:
SQL> select * from map;
COLUMN CB_MODIFY VALUE WHERE_CLAUSE
------ ---------- ----- -------------
NAME Y Scott where id <= 3
SALARY N 1000 where 1 = 1
Procedure loops through all columns that are set to be modified, composes the dynamic update statement and executes it:
SQL> declare
2 l_str varchar2(1000);
3 begin
4 for cur_r in (select m.column_name, m.value, m.where_clause
5 from map m
6 where m.cb_modify = 'Y'
7 )
8 loop
9 l_str := 'update test set ' ||
10 cur_r.column_name || ' = ' || chr(39) || cur_r.value || chr(39) || ' ' ||
11 cur_r.where_clause;
12 execute immediate l_str;
13 end loop;
14 end;
15 /
PL/SQL procedure successfully completed.
Result:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Scott 100
2 Scott 200
3 Scott 0
4 0
SQL>

How to make a PL/SQL program to update Student Fees when status is active?

I am a newbie in PL/SQL and i have a table called STUDENT, and it contains the following columns: REGNO, NAME, FNAME, DOMICILE, FEES, STATUS.
what i want to do is when a new record is created and if the student domicile for example DOMICILE = 'TEXAS' and STATUS = 'ACTIVE' then i want to give 50% Discount in FEES.
Here is my code:
CREATE OR REPLACE TRIGGER MYTRIGGER
BEFORE INSERT ON STUDENT
FOR EACH ROW
BEGIN
IF :NEW.DOMICILE = 'TEXAS' AND :NEW.STATUS = 'ACTIVE' THEN
UPDATE STUDENT SET FEES = FEES - 0.50 * FEES;
END IF;
END MYTRIGGER;
/
the trigger gets created but it does not work properly..
example:
SQL> INSERT INTO STUDENT VALUES(1,'MARK','SMITH','TEXAS',5000,'ACTIVE');
1 row created.
SQL> SELECT * FROM STUDENT;
REGNO NAME FNAME DOMICILE FEES STATUS
---------- ------------------------------ ------------------------------ ------------------------------ ---------- --------------------
1 MARK SMITH TEXAS 5000 ACTIVE
SQL> INSERT INTO STUDENT VALUES(2,'JAMES','FORD','TEXAS',5000,'ACTIVE');
1 row created.
SQL> INSERT INTO STUDENT VALUES(3,'SAM','MILLER','NEW JERSEY',5000,'ACTIVE');
1 row created.
SQL> SELECT * FROM STUDENT;
REGNO NAME FNAME DOMICILE FEES STATUS
---------- ------------------------------ ------------------------------ ------------------------------ ---------- --------------------
1 MARK SMITH TEXAS 2500 ACTIVE
2 JAMES FORD TEXAS 5000 ACTIVE
3 SAM MILLER NEW JERSEY 5000 ACTIVE
SQL>
any suggestions?
You don't need update statement there, all you need is just to set new value to :NEW.FEES:
CREATE OR REPLACE TRIGGER MYTRIGGER
BEFORE INSERT ON STUDENT
FOR EACH ROW
BEGIN
IF :NEW.DOMICILE = 'TEXAS' AND :NEW.STATUS = 'ACTIVE' THEN
:NEW.FEES := 0.50 * :NEW.FEES;
END IF;
END MYTRIGGER;
/
Your trigger is working just fine as it is written. But, obviously not as to what you want nor expected. What you want is lower by 1/2 the fees of the incoming row. However it is your expectation that is wrong. What your update statement actually does is lowering the fees by 1/2 for ever existing row in the table. Since the incoming row does not exist yet it does NOT participate in the update. That is why it appears to work on some but not all inserts.(Actually I was surprised as I expected an "ORA-04091: table is mutating..." exception). The reason for this is that your Update statement does not have a WHERE clause so it updates every row. See here a fiddle that shows what is happening by displaying the table after each insert to see just what that statement actually did. Often a useful technique when results are not as expected. #SayanMalakshinov is correct: Do not update instead just do an assignment.
:new.fees := :new.fees * .5;

Sort a list using loops

I'm still fairly new to PL/SQL and I tried to solve the following problem for a long time on my own, but I can not get to a solution (and i did not find a similar problem solved) hence I thought I ask myself.
I am trying to sort a list by the date (which indicates the 'time_left_to_live') so that the result is the same list but, as you can guess, sorted by the date.
EDIT: Maybe i should be even more precise:
My original goal is to write an AFTER UPDATE TRIGGER, which sorts the table again if the time_left_to_live is updated. My idea was to write a Procedure which sorts (or more updates) the list and call it.
Example for clarification:
UPDATE testv1 SET time_left_to_live = '01.05.2020' WHERE list_id = 3;
so after that the old data of list_id 3 should be 1 since it has the shortest amount left to live and the other datas should be incremented by 1.
1 01.05.2020
2 03.05.2020
3 31.05.2020
I hope I could explain the situation somewhat good.
I tried it with a nested loop but I simply can't think inside. Any help is appreciated.
My table:
DROP TABLE testv1;
DROP PROCEDURE wlp_sort;
CREATE TABLE testv1(
list_ID INT,
time_left_to_live DATE
);
INSERT INTO testv1 VALUES (3, '01.06.2020');
INSERT INTO testv1 VALUES (2, '31.05.2020');
INSERT INTO testv1 VALUES (1, '03.05.2020');
--UPDATE testv1 SET time_left_to_live = '01.05.2020' WHERE list_ID = 2;
SELECT * FROM testv1;
I'd suggest you not to do that. I presume that table you posted as an example is exactly that - an example. In reality, it is more complex. Furthermore, I presume that list_id column identifies each row which is OK, but wrong to be used for sorting purposes, especially not the way you wanted - by updating its value via database triggers.
What should you do, then? Use ORDER BY as it is the only mechanism that guarantees that rows will be returned in desired order. A long time ago, I think the last Oracle database version was 8i, you could use group by clause which in the background sorted result for you, but those times have passed so - order by it is.
Here's an example of what I mean.
This is the original contents of your table. Query includes additional column - the one you might want to use - row_number analytic function which "calculates" ordinal number on-the-fly. At first, it matches list_id value.
SQL> select list_id,
2 time_left_to_live,
3 row_number() over (order by time_left_to_live) rn
4 from testv1
5 order by time_left_to_live;
LIST_ID TIME_LEFT_ RN
---------- ---------- ----------
1 03.05.2020 1
2 31.05.2020 2
3 01.06.2020 3
SQL>
Let's update one row (also from your example) and see what happens (note that I used date literal; you updated a date column with a string. Oracle did implicitly try - and succeed - to convert it to a valid date value, but you shouldn't rely on that. 01.05.2020 could be 1st of May or 5th of January, depending on date format which may change and might be different in different databases. Date literal is, on the other hand, always in format date 'yyyy-mm-dd' and there's no confusion):
SQL> update testv1 set time_left_to_live = date '2020-05-01' where list_id = 2;
1 row updated.
SQL> select list_id,
2 time_left_to_live,
3 row_number() over (order by time_left_to_live) rn
4 from testv1
5 order by time_left_to_live;
LIST_ID TIME_LEFT_ RN
---------- ---------- ----------
2 01.05.2020 1
1 03.05.2020 2
3 01.06.2020 3
SQL>
ORDER BY clause sorts data, but now list_id and rn are different; list_id didn't change, but rn represents new order.
If your next step is to do something with a row whose ordinal number is 1, you'd just use query I suggested as an inline view and fetch values whose rn = 1:
SQL> select list_id,
2 time_left_to_live
3 from (select list_id,
4 time_left_to_live,
5 row_number() over (order by time_left_to_live) rn
6 from testv1
7 )
8 where rn = 1;
LIST_ID TIME_LEFT_
---------- ----------
2 01.05.2020
SQL>
I suggest you use this option.
Additional drawbacks related to database trigger: if you write an update statement that modifies list_id, no problem - it works outside of trigger and list_id and rn are synchronized again:
SQL> update testv1 a set
2 a.list_id = (select x.rn
3 from (select b.list_id,
4 b.time_left_to_live,
5 row_number() over (order by b.time_left_to_live) rn
6 from testv1 b
7 ) x
8 where x.list_id = a.list_id
9 );
3 rows updated.
SQL> select list_id,
2 time_left_to_live,
3 row_number() over (order by time_left_to_live) rn
4 from testv1
5 order by time_left_to_live;
LIST_ID TIME_LEFT_ RN
---------- ---------- ----------
1 01.05.2020 1
2 03.05.2020 2
3 01.06.2020 3
SQL>
But, in a trigger, you modify a column which fires a trigger which modifies a column which fires a trigger which modifies a column ... until you exceed maximum number of recursive SQL levels and then Oracle raises an error.
Or, if you planned to select from the table and then do something with it, you'll hit the mutating table error as you can't select from a table which is just being modified. True, you might use a compound trigger (or - in previous Oracle database versions - package and custom type), but - once again, in my opinion, that's just not the way you should handle the problem.
Agree fully with #Littlefoot. But even if you could build a user written sort in a trigger there is NO quarantine that order is preserved when written. A table is by definition an unordered set of rows and the SQL engine in under no obligation to maintain any ordering of presented rows. The way to guarantee any sequence is the order by clause

Trigger to monitor is value acceptable or not

I have Oracle 11g and a table called CODES and there is column ID and CODESA as follow:
ID CODESA
1 9999
1 8889
2 77777
2 99999
3 1234
3 4321
4 565656
etc.
Then I need to update another table CODES2 and column CODESB based on ID in CODES table
I need a trigger to monitor this.
Let´s say I monitoring ID = 2 with this trigger and all different CODESA´s under that ID,
you can see that only these are possible to update in CODESB
2 77777
2 99999
How to make a trigger to launch if user is trying to enter some code in CODESB
which is for example from ID = 3 ?
Appreciate your help. Thanks,
Some_user
#APC is correct. We can use foreign key but lets say OP dont want those columns to be primary or unique, in that case trigger is the solution.
Create or replace trigger codes2_trg
Before insert or update On codes2
For each row
Declare
Cnt number;
Begin
Select count(1) into cnt
From codes where (id, codesa) = (:new.id, :new.codesb);
If cnt = 0 then
Raise_application_error('-20001', 'these balues are not allowed.');
End if;
End;
/
Cheers!!

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