How to unlock a row if i use FOR UPDATE clause - stored-procedures

In procedure if we use FOR UPDATE clause, it will lock particular row and allow only one client to update whereas other client can only fetch data in the same row at that time.
My question is when will it unlock the row, what should we do to unlock the row while writing procedure?
Take this example here I'm using FOR UPDATE clause for client_count, when ll it unlock that particular row in this procedure.
create or replace PROCEDURE newprocedur(inMerid IN VARCHAR2,outCount OUT NUMBER) AS
CURSOR c1 IS
select CLIENT_COUNT
from OP_TMER_CONF_PARENT
where MER_ID = inMerid
FOR UPDATE OF CLIENT_COUNT;
BEGIN
Open c1;
loop
fetch c1 into outCount;
exit when c1%NOTFOUND;
outCount:=outCount+1;
update OP_TMER_CONF_PARENT
set CLIENT_COUNT = outCount
where current of c1;
end loop;
close c1;
END;

The updated rows will be unlocked after a commit.

Related

create columns in existing table and populate data using procedure

I will try to keep the query as short as possible. This involves 2 tables - lets call them staging_data and audit_data. STAGING_DATA has 3 columns:
user_no with data type number,
update_date_time with data type as date in DD-MON-YYYY HH24:MI:SS format
status_code which is varchar(1).
audit_data table also has the same 3 columns. The ask is to add 3 columns to audit_data table
seq_no (which will be unique to every user),
active_from (date type without the time format)
active_to (date type without the time format).
There is a procedure that inserts data from staging_data to audit_data.
Sample of the table audit_data
That data in audit table should look like :
For the next record for user_no 523(lets assume update_date_time is '23-Nov-2020 10:20') seq_no becomes 3, active_from_date becomes '23-Nov-2020', active_to becomes 31-Dec-99 and the active_to of user_no 523 with seq_no 2 becomes '22-Nov-2020'. So the data should look like this :
Highlighted the 3rd record which will be added later in light green.
So here goes my solution : I suggested to use row_number() over(partition by user_no) analytical function to get seq_no for each user. I wanted to create a view based on that but Boss doesn't want a view. He strictly wants to use a procedure. Procedure should check if the user_no exists (in this example 523). If exists then seq_no increases and active_to of the previous record for 523 changes to latest active_from - 1 date. I will be honest - I have no clue how to achieve this in Procedure. I understand I can create a cursor with the query I had in my mind for the view. But to add seq_no and change active_to date is something that has puzzled me. Can anyone please guide me in right direction/s? Also I apologise in advance if I have left out any other details. Its midnight here now and after 8 hours of racking my brain on this I am very hungry!
edit 11th Mar : here is the code for the procedure I wrote to insert data into the audit table for situation when a particular user_no has no record in audit table :
create or replace procedure test_aud IS
user_found_audit number;
lv_user_no AUDIT_DATA.user_no%TYPE;
cursor member_no is select distinct user_no from STAGING_DATA;
begin
open member_no;
loop
fetch member_no into lv_user_no;
exit when member_no%notfound;
select count(*) into user_found_audit from AUDIT_DATA where user_no = lv_user_no;
if user_found_audit = 0 then
insert into AUDIT_DATA(user_no, update_date_time,status_code, seq_no, last_update_date, active_from, active_to)
select user_no, update_date_time,status_code,row_number() over(partition by user_no order by UPDATE_DATE_TIME) as seqno,
to_char(trunc(update_date_time),'DD-MON-YYYY'),
to_char(trunc(update_date_time),'DD-MON-YYYY'),
lead(to_char(trunc(update_date_time)-1,'DD-MON-YYYY'),1,'31-DEC-99') over(PARTITION BY user_no ORDER BY UPDATE_DATE_TIME) from STAGING_DATA where user_no = lv_user_no;
commit;
else
dbms_output.put_line(lv_user_no||' exists in audit table');
-- to code the block when user_no exists, involves an update and insert
end if;
end loop;
close member_no;
end;
/
Well you need to collect a couple things. The latest stage row and the latest audit row. Then it is just a matter of generating the new audit information and updating the previous latest one. The following makes a couple simplifying assumptions:
Only the latest stage data for a given user_no needs processed as
all prior have been processed, However it does not assume the stage
table has been cleared.
The sequencing of 'Y' and 'N' status_codes are properly order in
that manner. In fact it does not even check the value.
It need not concern itself with the inherent race condition. The
condition is derives from seq_no being generated as Max()+1. This
structure virtually guarantees a duplicate will eventually be
created.
The nested procedure "establish_audit" does all the actual work. The rest are just supporting characters, including a couple just for debug purpose. See fiddle.
create or replace
procedure generate_stage_audit(user_no_in staging_data.user_no%type)
as
k_end_of_time constant date := date '9999-12-31';
l_latest_user_stage staging_data%rowtype;
l_latest_user_audit audit_data%rowtype;
procedure establish_audit
is
begin
insert into audit_data(user_no, update_date_time, status_code
,seq_no, active_from, active_to)
select l_latest_user_stage.user_no
, l_latest_user_stage.update_date_time
, l_latest_user_stage.status_code
, coalesce(l_latest_user_audit.seq_no,0) + 1
, trunc(l_latest_user_stage.update_date_time)
, k_end_of_time
from dual;
update audit_data
set active_to = trunc(l_latest_user_stage.update_date_time - 1)
where user_no = l_latest_user_audit.user_no
and seq_no = l_latest_user_audit.seq_no;
end establish_audit;
procedure retrieve_latest_stage
is
begin
select *
into l_latest_user_stage
from staging_data
where (user_no, update_date_time) =
( select user_no, max(update_date_time)
from staging_data
where user_no = user_no_in
group by user_no
);
end retrieve_latest_stage;
procedure retrieve_latest_audit
is
begin
select *
into l_latest_user_audit
from audit_data
where (user_no, seq_no) =
( select user_no, max(seq_no)
from audit_data
where user_no = user_no_in
group by user_no
);
exception
when no_data_found then
null;
end retrieve_latest_audit;
---- for debugging ---
procedure show_stage
is
begin
dbms_output.put_line('-------- Stage Row -------');
dbms_output.put_line(' user_no==>' || to_char(l_latest_user_stage.user_no));
dbms_output.put_line('update_date_time==>' || to_char(l_latest_user_stage.update_date_time));
dbms_output.put_line(' status_code==>' || to_char(l_latest_user_stage.status_code));
end show_stage;
procedure show_audit
is
begin
dbms_output.put_line('-------- Audit Row -------');
dbms_output.put_line(' user_no==>' || to_char(l_latest_user_audit.user_no));
dbms_output.put_line('update_date_time==>' || to_char(l_latest_user_audit.update_date_time));
dbms_output.put_line(' status_code==>' || to_char(l_latest_user_audit.status_code));
dbms_output.put_line(' seq_no==>' || to_char(l_latest_user_audit.seq_no));
dbms_output.put_line(' active_from==>' || to_char(l_latest_user_audit.active_from));
dbms_output.put_line(' active_to==>' || to_char(l_latest_user_audit.active_to));
end show_audit;
begin -- the main event
retrieve_latest_stage;
show_stage;
retrieve_latest_audit;
show_audit;
establish_audit;
end generate_stage_audit;
A couple warnings:
It seems you may be tempted to use string data type for the audit
columns Active_Form and Active_to as you are trying to declare then
"date type without the time". However there is no such data type in
Oracle; time is part of all dates. Do not do so, store them as
standard dates. (Note Dates are not stored in any format, but an
internal structure. Formats are strictly a visual representation).
Just throwaway the time with the format on the query or by setting
nls_date_format.
You may be tempted to convert call this through a trigger. Do not,
it will likely result in an "ORA-04091: Table is mutating"
exception.

Invalid identifier procedure

I'm writing procedure to transfer data from table in one Db named as 'Dev( or testing)' to another table in fdw db where we'll have repository table. Every month, last business day -2, we'll clean data from dev table and transfer it to repository table.
I'm super new to this technology and testing procedure and getting follwiing error
[Error] ORA-00904 (12: 15):
PL/SQL: ORA-00904: "USB"."BAS2_AGENCY_TO_RISKRATE_REPOS"."AS_OF_DATE: invalid identifier
CREATE OR REPLACE PROCEDURE USB.Basel2_riskrating
AS
BEGIN
INSERT INTO USB.BAS2_AGENCY_TO_RISKRATE_REPOS#OFSADEV --INSERTS DATA IN REPOSITORY TABLE
SELECT *
FROM USB.BAS2_AGENCY_TO_RISKRATING_TRAN M
WHERE USB.BAS2_AGENCY_TO_RISKRATE_REPOS.AS_OF_DATE != M.AS_OF_DATE ; --COMPARES DATE COLUMN TO REMOVE DUPLICACY
COMMIT;
END Basel2_riskrating;
Could you please help me in this. Also, it will be great if one could guide me with sample procedure code in wiping data from USB.BAS2_AGENCY_TO_RISKRATING_TRAN table at each month end.
As suggested in the comments, use NOT EXISTS. It's not celar whether you want to check for duplicates in the remote table usb.bas2_agency_to_riskrate_repos#ofsadev or a local version of the same. Use the appropriate table inside NOT EXISTS to do the comparison.
CREATE OR REPLACE PROCEDURE usb.basel2_riskrating AS
BEGIN
INSERT INTO usb.bas2_agency_to_riskrate_repos#ofsadev
SELECT * FROM usb.bas2_agency_to_riskrating_tran m
WHERE NOT EXISTS ( select 1 from
usb.bas2_agency_to_riskrate_repos e --#ofsadev?
where e.as_of_date = m.as_of_date
);
COMMIT; --Try to avoid commits inside procedure, move it to execution section
END basel2_riskrating;
SELECT * FROM USB.BAS2_AGENCY_TO_RISKRATING_TRAN M WHERE USB.BAS2_AGENCY_TO_RISKRATE_REPOS.AS_OF_DATE != M.AS_OF_DATE ;
is wrong. You can not run such a select against DB, it has no USB.BAS2_AGENCY_TO_RISKRATE_REPOS in from clause

PL SQL Procedure Exercise

I'm not really sure how to approach this question.. I understand the basic syntax of writing a procedure. This is an exercise for a beginner database class (which seems to be at a level way above beginner)
Create a procedure to place a purchase order for a specified date based on data in the inventory report table.
Name the procedure placeorder [procedure name is important].
The procedure should take one parameter: inputDate (use the datatype of the PODate column in PURCHASEORDERS). The input date format accepted should be: 'DD-MON-YYYY', e.g., 01-JAN-2017.
For each raw material in the Inventory Report table (where: ReportDate matches inputDate), make a separate entry in the PURCHASEORDERS table for a next-day delivery order and a same-day delivery order (each raw material may generate up to 2 inserts).
Corresponding order type should be either next_day or same_day
Only make an entry (insertion) in PURCHASEORDERS if needed, i.e., if
an entry exists for a raw material and report date combination in the
Inventory Report table. If the Inventory Report for a day (e.g.,
30-NOV-2017) has a 0 value for the ordersameday attribute, it means
no same_day order is needed.
If no order is needed for the provided input date (i.e., no order at all across ALL raw materials), raise an application error with a message: “no order needed” (see Triggers and Procedures tutorial on D2L for an example of how to raise this error). You can use any suitable error-number.  Your procedure should leave the “Price” (for the purchase order) empty (i.e., it can remain null). Assume it’ll be populated later.
Thanks!
Sorry this is what i've written so far.. #kara
CREATE OR REPLACE PROCEDURE placeorder (inputDate in DATE)
AS
new_inputDate PURCHASEORDERS.PODate%TYPE;
new_orderType PURCHASEORDERS.ORDERTYPE%TYPE;
c_orderSameDay INVENTORYREPORT.ORDERSAMEDAY%TYPE;
c_orderNextDay INVENTORYREPORT.ORDERNEXTDAY%TYPE;
CURSOR C1 IS
SELECT REPORTDATE INTO inputDate FROM dual;
SELECT ir.itemId, ir.ORDERSAMEDAY, ir.ORDERNEXTDAY FROM INVENTORYREPORT
WHERE
ir.REPORTDATE = inputDate;
BEGIN
OPEN C1;
WHILE C1%FOUND LOOP
FETCH C1 INTO new_inputDate, new_orderType, c_orderSameDay, c_orderNextDay;
IF c_orderSameDay > 0
THEN INSERT INTO PURCHASEORDERS (new_orderType) VALUES (orderSameDay);
ELSE <application error>;
END IF;
IF c_orderNextDay > 0
THEN INSERT INTO PURCHASEORDERS (new_orderType) VALUES (orderNextDay);
ELSE <application error>;
END IF;
END LOOP;
CLOSE C1;
END;
/
#kara I added to the if statements but still getting a couple errors when trying to compile the procedure. It this doing what it's supposed to be doing?
CREATE OR REPLACE PROCEDURE placeorder (inputDate in DATE)
AS
new_inputDate PURCHASEORDERS.PODate%TYPE;
new_orderType PURCHASEORDERS.ORDERTYPE%TYPE;
c_orderSameDay INVENTORYREPORT.ORDERSAMEDAY%TYPE;
c_orderNextDay INVENTORYREPORT.ORDERNEXTDAY%TYPE;
CURSOR C1 IS
SELECT REPORTDATE INTO inputDate FROM dual;
SELECT ir.itemId, ir.ORDERSAMEDAY, ir.ORDERNEXTDAY FROM INVENTORYREPORT
WHERE
ir.REPORTDATE = inputDate;
BEGIN
OPEN C1;
WHILE C1%FOUND LOOP
FETCH C1 INTO new_inputDate, new_orderType, c_orderSameDay, c_orderNextDay;
IF c_orderSameDay > 0
THEN INSERT INTO PURCHASEORDERS (new_orderType) VALUES (orderSameDay);
ELSE INSERT INTO PURCHASEORDERS (new_orderType) VALUES ('no order needed');
END IF;
IF c_orderNextDay > 0
THEN INSERT INTO PURCHASEORDERS (new_orderType) VALUES (orderNextDay);
ELSE INSERT INTO PURCHASEORDERS (new_orderType) VALUES ('no order needed');
END IF;
FETCH C1 INTO new_inputDate, new_orderType, c_orderSameDay, c_orderNextDay;
END LOOP;
CLOSE C1;
COMMIT;
END placeorder;
/
There are a bunch of examples how to create a procedure. Here a small one:
CREATE OR REPLACE PROCEDURE ProcName (paraName IN VARCHAR2)
AS
myStringVariable VARCHAR2 (4000);
myDateVariable DATE;
BEGIN
SELECT orderDateAsString
INTO myStringVariable
FROM orders
WHERE orderId = paraName;
myDateVariable := TO_DATE (myStringVariable, 'dd.mm.yyyy HH24:MI:SS'); -- '13.03.2018 23:59:59'
dbms_output.put_line('My date: ' || myStringVariable);
END;
And some example-code, to call the procedure:
begin
ProcName('1234');
end;
But you should look at your exercise first and think about you tasks. I think, this is what you got to do:
Create a Table.
Create Insert-Statements for the table
Create some PL/SQL-code which performs the insert with conditions.
move the PL/SQL-code to a procedure.
Call the procedure with a PL/SQL-block.
Explaination for you edits
You should build your code step-by-step if. You got multiple errors.
Check you Cursor. The Select-statement is not valid.
You wrote two selects.
You do not use new_inputdate. Why did you define it and what should it do?
Run you SELECT-statement alone without your code. if you like your data you can put it in an cursor.
Example with your code:
CREATE OR REPLACE PROCEDURE placeorder (inputDate IN DATE)
AS
-- new_inputDate PURCHASEORDERS.PODate%TYPE; --you don't use this one
new_orderType PURCHASEORDERS.ORDERTYPE%TYPE;
c_orderSameDay INVENTORYREPORT.ORDERSAMEDAY%TYPE;
c_orderNextDay INVENTORYREPORT.ORDERNEXTDAY%TYPE;
-- you mixed up your cursors. seems like you didn't try the select-statement alone..
CURSOR C2
IS
SELECT ir.itemId, ir.ORDERSAMEDAY, ir.ORDERNEXTDAY
FROM INVENTORYREPORT
WHERE ir.REPORTDATE = inputDate;
BEGIN
OPEN C2;
WHILE C2%FOUND
LOOP
FETCH C2 INTO new_orderType, c_orderSameDay, c_orderNextDay;
NULL; -- Do something
END LOOP;
CLOSE C2;
END;
/
Think about what you want to do in the loop. Build a small skript with the variables and think about what it should do:
Example-Skript of you Loop-content:
DECLARE
-- new_inputDate PURCHASEORDERS.PODate%TYPE; --you don't use this one
new_orderType NUMBER := 0;
c_orderSameDay NUMBER := 1;
c_orderNextDay NUMBER := 2;
BEGIN
-- this is what you're doing in you loop.
IF c_orderSameDay > 0 -- check if c_orderSameDay is greater than 0? In Oracle you use 'NULL' as empty value. Perhaps it should be 'c_orderSameDay IS NOT NULL'
THEN
INSERT INTO PURCHASEORDERS (new_orderType) -- you perform your insert.
VALUES (orderSameDay);
ELSE
RAISE; -- you raise an exception? this means c_orderSameDay has alsway to be set.
END IF;
IF c_orderNextDay > 0
THEN
INSERT INTO PURCHASEORDERS (new_orderType)
VALUES (orderNextDay);
ELSE
RAISE; -- you raise an exception? this means c_orderNextDay has alsway to be set.
END IF;
END;

select for update in stored procedure (concurrently increment a field)

I want to retrieve the value of a field and increment it safely in Informix 12.1 when multiple users are connected.
What I want in C terms is lastnumber = counter++; in a concurrent environment.
The documentation mentions one way of doing this which is to make everyone connect with a wait parameter, lock the row, read the data, increment it and release the lock.
So this is what I tried:
begin work;
select
lastnum
from tbllastnums
where id = 1
for update;
And I can see that the row is locked until I commit or end my session.
However when I put this in a stored procedure:
create procedure "informix".select_for_update_test();
define vLastnum decimal(15);
begin work;
select
lastnum
into vLastnum
from tbllastnums
where id = 1
for update;
commit;
end procedure;
The database gives me a syntax error. (tried with different editors) So why is it a syntax error to write for update clause within a stored procedure? Is there an alternative to this?
Edit
Here's what I ended up with:
DROP TABLE if exists tstcounter;
^!^
CREATE TABLE tstcounter
(
id INTEGER NOT NULL,
counter INTEGER DEFAULT 0 NOT NULL
)
EXTENT SIZE 16
NEXT SIZE 16
LOCK MODE ROW;
^!^
ALTER TABLE tstcounter
ADD CONSTRAINT PRIMARY KEY (id)
CONSTRAINT tstcounter00;
^!^
insert into tstcounter values(1, 0);
^!^
select * from tstcounter;
^!^
drop function if exists tstgetlastnumber;
^!^
create function tstgetlastnumber(pId integer)
returning integer as lastCounter
define vCounter integer;
foreach curse for
select counter into vCounter from tstcounter where id = pId
update tstcounter set counter = vCounter + 1 where current of curse;
return vCounter with resume;
end foreach;
end function;
^!^
SPL and cursors 'FOR UPDATE'
If you manage to find the right bit of the manual — Updating or Deleting Rows Identified by Cursor Name under the FOREACH statement in the SPL (Stored Procedure Language) section of the Informix Guide to SQL: Syntax manual — then you'll find the magic information:
Specify a cursor name in the FOREACH statement if you intend to use the WHERE CURRENT OF cursor clause in UPDATE or DELETE statements that operate on the current row of cursor within the FOREACH loop. Although you cannot include the FOR UPDATE keywords in the SELECT ... INTO segment of the FOREACH statement, the cursor behaves like a FOR UPDATE cursor.
So, you'll need to create a FOREACH loop with a cursor name and take it from there.
Access to the manuals
Incidentally, if you go to the IBM Informix Knowledge Center and see this icon:
that is the 'show table of contents' icon and you need to press it to see the useful information for navigating to the manuals. If you see this icon:
it is the 'hide table of contents' icon, but you should be able to see the contents down the left side. It took me a while to find out this trick. And I've no idea why the contents were hidden by default for me, but I think that was a UX design mistake if other people also suffer from it.

Sybase ASE Stored Precedure

I want to write a Stored Proc which will need to perform below steps
Get all the rows from a table where flag= 'Y' and status = != 'PROCESSED'
Update rows from step 1 , set status = 'PROCESSED'
I want to do this because this SP will be called every 5 mins from my java program and i do not want to pick the rows which i have already returned from SP thats why i need to mark them processed.
Something like this?
Retrieve the rows you're interested in. Use the holdlock keyword to ensure nothing can sneak in an extra row between the select and the update. The lock is held until the end of the transaction.
The stored procedure performs the retrieval with the shared lock and then upgrades that to exclusive with the update statement.
When the transaction commits the locks are released.
create proc update_status as
begin transaction
select *
from
t1 holdlock
where
flag = 'Y'
and status != 'PROCESSED'
update t1 set
status = 'PROCESSED'
where
flag = 'Y'
and status != 'PROCESSED'
commit
go

Resources