Using a Cursor in a Stored Procudure - stored-procedures

I have the following code extremely slow code in a MS SQL 2016 Stored Procedure (I think it is stuck in a never ending loop):
#DBID Integer
AS
BEGIN
DECLARE TagCursor CURSOR FOR SELECT MemberID
FROM ADMIN_API_Master_Members_List
WHERE DBID= #DBID AND Len(Pending) > 0
DECLARE #ProgramCode VARCHAR(10)
DECLARE #Values VARCHAR(MAX)
DECLARE #tag nvarchar(10)
OPEN TagCursor
FETCH NEXT FROM TagCursor INTO #tag
WHILE (##FETCH_STATUS = 0)
BEGIN
SELECT #ProgramCode = Program_Code, #Values= Pending FROM ADMIN_API_Master_Members_List WHERE MemberID= #tag
DELETE FROM ADMIN_API_PMID_PROGRAM_CODE_HOLDING_TABLE
WHERE (MemberID =#tag)
INSERT INTO ADMIN_API_PMID_PROGRAM_CODE_HOLDING_TABLE ( ProgramCode, MemberID, DBID, PMID)
SELECT #ProgramCode, #tag, #DBID , Value FROM STRING_SPLIT(#Values, ',')
END
CLOSE TagCursor
DEALLOCATE TagCursor
END
This Procedure is only a maintenance process and does not get run very often but when it does it would be nice to only take a few seconds. The purpose is to normalize in a Table one record for each comma seperated value in the ADMIN_API_Master_Members_List Table and put it into the ADMIN_API_PMID_PROGRAM_CODE_HOLDING_TABLE with the Program_Code and MemberID and DBID.
There are only about 150 records in the Master Table and the comma separated strings may have 5 values. I am receptive to other solutions.
Thanks in advance

I haven't tested this, but like I mentioned in the comments, using a CURSOR is a bad idea; they are inherently slow as SQL Server excels at set based methods not iterative tasks (and a CURSOR is the latter).
I suspect that this achieves the answer you're after and avoids the CURSOR all together:
CREATE PROC YourProc #DBID integer
AS
BEGIN
DECLARE #Deleted table (ProgramCode varchar(10),
[Value] varchar(MAX),
Tag nvarchar(10));
DELETE HT
OUTPUT deleted.Program_Code,
deleted.Pending,
deleted.MemberID
INTO #Deleted (ProgramCode,
[Value],
Tag)
FROM ADMIN_API_PMID_PROGRAM_CODE_HOLDING_TABLE AS HT
JOIN ADMIN_API_Master_Members_List AS MML ON HT.MemberID = MML.MemberID
WHERE MML.[DBID] = #DBID
--AND LEN(Pending) > 0; --Changed this to below to be SARGable, as only a string with the value '' will have a length of 0.
AND Pending != '';
INSERT INTO ADMIN_API_PMID_PROGRAM_CODE_HOLDING_TABLE (ProgramCode,
MemberID,
DBID,
PMID)
SELECT D.ProgramCode,
D.Tag,
#DBID,
SS.Value
FROM #Deleted AS D
CROSS APPLY STRING_SPLIT(D.[Value], ',') AS SS;
END;

The reason for the infinite loop may be that you have no "Fetch next" inside your loop
Try the below:
#DBID Integer
AS
BEGIN
DECLARE TagCursor CURSOR FOR SELECT MemberID
FROM ADMIN_API_Master_Members_List
WHERE DBID= #DBID AND Len(Pending) > 0
DECLARE #ProgramCode VARCHAR(10)
DECLARE #Values VARCHAR(MAX)
DECLARE #tag nvarchar(10)
OPEN TagCursor
FETCH NEXT FROM TagCursor INTO #tag
WHILE (##FETCH_STATUS = 0)
BEGIN
SELECT #ProgramCode = Program_Code, #Values= Pending FROM ADMIN_API_Master_Members_List WHERE MemberID= #tag
DELETE FROM ADMIN_API_PMID_PROGRAM_CODE_HOLDING_TABLE
WHERE (MemberID =#tag)
INSERT INTO ADMIN_API_PMID_PROGRAM_CODE_HOLDING_TABLE ( ProgramCode, MemberID, DBID, PMID)
SELECT #ProgramCode, #tag, #DBID , Value FROM STRING_SPLIT(#Values, ',')
FETCH NEXT FROM TagCursor INTO #tag
END
CLOSE TagCursor
DEALLOCATE TagCursor
END

Related

Error -206 creating Interbase stored procedure

I am attempting to create a stored procedure using an IBConsole interactive SQL window. The intent of the procedure is to gather data from two tables, do some processing on that data, insert rows containing that data into a working table, then return the rows from the working table as the stored procedure result. The stored procedure:
SET TERM ^;
CREATE PROCEDURE BIBLIO_SUBJECT
RETURNS (ID INTEGER, SUBJECT VARCHAR(200))
AS
DECLARE VARIABLE CUR_SUBJECT VARCHAR(200);
DECLARE VARIABLE SUBJECT_LIST VARCHAR(500);
DECLARE VARIABLE BIBLIOID INTEGER;
DECLARE VARIABLE NEXTPOS INTEGER;
BEGIN
DELETE FROM TSUBJECTS;
FOR SELECT BIBLIO.ID, BIBLIO.SUBJECTS
FROM BIBLIO
WHERE BIBLIO.PUBLISH = TRUE
INTO :BIBLIOID, :SUBJECT_LIST
DO
BEGIN
SUBJECT_LIST = BIBLIO.SUBJECTS + ';';
NEXTPOS = LOCATE(";", SUBJECT_LIST);
WHILE (:NEXTPOS > 1) DO
BEGIN
CUR_SUBJECT = SUBSTR(:SUBJECT_LIST, 1, NEXTPOS - 1);
SUBJECT_LIST = SUBSTR(:SUBJECT_LIST, NEXTPOS + 1, STRLEN(SUBJECT_LIST));
INSERT INTO TSUBJECTS (SUBJECT, ID) VALUES(:CUR_SUBJECT, :BIBLIOID);
NEXTPOS = LOCATE(";", SUBJECT_LIST);
END
END
FOR SELECT BIBLIO_BEAST.BIBLIO_ID, BEAST.BEAST_NAME
FROM BIBLIO_BEAST
INNER JOIN BEAST ON (BEAST.ID = BIBLIO_BEAST.BEAST_ID)
INNER JOIN BIBLIO ON (BIBLIO.ID = BIBLIO_BEAST.BIBLIO_ID)
WHERE BIBLIO.PUBLISH = TRUE
INTO :BIBLIOID, :CUR_SUBJECT
DO
BEGIN
INSERT INTO TSUBJECTS (SUBJECT, ID) VALUES(:CUR_SUBJECT, :BIBLIOID);
END
FOR SELECT ID, SUBJECT
FROM TSUBJECTS
INTO :ID, :SUBJECT
DO
SUSPEND;
END^
SET TERM ;^
When I execute the above code in IBConsole I get the error:
Error at line 2
Dynamic SQL Error
SQL error code = -206
Column unknown
SQL - CREATE PROCEDURE BIBLIO_SUBJECT
RETURNS (ID INTEGER, SUBJECT VARCHAR(200))
AS
DECLARE VARIABLE CUR_SUBJECT VARCHAR(200);
DECLARE VARIABLE SUBJECT_LIST VARCHAR(500);
DECLARE VARIABLE BIBLIOID INTEGER;
DECLARE VARIABLE NEXTPOS INTEGER;
BEGIN
DELETE FROM TSUBJECTS;
FOR SELECT BIBLIO.ID, BIBLIO.SUBJECTS
FROM BIBLIO
WHERE BIBLIO.PUBLISH = TRUE
INTO :BIBLIOID, :SUBJECT_LIST
DO
BEGIN
SUBJECT_LIST = BIBLIO.SUBJECTS + ';';
NEXTPOS = LOCATE(";", SUBJECT_LIST);
WHILE (:NEXTPOS > 1) DO
BEGIN
CUR_SUBJECT = SUBSTR(:SUBJECT_LIST, 1, NEXTPOS - 1);
SUBJECT_LIST = SUBSTR(:SUBJECT_LIST, NEXTPOS + 1, STRLEN(SUBJECT_LIST));
INSERT INTO TSUBJECTS (SUBJECT, ID) VALUES(:CUR_SUBJECT, :BIBLIOID);
NEXTPOS = LOCATE(";", SUBJECT_LIST);
END
END
FOR SELECT BIBLIO_BEAST.BIBLIO_ID, BEAST.BEAST_NAME
FROM BIBLIO_BEAST
INNER JOIN BEAST ON (BEAST.ID = BIBLIO_BEAST.BEAST_ID)
INNER JOIN BIBLIO ON (BIBLIO.ID = BIBLIO_BEAST.BIBLIO_ID)
WHERE BIBLIO.PUBLISH = TRUE
INTO :BIBLIOID, :CUR_SUBJECT
DO
BEGIN
INSERT INTO TSUBJECTS (SUBJECT, ID) VALUES(:CUR_SUBJECT, :BIBLIOID);
END
FOR SELECT ID, SUBJECT
FROM TSUBJECTS
INTO :ID, :SUBJECT
DO
SUSPEND;
END
It doesn't say which column is "unknown", and line 2 is (apparently) the RETURNS clause of the CREATE PROCEDURE. Not at all helpful.
Each of the SQL statements in the stored procedure (3 SELECTs, a DELETE, 2 INSERTs) work without error if executed separately in an Interactive SQL window, so all of their columns are "known". BIBLIO_SUBJECT is not the name of an existing stored procedure, nor is it the name of an existing table, or anything else in the database.
This has me baffled. Online searches provide no answer. So I am hoping the clever Stack Overflow people can help me get this working.
David
Well, I figured it out. It was the statement:
SUBJECT_LIST = BIBLIO.SUBJECTS + ';';
The SUBJECT_LIST variable is already used in the preceding select. A silly mistake, but I am old.
Not that it matters since the Interbase UDF functions Locate() and Substr() can only handle 80 character strings, which makes them really useless to me... and probably useless to most.

DB2 - how to call a stored procedure that returns a result set in another user defined table function

I have a db2 stored procedure that takes in some parameters, gets some data from somewhere and then returns a result set through a cursor.
Now I want to write a table function in db2, that will call this stored procedure, read from the result set and return the data in the result set as a table (eventually I want to use this table function in a join).
I would like to know if this is permitted in db2 (we're using DB2 v10.5), i.e. execute a stored procedure in a table function and fetch and read from the result set from the stored procedure. If so, what is the right syntax for calling the stored procedure and reading the result set inside a table function in db2? Thanks!
Yes, it's possible. See the example below.
--#SET TERMINATOR #
CREATE OR REPLACE PROCEDURE TEST_PROC(P_TABSCHEMA VARCHAR(128))
DYNAMIC RESULT SETS 1
READS SQL DATA
BEGIN
DECLARE C1 CURSOR WITH HOLD WITH RETURN FOR
SELECT TABSCHEMA, TABNAME, COLCOUNT
FROM SYSCAT.TABLES
WHERE TABSCHEMA=P_TABSCHEMA;
OPEN C1;
END#
--CALL TEST_PROC('SYSCAT')#
CREATE OR REPLACE FUNCTION TEST_PROC(P_TABSCHEMA VARCHAR(128))
RETURNS TABLE (
TABSCHEMA VARCHAR(128)
, TABNAME VARCHAR(128)
, COLCOUNT INT
)
READS SQL DATA
BEGIN
DECLARE SQLSTATE CHAR(5);
DECLARE V_TABSCHEMA VARCHAR(128);
DECLARE V_TABNAME VARCHAR(128);
DECLARE V_COLCOUNT INT;
DECLARE V1 RESULT_SET_LOCATOR VARYING;
CALL TEST_PROC(P_TABSCHEMA);
ASSOCIATE RESULT SET LOCATOR (V1) WITH PROCEDURE TEST_PROC;
ALLOCATE C1 CURSOR FOR RESULT SET V1;
L1: LOOP
FETCH C1 INTO V_TABSCHEMA, V_TABNAME, V_COLCOUNT;
IF SQLSTATE<>'00000' THEN LEAVE L1; END IF;
PIPE(V_TABSCHEMA, V_TABNAME, V_COLCOUNT);
END LOOP L1;
CLOSE C1;
RETURN;
END#
SELECT * FROM TABLE(TEST_PROC('SYSCAT'))#
You need to create the DB2 table-function as follows:
CREATE FUNCTION database_schema.function_name ( IN_PARTID VARCHAR(1000) )
RETURNS TABLE ( PARTNO CHAR(25), PARTDS CHAR(30), QUANTITY INT )
BEGIN
RETURN SELECT PARTNO , PARTDS , CASE WHEN QUANTITY > 0 THEN QUANTITY ELSE 0 END QUANTITY
FROM
(
SELECT PARTNO
,MAX(PARTDS) AS PARTDS
,SUM(QUANTITY) AS QUANTITY
FROM database_schema.table_name
WHERE 1=1
AND PARTID = (CAST(IN_PARTID AS INT))
GROUP BY PARTNO
) AA;
END;
Then invoke the table-function as join or straight SQL:
SELECT partno,partds,quantity
FROM TABLE(database_schema.function_name('parameter_1'))

How can I create a stored procedure to only allow inserts in a particular month?

I want to create a stored procedure that only does inserts in the month of March. The stored procedure should accept values for the table but use the system date to determine if the records should be inserted.
This is what I was trying but procedure created with errors.
CREATE OR REPLACE PROCEDURE sp_time_1203383 (
p_sales_id IN sales_1203383.SALES_ID%TYPE,
p_product IN sales_1203383.PRODUCT%TYPE,
p_unitcost IN sales_1203383.UNITCOST%TYPE,
p_quantity IN sales_1203383.QUANTITY%TYPE
)
IS
BEGIN
IF( MONTH( GETDATE() ) = 3 )
BEGIN
INSERT INTO sales_1203383 ("SALES_ID", "PRODUCT", "UNITCOST", "QUANTITY")
VALUES (p_sales_id, p_product,p_unitcost, p_quantity);
END
ELSE
BEGIN
SELECT 'Can Only insert during the month of March'
END
COMMIT;
END;
I think Praveen gave you the right advice.
I personally would not use the
SELECT count(1) into v_cnt FROM dual WHERE month(sysdate) = 3;
but you can check in a if ... then ... else ... end; statement with the following code:
if to_char(sysdate,'mm') = '03' then ... end;
There are a few errors in your stored procedure:
You cannot have select with if statement
Use then with IF statement and at end End if
; has to be used with End statement
You cannot return a select set from a procedure/function/block (ref cursor should be used)
If you want raise error in the else part use raise_application_error.
If your select statement is like select 'something' use dummy table dual, select 'something' from dual
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
CREATE OR REPLACE PROCEDURE sp_time_1203383 (
p_sales_id IN sales_1203383.SALES_ID%TYPE,
p_product IN sales_1203383.PRODUCT%TYPE,
p_unitcost IN sales_1203383.UNITCOST%TYPE,
p_quantity IN sales_1203383.QUANTITY%TYPE
)
IS
v_cnt NUMBER;
BEGIN
SELECT count(1) into v_cnt FROM dual WHERE month(sysdate) = 3;
IF v_cnt > 0 THEN
INSERT INTO sales_1203383 ("SALES_ID", "PRODUCT", "UNITCOST", "QUANTITY")
VALUES (p_sales_id, p_product, p_unitcost, p_quantity);
ELSE
raise_application_error(-20101, 'Can Only insert during the month of March');
END IF;
END;

Passing Multi Value Paramter in stored proc

Firstname emp_Fullname
--------------------------------------
chetan Patel, Chetan
mike Shah, Mike
ronie Desai, Ronie
create proc stored_procedure
#firstnamer varchar(max)
#fullname varchar(max)
as
begin
select ......
from....
where Firstname in (SELECT Value FROM dbo.FnSplit(#firstname,','))
--and emp_Fullname in (SELECT Value FROM dbo.FnSplit(#fullname,','))
I want result for below statement
exec stored_procedure 'chetan,ronie', 'Patel, Chetan,Shah, Mike'
How can I pass more than 2 emp_fullname in parameter in given stored procedure? Below is my function dbo.FnSplit that worked for multi value Firstname parameter but not working multi value fullname parameter.
ALTER FUNCTION [dbo].[FnSplit]
(
#List nvarchar(2000),
#SplitOn nvarchar(5)
)
RETURNS #RtnValue table (Id int identity(1,1), Value nvarchar(100))
AS
BEGIN
WHILE(Charindex(#SplitOn, #List) > 0)
BEGIN
INSERT INTO #RtnValue (value)
SELECT
VALUE = ltrim(rtrim(Substring(#List, 1, Charindex(#SplitOn, #List) - 1)))
SET #List = SUBSTRING(#List, Charindex(#SplitOn, #List) + len(#SplitOn), len(#List))
END
INSERT INTO #RtnValue (Value)
SELECT
VALUE = ltrim(rtrim(#List))
RETURN
END
Firstname in (SELECT Value FROM dbo.FnSplit(#firstname,'|'))
and emp_Fullname in (SELECT Value FROM dbo.FnSplit(#fullname,'|'))
and I figured that still in SSRS double click on dataset click parmater instead of default value choose expression and set it to "join(#firstname.value,"|")" and samething for other "join(#fullname.value,"|")" and now run it. Multi valye parameter should work find by doing above procedure.
Thanks to my self lol:) it took me 3 days to figured, thought you guys can use it!

Autoincrement dependent of other field

What is the best way to get an autoincrement "counter" thath depends of other field?.
Imagine this table
CUSTOMER - COUNTER
1 - 1
1 - 2
1 - 3
2 - 1
2 - 2
I need Counter increments 1 every record I add for every customer.
Regards.
Create additional table to hold counters for every customer:
CREATE TABLE customer_counter
(
customer_id INTEGER NOT NULL,
counter INTEGER NOT NULL,
PRIMARY KEY (customer_id)
)
Use following procedure to obtain next counter number for given customer:
CREATE PROCEDURE get_customer_counter (customer_id INTEGER)
RETURNS (counter INTEGER)
AS
BEGIN
SELECT SUM(counter) FROM customer_counter
WHERE customer_id = :customer_id
INTO :counter;
counter = COALESCE(:counter, 0);
EXECUTE STATEMENT
'INSERT INTO customer_counter (customer_id, counter) ' ||
'VALUES (' || :customer_id || ', 1)'
WITH AUTONOMOUS TRANSACTION;
END
See how we use sum and insert delta values instead of updating single record. Thus we prevent deadlocks.
On regular basis we need to clear counter table by merging delta records into one with a total value:
CREATE TRIGGER on_disconnect_database
ACTIVE
ON DISCONNECT
AS
DECLARE VARIABLE sm INTEGER;
DECLARE VARIABLE cnt INTEGER;
DECLARE VARIABLE customer_id INTEGER;
BEGIN
FOR
SELECT customer_id, SUM(counter), COUNT(counter)
FROM customer_counter
GROUP BY customer_id
INTO :customer_id, :sm, :cnt
DO BEGIN
IF (:cnt > 1) THEN
BEGIN
DELETE FROM customer_counter WHERE customer_id = :customer_id;
INSERT INTO customer_counter (customer_id, counter)
VALUES (:customer_id, :sm);
WHEN ANY DO
BEGIN
END
END
END
END
It's hard to really tell what you exactly want of what you have given, but I assume you want to loop through each X field and it's Y sub-field, use it for a counter, or whatever else.
<?php
$x = array("customer1","customer2","customer3");
$y = array("name","address","phone");
$counter;
foreach($x as $eachX):
$counter = 1;
foreach($y as $eachY):
//do stuff here
$counter++;
endforeach;
endforeach;
?>
I think this stored procedure will be ok!
create procedure New_Procedure
returns (
o_customer integer,
o_counter integer)
as
declare variable v_oldcostomer integer;
begin
for select customer
from customers
order by 1
into o_customer
do begin
if (v_oldcostomer is null or (v_oldcostomer is not null and v_oldcostomer <> o_customer) ) then
o_counter = 0;
o_counter = o_counter + 1;
v_oldcostomer = o_customer;
suspend;
end
end

Resources