DB2 Disable auto commit inside stored procedure - stored-procedures

I have several inserts in my stored procedure. But if any block has an error, all made inserts should be reverted. This means all the operations inside the stored procedure should be atomar.
Concretely in my example below, when block 2 failed, than all the inserts from block 1 should be reverted. How can I do it?
DECLARE C1 CURSOR FOR S1;
--Block 1
PREPARE S1 FROM
'WITH TEMP AS
(
SELECT *
FROM TABLEE
WHERE ID = 2
),
TEMP_1 AS
(
SELECT COUNT(1) AS ID
FROM NEW TABLE
(
INSERT INTO TABLE_A (Col_1, Col_2)
SELECT Col_1, ''' || V || '''
FROM TABLE_A
JOIN TABLEE ON ID = Col_1
)
),
SELECT 1
FROM SYSIBM.SYSDUMMY1';
OPEN C1;
CLOSE C1;
--Block 2
PREPARE S1 FROM
'WITH TEMP AS
(
SELECT *
FROM TABLEEX
WHERE ID = 2
),
TEMP_1 AS
(
SELECT COUNT(1) AS ID
FROM NEW TABLE
(
INSERT INTO TABLE_AA (Col_1, Col_2)
SELECT Col_1, ''' || V || '''
FROM TABLE_AA
JOIN TABLEE ON ID = Col_1
)
),
SELECT 1
FROM SYSIBM.SYSDUMMY1';
OPEN C1;
CLOSE C1;

Example.
--#SET TERMINATOR #
DECLARE GLOBAL TEMPORARY TABLE SESSION.TEST_SP (I INT) WITH REPLACE ON COMMIT PRESERVE ROWS#
COMMIT#
INSERT INTO SESSION.TEST_SP (I) VALUES 1#
BEGIN
--/*
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
--ROLLBACK TO SAVEPOINT SP1;
END;
--*/
--SAVEPOINT SP1 ON ROLLBACK RETAIN CURSORS;
INSERT INTO SESSION.TEST_SP (I) VALUES 2;
SIGNAL SQLSTATE '75000' SET MESSAGE_TEXT = 'Error!';
END#
COMMIT#
SELECT * FROM SESSION.TEST_SP#
If you run this script as is with autocommit switched off, you get 2 rows in the session table. The same is when the exception handler definition is commented out entirely.
But if you uncomment the 2 commented out lines (with SAVEPOINT), you get 1 row in this table (inserted by the 1-st standalone INSERT) - all the changes made by the BEGIN END block will be rolled back by the ROLLBACK TO SAVEPOINT statement.
Note, that you could achieve the same with an UNDO handler (see handler-declaration), but it's allowed in the inlined compound sql only (BEGIN ATOMIC ... END), and you can't use it since you need dynamic sql (PREPARE).

Related

Solve the syntax error with Redshift operator does not exist and add explicit casts

I am a newbie in the area of redshift data modeling and got myself into trouble with an error.ERROR:
--Final version
syntax error ERROR: operator does not exist: text | record Hint: No
operator matches the given name and argument type(s). You may need to
add explicit type casts. Where: SQL statement "SELECT 'create temp
table ' || $1 || ' as select * from' | $2 |" PL/pgSQL function "egen"
line 36 at execute statement [ErrorId:
1-61dc32bf-0a451f5e2c2639235abb8876]
I am trying to do a simple transformation that gets returned in output when the procedure is called. (As of now I got to find from the documentation we have to use either temp table or cursors to achieve this)
Pseudocode:
I am trying to restrict data to its latest one in (2019) Get the
list of managers create columns if a person is a manager or not from the list.
Return it as a result
Data looks as follows Employee Data
My Select query works fine out of the procedure, please find my complete code below.
CREATE OR REPLACE PROCEDURE EGEN(tmp_name INOUT varchar(256) )
AS $$
DECLARE
--As i have less data managed to create it as an array or please use temp or table and join it with the actual query to perform transformation
MGR_RECORD RECORD;
DATAS RECORD;
item_cnt int := 0;
V_DATE_YEAR int := 0;
BEGIN
--EXECUTE (select cast(extract(year from current_date) as integer)-3) INTO V_DATE_YEAR;
--Manager Records are stored here below
SELECT DISTINCT managerid from "dev"."public"."emp_salary" INTO MGR_RECORD;
SELECT employeeid,
managerid,
promotion,
q_bonus,
d_salary,
case when contractor = 'x'
then 'TemporaryEmployee'
else 'PermanentEmployee'
END as EmployeeType,
-- IFstatement not supported under select query
case when employeeid in (select distinct managerid FROM "dev"."public"."emp_salary" )
then 'Manager'
else 'Ordinary FTE'
END as FTETYPE
FROM "dev"."public"."emp_salary" where cast(extract(year from promotion) as int ) >= 2019 into DATAS;
--COMMIT;
tmp_name := 'ManagerUpdatedTable';
EXECUTE 'drop table if exists ' || tmp_name;
EXECUTE 'create temp table ' || 'ManagerUpdatedTable' || ' as select * from' |DATAS| ;
END;
$$ LANGUAGE plpgsql;
-- Call tests CALL EGEN('myresult'); SELECT * from myresult;
Also, additional query (Can we replace )
case when employeeid in (select distinct managerid FROM "dev"."public"."emp_salary" )
then 'Manager'
else 'Ordinary FTE'
END as FTETYPE
this transform in query to IF , if possible please provide details.
Thanks and Regards,
Gabby

Convert Procedure from Oracle to DB2, Error when try to update table on runtime

Hello DB2 Experts I need your assistance in converting below Procedure to something more dynamic.
We have to update multiple sequences with max of id column for each table.
CREATE PROCEDURE mySchema.UPDATE_SEQUENCE ( )
DYNAMIC RESULT SETS 1
MODIFIES SQL DATA
----------------------------------------------------------------------
-- SQL Stored Procedure
----------------------------------------------------------------------
P1: BEGIN
DECLARE counter BIGINT;
DECLARE q VARCHAR(500);
set (counter) = (select max(N_PRI_KEY) from mySchema.myTable);
set q = 'alter sequence mySchema.mySequence RESTART WITH ' || counter;
EXECUTE IMMEDIATE q;
END P1
#
This is what I have written from the above code:
Here I want N_PRI_KEY to be dynamic and mySchema.myTable to be updated on run time with the values from the table.
CREATE OR REPLACE PROCEDURE getText ()
LANGUAGE SQL
DYNAMIC RESULT SETS 1
BEGIN
DECLARE maxval INTEGER DEFAULT 0;
CALL DBMS_OUTPUT.PUT( 'a' );
FOR vrows AS
SELECT NAME, SEQUENCENAME, TBNAME FROM MAXSEQUENCE WHERE SEQUENCENAME='ASSETSEQ'
DO
SELECT MAX(vrows.NAME) INTO maxval FROM vrow.TBNAME; -- This is where I am getting error.
EXECUTE IMMEDIATE 'ALTER SEQUENCE '||vrows.SEQUENCENAME||' RESTART WITH '|| maxval;
END FOR;
END#
This is the error that I am getting when I try to create the Procedure.
DB21034E The command was processed as an SQL statement because it was not a
valid Command Line Processor command. During SQL processing it returned:
SQL0204N "VROW.TBNAME" is an undefined name. LINE NUMBER=18. SQLSTATE=42704
When I run this line it works and inserts the max value in the TEMPOUTPUT table.
execute immediate 'INSERT INTO TEMPOUTPUT VALUES (select max('||vrow.NAME||') from '||vrow.TBNAME||')';
I have tried doing this but it did not work.
execute immediate 'ALTER SEQUENCE '||SEQUENCENAME||' RESTART WITH select max('||vrow.NAME||') from '||vrow.TBNAME;
Just for Reference - This is a procedure which is written in Oralce which is doing something similar.
declare
maxval int;
seqval int;
begin
for i in ( select ucc.column_name, s.sequence_name, uc.table_name
from user_cons_columns ucc,
user_constraints uc,
user_sequences s
where uc.constraint_name = ucc.constraint_name
and uc.constraint_type = 'P'
and ucc.position = 1
and s.sequence_name = 'SEQ_'||uc.table_name
)
loop
execute immediate 'select max('||i.column_name||') from '||i.table_name into maxval;
execute immediate 'select '||i.sequence_name||'.nextval from dual' into seqval;
dbms_output.put_line(maxval||','||seqval);
if maxval > seqval then
execute immediate 'alter sequence '||i.sequence_name||' increment by '|| ( maxval - seqval );
execute immediate 'select '||i.sequence_name||'.nextval from dual' into seqval;
execute immediate 'alter sequence '||i.sequence_name||' increment by 1';
execute immediate 'select '||i.sequence_name||'.nextval from dual' into seqval;
dbms_output.put_line(maxval||','||seqval);
end if;
end loop;
end;
Try the following instead of the row with SELECT MAX(...) where you get the error:
PREPARE S1 FROM 'SET ? = (SELECT MAX(' || vrows.NAME || ') FROM ' || vrows.TBNAME || ')';
EXECUTE S1 INTO maxval;

Create table with stored procedure in Teradata

I want to create a stored procedure where I can pass in variable to the WHERE clause below.
DROP TABLE fan0ia_mstr.Store_List;
CREATE TABLE fan0ia_mstr.Store_List AS(
SELECT
a11.ANA_Code,
a11.Premise_Name_Full,
a11.Store_Code,
a11.Estates_Segment,
a12.Post_Code
FROM Store_Dimension_Hierarchy a11
JOIN Location a12
ON a11.ANA_Code = a12.ANA_Code
WHERE a11.Area_Desc = 'VARIABLE' ) WITH DATA
PRIMARY INDEX (ANA_Code)
The VARIABLE will be a character string. I don't need to display the results, I just want the table to be created.
Also how do I trap any errors e.g. if the table doesn't exist for some reason I still want it to be created
thanks
As you don't have variable database/table/column names you simply need to wrap your existing code (slightly modified) into a Stored Procedure:
replace procedure myproc(IN variable varchar(100))
begin
BEGIN
-- simply try dropping the table and ignore the "table doesn't exist error"
DECLARE exit HANDLER FOR SQLEXCEPTION
BEGIN -- 3807 = table doesn't exist
IF SQLCODE <> 3807 THEN RESIGNAL; END IF;
END;
DROP TABLE fan0ia_mstr.Store_List;
END;
CREATE TABLE fan0ia_mstr.Store_List AS(
SELECT
a11.ANA_Code,
a11.Premise_Name_Full,
a11.Store_Code,
a11.Estates_Segment,
a12.Post_Code
FROM Store_Dimension_Hierarchy a11
JOIN Location a12
ON a11.ANA_Code = a12.ANA_Code
WHERE a11.Area_Desc = :variable ) WITH DATA
PRIMARY INDEX (ANA_Code);
end;
Of course a DELETE/INSERT or a Temporary table might be more efficient.
Edited... second option needs a execute immediate too...
CREATE PROCEDURE PROCEDURE1(
V_AREA_DESC IN VARCHAR2 )
AS
BEGIN
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE fan0ia_mstr.Store_List';
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
EXECUTE IMMEDIATE 'CREATE TABLE fan0ia_mstr.Store_List AS
(SELECT a11.ANA_Code,
a11.Premise_Name_Full,
a11.Store_Code,
a11.Estates_Segment,
a12.Post_Code
FROM Store_Dimension_Hierarchy a11
JOIN Location a12
ON a11.ANA_Code = a12.ANA_Code
WHERE a11.Area_Desc = ''' || v_area_desc || '''
) WITH DATA PRIMARY INDEX (ANA_Code)';
END PROCEDURE1;
but you can avoid drop / create with truncate / insert
CREATE PROCEDURE PROCEDURE1(
V_AREA_DESC IN VARCHAR2 )
AS
BEGIN
execute immediate 'truncate TABLE fan0ia_mstr.Store_List';
insert into fan0ia_mstr.Store_List (SELECT a11.ANA_Code,
a11.Premise_Name_Full,
a11.Store_Code,
a11.Estates_Segment,
a12.Post_Code
FROM Store_Dimension_Hierarchy a11
JOIN Location a12
ON a11.ANA_Code = a12.ANA_Code
WHERE a11.Area_Desc = v_area_desc
);
commit;
END PROCEDURE1;

Transactions in Informix dbaccess here document approach

I am writing a shell script that invokes dbaccess.
I would like to begin a transaction, do some stuff (e.g. call some procedures) and then make a decision and either commit or rollback the current work. Is this possible?
Here's an example of what I am trying to accomplish
#!/bin/bash
v_value
dbaccess $DB - << SQL
unload to "abc.csv"
select value from table1 where id=1;
SQL
IFS=$'|' arr=( $(awk -F, -v OFS='\n' '{$1=$1}1' abc.csv) )
v_value=${arr[0]}
dbaccess $DB - << SQL
begin;
execute procedure progname();
-- here check everything is ok (e.g. using the previously retrieved $v_value) and either commit or rollback
-- commit|rollback
SQL
You probably want to use the DBACCNOIGN environment variable, which makes DB-Access pay attention to failing statements — and stop. If that's set and you start a transaction, and then a statement within the transaction fails, DB-Access will terminate, which means the transaction will be rolled back.
For example:
$ DBACCNOIGN=1 dbaccess stores - <<'EOF'
> begin work;
> create table anything (num INT NOT NULL, str VARCHAR(20) NOT NULL);
> insert into anything values(1, "one");
> select * from abelone;
> insert into anything values(2, "two");
> select * from anything;
> commit work;
> EOF
Database selected.
Started transaction.
Table created.
1 row(s) inserted.
206: The specified table (abelone) is not in the database.
111: ISAM error: no record found.
Error in line 1
Near character position 21
377: Must terminate transaction before closing database.
853: Current transaction has been rolled back due to error
or missing COMMIT WORK.
$ dbaccess stores - <<'EOF'
> begin work;
> create table anything (num INT NOT NULL, str VARCHAR(20) NOT NULL);
> insert into anything values(1, "one");
> select * from abelone;
> insert into anything values(2, "two");
> select * from anything;
> commit work;
> EOF
Database selected.
Started transaction.
Table created.
1 row(s) inserted.
206: The specified table (abelone) is not in the database.
111: ISAM error: no record found.
Error in line 1
Near character position 21
1 row(s) inserted.
num str
1 one
2 two
2 row(s) retrieved.
Data committed.
Database closed.
$
I then had to use DB-Access again to drop the table Anything that was created.
The value that DBACCNOIGN is set to doesn't matter very much; setting it to 0 or 1 or an empty string all worked equally well.
This is a limited facility; you don't have programmatic control over whether to ignore the error from any given statement. You either abandon ship on the first error or you continue to the end regardless of errors.
You could consider the 'real' SQLCMD program (rather than Microsoft's johnny-come-lately) which is available from the IIUG (International Informix User Group) Software Archive. It allows you to control whether errors from any given group of statements are ignored or not. However, it does not give you full flow control — you can't conditionally execute statements.
Maybe you able to commit/rollback inside of a procedure for this.
But the way you wrote your script, I don't consider necessary create a procedure for that , you can solve using shell script :
#!/bin/bash
v_value=""
dbaccess $DB - << SQL
unload to "abc.csv"
select value from table1 where id=1;
SQL
IFS=$'|' arr=( $(awk -F, -v OFS='\n' '{$1=$1}1' abc.csv) )
v_value=${arr[0]}
{
echo "
begin work;
execute procedure progname();
"
if [ "$v_value" = "1" ] ; then
echo "commit ;"
else
echo "rollback;"
fi;
} | dbaccess $DB -
PLUS
About the "unload" , just as suggestion (I don't like use unload for this kind of script) :
v_value=$( echo "select first 1 value from table1 where id=1;" | dbaccess $DB 2>/dev/null | egrep -v '^ *$|^ *value" | awk '{print $1}')
USING PROCEDURE
If you want avoid use shell script and keep all into SQL code, you will need create a specific procedure for that , something like :
create table test( desc char(10) ) ;
--drop procedure commit_rollback ;
create procedure commit_rollback()
define x int ;
select count(*) into x from test ;
if x > 5 then
commit work;
else
rollback work ;
end if ;
end procedure ;
begin work ;
insert into test values ( '111') ;
insert into test values ( '222') ;
insert into test values ( '333') ;
insert into test values ( '444') ;
insert into test values ( '555') ;
execute procedure commit_rollback() ;
select * from test ;
begin work;
insert into test values ( '111') ;
insert into test values ( '222') ;
insert into test values ( '333') ;
insert into test values ( '444') ;
insert into test values ( '555') ;
insert into test values ( '666') ;
execute procedure commit_rollback() ;
select * from test ;
The code above will have this output
desc
desc
111
222
333
444
555
666

Informix trigger to change inserted values

I would like to change a couple of column values before they get inserted.
I am using Informix as database.
I have a table consisting of 3 columns: Name (NVARCHAR), Type (INT), Plan (NVARCHAR).
Every time a new record is inserted, I would like to check the Name value before inserting it. If the Name starts with an F, I would like to set the Type value to 1 and the Plan Name to "Test"
In short, what I want the trigger to do is:
For every new insertion, first check if Name value starts with F.
If yes, set the Type and Plan to 1 and "Test" then insert.
If no, insert the values as-is.
I have looked up the CREATE TRIGGER statement with BEFORE and AFTER. However, I would like to have a clearer example. My case would probably involve BEFORE though.
The answer of #user3243781 get close, but did not work because it returns the error:
-747 Table or column matches object referenced in triggering statement.
This error is returned when a triggered SQL statement acts on the
triggering table, or when both statements are updates, and the column
that is updated in the triggered action is the same as the column that
the triggering statement updates.
So the alternative is handle with the NEW variable directly.
For that you need to use a procedure with the triggers reference resource, which means the procedure will able to act like the trigger by self.
Below is my example which I run with dbaccess over a Informix v11.70.
This resource is available only for versions +11 of the engine, as far I remember.
create table teste ( Name NVARCHAR(100), Type INT , Plan NVARCHAR(100) );
Table created.
create procedure check_name_values()
referencing new as n for teste ;;
define check_type integer ;;
define check_plan NVARCHAR ;;
if upper(n.name) like 'F%' then
let n.type = 1;;
let n.plan = "Test";;
end if
end procedure ;
Routine created.
;
create trigger trg_tablename_ins
insert on teste
referencing new as new
for each row
(
execute procedure check_name_values() with trigger references
);
Trigger created.
insert into teste values ('cesar',99,'myplan');
1 row(s) inserted.
insert into teste (name) values ('fernando');
1 row(s) inserted.
insert into teste values ('Fernando',100,'your plan');
1 row(s) inserted.
select * from teste ;
name cesar
type 99
plan myplan
name fernando
type 1
plan Test
name Fernando
type 1
plan Test
3 row(s) retrieved.
drop table if exists teste;
Table dropped.
drop procedure if exists check_name_values;
Routine dropped.
create trigger trg_tablename_ins
insert on tablename
referencing new as new
for each row
(
execute procedure check_name_values
(
new.name,
new.type,
new.plan
)
);
create procedure check_name_values
(
name NVARCHAR,
new_type integer,
new_plan NVARCHAR,
)
define check_type integer ;
define check_plan NVARCHAR ;
let check_type = 1;
let check_plan = "Test";
if name = 'F%'
then
insert into tablename (name,type,plan) values (name,check_type,check_plan);
else
insert into tablename (name,type,plan) values (name,new_type,new_plan);
end if ;
end procedure ;
Here is my version an adaptation of an old example I found in the informix usenet group.
It is possible to update columns in a trigger statement but not very straight forward. You have to use stored procedures an the into statement with the execute procedure command.
It worked here for IBM Informix Dynamic Server Version 12.10.FC11WE.
drop table if exists my_table;
drop sequence if exists my_table_seq;
create table my_table (
id INTEGER
NOT NULL,
col_a char(32)
NOT NULL,
col_b char(20)
NOT NULL,
hinweis char(64),
uslu char(12)
DEFAULT USER
NOT NULL,
dtlu DATETIME YEAR TO SECOND
DEFAULT CURRENT YEAR TO SECOND
NOT NULL
)
;
create sequence my_table_seq
increment 1
start 1;
drop procedure if exists get_user_datetime();
create function get_user_datetime() returning char(12),datetime year to second;
return user, current year to second;
end function
;
drop trigger if exists ti_my_table;
create trigger ti_my_table insert on my_table referencing new as n for each row (
execute function get_user_datetime() into uslu, dtlu
)
;
drop trigger if exists tu_my_table;
create trigger tu_my_table update on my_table referencing new as n for each row (
execute function get_user_datetime() into uslu, dtlu
)
;
insert into my_table values (my_table_seq.nextval, "a", "b", null, "witz", mdy(1,1,1900)) ;
SELECT *
FROM my_table
WHERE 1=1
;

Resources