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
Related
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).
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
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;
I'm trying to create a script with a set of instructions inside a BEGIN WORK / COMMIT block, but for some reason the temporary table is not being created or is being deleted at some point:
this is the kind of script I'm working on:
-- PHASE2:
BEGIN WORK;
--create a temp table
CREATE temp table IF NOT EXISTS temp_users (
reg_id char(2),
cntry char(2),
name varchar(25)
);
--insert to temp table
INSERT INTO temp_users(reg_id,cntry,name)
SELECT region,country,usr_name FROM user_data WHERE cntry = 'AU';
-- more work with the temp table below..
COMMIT;
But when I execute this script I'm getting an error because temp_users doesn't exists .
I'm not sure what problem you're running into. Using Informix 12.10.FC6 (time to upgrade — and then some) on Ubuntu 18.04, I can run this without error:
DROP TABLE IF EXISTS user_data;
CREATE TABLE user_data
(
region CHAR(2) NOT NULL,
country CHAR(2) NOT NULL,
cntry CHAR(2) NOT NULL,
usr_name VARCHAR(25) NOT NULL
);
-- PHASE2:
BEGIN WORK;
--create a temp table
CREATE TEMP TABLE IF NOT EXISTS temp_users (
reg_id CHAR(2),
cntry CHAR(2),
name VARCHAR(25)
);
--insert to temp table
INSERT INTO temp_users(reg_id, cntry, name)
SELECT region, country, usr_name FROM user_data WHERE cntry = 'AU';
-- more work with the temp table below..
COMMIT;
I don't know why your user_data table has columns country and cntry; that's not obvious.
Try creating a temporary database (a database with a new name that you'll get rid of soon), and then run the script shown against it with DB-Access. It should work, creating a trace like this (I used stores as the database and the file xyz.sql to contain the script):
$ dbaccess stores xyz
Database selected.
Table dropped.
Table created.
Started transaction.
Temporary table created.
0 row(s) inserted.
Data committed.
Database closed.
$
What do you get?
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
;