DB2 FUNCTION EXCEPTION - sql

I'm converting Oracle plsql function into DB2 plsql function.Below I mentioned the structure of Oracle and DB2 query.
CREATE FUNCTION FUNCTION_NAME (IN PARAMETER1 DATATYPE)
Return varchar(4000) IS
N_COLUMN1 varchar(1);
N_COLUMN2 INT;
N_COLUMN3 VARCHAR(2000);
BEGIN
SELECT A.COLUMN1,A.COLUMN2 INTO N_COLUMN1,N_COLUMN2 FROM TABLE A WHERE A.COLUMN1=PARAMETER1;
IF N_COLUMN1 = 'A' then N_COLUMN3:= 'NEW_A';
ELSEIF N_COLUMN1 = 'B' then N_COLUMN3:= 'NEW_B';
END IF;
exception when others then N_COLUMN3:= 'OTHERS'
RETURN N_COLUMN3;
END;
/
DB2 query:
CREATE FUNCTION FUNCTION_NAME (IN PARAMETER1 DATATYPE)
Returns varchar(4000)
BEGIN
declare N_COLUMN1 varchar(1);
declare N_COLUMN2 INT;
declare N_COLUMN3 VARCHAR(2000);
SELECT A.COLUMN1,A.COLUMN2 INTO N_COLUMN1,N_COLUMN2 FROM TABLE A WHERE A.COLUMN1=PARAMETER1;
IF N_COLUMN1 = 'A' then set N_COLUMN3= 'NEW_A';
ELSEIF N_COLUMN1 = 'B' then set N_COLUMN3= 'NEW_B';
END IF;
exception when others then set N_COLUMN3= 'OTHERS'
RETURN N_COLUMN3;
END;
I'm getting error on Exception part.I searched over internet and i'm getting DECLARE type HANDLER FOR SQLEXCEPTION they provide this syntax and mentioned before execution part need to declare this one.i'm not sure what word i need to use in place of type.can anyone help me what is the equaivalent DB2 syntax for exception when others

This depends on where you want to pass the control in your code after the handler invocation.
Look at the handler-declaration description.
Below is a possible example of the function code.
CREATE FUNCTION FUNCTION_NAME
(
IN PARAMETER1 VARCHAR (1)
)
Returns varchar(4000)
BEGIN
declare N_COLUMN1 varchar(1);
declare N_COLUMN2 INT;
declare N_COLUMN3 VARCHAR(2000);
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
-- The last statement executed in the function
-- when the handler fires.
-- The control is passed out of the function body
-- since the handler is declared
-- in the main BEGIN END function block.
RETURN 'OTHERS';
END;
SELECT A.COLUMN1,A.COLUMN2
INTO N_COLUMN1,N_COLUMN2
FROM
--TABLE A
(
VALUES
('A', 0)
, ('A', 0)
, ('B', 0)
) A (COLUMN1, COLUMN2)
WHERE A.COLUMN1=PARAMETER1;
IF N_COLUMN1 = 'A' then
set N_COLUMN3 = 'NEW_A';
ELSEIF N_COLUMN1 = 'B' then
set N_COLUMN3 = 'NEW_B';
END IF;
--exception when others then set N_COLUMN3= 'OTHERS'
RETURN N_COLUMN3;
END
SELECT P, FUNCTION_NAME (P) AS F
FROM (VALUES 'A', 'B') T (P)
P
F
A
OTHERS
B
NEW_B
fiddle

Related

Exit handler exception in SAP HANA procedure

In SAP HANA I tried to capture the error log in separate table but I can't...
This is my code:
create procedure "KABIL_PRACTICE"."PROC_ADV_SALES_INVENTORY"
(
IN "P_ID" integer,
IN "P_NAME" nvarchar(35),
IN "QTY" integer,
OUT Result "KABIL_PRACTICE"."TT_SALES_PRODUCT_INVENTORY")
LANGUAGE SQLSCRIPT
SQL SECURITY INVOKER
as begin LANGUAGE SQLSCRIPT
SQL SECURITY INVOKER
as begin
declare PROD_ID integer;
declare INV integer;
declare Q integer;
declare EP decimal (34,2);
declare SALES_AMOUNT Decimal(34,2);
select count(*) into PROD_ID from "KABIL_PRACTICE"."PRODUCT_ITEM_PROC" where "P_ID" = :P_ID;
select "Stock" into INV from "KABIL_PRACTICE"."PRODUCT_MASTER_PROC" where "P_ID" = :P_ID;
select "USP" into EP from "KABIL_PRACTICE"."PRODUCT_ITEM_PROC" where "P_ID" = :P_ID;
IF PROD_ID = 0 THEN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
insert into "KABIL_PRACTICE"."ERROR_LOG" values (::SQL_ERROR_CODE,::SQL_ERROR_MESSAGE);
select * from "KABIL_PRACTICE"."ERROR_LOG";
END;
ELSE IF INV >= :QTY THEN
SALES_AMOUNT := (:QTY * :EP);
insert into "KABIL_PRACTICE"."SALES_PROC" ("P_ID","P_NAME","QTY","Sales_AMount","Time_Of_Sale") values (:P_ID,:P_NAME,:QTY,:SALES_AMOUNT,CURRENT_TIMESTAMP);
update "KABIL_PRACTICE"."PRODUCT_MASTER_PROC" set "Stock" = "Stock"- :QTY where "P_ID" = :P_ID;
Result = select "S_ID","P_ID","P_NAME","QTY","Sales_AMount" from "KABIL_PRACTICE"."SALES_PROC";
END IF;
END IF;
end;
The same code for exit handler exception has worked in another procedure.
That is this code:
CREATE PROCEDURE "KABIL_PRACTICE"."EXCEPTION_EXAMPLE2" (
IN ip_id integer,
IN ip_name nvarchar(40),
OUT ex_message nvarchar(200) )
LANGUAGE SQLSCRIPT
SQL SECURITY INVOKER
AS
BEGIN
declare E integer;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
E := ::SQL_ERROR_CODE;
insert into "KABIL_PRACTICE"."ERROR_LOG" values
(:E,::SQL_ERROR_MESSAGE);
ex_message := 'SQL Exception occured. Error Code is: ' ||
::SQL_ERROR_CODE || ' Error message is: ' || ::SQL_ERROR_MESSAGE;
END;
INSERT INTO "KABIL_PRACTICE"."ERROR_TEST" VALUES( :ip_id, :ip_name);
ex_message := 'Product "' || :ip_id || '" inserted successfully';
END;
My doubt is: is my if condition correct or not... or the placement of that code is correct or not?
I think exception declaration just as parameter declarations should take place before SQL codes

calculate NULL values in mathematical string without replacing them with 0 PL/SQL

I need to calculate the result of a mathematical expression stored in a string (For example: 'A*(B+C)+(D*E)+F+G*(H+I)').
The string can contain any mathematical operator.
The first step of my program (a PL/SQL procedure) is to replace the variables with their values (which can be NULL)
But I can't replace the NULL values with zeros because I need to distinguish if the total result is NULL or 0.
example #1:
I expect to have NULL as a result and not 0.
NULL*(B+C)+(NULL*10)+NULL+5*(NULL+NULL)
example #3:
I expect to have 0 as a result
NULL*(B+C)+(NULL*10)+0+5*(NULL+NULL)
example 2:
I expect to have 99 as a result
NULL*(B+C)+(NULL*10)+99+5*(NULL+NULL)
Is there any way to do this dynamically? the mathematical expression can be of any other type ('A*B+C*D+E*G' or 'A*(B+C)+D*(E+F)' or simple A*B or A+B+C).
If there are no brackets, the mathematical operators' priorities must be respected as in standard calculation.
After replacing all values and cleaning the expression from NULL values, I use execute immediate to calculate the final result.
Thanks for your help!
You can do it using dynamic SQL (using Oracle's logic for NULL values in calculations - so NULL + anything = NULL and NULL * anything = NULL):
DECLARE
SUBTYPE VARIABLE_TYPE IS VARCHAR2(5);
TYPE VARIABLE_MAP IS TABLE OF NUMBER INDEX BY VARIABLE_TYPE;
variables VARIABLE_MAP;
expression VARCHAR2(4000) := 'A*(B+C)+(D*E)+F+G*(H+I)';
v VARIABLE_TYPE;
e VARCHAR2(4000);
r NUMBER;
BEGIN
-- Populate the variables values
variables('A') := 1;
variables('B') := 2;
variables('C') := 3;
variables('D') := 4;
variables('E') := 5;
variables('F') := 6;
variables('G') := 7;
variables('H') := 8;
variables('I') := 9;
-- Replace the variables with their values:
e := expression;
v := variables.FIRST;
WHILE v IS NOT NULL LOOP
e := REPLACE( e, v, COALESCE( TO_CHAR( variables(v) ), 'NULL' ) );
v := variables.NEXT(v);
END LOOP;
-- Perform the calculation
EXECUTE IMMEDIATE 'BEGIN :1 := ' || e || '; END;' USING IN OUT r;
-- Output the result
DBMS_OUTPUT.PUT_LINE( r );
END;
/
Output:
150
There is a nice function here https://social.msdn.microsoft.com/Forums/sqlserver/en-US/434baca4-56dd-4ba3-896c-a04e2d29b5ef/stack-with-sql-server?forum=transactsql
but this is for SQL Server. You will have to adapt it for handling operators such as -*,/, (& ). & you can manage your nulls here.
Basically you have to push this expression in a stack & evaluate (Pop) & solve your expression.
create table dbo.Stack
(
stkID int identity(1,1) not null primary key,
stkData varchar(50) not null,
)
go
create procedure dbo.spStackPush
(
#stkData varchar(10)
)
as
begin
set nocount on
insert into Stack (stkData) values (#stkData)
if ##error <> 0 raiserror('Could not push to stack', 16, 1)
return 0
end
go
create procedure dbo.spStackPop
(
#stkData varchar(10) output
)
as
begin
set nocount on
declare #stkID int
select
#stkData = stkData,
#stkID = stkID
from dbo.Stack
where stkID = (select max(stkID) from dbo.Stack)
delete from dbo.Stack where stkID = #stkID
return 0
end
go
create procedure dbo.spStackEvaluate
(
#stkData varchar(10) output
)
as
begin
set nocount on
declare #stkLeft varchar(10)
declare #stkRight varchar(10)
declare #stkNext varchar(10)
exec dbo.spStackPop #stkData output
if #stkData = '+'
begin
exec dbo.spStackPop #stkLeft output
exec dbo.spStackPop #stkRight output
set #stkData = cast(#stkLeft as int) + cast(#stkRight as int)
end
if exists(select 1 from dbo.Stack)
begin
exec dbo.spStackPop #stkNext output
exec dbo.spStackPush #stkData
exec dbo.spStackPush #stkNext
exec dbo.spStackEvaluate #stkData output
end
return 0
end
go
-- Test script
delete dbo.Stack
declare #stkData varchar(10)
exec dbo.spStackPush '10'
exec dbo.spStackPush '+'
exec dbo.spStackPush '20'
exec dbo.spStackPush '+'
exec dbo.spStackPush '30'
exec dbo.spStackPush '40'
exec dbo.spStackPush '+'
exec dbo.spStackEvaluate #stkData output
print #stkData

PL/SQL: ORA-00904: : invalid identifier

I am running the following SP but getting the error c1.pyid is invalid identifier. I am trying to use two different query results from one cursor. If there is any other way of using IF-else clause in a cursor, i am open to that too.
CREATE OR REPLACE
PROCEDURE FIX_DOCUMENT_RECORDS ( i_flag in varchar)
AS
Op_ID VARCHAR(8);
Op_Name VARCHAR(32);
skill VARCHAR(32);
temp_count VARCHAR(8);
temp_status VARCHAR(8):='Submitted';
QRYSTR VARCHAR2(400);
TYPE REF_CUR IS REF CURSOR;
c1 REF_CUR;
BEGIN
IF (i_flag='1') THEN
QRYSTR:='SELECT *
FROM dims_doc_master
WHERE concat_prod_id IS NULL
OR documenttypeid IS NULL
AND (pystatuswork = temp_status);';
ELSE
QRYSTR:='SELECT *
FROM dims_doc_master
WHERE (documentimageid IS NULL
AND p8id IS NULL)
AND (pystatuswork = temp_status);';
END IF;
open c1 FOR QRYSTR;
LOOP
BEGIN
DBMS_OUTPUT.PUT_LINE('loop begin');
UPDATE DIMS_DOC_MASTER
SET pystatuswork ='Cancelled',
documentstatus ='Cancelled',
cancellationdate='31-JAN-14',
cancelledbysid = c1.pxcreateoperator,
cancelreason ='Cancelled due to corruption.'
WHERE pyid =c1.pyid;
DBMS_OUTPUT.PUT_LINE('After updation'||c1.pyid );
--Begin PC_DOCUMENT UPDATION
UPDATE PC_DOCUMENT
SET pystatuswork ='Cancelled',
cancellationdate='31-JAN-14'
WHERE pyid =c1.pyid;
--Begin insert into History
--Select Operator name and ID
SELECT skill
INTO skill
FROM operator_map_skill
WHERE pyuseridentifier=c1.pxcreateoperator
AND rownum =1;
INSERT
INTO DIMS_DOC_HIST
(
DIMS_DOC_ID,
DOC_CHG_USR,
DOC_CHG_DT,
DOC_NEW_STS,
DOC_CHG_CMNT,
CRE_TS,
ROLE,
RSN_DESC,
TARGETROLE,
DOC_CHG_USR_ID,
DOC_ASG_USR_ID,
DOC_ASG_USR,
PREVSTATUS,
PREVSTATUSDT,
ASSIGNEDTODT,
TODISPLAY,
ACTIVITY_NAME
)
VALUES
(
c1.pyid,
'DIMS',
systimestamp,
'Cancelled',
'Cancelled due to corruption',
'31-JAN-14',
skill,
NULL,
skill,
c1.pxcreateoperator,
c1.pxcreateoperator,
c1.pxcreateopname,
'Submitted',
NULL,
systimestamp,
'Y',
'Updation through Script'
);
dbms_output.put_line
(
'Document ID= '||c1.pyid
)
;
SELECT COUNT(*)
INTO temp_count
FROM PC_ASSIGN_WORKBASKET
WHERE pxrefobjectinsname=c1.pyid;
IF(temp_count IS NOT NULL) THEN
DELETE FROM PC_ASSIGN_WORKBASKET WHERE pxrefobjectinsname=c1.pyid;
ELSE
DELETE FROM PC_ASSIGN_WORKLIST WHERE pxrefobjectinsname=c1.pyid;
END IF;
COMMIT;
END;
END LOOP;
CLOSE c1;
END;
You seem confusing cursor and fetched row.
In your current procedure: you open a cursor, do a loop (which looks to be endless since there is no EXIT statement), and after the loop you close the cursor (but it looks it will never happen)
To fetch results from a cursor, do the following:
CREATE OR REPLACE PROCEDURE ...
...
c1 REF_CUR;
ddm_record dims_doc_master%rowtype;
BEGIN
...
OPEN c1;
LOOP
FETCH c1 INTO ddm_record;
EXIT WHEN c1%NOTFOUND;
...
DBMS_OUTPUT.PUT_LINE('Document ID= ' || ddm_record.pyid); -- not c1.pyid
END LOOP;
CLOSE c1;
END;
/
Inspired from examples here: http://plsql-tutorial.com/plsql-explicit-cursors.htm
Try embedding the flag in your where clause:
open c1 FOR
SELECT *
FROM dims_doc_master
WHERE (i_flag='1' AND
(concat_prod_id IS NULL
OR documenttypeid IS NULL
AND (pystatuswork = temp_status))
OR (i_flag<>'1' AND
(documentimageid IS NULL
AND p8id IS NULL)
AND (pystatuswork = temp_status));
The logic can probably be simplified but logically that would work.

How to input an array parameter of values to Firebird Stored Procedure?

I would like to input an array parameter of IDs to Firebird Stored Procedure.
:INPUT_LIST_ID = [1, 2, 12, 45, 75, 45]
I'm need to execute this SQL command:
SELECT *
FROM CITY
WHERE ID_CITY IN (:INPUT_LIST_ID)
Is it possible?
Thanks!
You could also use something like this:
SELECT *
FROM CITY
WHERE ID_CITY IN (SELECT ID FROM GetIntegerList('1, 2, 12, 45, 75, 45'))
You would have to create a new Firebird Procedure called "GetIntegerList" which would look something like this:
CREATE OR ALTER PROCEDURE "GETINTEGERLIST"("AINTEGERLIST" VARCHAR(32000))
returns (
ID integer
)
as
declare variable IntegerList varchar(32000);
declare variable CommaPos integer;
declare variable IntegerVal varchar(10);
begin
IntegerList = AIntegerList || ' ';
CommaPos = Position(',', IntegerList);
while (CommaPos > 0) do
begin
IntegerVal = Trim(SubString(IntegerList from 1 for CommaPos - 1));
if (Char_Length(IntegerVal) > 0) then
begin
if (IntegerVal similar to '[0-9]*') then
begin
ID = Cast(IntegerVal as integer);
suspend;
end
end
if (Char_Length(IntegerList) > CommaPos) then
IntegerList = SubString(IntegerList from CommaPos + 1);
else
IntegerList = '';
CommaPos = Position(',', IntegerList);
end
IntegerList = Trim(IntegerList);
if (Char_Length(IntegerList) > 0) then
begin
if (IntegerList similar to '[0-9]*') then
begin
ID = Cast(IntegerList as integer);
suspend;
end
end
end
Note, this was done in Firebird 2.5.2.
AFAIK no, thats not possible. While Firebird does have array data type, support for it is rudimentary and use of arrays is generally not recommended. I think the easiest solution would be to pass the array as (comma separated) string and then use the for execute statement statement to get the resultset, something like
create procedure CITY (INPUT_LIST_ID varchar(1024))
returns( ... )
as
begin
for execute statement
'select ... from T where ID_CITY IN ('|| INPUT_LIST_ID ||')' into ...
do begin
suspend;
end
end
This means however that the statement you use to get the result also changes, instead of WHERE you would use the parameter of the stored procedure CITY:
SELECT * FROM CITY('1, 2, 12, 45, 75, 45')
Another option to send the parameter list is to use global temporary table. This has the pro that you can send huge number of IDs without exceeding the maximum allowed statements size but it is more work to set up the call...
create global temporary table SP_CITY_PARAMS (
id int not null primary key
)
on commit delete rows;
create procedure CITY
returns( ... )
as
begin
for select ... from T where ID_CITY IN (
select id from SP_CITY_PARAMS
) into ...
do begin
suspend;
end
end
Try this:
SELECT *
FROM CITY
WHERE '/city1/city2/city.../' containing '/' || ID_CITY || '/';
If you use Firebird 1.5 (It should work on higher versions too), you can use this simple function i made to convert a single string into an integer array:
create or alter procedure INTEGER_LIST (
input varchar(4096))
returns (
INT_VALUE integer)
as
declare variable CHAR_COUNT integer;
declare variable PARAM_LENGTH integer;
declare variable READ_VALUE char(1);
declare variable CURRENT_INTEGER varchar(20);
begin
param_length = strlen(input);
char_count = 0;
current_integer = '';
while (char_count < param_length) do begin
char_count = :char_count + 1;
read_value = substr(:input, :char_count, :char_count);
if (:read_value <> ',') then begin
current_integer = :current_integer || :read_value;
end else if (:read_value <> ' ') then begin
int_value = cast(:current_integer as integer);
current_integer = '';
suspend;
end
if (:char_count = :param_length) then begin
int_value = cast(:current_integer as integer);
suspend;
end
end
end
Usage
select int_value from integer_list('1,2,3,4, 5, 200, 1, 598415, 2')
Will return this:
INT_VALUE
1
2
3
4
5
200
1
598415
2

Getting an error in sql, when executing code below.How to declare a table type in plsql. Am a beginner . Please suggest

create or replace procedure BAS_NUM_UPD is
cursor cur is
select distinct o.oi_b,mpr.pa_ke_i,ltrim(substr(convert_171_to_711(cp.p_t_num),1,7),'0') bs_nbr
from t_obj o, mat_pa_rel mp, cor_pa cp
where o.ob_t = 'something'
and o.oi_b = mp.oi_b
and mp.pa_ke_i = cp.pa_ke_i;
l_ba_num_at_i number(10) := get_attribute_id('Ba timber');
flag1 VARCHAR2(10);
type t1 is table of varchar2(10);
par_k t1;
BEGIN
for x in cur loop
BEGIN
select pa_ke_i into par_k from mat_pa_rel where oi_b=x.oi_b ;
if par_k.count=null then
insert into cs_val (oi_b, at_i, value, flag, ) values (x.oi_b, l_ba_num_at_i, null, 1);
end if;
select flag into flag1 from cs_val where at_i = l_ba_num_at_i and oi_b = x.oi_b
and value = x.bs_nbr;
EXCEPTION
when NO_DATA_FOUND THEN
insert into cs_val (oi_b, at_i, value, flag, )
values (x.oi_b, l_ba_num_at_i, x.bs_nbr, 1);
flag1 :='Nothing';
when OTHERS
then
raise_application_error(-20011,'Unknown Exception in PROCEDURE');
END;
end loop;
end BAS_NUM_UPD;
error:
PLS-00642: local collection types not allowed in SQL statements
You should get it running if you do a bulk collect
select pa_ke_i bulk collect into par_k from mat_pa_rel where oi_b=x.oi_b ;
Then I think the if is not right. I think you need to do
if par_k.count = 0 then
But to be honest you might just make a count
select count(*) into l_cnt from mat_pa_rel where oi_b=x.oi_b;
If l_cnt = 0 then ...
Of course l_cnt has to be defined.
You should create type t1 in the schema and not in the pl/sql block.