DB2 Stored Procedure - controlled large record deletion - database

Ive written a DB2 Stored Procedure for a housekeeping job that in theory could be deleting large volumes for data from the database.
The requirement is to control the deletion by committing a defined number of records at a time.
Firstly, I would like some feedback on my Stored Procedure to see if there is any improvements I could make.
Secondly, Ive got a question about SQL Errors. If an error occurs during an iteration of the loop, does the Stored Procedure exit immediately ? Ideally I would like to continue the loop trying to delete as many records as I can. Im not sure if my script works in this way or not.
CREATE PROCEDURE leave_loop(IN commit_unit INTEGER, OUT counter INTEGER)
LANGUAGE SQL
BEGIN
DECLARE v_prod_id INTEGER;
DECLARE v_delete_counter INTEGER DEFAULT 0;
DECLARE v_total INTEGER DEFAULT 0;
DECLARE not_found CHAR(1) DEFAULT 'N';
DECLARE c1 CURSOR WITH HOLD FOR
SELECT prod_id
FROM product
WHERE status_deleted = 1
ORDER BY prod_id;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET not_found = 'Y';
SET counter = 0;
OPEN c1;
delete_loop:
LOOP
-- Fetch the record
FETCH c1 INTO v_prod_id;
-- If not row found then leave the loop
IF not_found = 'Y' THEN
-- If we have not reached the commit unit the commit the outstanding records
IF v_delete_counter > 0 THEN
COMMIT;
END IF;
LEAVE delete_loop;
END IF;
-- Perform the deletion
DELETE FROM product WHERE prod_id = v_prod_id;
SET v_delete_counter = v_delete_counter + 1;
-- Check if the commit unit has been reached
IF MOD(v_delete_counter, commit_unit) = 0 THEN
COMMIT;
SET v_delete_counter = 0;
SET v_total = v_total + 1;
END IF;
END LOOP delete_loop;
CLOSE c1;
SET total = v_total;
END #

I had similar requirements in the past. Please find below the stored procedure which I wrote to serve my needs.
SET SERVEROUTPUT ON#
drop procedure DELETE_WITH_COMMIT_COUNT#
CREATE PROCEDURE DELETE_WITH_COMMIT_COUNT(IN v_TABLE_NAME VARCHAR(24), IN v_COMMIT_COUNT INTEGER, IN v_WHERE_CONDITION VARCHAR(1024))
NOT DETERMINISTIC
LANGUAGE SQL
BEGIN
-- DECLARE Statements
DECLARE SQLCODE INTEGER;
DECLARE v_COUNTER INTEGER DEFAULT 0;
DECLARE v_DELETE_QUERY VARCHAR(1024);
DECLARE v_DELETE_STATEMENT STATEMENT;
SET v_DELETE_QUERY = 'DELETE FROM (SELECT 1 FROM ' || v_TABLE_NAME || ' WHERE ' || v_WHERE_CONDITION
|| ' FETCH FIRST ' || RTRIM(CHAR(v_COMMIT_COUNT)) || ' ROWS ONLY) AS DELETE_TABLE';
PREPARE v_DELETE_STATEMENT FROM v_DELETE_QUERY;
DEL_LOOP:
LOOP
SET v_COUNTER=v_COUNTER + 1;
EXECUTE v_DELETE_STATEMENT;
IF SQLCODE = 100 THEN
LEAVE DEL_LOOP;
END IF;
COMMIT;
END LOOP;
COMMIT;
END#
You can add 'DECLARE CONTINUE HANDLER FOR SQLSTATE XXXX' to your stored procedure which will bypass the erroneous executions and shall avoid abrupt termination of your stored proc.

Related

Stored procedure always returns 1 instead of computed value of a variable

The purpose of this stored procedure is to check whether a record exists (by input parameter):
if not, create it with default value 0 and return 0.
if it does, then increment the last value by 1 and return new value
But no matter how I rearrange this, it always returns 1, and not my computed value. Other operations are fine, records are properly created, values are properly incremented.
CREATE OR ALTER PROCEDURE ORD_SCHEMA.ORDER_NUMBER_INCREMENTER
#allocatedDate DATE
AS
BEGIN
DECLARE #resultNumberValue int;
DECLARE #currentNumberValue int;
BEGIN TRANSACTION INCREMENTER_TRANSACTION
BEGIN TRY
SET #currentNumberValue = (SELECT NUMBER_VALUE
FROM ORD_SCHEMA.ORD_ORDER_NUMBER
WHERE ALLOCATED_DATE = #allocatedDate);
IF(#currentNumberValue IS NULL)
BEGIN
INSERT INTO ORD_SCHEMA.ORD_ORDER_NUMBER (ALLOCATED_DATE, NUMBER_VALUE)
VALUES(#allocatedDate, 0);
SET #resultNumberValue = 0;
END
ELSE
BEGIN
UPDATE ORD_SCHEMA.ORD_ORDER_NUMBER
SET NUMBER_VALUE = NUMBER_VALUE + 1
WHERE ALLOCATED_DATE = #allocatedDate;
SET #resultNumberValue = #currentNumberValue + 1;
END
COMMIT TRANSACTION INCREMENTER_TRANSACTION
RETURN #resultNumberValue;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION INCREMENTER_TRANSACTION
END CATCH
END

Stored procedure inserting the same record repeatedly instead of looping through list from SELECT

I am fairly new at writing procedures (beyond the basics)
I am trying to write a stored procedure that inserts into a table (dbo.billing_batch) based on a select statement that loops through the list of results (#DealerID FROM dbo.vehicle_info).
The SELECT DISTINCT... statement on its own works perfectly and returns a list of 54 records.
The result of the SELECT statement is dynamic and will change from week to week, so I cannot count on 54 records each time.
I am trying to use WHILE #DealerID IS NOT NULL to loop through the INSERT routine.
The loop is supposed to update dbo.billing_batch, however it is inserting the same 1st record (BillingBatchRosterID, DealerID) over and over and over to infinity.
I know I must be doing something wrong (I have never written a stored procedure that loops).
Any help would be greatly appreciated!
Here is the stored procedure code:
ALTER PROCEDURE [dbo].[sp_billing_batch_set]
#varBillingBatchRosterID int
AS
SET NOCOUNT ON;
BEGIN
DECLARE #DealerID int
SELECT DISTINCT #DealerID = vi.DealerID
FROM dbo.vehicle_info vi
LEFT JOIN dbo.dealer_info di ON di.DealerID = vi.DealerID
WHERE di.DealerActive = 1
AND (vi.ItemStatusID < 4 OR vi.ItemStatusID = 5 OR vi.ItemStatusID = 8)
END
WHILE #DealerID IS NOT NULL
BEGIN TRY
INSERT INTO dbo.billing_batch (BillingBatchRosterID, DealerID)
VALUES(#varBillingBatchRosterID, -- BillingBatchRosterID - int
#DealerID) -- DealerID - int
END TRY
BEGIN CATCH
SELECT ' There was an error: ' + error_message() AS ErrorDescription
END CATCH
You have the same problems as another recent post here: Iterate over a table with a non-int id value
Why do a loop? Just do it as a single SQL statement
If you must use a loop, you will need to update your #Dealer value at each run (e.g., to the next DealerId) otherwise it will just infinitely loop with the same DealerID value
Don't do a loop.
Here's an example not needing a loop.
ALTER PROCEDURE [dbo].[P_billing_batch_set]
#varBillingBatchRosterID int
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
INSERT INTO dbo.billing_batch (DealerID, BillingBatchRosterID)
SELECT DISTINCT vi.DealerID, #varBillingBatchRosterID
FROM dbo.vehicle_info vi
INNER JOIN dbo.dealer_info di ON di.DealerID = vi.DealerID
WHERE di.DealerActive = 1
AND (vi.ItemStatusID < 4
OR vi.ItemStatusID = 5
OR vi.ItemStatusID = 8
);
END TRY
BEGIN CATCH
SELECT ' There was an error: ' + error_message() AS ErrorDescription;
END CATCH;
END;
Note I
Changed the LEFT JOIN to an INNER JOIN as your WHERE clause needs the record to exist in the dealer_info table
Moved the SET NOCOUNT ON; to be within the BEGIN-END section
Moved the END to the end
Renamed your stored procedure as per the excellent comment from #marc_s (on the question itself)

How can I increase stored procedure performance with multiple if else statement?

I have a stored procedure with multiple if-elseif- else statements. When I try to remove this if elseif statement and execute only single portion of that query, it returns results very fast, but when use this query with multiple another queries using if-elseif case statement, then it takes too much time...
For example:
if #Status = 1
begin
select .....
end
else if #Status = 2
begin
select .....
end
else if #Status = 3
begin
select .....
end
else if....
There are many more else if statements in this stored procedure..
Although it may be in some "don't do this" books, you can try to create a stored procedure for each status value and call it directly by building a dynamic TSQL statement ("exec yourproc_" + statusValue).
create proc youproc_1 as
begin
select your_data_for_status1;
end;
create proc youproc_2 as
begin
select your_data_for_status2;
end;
etc...

Update a table with the result of a store procedure actions over a column from the table

I have a table name resultado, that is created by calling and executing another stored procedures or functions. As a result a have a column that contains '1' and '0' as a vector like this '111100001'. Also I have a store procedured that count the numbers of 1 in the 'vector' as it follows.
ALTER PROC [dbo].[CharCount](#String VARCHAR(4000),#caracter VARCHAR(2))
AS
BEGIN
DECLARE #long INT
DECLARE #numeroveces INT =0
DECLARE #consecutivo INT =0
DECLARE #consecutivo1 INT =0
SET #numeroveces=0
SET #long=LEN(#String)
WHILE #long>=0
BEGIN
IF #caracter=SUBSTRING(#String,#long,1)
BEGIN
set #numeroveces = #numeroveces + 1
set #consecutivo = #consecutivo + 1
END
IF #caracter<>SUBSTRING(#String,#long,1) or (#long = 0)
BEGIN
IF #consecutivo > #consecutivo1
BEGIN
set #consecutivo1 = #consecutivo
END
set #consecutivo = 0
END
SET #long=#long-1
END
SELECT #numeroveces
SELECT #consecutivo1
END
What I need is to execute the store procedure over a column from my table resultado and update the column with the result of the SP.
OriginalColumn--->SPResult---->UpdatedColumn
110111 ---> 5 ----> 5
Your procedure has 2 result sets the way you have it coded. Since in the example you posted here you are only trying to get the count of a given character I would strongly advise you to not use this procedure. It is horribly inefficient for such a simple task. This simple code will get the number of occurrences of a character without doing any looping. If you also need the longest consecutive count of occurrences there are better ways to do that too.
declare #String varchar(4000) = '111100001'
, #caracter VARCHAR(2) = '1'
select LEN(#String) - LEN(Replace(#String, #caracter, ''))

Prevent concurrent access to stored procedure in sql server

i have sequences table that consists of three columns:
Number,Year,Type
and for each new year a three new records gets created and updated all along this year.
my stored procedure for generating the sequence is used inside other stored procedures, and my issue is that i want to block concurrent access to this stored procedure and make the access as queue so if concurrent access occur one has to wait for another to finish so that two users don't get same sequence number, the code is as follows:
ALTER PROCEDURE [dbo].[GETSEQUENECENO]
#p_hijricYear INT ,
#p_typeId INT ,
#return_val INT OUTPUT
AS
BEGIN
DECLARE #newSequence INT
BEGIN TRY
SELECT #return_val = 0
SELECT #newSequence = ISNULL( max(correspondencenumber) ,0 )
FROM io_sequencenumbers with (XLOCK)
WHERE
hijricyear = #p_hijricyear
AND
typeid = #p_typeid
END TRY
BEGIN CATCH
SELECT #newSequence = -1
END CATCH
IF #newSequence != -1
BEGIN
IF #newSequence = 0
BEGIN
SELECT #newSequence = 1
INSERT INTO io_sequencenumbers
VALUES
( #newSequence ,
#p_hijricYear ,
#p_typeId )
END
ELSE
BEGIN
SELECT #newSequence = #newSequence + 1
UPDATE io_sequencenumbers
SET
correspondencenumber = #newSequence
WHERE hijricyear = #p_hijricyear
AND
typeid = #p_typeid
END
END -- end of #newSequence!= -1 --
SELECT #return_val = #newSequence
END
i read that setting isolation level to serializable may solve it, is that enough or i have to use also begin and end transaction in stored procedure and manually handling rollback and commit ?
One approach could be the use of SQL Server application locks, see sp_getapplock and sp_releaseapplock. This will let you serialise your sequence generation through the SP without the need for serialisable transactions but won't prevent access to the io_sequecenumbers table by other code so you'll need to be sure that this SP is the only place that updates this table.
I was able to optimize the sequence generation this way:
ALTER PROCEDURE [dbo].[GETSEQUENECENO]
#p_hijricYear INT ,
#p_typeId INT ,
#return_val INT OUTPUT
AS
BEGIN
DECLARE #newSequence numeric(18,0)
BEGIN TRY
UPDATE IO_SEQUENCENUMBERS WITH (READCOMMITTEDLOCK)
SET #newSequence = correspondencenumber = correspondencenumber + 1
WHERE
hijricyear = #p_hijricyear
AND
typeid = #p_typeid
END TRY
BEGIN CATCH
SELECT #newSequence = -1
END CATCH
SELECT #return_val = #newSequence
END

Resources