Informix trigger to change inserted values - informix

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
;

Related

SQL Server 2017 Stored Procedure wants table variable declared but it already exists

Been a while since I have done any sereious T-SQL. I thought I had this right but I am receiving an error that I cannot figure out the cause of. This is for a stored procedure that is not complex. The code is below:
--======================================================
-- Create Natively Compiled Stored Procedure Template
--======================================================
USE Viper;
GO
-- Drop stored procedure if it already exists
IF OBJECT_ID('API.newCategory','P') IS NOT NULL
DROP PROCEDURE API.newCategory;
GO
CREATE PROCEDURE API.newCategory
-- Add the parameters for the stored procedure here
#Category varchar(20) = null
WITH NATIVE_COMPILATION, SCHEMABINDING
AS BEGIN ATOMIC WITH
(
TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'us_english'
)
--Insert statements for the stored procedure here
DECLARE #tmp int = 0;
IF #Category IS NOT NULL
AND #Category != ''
SET #tmp = ISNULL(
(SELECT idCategory
FROM Products.Category
WHERE Category = #Category),0);
IF #tmp = 0
INSERT INTO Products.Category (Category)
OUTPUT inserted.idCategory INTO #tmp
VALUES (#Category);
ELSE
UPDATE Category
SET active = 1
WHERE idCategory = #tmp;
RETURN #tmp;
END;
GO
The error message that I am receiving is:
Msg 1087, Level 16, State 1, Procedure newCategory, Line 22 [Batch
Start Line 11]
Must declare the table variable "#tmp".
I hope someone can point me in the right direction. I am sure that it is something stupidly simple, I just can't see it right now. A bit rusty I'm afraid.
Just to be clear, the operational goal of the SP is to:
1/ Check that there is actually a Category supplied to work with
2/ If there is then try and get its primary key id (idCategory)
3/ If there is no PK for the Category then insert it and return the idCategory into #tmp
4/ If there is a PK then make sure the active column is set to 1
5/ return #tmp as the result (either the PK or 0)
Cheers and thanks for any help
The Frog
Your problem is in this statement:
IF #tmp = 0
INSERT INTO Products.Category (Category)
OUTPUT inserted.idCategory INTO #tmp
VALUES (#Category);
You are doing an OUTPUT INTO where what you are output into your previously declare #tmp which is declared as an int. OUTPUT statements can only be against tables, temp tables or table variables.
A workaround could be to declare a table variable #catTab: DECLARE #catTab AS TABLE(CatID int) and OUTPUT into that variable followed by a: SELECT #tmp = CatID FROM #catTab. That should do it.

Getting invalid identifier and SQL ignored errors in stored procedure in Oracle

I am trying to create a stored procedure in order to insert some data into some table in Oracle 11g.
For this purpose, I need to read the last amount from the latest insert for that user, and then add it to my new value and save the changes as a new row and if any kind of exception is occurred just rollback. This is what I have come up with so far:
CREATE OR REPLACE PROCEDURE MYTESTDB.INSERT_INTO_TESTBANK
(
ID IN TBLTESTBANK.ID%TYPE,
USERID IN TBLTESTBANK.USERID%TYPE,
TYPE IN TBLTESTBANK.TYPE%TYPE,
AMOUNT IN TBLTESTBANK.AMOUNT%TYPE,
DATETIMESTAMP IN TBLTESTBANK.DATETIMESTAMP%TYPE,
TRANSACTION_ID IN TBLTESTBANK.IDTRANS%TYPE,
TOTAL_MONEY IN TBLTESTBANK.TOTALMONEY%TYPE,
COMPUTED_HASH IN TBLTESTBANK.HASH%TYPE
)
IS
BEGIN
DECLARE
LastAmount TBLTESTBANK.TOTALMONEY%TYPE;
BEGIN
SELECT TBLTESTBANK.TOTALMONEY INTO LASTAMOUNT
FROM
(
SELECT TBLTESTBANK.ID
FROM TBLTESTBANK tblbnk
WHERE tblbnk.USERID = USERID
order by max(tblbnk.DATETIMESTAMP)
)
where ROWNUM<2;
EXCEPTION WHEN NO_DATA_FOUND
THEN LastAmount := 0;
END;
LastAmount := LastAmount+ AMOUNT;
INSERT INTO TBLTESTBANK (ID, USERID, TYPE, LastAmount,
DATETIMESTAMP, IDTRANS, TOTALMONEY, HASH
)
VALUES (ID, USERID, TYPE, AMOUNT, DATETIMESTAMP, TRANSACTION_ID, TOTAL_MONEY, COMPUTED_HASH);
COMMIT;
EXCEPTION WHEN OTHERS
THEN
ROLLBACK;
END;
/
Whenever I try to test it, I get a red wiggly line under the first SELECT , and then he TESTBANK.ID in the inner select statements. For the select command it says, sql statement is ignored, and for the TESTBANK.ID it says invalid identifier!
It also doesn't let me add two variables, and keeps saying LastAmount must be declared.
And these are the errors I get :
Error(19,7): PL/SQL: SQL Statement ignored
Error(22,16): PL/SQL: ORA-00904: "TBLTESTBANK"."ID": invalid identifier
Error(32,3): PLS-00201: identifier 'LASTAMOUNT' must be declared
Error(32,3): PL/SQL: Statement ignored
Error(34,3): PL/SQL: SQL Statement ignored
Error(34,47): PL/SQL: ORA-00904: "LASTAMOUNT": invalid identifier
You have two problems:
In your select statement:
SELECT TBLTESTBANK.ID
FROM TBLTESTBANK tblbnk
WHERE tblbnk.USERID = USERID
order by max(tblbnk.DATETIMESTAMP)
You have given the table TBLTESTBANK the alias tblbnk therefor you must use the alias throughout the statement:
SELECT tblbnk.ID -- use the correct alias here
FROM TBLTESTBANK tblbnk
WHERE tblbnk.USERID = USERID
order by max(tblbnk.DATETIMESTAMP)
Second:
in the INSERT statement you have to list columns not values.
INSERT INTO TBLTESTBANK (ID, USERID, TYPE, LastAmount,
DATETIMESTAMP, IDTRANS, TOTALMONEY, HASH
)
should be:
INSERT INTO TBLTESTBANK (ID, USERID, TYPE, AMOUNT, --- column name instead of variable name
DATETIMESTAMP, IDTRANS, TOTALMONEY, HASH
)
And then of course you need to use LastAmount in the values part, not AMOUNT
In general it's not a good idea to have variables with the same name as columns of tables that you use in the procedure. It's easy to shadow a variable with a column. You should rename the variables to avoid any problems there.
Edit (I didn't notice this at first).
You also have an error in the structure of the code. A stored procedure (or function) does not have a DECLARE section:
Your declaration needs to look like this:
CREATE OR REPLACE PROCEDURE MYTESTDB.INSERT_INTO_TESTBANK
(
....
)
IS
-- no DECLARE
-- variables right after the IS keyword
LastAmount TBLTESTBANK.TOTALMONEY%TYPE;
BEGIN
...
END;
You can try below select query to get latest record.
SELECT TB.TOTALMONEY INTO LASTAMOUNT
FROM
TESTBANK TB
WHERE
TB.USERID=USERID
AND
DATETIMESTAMP= (SELECT MAX (TB2.DATETIMESTAMP) FROM TESTBANK TB2
WHERE TB2.USERID=USERID);

Is it possible to have a DB uniqueness constraint across columns of two tables?

I have a mysql DB with rails, and a column "shorthand" (string) that I'd like to make unique across multiple tables. Is there a way I can do this without making a third table?
Expression
id
shorthand
...
etc
Variable
id
shorthand
...
etc
I want the values in the 'shorthand' columns of both tables to be unique between each other ie. a record shorthand value "xyz" in Expression would be rejected if a Variable with shorthand value "xyz" were to exist in the DB already.
Any thoughts appreciated, even "you have to use a third table" :)
Here an example using a third table:
-- TEMP SCHEMA for testing
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE shorthand
( shorthand varchar NOT NULL PRIMARY KEY
, one_or_two varchar NOT NULL
);
CREATE TABLE table_one
( one_id INTEGER NOT NULL PRIMARY KEY
, shorthand varchar NOT NULL REFERENCES shorthand(shorthand)
ON DELETE CASCADE ON UPDATE CASCADE
DEFERRABLE INITIALLY DEFERRED
, etc_one varchar
);
CREATE TABLE table_two
( two_id INTEGER NOT NULL PRIMARY KEY
, shorthand varchar NOT NULL REFERENCES shorthand(shorthand)
ON DELETE CASCADE ON UPDATE CASCADE
DEFERRABLE INITIALLY DEFERRED
, etc_two varchar
);
-- Trigger function for BOTH tables
CREATE FUNCTION set_one_or_two( ) RETURNS TRIGGER
AS $func$
BEGIN
IF (TG_OP = 'INSERT') THEN
INSERT INTO shorthand (shorthand, one_or_two)
VALUES(new.shorthand, TG_TABLE_NAME)
;
ELSEIF (TG_OP = 'UPDATE') THEN
UPDATE shorthand SET shorthand = new.shorthand
WHERE shorthand = old.shorthand
;
ELSEIF (TG_OP = 'DELETE') THEN
DELETE FROM shorthand
WHERE shorthand = old.shorthand
;
END IF;
RETURN NULL;
END
$func$ LANGUAGE plpgsql
;
-- Triggers for I/U/D
CREATE CONSTRAINT TRIGGER check_one
AFTER INSERT OR UPDATE OR DELETE
ON table_one
FOR EACH ROW
EXECUTE PROCEDURE set_one_or_two ( )
;
CREATE CONSTRAINT TRIGGER check_two
AFTER INSERT OR UPDATE OR DELETE
ON table_two
FOR EACH ROW
EXECUTE PROCEDURE set_one_or_two ( )
;
-- Some tests (incomplete)
INSERT INTO table_one (one_id,shorthand,etc_one) VALUES (1, 'one' , 'one' );
INSERT INTO table_two (two_id,shorthand,etc_two) VALUES (1, 'two' , 'two' );
SELECT * FROM shorthand;
\echo this should fail
INSERT INTO table_one (one_id,shorthand,etc_one) VALUES (11, 'two' , 'eleven' );
SELECT * FROM shorthand;
UPDATE table_one SET shorthand = 'eleven' WHERE one_id = 1;
SELECT * FROM shorthand;
I think this older article does exactly what you are looking for (simulating multi table constraints):
http://classes.soe.ucsc.edu/cmps180/Winter04/constraints.html
You might also like to investigate postgres CREATE CONSTRAINT TRIGGER using a function similar to the check_nojoin() function in the article.
http://www.postgresql.org/docs/9.0/static/sql-createconstraint.html
Once you have the exact SQL you need you can put it in your rails migration with execute "the required SQL"
An alternative approach is to use a third table 'shorthands' with columns 'shorthand' and 'src'. Define shorthand as the unique primary key on that table. On each of your other two tables define 'src' as a single char field defaulting to 'A' and 'B' on each table respecitively. Add a foreign key constraint on each of your two tables consisting of both 'shorthand' and 'src' and referencing table 'shorthands'. When inserting or updating rows in either of your two tables you need to ensure the 'shorthands' table is updated either explicity as part of your transaction or via a trigger and set both 'shorthand', and 'src' to the respective table ie 'A' or 'B'.
What the foreign key constraints do is ensure that the shorthand value exists in the shorthand's table for the respective src table but because of the uniqueness constraint on just the 'shorthand' column in the shorthand's table if the other table has already defined the shorthand value a key violation will occur thus guaranteeing uniqueness across two (or even more) tables.
Whatever you do, it is best to put the referential integrity into the database, not in orm/active record validations.

Informix procedure — how to return an empty table?

I need to create an Informix procedure to return a table with rows if I found some value and an empty table if no value found.
I know how to return a table with rows, but I don't know how to return the empty table; can anyone help?
CREATE row type AType (
id VARCHAR(255),
name VARCHAR(255)
);
CREATE PROCEDURE getmeasurement (p_date DATETIME YEAR TO SECOND)
RETURNING MULTISET(AType NOT NULL);
DEFINE AType_TABLE MULTISET (AType NOT NULL);
DEFINE v_id VARCHAR(255);
DEFINE v_name VARCHAR(255);
....
IF( FoundValue ) THEN
-- INSERT INTO TABLE
INSERT INTO TABLE (AType_TABLE) VALUES (ROW(v_id,v_name)::AType);
ELSE
// how to initial a AType_TABLE instance with empty records.
END IF
....
RETURN AType_TABLE;
END PROCEDURE;
Despite what it says in the SQL Syntax Manual, SPL (Stored Procedure Language) procedures can return collection types (COLLECT, SET, MULTISET or LIST). I've reported a documentation bug against that — which misled me earlier this week.
I'm not able to get this procedure to compile under Informix 11.70.FC6 on Mac OS X 10.7.5, but the error it comes up with is:
SQL -9905: [Internal] No extended type information for domain.
I've run into various issues trying various variants of the above code. You can't have a WHERE clause on a DELETE from a multiset, it seems (different error message). You can also run into problems if you rollback the creation of the type and then try again.
However, I was able to test it with a pre-release of 11.70.FC8 on Linux (RHEL 5, x86/64) and got the desired output:
CREATE ROW TYPE IF NOT EXISTS AType
(
id VARCHAR(255),
name VARCHAR(255)
);
CREATE PROCEDURE getmeasurement (p_date DATETIME YEAR TO SECOND)
RETURNING MULTISET(AType NOT NULL);
DEFINE AType_TABLE MULTISET(AType NOT NULL);
DEFINE v_id VARCHAR(255);
DEFINE v_name VARCHAR(255);
LET v_id = "Polynomial - " || p_date;
LET v_name = "Euclid's Geometry of the Plane";
INSERT INTO TABLE (AType_TABLE) VALUES(ROW(v_id, v_name)::AType);
IF 1 = 1 THEN
-- how TO initial a AType_TABLE instance WITH empty records.
DELETE FROM TABLE(AType_TABLE);
END IF
RETURN AType_TABLE;
END PROCEDURE;
EXECUTE PROCEDURE getmeasurement(CURRENT);
ROLLBACK;
The output was:
MULTISET{}
which is an empty multiset. If you don't insert something into the multiset first, the delete fails. This is a trifle odd.
Summary
You may be OK with Informix 11.70.xC7; it may work on some other versions too. But there are likely to be a lot of versions of Informix where it does not work.

How to use procedure parameters in merge statement

i'm creating a procedure to update/insert a table using merge statement(upsert).now i have a problem: using procedure parameters i have to do this upsert.
procedure xyz( a in table.a%type,b in table.b%type,....)
is
some local variables;
begin
merge into target_table
using source_table --instead of the source table, i have to use procedure parameters here
on (condition on primary key in the table)
when matched then
update the table
when not matched then
insert the table ;
end xyz;
so how to use procedure parameters instead of source table in merge statement?? or
suggest me a query to fetch the procedure parameters and use it as source table values.
help me please.
Thanks in advance.
I know that I'm eight years late to the party, but I think that I was trying to do something similar to what you were doing, but trying to Upsert based on parameters passed into a stored procedure that returns an empty string on success and an error on failure back to my VB Code. Below is all of my code along with comments explaining what I did, and why I did it. Let me know if this helps you or anyone else. This is my first time answering a post.
PROCEDURE UpsertTSJobData(ActivitySeq_in IN NUMBER,
Owner_in In VARCHAR2,
NumTrailers_in IN NUMBER,
ReleaseFormReceived_in IN NUMBER,
Response_out OUT VARCHAR2) AS
err_num NUMBER;
err_msg VARCHAR2(4000);
BEGIN
--This top line essentially does a "SELECT *" from the named table
--and looks for a match based on the "ON" statement below
MERGE INTO glob1app.GFS_TS_JOBDATA_TAB tsj
--This select statement is used for the INSERT when no match
--is found and the UPDATE when a match is found.
--It creates a "pseudo-table"
USING (
SELECT ActivitySeq_in AS ActSeq,
Owner_in As Owner,
NumTrailers_in As NumTrailers,
ReleaseFormReceived_in As ReleaseFormReceived
FROM DUAL) input
--This ON statement is what we're doing the match on to find
--matching records. This decides whether it will be an
--INSERT or an UPDATE
ON (tsj.Activity_seq = ActivitySeq_in)
WHEN MATCHED THEN
--Here we UPDATE based on the passed in input table
UPDATE
SET OWNER = input.owner,
NUMTRAILERS = input.NumTrailers,
RELEASEFORMRECEIVED = input.releaseformreceived
WHEN NOT MATCHED THEN
--Here we INSERT based on the passed in input table
INSERT (
ACTIVITY_SEQ,
OWNER,
NUMTRAILERS,
RELEASEFORMRECEIVED
)
VALUES (
input.actseq,
input.owner,
input.numtrailers,
input.releaseformreceived
);
Response_out := '';
EXCEPTION
WHEN OTHERS THEN
err_num := SQLCODE;
err_msg := SUBSTR(SQLERRM, 1, 3900);
Response_out := TO_CHAR (err_num) || ': ' || err_msg;
END;
Maby something like
DECLARE V_EXISTS NUMBER;
BEGIN SELECT COUNT(*) INTO V_EXISTS FROM TARGET_TABLE WHERE PK_ID = :ID;
IF V_EXISTS > 0 THEN
-- UPDATE
ELSE
-- INSERT
END IF;
END;
Also, you may try to use so-called tempotary table (select from DUAL)
CREATE TABLE TEST (N NUMBER(2), NAME VARCHAR2(20), ADRESS VARCHAR2(100));
INSERT INTO TEST VALUES(1, 'Name1', 'Adress1');
INSERT INTO TEST VALUES(2, 'Name2', 'Adress2');
INSERT INTO TEST VALUES(3, 'Name3', 'Adress3');
SELECT * FROM TEST;
-- test update
MERGE INTO TEST trg
USING (SELECT 1 AS N, 'NameUpdated' AS NAME,
'AdressUpdated' AS ADRESS FROM Dual ) src
ON ( src.N = trg.N )
WHEN MATCHED THEN
UPDATE
SET trg.NAME = src.NAME,
trg.ADRESS = src.ADRESS
WHEN NOT MATCHED THEN
INSERT VALUES (src.N, src.NAME, src.ADRESS);
SELECT * FROM TEST;
-- test insert
MERGE INTO TEST trg
USING (SELECT 34 AS N, 'NameInserted' AS NAME,
'AdressInserted' AS ADRESS FROM Dual ) src
ON ( src.N = trg.N )
WHEN MATCHED THEN
UPDATE
SET trg.NAME = src.NAME,
trg.ADRESS = src.ADRESS
WHEN NOT MATCHED THEN
INSERT VALUES (src.N, src.NAME, src.ADRESS);
SELECT * FROM TEST;
DROP TABLE TEST;
see here
Its very difficult to tell from you question exactly what you what, but I gather you want the table that you are merging into ( or on ) to be dynamic. In that case, what you should be using is the DBMS_SQL package to create dynamic SQL

Resources