Automate bulk of update queries in pl\sql - oracle

For frequent period of time, i'm doing same process of updating few tables with consecutive values. Hope to make this simple an example below,
UPDATE Table_1 SET vchar_val = REPLACE (vchar_val, '.360/', '.370/'),
WHERE vchar_val LIKE 'http://services%.360/%'
AND c_version IN ('ALL', 'N/A', '37.0');
For 37th version, i'm replacing the places where '36' with '37'. The same i'll do for '38' and it continues...This is making me bore and time consuming process as i've 50 plus records like this for different tables for which i'm manually editing all update queries.
So i planned to write a scheduler which i can trigger for each version by giving input as previous version and current version, in which i'll put all this update queries.
Here comes the place where i struck, if i go by giving version values as input, i'm supposed to introduce local parameter to store. HOW CAN I ASSIGN THAT LOCAL VARIABLE TO MY UPDATE SCRIPT.??????
I go with concatenate the texts like
REPLACE (vchar_val, '.'+ #PrevVersion +'/', '.'+ #CurrentVersion +'/')
PrevVer** & CurrentVer** is my local variable with values .360 & .370 resp.
I think i miss quiet piece of code in this snippet as i'm getting error when running this.
Please help me guys to rearrange this code to automate the query or ur comments to do this job in any alternative way...
Thanks
-VIno

begin
for i in 36 .. 50 loop
UPDATE Table_1
SET vchar_val = REPLACE (vchar_val, '.'|| i ||'0/', '.'|| i+1 ||'0/')
WHERE vchar_val LIKE 'http://services%.'|| i ||'0/%'
AND c_version IN ('ALL', 'N/A', i+1 ||'.0');
end loop;
end;
Of course you could do that in one single update with some fancy reg_exp, but I leave that exercice to another fellow stackoverflower :)

Local variable:
declare
my_local_valiable number;
begin
update my_table ... ;
commit;
end;
Autoincrement: sequence
update table_1 set my_field = '.' || my_sequence.nextval where ...;
UPD
Number always in the same position (for example, 2 digits, 10th and 11th symbols in the string):
update table_1 set my_field = substr(my_field, 1, 9) || to_char(to_number(substr(my_field, 10, 2)) + 1) || substr(my_field, 12);
This converts string 'abracadab29ra' to 'abracadab30ra'.
The same with replace:
update table_1 set my_field = replace(my_field, substr(my_field, 10, 2), to_char(to_number(substr(my_field, 10, 2)) + 1));
Number always follows after a string 'value = ' and has two digits:
update table_1 set my_field = replace(my_field, substr(my_field, instr(my_field, 'value = ', 1) + 8, 2), to_char(to_number(substr(my_field, instr(my_field, 'value = ', 1) + 8, 2)) + 1))
This converts string 'my value = 33' to 'my value = 34'.

Related

Oracle equivalent query for this postgress query - CONFLICT [duplicate]

The UPSERT operation either updates or inserts a row in a table, depending if the table already has a row that matches the data:
if table t has a row exists that has key X:
update t set mystuff... where mykey=X
else
insert into t mystuff...
Since Oracle doesn't have a specific UPSERT statement, what's the best way to do this?
The MERGE statement merges data between two tables. Using DUAL
allows us to use this command. Note that this is not protected against concurrent access.
create or replace
procedure ups(xa number)
as
begin
merge into mergetest m using dual on (a = xa)
when not matched then insert (a,b) values (xa,1)
when matched then update set b = b+1;
end ups;
/
drop table mergetest;
create table mergetest(a number, b number);
call ups(10);
call ups(10);
call ups(20);
select * from mergetest;
A B
---------------------- ----------------------
10 2
20 1
The dual example above which is in PL/SQL was great becuase I wanted to do something similar, but I wanted it client side...so here is the SQL I used to send a similar statement direct from some C#
MERGE INTO Employee USING dual ON ( "id"=2097153 )
WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
WHEN NOT MATCHED THEN INSERT ("id","last","name")
VALUES ( 2097153,"smith", "john" )
However from a C# perspective this provide to be slower than doing the update and seeing if the rows affected was 0 and doing the insert if it was.
An alternative to MERGE (the "old fashioned way"):
begin
insert into t (mykey, mystuff)
values ('X', 123);
exception
when dup_val_on_index then
update t
set mystuff = 123
where mykey = 'X';
end;
Another alternative without the exception check:
UPDATE tablename
SET val1 = in_val1,
val2 = in_val2
WHERE val3 = in_val3;
IF ( sql%rowcount = 0 )
THEN
INSERT INTO tablename
VALUES (in_val1, in_val2, in_val3);
END IF;
insert if not exists
update:
INSERT INTO mytable (id1, t1)
SELECT 11, 'x1' FROM DUAL
WHERE NOT EXISTS (SELECT id1 FROM mytble WHERE id1 = 11);
UPDATE mytable SET t1 = 'x1' WHERE id1 = 11;
None of the answers given so far is safe in the face of concurrent accesses, as pointed out in Tim Sylvester's comment, and will raise exceptions in case of races. To fix that, the insert/update combo must be wrapped in some kind of loop statement, so that in case of an exception the whole thing is retried.
As an example, here's how Grommit's code can be wrapped in a loop to make it safe when run concurrently:
PROCEDURE MyProc (
...
) IS
BEGIN
LOOP
BEGIN
MERGE INTO Employee USING dual ON ( "id"=2097153 )
WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
WHEN NOT MATCHED THEN INSERT ("id","last","name")
VALUES ( 2097153,"smith", "john" );
EXIT; -- success? -> exit loop
EXCEPTION
WHEN NO_DATA_FOUND THEN -- the entry was concurrently deleted
NULL; -- exception? -> no op, i.e. continue looping
WHEN DUP_VAL_ON_INDEX THEN -- an entry was concurrently inserted
NULL; -- exception? -> no op, i.e. continue looping
END;
END LOOP;
END;
N.B. In transaction mode SERIALIZABLE, which I don't recommend btw, you might run into
ORA-08177: can't serialize access for this transaction exceptions instead.
I'd like Grommit answer, except it require dupe values. I found solution where it may appear once: http://forums.devshed.com/showpost.php?p=1182653&postcount=2
MERGE INTO KBS.NUFUS_MUHTARLIK B
USING (
SELECT '028-01' CILT, '25' SAYFA, '6' KUTUK, '46603404838' MERNIS_NO
FROM DUAL
) E
ON (B.MERNIS_NO = E.MERNIS_NO)
WHEN MATCHED THEN
UPDATE SET B.CILT = E.CILT, B.SAYFA = E.SAYFA, B.KUTUK = E.KUTUK
WHEN NOT MATCHED THEN
INSERT ( CILT, SAYFA, KUTUK, MERNIS_NO)
VALUES (E.CILT, E.SAYFA, E.KUTUK, E.MERNIS_NO);
I've been using the first code sample for years. Notice notfound rather than count.
UPDATE tablename SET val1 = in_val1, val2 = in_val2
WHERE val3 = in_val3;
IF ( sql%notfound ) THEN
INSERT INTO tablename
VALUES (in_val1, in_val2, in_val3);
END IF;
The code below is the possibly new and improved code
MERGE INTO tablename USING dual ON ( val3 = in_val3 )
WHEN MATCHED THEN UPDATE SET val1 = in_val1, val2 = in_val2
WHEN NOT MATCHED THEN INSERT
VALUES (in_val1, in_val2, in_val3)
In the first example the update does an index lookup. It has to, in order to update the right row. Oracle opens an implicit cursor, and we use it to wrap a corresponding insert so we know that the insert will only happen when the key does not exist. But the insert is an independent command and it has to do a second lookup. I don't know the inner workings of the merge command but since the command is a single unit, Oracle could execute the correct insert or update with a single index lookup.
I think merge is better when you do have some processing to be done that means taking data from some tables and updating a table, possibly inserting or deleting rows. But for the single row case, you may consider the first case since the syntax is more common.
A note regarding the two solutions that suggest:
1) Insert, if exception then update,
or
2) Update, if sql%rowcount = 0 then insert
The question of whether to insert or update first is also application dependent. Are you expecting more inserts or more updates? The one that is most likely to succeed should go first.
If you pick the wrong one you will get a bunch of unnecessary index reads. Not a huge deal but still something to consider.
Try this,
insert into b_building_property (
select
'AREA_IN_COMMON_USE_DOUBLE','Area in Common Use','DOUBLE', null, 9000, 9
from dual
)
minus
(
select * from b_building_property where id = 9
)
;
From http://www.praetoriate.com/oracle_tips_upserts.htm:
"In Oracle9i, an UPSERT can accomplish this task in a single statement:"
INSERT
FIRST WHEN
credit_limit >=100000
THEN INTO
rich_customers
VALUES(cust_id,cust_credit_limit)
INTO customers
ELSE
INTO customers SELECT * FROM new_customers;

How to update each row in a table Oracle PLSQL

How can I create a procedure to verify each row of a table and update a field according to the established statement?
I have 4 fields that I work with
SYS_UPDATE_PING
UPDATE_PING
PING_STATUS
TIME_OUT_PING
Here is my code:
create or replace procedure SP_DASHBOARD_PINGSTATUS is
begin
declare
sp_ping_final number;
BEGIN
Update rsmes.tb_op_pc_monitoring_v4 t
set t.sys_update_ping = sysdate;
for c in (select ((SYS_UPDATE_PING - UPDATE_PING)*60*60*24) as PING_RESULT into sp_ping_final from TB_OP_PC_MONITORING_V4)
loop
sp_ping_final := c.ping_result;
if c.ping_result <= 5 then
Update rsmes.tb_op_pc_monitoring_v4 tg
set tg.ping_status = 'OK',
tg.time_out_ping = sp_ping_final;
else
Update rsmes.tb_op_pc_monitoring_v4 tn
set tn.ping_status = 'NG',
tn.time_out_ping = sp_ping_final;
end if;
end loop;
commit;
END;
end SP_DASHBOARD_PINGSTATUS;
I make a time difference between SYS_UPDATE_PING and UPDATE_PING, if the result in seconds is less than 5 it must update the PING_STATUS field to OK and put the seconds difference in TIME_OUT_PING, otherwise it will update NG and put the seconds difference in TIME_OUT_PING,
I want it to compare on each row but it updates me on all fields instead of one by one.
I know I'm forgetting something but could you help me find my fault and know the solution?
Thank you
That's because UPDATE statements miss the WHERE clause. Without it, you're always updating all rows in the table.
It means that cursor should contain some kind of an ID, which you'll the reuse in UPDATE. For example:
for c in (select id, --> this
((sys_update_ping - ...)
) loop
...
update tb_op_pc_monitoring_v4 tg set
tg.ping_status = 'OK'
where tg.id = c.id; --> this
...
end loop;
Also, you don't SELECT INTO in cursor. Remove the sp_ping_final entirely.
Finally, I'd say that you don't need PL/SQL (loop especially) at all. The whole code you wrote can be rewritten into a single
UPDATE tb_op_pc_monitoring_v4 tg
SET tg.ping_status =
CASE
WHEN (SYSDATE - tg.update_ping) * 60 * 60 * 24 <= 5 THEN 'OK'
ELSE 'NG'
END,
tg.time_out_ping = (SYSDATE - tg.update_ping) * 60 * 60 * 24;
which should work way faster than your row-by-row processing.

Which REGEXP to use

I would suppose this can be best done with regexp, as that's probably the shortest way, but anything else as a suggestion is also good.
Say, I have a string in PL/SQL, and want to capitalize every Nth character if its small, and lower if its capital. For example, every fifth letter.
I'd want to examine the possibilities to achieve that.
Thanks.
Since I have nothing better to do on a Saturday night (after Dark Matter of course) I decided to challenge my brain a bit. So after learning that SQLFiddle is broken, I installed Oracle 11g R2 Express Edition on my computer to work this out... yeah, I'm really that bored.
Directly from a table:
drop table test;
create table test (nbr number, txt varChar2(26));
insert into test (nbr, txt) values (1,'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
insert into test (nbr, txt) values (2,'abcdefghijklmnopqrstuvwxyz');
insert into test (nbr, txt) values (3,'ABCDEFGHIJklmnopqrstUVWXYZ');
insert into test (nbr, txt) values (4,'abcdefghijKLMNOPQRSTuvwxyz');
select
nbr,
(
select
listAgg
(
case subStr(subStr(t.txt,((level - 1) * 5) + 1,5),5,1)
when upper(subStr(subStr(t.txt,((level - 1) * 5) + 1,5),5,1))
then subStr(subStr(t.txt,((level - 1) * 5) + 1,5),1,4) ||
lower(subStr(subStr(t.txt,((level - 1) * 5) + 1,5),5,1))
when lower(subStr(subStr(t.txt,((level - 1) * 5) + 1,5),5,1))
then subStr(subStr(t.txt,((level - 1) * 5) + 1,5),1,4) ||
upper(subStr(subStr(t.txt,((level - 1) * 5) + 1,5),5,1))
else subStr(t.txt,((level - 1) * 5) + 1,5)
end
)
within group (order by level)
from
dual
connect by
subStr(t.txt,((level - 1) * 5) + 1,5) is not null
) as "newTxt"
from
test t
;
Or you can use a PL/SQL function:
declare
function inverseCase(
txt varChar2,
nbr number
)
return varChar2 as
newTxt varChar2(254);
txtSeg varChar2(254);
segA varChar2(254);
segB varChar2(254);
begin
for i in 0..floor(length(txt)/nbr) loop
txtSeg := subStr(txt, (i * nbr) + 1, nbr);
segA := subStr(txtSeg, 1, nbr - 1);
segB := subStr(txtSeg, nbr, 1);
newTxt := newTxt || case segB
when upper(segB) then segA || lower(segB)
when lower(segB) then segA || upper(segB)
else txtSeg
end;
end loop;
return newTxt;
end;
begin
dbms_output.put_line(inverseCase('ABCDEFGHIJKLMNOPQRSTUVWXYZ',5));
dbms_output.put_line(inverseCase('abcdefghijklmnopqrstuvwxyz',5));
dbms_output.put_line(inverseCase('ABCDEFGHIJklmnopqrstUVWXYZ',5));
dbms_output.put_line(inverseCase('abcdefghijKLMNOPQRSTuvwxyz',5));
end;
Both return the following output.
nbr newTxt
1 ABCDeFGHIjKLMNoPQRStUVWXyZ
2 abcdEfghiJklmnOpqrsTuvwxYz
3 ABCDeFGHIjklmnOpqrsTUVWXyZ
4 abcdEfghiJKLMNoPQRStuvwxYz
Which I believe to be the text you are looking for.
It's been a while and I forgot how much fun this website is when I'm bored!
As one of options, you can check 5-th character using CASE statement
SELECT ( case
when SUBSTR(fieldname, 5,1) = upper (SUBSTR(fieldname, 5,1)) then lower (SUBSTR(fieldname, 5,1))
when SUBSTR(fieldname, 5,1) = lower (SUBSTR(fieldname, 5,1)) then upper (SUBSTR(fieldname, 5,1))
else SUBSTR(fieldname, 5,1) ) 5char
FROM YourTable
The problem will occur afterwards as you'll have to divide string on 3, change letter case and concatenate them back. This will be bulky. Or play around with REPLACE function, but again, as far as I know it does not work with particular character position, only with substrings by pattern, thus you will end up with dividing and concatenating again.
Regexp probably least painfull
That is if we limit transformation to be in SELECT statement only, no PL/SQL procedures

PL SQL Query taking too long to execute

Person table have 2 Million rows and 3 Million rows in Finance and on HR schema respectively. Now I'm going to update the Person status on Finance schema with the person_status on the HR schema. Query is running on M3000 Server with 32 GB RAM with Solaris 10 and Oracle 11.2.2. It took 117 hours and still runnning. How can I optimize this query. I have created index on person_no.
DECLARE
CURSOR c IS
SELECT p1.person_no AS "PersonNo1"
, p2.person_no AS "PersonNo2"
, p2.person_status AS "P2_PERSON_STATUS"
FROM person p1
, hr.person p2
WHERE (lower(p1.person_no) = lower(p2.person_no)
OR substr(p1.person_no, instr(p1.person_no, '-') + 1, length(p1.person_no))
= substr(p2.person_no, instr(p2.person_no, '-') + 1, length(p2.person_no)));
BEGIN
FOR i IN c LOOP
UPDATE person
SET person_status_id = decode(lower(i.P2_PERSON_STATUS), 'y', 1, 'n', 2)
WHERE (lower(person_no) = lower(i.PersonNo2)
OR substr(person_no, instr(person_no, '-') + 1, length(person_no))
= substr(i.PersonNo2, instr(i.PersonNo2, '-') + 1, length(i.PersonNo2)));
COMMIT;
END LOOP;
END;
/
There is several things you can do to optimize this. It would be helpful to see a query plan.
You can use a merge statement. Then you have only one statement and you can work optimizing this statement. Something like this
merge into person
using hr.person person_hr
on (person.person_no=person_hr.person_no)
when matched then
update set person_status_id=decode(lower(person_status),'y',1,'n',2);
You have to adjust the on part to match your where statement.
This might then be the source of best optimization. Maybe you have to create an index for lower and also for substr.
Something like
CREATE INDEX person_idx
ON person (lower(person_no))
Also of course for the substrings etc. Hope this helps.
In the code provided as question i can see there are two aspects.
1) Query Optimization
2) ROW BY ROW Update --> Never Recommended for such a huge records count..
Approach to Optmization.
1) Use Merge Statement as it will Bundle up the block into pure SQL thus will enhance your query.
2) Use Function based Index as i can see there are lot of functions like "LOWER" is used in the query "WHERE" conditions.
NOTE : [Over INDEXING may also cause deterioration in the performance}
3) Last but not the least if you have to go by Anonymous block only then avoid using ROW BY ROW Update. Try using Bulk collect Options as illustrated below.
Since i do not have workstation handy with me please bear with any Syntax Errors.
Let me know if this helps.
DECLARE
TYPE p1
IS
TABLE OF <table_name>.<COLUMN_NAME>%TYPE;
PersonNo1 p1;
TYPE p2
IS
TABLE OF <table_name>.<COLUMN_NAME>%TYPE;
PersonNo2 p2;
TYPE status
IS
TABLE OF <table_name>.<COLUMN_NAME>%TYPE;
P2_PERSON_STATUS status;
BEGIN
SELECT p1.person_no AS "PersonNo1" ,
p2.person_no AS "PersonNo2" ,
p2.person_status AS "P2_PERSON_STATUS" BULK COLLECT
INTO PersonNo1,
PersonNo2,
P2_PERSON_STATUS
FROM person p1 ,
hr.person p2
WHERE (lower(p1.person_no) = lower(p2.person_no)
OR SUBSTR(p1.person_no, instr(p1.person_no, '-') + 1, LENGTH(p1.person_no)) = SUBSTR(p2.person_no, instr(p2.person_no, '-') + 1, LENGTH(p2.person_no)));
FORALL I IN PersonNo1.FIRST..PersonNo1.LAST
UPDATE person
SET person_status_id = DECODE(lower(P2_PERSON_STATUS(I)), 'y', 1, 'n', 2)
WHERE (lower(person_no) = lower(PersonNo2(I))
OR SUBSTR(person_no, instr(person_no, '-') + 1, LENGTH(person_no)) = SUBSTR(PersonNo2(I), instr(PersonNo2(I), '-') + 1, LENGTH(PersonNo2(I))));
COMMIT;
END;

identify selected checkboxes in apex interactive report

I have an Interactive Report which I'm generating using a collection.
select apex_item.checkbox(1,'obj_name') ' ',
col 01,
col 02
from apex_collections where collection_name='XYZ';
The table on which this report is being generated has a composite primary key,
so when the user selects multiple checkboxes, I'm not able to figure out how to identify which all rows were selected by user. This is because according to my knowledge, through p_value in apex_item.checkbox(p_idx,p_value) I can just pass one column/field/item. But the requirement is to pass both obj_name and col 01 back to the pl/sql code.
To explain it better, I have an on submit process associated to it.
FOR I in 1..APEX_APPLICATION.G_F01.COUNT LOOP
DELETE FROM abc
WHERE obj_name = (APEX_APPLICATION.G_F01(i))
AND tab_col = col 01;
END LOOP;
So how can I send the value of col 01 of the selected checkboxes to the above process is what my question is. Any help would be great.
A better approach which I've found now is using rownum as unique value, that actually reduces a lot of logic in my code. Just calling it out.
Could you concatenate the obj_name and col_name?
select apex_item.checkbox(1,'obj_name-'||col_01) ' ', from apex_collections where collection_name='XYZ';
Then in the processing section
FOR I in 1..APEX_APPLICATION.G_F01.COUNT LOOP
my_object_name := substr( G_F01(i), 1, instr( G_F01(i), '-' ) - 1 );
my_column_name := substr( G_F01(i), instr( G_F01(i), '-' ) + 1 );
DELETE FROM abc WHERE obj_name = my_object_name AND tab_col = my_column_name;
END LOOP;

Resources