Sybase CURSOR UPDATE extremeley slow / locking up - performance

I have a temp table with 13,000 rows. Most of the rows have a numeric price (100) but some are quoted in 32nds, i.e. 100-30 = 100 + 30/32 = 100.9375. (Some even have fractions of a 32th)
I am opening a cursor FOR UPDATE and iterating over the temp table. It takes so long to execute, I am not even sure it is working (my DBA says the exec plan looks 'strange')
Can anyone suggest why this is so ridiculously slow?
Better still, could anyone suggest a better alternative? I have control over how the temp table is created, but I don't fancy trying to compact the logic for 100-30 to 100.9375 into a single update statement.
I'd like to write a function to do this, but as far as I can tell, I have to install Java to enable UDFs?!
Lastly, any idea why Sybase is such an awful, primitive database even at version 12?
My stored proc:
DECLARE cur CURSOR FOR SELECT ticket_no, price_st, price, cur FROM #t FOR UPDATE OF price
DECLARE
#ticket_no INT,
#price_st VARCHAR(20),
#price FLOAT,
#int FLOAT,
#32s FLOAT,
#frac VARCHAR(6),
#num FLOAT,
#denom FLOAT
OPEN cur
FETCH cur INTO #ticket_no, #price_st
WHILE (##SQLSTATUS != 2)
BEGIN
IF isnumeric(#price_st) = 1
BEGIN
SELECT #price = convert(FLOAT, #price_st)
END
ELSE
BEGIN
-- Convert a price like '99-22 3/4' to
-- 99 + (22/32) + (3/4 * 1/32)
SELECT #int = convert(FLOAT, substring(#price_st, 1, charindex('-', #price_st)-1))
SELECT #32s = convert(FLOAT, substring(#price_st, charindex('-', #price_st)+1, 2))
SELECT #frac = substring(#price_st, charindex(' ', #price_st)+1, 10)
SELECT #num = convert(FLOAT, substring(#frac, 1, charindex('/', #frac)-1))
SELECT #denom = convert(FLOAT, substring(#frac, charindex('/', #frac)+1, 3))
SELECT #price = #int + (#32s / 32) + (#num / (#denom * 32))
END
UPDATE #t SET price = #price WHERE CURRENT OF cur
FETCH cur INTO #ticket_no, #price_st
END
CLOSE cur
DEALLOCATE cur

Ah! I had a column in the table, with the same name as the cursor:
DECLARE cur CURSOR FOR SELECT ticket_no, price_st, price, cur FROM #t FOR UPDATE OF price
^ ^
This seems to put the Sybase server into a tailspin...

Cursors in Sybase are slower than SET based operations. Despite your reluctance I doubt you will do better than putting all the logic into one Update statement as below.
UPDATE #t
SET price =
CASE
WHEN isnumeric(price_st) = 1
THEN CONVERT(FLOAT, price_st)
ELSE
CONVERT(FLOAT, substring(price_st, 1, charindex('-', price_st)-1)) +
(CONVERT(FLOAT, substring(price_st, charindex('-', price_st)+1, 2)) / 32) +
(CONVERT(FLOAT, substring(substring(price_st, charindex(' ', price_st)+1, 10), 1, charindex('/', substring(price_st, charindex(' ', price_st)+1, 10))-1)) /
(CONVERT(FLOAT, substring(substring(price_st, charindex(' ', price_st)+1, 10), charindex('/', substring(price_st, charindex(' ', price_st)+1, 10))+1, 3)) * 32))
END

Related

Unique constraint violated message on inserting code below

I am writing this code which throws an error in primary key:
DECLARE CURSOR A1 AS
SELECT
TRANS_DET_ID,
(SELECT MAX (NVL(TRANS_DET_DET_ID, 0) + 1)
FROM PROD_OPERATIONS_RATE) DET_ID,
OPER_CODE, ART_CODE, RATE, FROM_DATE, CLOSE_IND
FROM
PROD_OPERATIONS_RATE
WHERE
TRANS_DET_ID = 1
AND OPER_CODE = 1
AND RATE = 2.3005;
a1_var A1%ROWTYPE;
BEGIN
OPEN A1;
LOOP
FETCH A1
INTO a1_var;
EXIT WHEN A1%NOTFOUND;
INSERT INTO PROD_OPERATIONS_RATE (
TRANS_DET_ID,TRANS_DET_DET_ID,OPER_CODE,ART_CODE,RATE,FROM_DATE,CLOSE_IND)
VALUES (1,a1_var.DET_ID,1,a1_var.ART_CODE,2.50,DATE '2022-05-01','N');
END LOOP;
CLOSE A1;
COMMIT;
END;
I want to insert data same table throw conditions met, it throws an error of unique constraint on column TRANS_DET_DET_ID which is the primary key. What am I doing wrong? Please can anyone help me with this? Regards
Unique (primary) key value which is calculated as MAX + 1 is almost always wrong. Switch to a sequence.
Find MAX trans_det_det_id value:
SELECT MAX (trans_det_det_id) max_id FROM PROD_OPERATIONS_RATE;
Create sequence as max_id + 1 (I put a dummy value of 1000; you'd use what query actually returns):
CREATE SEQUENCE seq START WITH 1000;
Now, use the sequence in your PL/SQL script:
DECLARE
CURSOR A1 IS
SELECT TRANS_DET_ID,
--(SELECT MAX (NVL (TRANS_DET_DET_ID, 0) + 1)
-- FROM PROD_OPERATIONS_RATE) DET_ID,
OPER_CODE,
ART_CODE,
RATE,
FROM_DATE,
CLOSE_IND
FROM PROD_OPERATIONS_RATE
WHERE TRANS_DET_ID = 1
AND OPER_CODE = 1
AND RATE = 2.3005;
a1_var A1%ROWTYPE;
BEGIN
OPEN A1;
LOOP
FETCH A1 INTO a1_var;
EXIT WHEN A1%NOTFOUND;
INSERT INTO PROD_OPERATIONS_RATE (TRANS_DET_ID,
TRANS_DET_DET_ID,
OPER_CODE,
ART_CODE,
RATE,
FROM_DATE,
CLOSE_IND)
VALUES (1,
seq.NEXTVAL, -- a1_var.DET_ID,
1,
a1_var.ART_CODE,
2.50,
DATE '2022-05-01',
'N');
END LOOP;
CLOSE A1;
COMMIT;
END;
By the way, if there's no particular reason for doing it slowly in a loop, use an ordinary INSERT INTO statement (SQL, not PL/SQL), it'll be much faster:
INSERT INTO prod_operations_rate (trans_det_id,
trans_det_det_id,
oper_code,
art_code,
rate,
from_date,
close_ind)
SELECT trans_det_id,
seq.NEXTVAL,
oper_code,
art_code,
rate,
from_date,
close_ind
FROM prod_operations_rate
WHERE trans_det_id = 1
AND oper_code = 1
AND rate = 2.3005;

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.

insert from one table to another table use procedure

I want to insert from one table to other with some edits in values. source table has 20,000,000 records and insert and commit them impossible . so i write a procedure to commit each 1000 insert in a loop. but it does not work. what is the problem?
CREATE OR REPLACE PROCEDURE CONVERT_INTO_K2 IS
batch_size number;
row_num number;
CURSOR trans IS
select rownum,KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRPAN),
t.TRPRRN, t.TRSIDE,t.switchcode,t.kcb_switchcode,
t.TERMID, t.TRTERMID,t.TRPOSCCOD,t.TRCAID,
t.TRMTI,t.TRPRCOD,t.TR87RSPCOD,t.TRMSGRSN,
t.TRREVFLG,t.TRSETTMD,t.TRCURCOD,t.TRAMNT,
t.TRORGAMNT,t.TRCHBLCUR,t.TRFEEAMNT,t.TRCHBLAMNT,
t.TRFRWIIC,t.TRACQIIC,t.TRRECIIC,t.PTRTRCNO,
t.BTRRN, t.TRTRACEDT, t.TRRSPDT, t.TRLDATE,
t.TRLTIME, t.BAID, t.BADATE,t.TRACNTID1,
KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRACNTID2),
t.ATJHNO,t.ATJHDAT, t.TRORGDTELM,t.TRADDATA,
KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRACNTID2),
Trn_ID.Nextval, Trn_diag.Nextval,
1, 1, 1, 1, 1, 1, 1, 1, 1
from P912.KCTRNS t
where t.trprcod != 47
and rownum < 200;
BEGIN
batch_size := 0;
row_num:=0;
FOR rec IN trans LOOP
batch_size := batch_size + 1;
row_num := row_num + 1;
if MOD( row_num, 1000 ) != 0 then
insert into P912.KCTRNS2
(srcpan,rfrnnum, trnsid,swchcod, prswchcod,intrtrmid,
xtrntrmid,trmcod, aptrid,msgtypidnt,trntyp,
rspcod, msqrsn,rvrsflg,sttlsts,currcod,
amt,origamt,crdhldrcurrcod,feeamt,
crdhldrdiscamt, isurcrdinstid,acqrcrdinstid,
rcvrcrdinstid,trcnum,intrrfrnnum,
rcvdt,rspdt, prrcvdtsctn,prrcvtmsctn,
btchid, btchiopendt,firsacctnum,
scndacctnum,docnum,docdt, origdtelmt,
dditdat,dstpan, id, diag, mngcod,
funccod, sttlcod,trnres, custno,
crdlesstrcno,accttyp1,accttyp2,chnltyp)
Values
(KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRPAN),
t.TRPRRN, t.TRSIDE,t.switchcode,t.kcb_switchcode,
t.TERMID, t.TRTERMID,t.TRPOSCCOD,t.TRCAID,
t.TRMTI,t.TRPRCOD,t.TR87RSPCOD,t.TRMSGRSN,
t.TRREVFLG,t.TRSETTMD,t.TRCURCOD,t.TRAMNT,
t.TRORGAMNT,t.TRCHBLCUR,t.TRFEEAMNT,t.TRCHBLAMNT,
t.TRFRWIIC,t.TRACQIIC,t.TRRECIIC,t.PTRTRCNO,
t.BTRRN, t.TRTRACEDT, t.TRRSPDT, t.TRLDATE,
t.TRLTIME, t.BAID, t.BADATE,t.TRACNTID1,
KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRACNTID2),
t.ATJHNO,t.ATJHDAT, t.TRORGDTELM,t.TRADDATA,
KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRACNTID2),
Trn_ID.Nextval, Trn_diag.Nextval,
1, 1, 0, 1, 1, 1, 1, 1, 1);
else
insert into P912.KCTRNS2
(srcpan,rfrnnum, trnsid,swchcod, prswchcod,intrtrmid,
xtrntrmid,trmcod, aptrid,msgtypidnt,trntyp,
rspcod, msqrsn,rvrsflg,sttlsts,currcod,
amt,origamt,crdhldrcurrcod,feeamt,
crdhldrdiscamt, isurcrdinstid,acqrcrdinstid,
rcvrcrdinstid,trcnum,intrrfrnnum,
rcvdt,rspdt, prrcvdtsctn,prrcvtmsctn,
btchid, btchiopendt,firsacctnum,
scndacctnum,docnum,docdt, origdtelmt,
dditdat,dstpan, id, diag, mngcod,
funccod, sttlcod,trnres, custno,
crdlesstrcno,accttyp1,accttyp2,chnltyp)
Values
(KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRPAN),
t.TRPRRN, t.TRSIDE,t.switchcode,t.kcb_switchcode,
t.TERMID, t.TRTERMID,t.TRPOSCCOD,t.TRCAID,
t.TRMTI,t.TRPRCOD,t.TR87RSPCOD,t.TRMSGRSN,
t.TRREVFLG,t.TRSETTMD,t.TRCURCOD,t.TRAMNT,
t.TRORGAMNT,t.TRCHBLCUR,t.TRFEEAMNT,t.TRCHBLAMNT,
t.TRFRWIIC,t.TRACQIIC,t.TRRECIIC,t.PTRTRCNO,
t.BTRRN, t.TRTRACEDT, t.TRRSPDT, t.TRLDATE,
t.TRLTIME, t.BAID, t.BADATE,t.TRACNTID1,
KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRACNTID2),
t.ATJHNO,t.ATJHDAT, t.TRORGDTELM,t.TRADDATA,
KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRACNTID2),
Trn_ID.Nextval, Trn_diag.Nextval,
1, 1, sttl_cod.Nextval, 1, 1, 1, 1, 1, 1);
end if;
IF batch_size = 10 THEN
begin
COMMIT;
end;
batch_size := 0;
end if;
END loop;
EXCEPTION
WHEN others THEN
ROLLBACK;
END CONVERT_INTO_K2;
The Values expression references t instead of rec the actual name of the cursor record. Try changing those t (only those in the Values expression, not those in the select) into rec.
Also make an alias for KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRPAN) it will be easier. Otherwise you will have to double quote (") and upper case the column name in the Values clause.
Finally and most importantly at first sight your need would be better served by using a insert into … from (select …) syntax. Does this not work?
Failling that, a more robust approach would be to add some form of state column to your source (PSAM952.KCTRNS) table that would indicate that the entry has already been inserted into the destination (PSAM961.KCTRNS2) table. Say 0 for the default value, 1 meaning it will be part of next batch, and 2 to say it has been copied.
In your example, you are doing a mod() of row_num to provide some conditional logic. But you do not have an order-by on you cursor, so the row to which the row_num is applied is not deterministic. Of course one you apply and order-by, then you need to rethink rownum, since that is applied prior to an order-by. So you really need to re-evaluate the logic of what you are trying to do.
Having said that, you can perform conditional logic during the insert as shown below.
insert /*+ APPEND */ into PSAM961.KCTRNS2
select KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRPAN)
, t.TRPRRN
,etc
...
case when xxx=0 then stt1_cod.nextval else 0 end
,1
,1
,etc
from PSAM952.KCTRNS t
where t.trprcod != 47
and rownum < 200;

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

Automate bulk of update queries in pl\sql

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'.

Resources