I've got an insert/update trigger set up which prevents an employee from existing in two tables at the same time. It works fine with catching illegal insertions/updates but i'm also getting another error report when testing the trigger with illegal insertions/updates.
Here's my code:
CREATE OR REPLACE TRIGGER check_foobar
BEFORE INSERT OR UPDATE OF VarX ON FOOBAR
FOR EACH ROW
DECLARE
counter NUMBER(38);
BEGIN
SELECT count(*)
INTO counter
FROM BARFOO
WHERE VarX = :NEW.VarX
GROUP BY VarX;
IF counter > 0 THEN
RAISE_APPLICATION_ERROR(-20001, 'This is an illegal insertion/update');
END IF;
EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('TEST');
END;
/
And the errors ORA-06512 and ORA-04088 i'm not sure of:
SQL> INSERT INTO DRIVER VALUES(2, 10345, 'AVAILABLE');
Error starting at line : 26 File #test.sql
In command -
INSERT INTO FOOBAR VALUES(2)
Error report -
ORA-20001: This is an illegal insertion/update
ORA-06512: at "HR.CHECK_FOOBAR", line 11
ORA-04088: error during execution of trigger 'HR.CHECK_FOOBAR'
When i add an exception handler for the select statement my trigger stops working properly and the illegal insertion isn't prevented. But the execution error is prevented.
UPDATE: I've added a group by and an exception to the trigger, so now the trigger still works with an exception handler but the errors ORA-06512 and ORA-04088 are still coming up with the illegal insertion/update.
Line 11 mentioned in the error is
GROUP BY VarX;
Any advice would be much appreciated.
There is no problem here, it is working as expected.
Error starting at line : 26 File #test.sql
In command -
INSERT INTO FOOBAR VALUES(2)
This is referring to the line in your script where you have the INSERT statement. It's telling you an exception was raised at this point.
Error report -
This is showing the error stack:
ORA-20001: This is an illegal insertion/update
This is the actual exception that was raised.
ORA-06512: at "HR.CHECK_FOOBAR", line 11
ORA-04088: error during execution of trigger 'HR.CHECK_FOOBAR'
These are additional messages just to tell you where the exception was originally raised, in this case, in your trigger on line 11, where the RAISE_APPLICATION_ERROR is, as you might expect. Note that line numbers for triggers refer to the executable portion of the trigger, so in your case the DECLARE is line 1.
The ORA-04088 error means that the trigger has an un-handled exception. You are raising a application error but then not handling it. You need to handle this exception as per below.
CREATE OR REPLACE TRIGGER check_foobar
BEFORE INSERT OR UPDATE OF VarX ON FOOBAR
FOR EACH ROW
DECLARE
counter NUMBER(38);
BEGIN
SELECT count(*)
INTO counter
FROM BARFOO
WHERE VarX = :NEW.VarX
GROUP BY VarX;
IF counter > 0 THEN
RAISE_APPLICATION_ERROR(-20001, 'This is an illegal insertion/update');
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('TEST');
WHEN OTHERS THEN
IF SQLCODE = -20001 THEN
-- do some logging
RAISE;
ELSE
-- do some logging and any other actions you feel are needed. Then depending on needs you can raise or not.
END IF;
END;
/
Related
I'm migrating some stored procedures from Oracle to Snowflake and I need to raise an exception in others segment of exception on snowflake exception including a column from cursor. Here is an example from oracle:
create or replace PROCEDURE sp
AS
CURSOR SCGR
IS
SELECT SP.REQUEST_NUM,
…
from table;
BEGIN
FOR I IN SCGR
LOOP
BEGIN
INSERT
INTO table(…)
VALUES
(…);
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001,'An error was encountered in CUST_GROUP_REQUEST insert for - '||I.REQUEST_NUM||' - '||SQLCODE||' -ERROR- '||SQLERRM);
EXIT;
END;
END;
With this part I'm having troubles in Snowflake:
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001,'An error was encountered in CUST_GROUP_REQUEST insert for - '||I.REQUEST_NUM||' - '||SQLCODE||' -ERROR- '||SQLERRM);
EXIT;
Can someone help me?
If you check the RAISE command, you will see that it accepts an exception name, not a string expression which you can build dynamically.
https://docs.snowflake.com/en/sql-reference/snowflake-scripting/raise.html
Do you really need to raise an exception, or want to return a result cotaining the exception details?
https://docs.snowflake.com/en/developer-guide/snowflake-scripting/exceptions.html?_ga=2.186509665.956198205.1648142906-1409600040.1607023304#handling-an-exception
return object_construct('Error type', 'Other error',
'SQLCODE', sqlcode,
'SQLERRM', sqlerrm,
'SQLSTATE', sqlstate);
I notice when this error is triggered within the stored procedure it returns 50,000. Is there a way to modify this to say 50,999 so the front-end app can specifically pick the error up and not confuse it with anything else.
RAISERROR('Client already has an Active Visit!',16,1)
As per the documentation of RAISERROR (Transact-SQL):
The message is returned as a server error message to the calling
application or to an associated CATCH block of a TRY…CATCH construct.
New applications should use THROW instead.
Emphasis mine. (THROW (Transact-SQL))
I don't know what your SQL statement looks like, but, instead you can therefore do something like:
BEGIN TRY
--Your INSERT statement
SELECT 0/0; --Causes an error
END TRY
BEGIN CATCH
THROW 50099, 'Client already has an Active Visit!',1;
END CATCH
With RAISEERROR, if you use message as the first parameter then you can't specify an error ID and it is implicitly 50000. However, you can create a custom message with parameters and pass your code there. ie:
RAISERROR('Client already has an Active Visit! - Specific Err.Number:[%d]',16,1, 50999)
Also Try\Catch is the suggested method for new applications.
You need to specify the first parameter of the raiseerror function, like so:
--configure the error message
sp_addmessage #msgnum = 50999,
#severity = 16,
#msgtext = N'Client %s already has an Active Visit!';
GO
-- throw error
RAISERROR (50999, -- Message id.
16, -- Severity,
1, -- State,
N'123456789'); -- First argument supplies the string.
GO
Output will be
Msg 50999, Level 16, State 1, Line 8
Client 123456789 already has an Active Visit!
If you don't specify the error number, the raiserror will be assumed to be 50000. Documentation here...
CREATE OR REPLACE PACKAGE emp_package AS
TYPE emp_data_type IS RECORD (
emp_number NUMBER,
hire_date VARCHAR2(12),
emp_name VARCHAR2(10));
PROCEDURE get_emp_data
(emp_data IN OUT emp_data_type);
END;
/
package was created
SQL>
CREATE OR REPLACE PACKAGE BODY emp_package AS
PROCEDURE get_emp_data
(emp_data IN OUT emp_data_type) IS
BEGIN
SELECT empno, ename, to_char(hiredate, 'DD/MON/YY')
INTO emp_data
FROM emp
WHERE empno = emp_data.emp_number;
END;
/
Warning: Package Body created with compilation errors.
SQL> show error;
Errors for PACKAGE BODY EMP_PACKAGE:
LINE/COL ERROR
9/4 PLS-00103: Encountered the symbol "end-of-file" when expecting
one of the following:
begin end function package pragma procedure form
can any one help on this error
Change END; to END get_emp_data; and then add END emp_package;
The idea is that you are indicating that the first line ends the definition of that procedure, not the body itself, while the second line indicates the end of the package body definition.
Please make sure that you have added END wherever you have to.
There's a situation like: If the Salary column in updated with a value lesser than it's original value, print an error message and let the update NOT happen. This is what I've written so far:
CREATE OR REPLACE TRIGGER TRIG1
BEFORE UPDATE OF SAL ON EMP
for each row
USER_XCEP EXCEPTION
WHEN (NEW.SAL<OLD.SAL)
BEGIN
RAISE USER_XCEP
EXCEPTION
WHEN USER_XCEP THEN
DBMS_OUTPUT.PUT_LINE('UPDATION NOT ALLOWED - ILLEGAL VALUES');
END;
And I get the error - Incorrect Trigger Specification
Is there any other way to achieve this?
You're almost there; you need a DECLARE block in a trigger if you want to declare anything; this means that your WHEN clause is in the wrong place.
create or replace trigger trig1
before update
of sal
on emp
for each row
when (new.sal < old.sal)
declare
user_xcep EXCEPTION;
PRAGMA EXCEPTION_INIT( user_xcep, -20001 );
begin
raise user_xcep;
end;
SQL Fiddle
A few points:
Never catch an exception and then call DBMS_OUTPUT.PUT_LINE; it's pointless. Someone has to be there to view the result for each and every record. If you don't want something to happen raise the exception and then catch it. I've added an error code to your exception so that you can catch this outside the trigger and handle it how you wish (don't print anything to stdout).
It's a minor point but I've added a little whitespace; not much. I couldn't initially see where the problem was with your code because you didn't have any.
You were missing semi-colons after the exception declaration and RAISE.
Read more about internally defined exceptions in the documentation
Hi I am using postgresql 8.1.22, I am trying to setup postgresql auditing using the following function.
CREATE OR REPLACE FUNCTION audit.if_modified_func() RETURNS TRIGGER AS $body$
DECLARE
v_old_data TEXT;
v_new_data TEXT;
BEGIN
/* If this actually for real auditing (where you need to log EVERY action),
then you would need to use something like dblink or plperl that could log outside the transaction,
regardless of whether the transaction committed or rolled back.
*/
/* This dance with casting the NEW and OLD values to a ROW is not necessary in pg 9.0+ */
IF (TG_OP = 'UPDATE') THEN
v_old_data := ROW(OLD.*);
v_new_data := ROW(NEW.*);
INSERT INTO audit.logged_actions (schema_name,table_name,user_name,action,original_data,new_data,query)
VALUES (TG_TABLE_SCHEMA::TEXT,TG_TABLE_NAME::TEXT,session_user::TEXT,substring(TG_OP,1,1),v_old_data,v_new_data, current_query());
RETURN NEW;
ELSIF (TG_OP = 'DELETE') THEN
v_old_data := ROW(OLD.*);
INSERT INTO audit.logged_actions (schema_name,table_name,user_name,action,original_data,query)
VALUES (TG_TABLE_SCHEMA::TEXT,TG_TABLE_NAME::TEXT,session_user::TEXT,substring(TG_OP,1,1),v_old_data, current_query());
RETURN OLD;
ELSIF (TG_OP = 'INSERT') THEN
v_new_data := ROW(NEW.*);
INSERT INTO audit.logged_actions (schema_name,table_name,user_name,action,new_data,query)
VALUES (TG_TABLE_SCHEMA::TEXT,TG_TABLE_NAME::TEXT,session_user::TEXT,substring(TG_OP,1,1),v_new_data, current_query());
RETURN NEW;
ELSE
RAISE WARNING '[AUDIT.IF_MODIFIED_FUNC] - Other action occurred: %, at %',TG_OP,now();
RETURN NULL;
END IF;
EXCEPTION
WHEN data_exception THEN
RAISE WARNING '[AUDIT.IF_MODIFIED_FUNC] - UDF ERROR [DATA EXCEPTION] - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM;
RETURN NULL;
WHEN unique_violation THEN
RAISE WARNING '[AUDIT.IF_MODIFIED_FUNC] - UDF ERROR [UNIQUE] - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM;
RETURN NULL;
WHEN OTHERS THEN
RAISE WARNING '[AUDIT.IF_MODIFIED_FUNC] - UDF ERROR [OTHER] - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM;
RETURN NULL;
END;
$body$
LANGUAGE plpgsql
SECURITY DEFINER
But if you observe in the above function current_query() is not coming with the mentioned language plpgsql. It throws some error. When I googled I found that in order to use current_query() function PL/CTL language must be installed. I tried to install as mentioned below. It throws an error. So kindly help me how to install PL/CTL language into my database so that current_query() function should work
-bash-3.2$ createlang -d dbname pltcl
createlang: language installation failed: ERROR: could not access file "$libdir/pltcl": No such file or directory
Okay as you suggested I created that current_query() function,but this time I got some thing like this , What i did is ,
CREATE TABLE phonebook(phone VARCHAR(32), firstname VARCHAR(32), lastname VARCHAR(32), address VARCHAR(64));
CREATE TRIGGER phonebook_auditt AFTER INSERT OR UPDATE OR DELETE ON phonebook
FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func();
INSERT INTO phonebook(phone, firstname, lastname, address) VALUES('9966888200', 'John', 'Doe', 'North America');
for testing the function i created a table named phonebook and created a trigger so that the function mentioned above audit.if_modified_func() will be executed after any insert or update or delete.the row is getting inserted but I am getting a error reg the audit.if_modified_func() function .the error is as follows
WARNING: [AUDIT.IF_MODIFIED_FUNC] - UDF ERROR [OTHER] - SQLSTATE: 42703, SQLERRM: column "*" not found in data type phonebook
Query returned successfully: 1 rows affected, 10 ms execution time.
Kindly tell me what can i do to get rid of the above error.
Not sure where you found the information about current_query and pltcl. These are unrelated. The reason why you can't find pltcl is simply because you're using too old PostgreSQL. current_query() has been added to Pg in version 8.4.
Is there any particular reason why you're using such old version? It is no longer supported, and it lacks almost 8 years of added features!
If you have to use 8.1, you might want to define:
create function current_query() returns text as '
select current_query from pg_stat_activity where procpid = pg_backend_pid();
' language sql;
But it is much better idea just to upgrade.
As for edited and added second question - it's very likely that Pg 8.1 cannot use "row.*" construct. Find who wrote the original code with the "dance comments", and ask about it. Perhaps it was meant to work in newer Pgs.