I am using Sybase ASE 12.5 at the moment. I have a code below:
create procedure test_launcher_fail_wrapper
as
begin
select convert(numeric(2),1234345)
if ##error != 0
begin
select "SP failed to execute"
return 1
end
end
Here, I am trying to convert a very large value/amount (1234345) to Numeric size 2. Which is not possible and it generates error.
Questions:
Is having ##error useful here? I ran this SP and it never went into
error handling
How to error handle these kind of scenarios?
I treat error handling in procs similarly to error handling in applications -- if there's an opportunity for you to contribute some actual value by handling the error, then by all means, do so, but if you can't really do anything to help, then you're better off just letting it go.
As an example of adding value, I've got one or two procs that add contextual information in the error message, like a list of ID values that conflict with an update operation. In this particular case, I know that the upstream consumer of the proc will log this error, and the text will be available to an operator who will find this information valuable when debugging the problem. I also know that while this condition is a real error, it's been known to happen from time-to-time, and the effort to format the error is worthwhile.
Does this catch your error?
create procedure test_launcher_fail_wrapper
as
begin
declare #database_err int
set #database_err = 0
select convert(numeric(2),1234345)
set #database_err = ##error
if #database_err <> 0
begin
PRINT 'SP failed to execute'
return 1
end
end
##error is the way to go but beware since:
Every Transact-SQL statement, including print statements and if tests, resets ##error, so the status check must immediately follow the batch for which success is in question.
As for a suggestion on how to handle error management in similar scenarios, have you considered using raiserror ?
An example:
create procedure proc1 as
begin
select convert(numeric(2),1234345)
if ##error <> 0
begin
raiserror 20001 "Error during convert in proc1"
return 1
end
end
Related
I'm trying to create this trigger in my PostgresSql environment:
CREATE TRIGGER MYTRIGGER
BEFORE INSERT
ON MYTABLE
FOR EACH ROW
BEGIN
IF( LENGTH( :NEW.VAL ) > 10 )
THEN
RAISE_APPLICATION_ERROR( -20003,
'Cannot exceed 10 chars' );
END IF;
IF :NEW.FQN_ID IS NULL THEN
:NEW.FQN_ID :=
CASE :NEW.SUBTYPECODE
WHEN NULL THEN 'A:'
WHEN 0 THEN 'B:'
WHEN 1 THEN 'C:'
WHEN 2 THEN 'D:'
ELSE 'Z:' || :NEW.SUBTYPECODE || '::'
--END || :NEW.OBJECTID;
END || STRUCTURE_FQNID_SEQ.NEXTVAL;
END IF;
END;
But I get this error:
ERROR: syntax error at or near "BEGIN"
LINE 5: BEGIN
^
SQL state: 42601
Character: 79
I think I'm missing something but I can't get it.
Any suggestion would be greatly appreciated.
Thank you.
Here are my notes about triggers in several DBMSs: https://github.com/iwis/SQL-notes. I marked the differences between the DBMSs in orange. I think that the notes are quite complete, so you don't have to read about triggers in Postgres documentation.
I see the following changes that need to done in your example:
Change Oracle :NEW to Postgres NEW.
Instead of a BEGIN ... END block, write EXECUTE FUNCTION my_trigger_function();, where my_trigger_function is a function that needs to be created like in the example given by a_horse_with_no_name.
This function should return NEW in your case - the reason is described here.
If a more complicated code is fired by a trigger, then you also need to understand the differences between PL/SQL and PL/pgSQL languages. These languages are quite similar, though there are some differences. Your code is simple so the differences are small. It's probably enough to:
Write Postgres RETURNS in the function definition instead of Oracle RETURN.
Write $$ before BEGIN, and $$ LANGUAGE plpgsql; after END.
Write Postgres RAISE 'Cannot exceed 10 chars'; instead of Oracle RAISE_APPLICATION_ERROR(-20003, 'Cannot exceed 10 chars');.
I don't know if sequences work in the same way in PostgreSQL - I haven't read about them yet.
Let me know if my notes are understandable - I'm not sure about it because they are super compact so you need to decipher the markings used by me.
I am struggling to figure out how to best handle the return of results or errors to my application from Postgres stored functions.
Consider the following contrived psudeocode example:
app.get_resource(_username text)
RETURNS <???>
BEGIN
IF ([ ..user exists.. ] = FALSE) THEN
RETURN 'ERR_USER_NOT_FOUND';
END IF;
IF ([ ..user has permission.. ] = FALSE) THEN
RETURN 'ERR_NO_PERMISSION';
END IF;
-- Return the full user object.
RETURN QUERY( SELECT 1
FROM app.resources
WHERE app.resources.owner = _username);
END
The function can fail with a specific error or succeed and return 0 or more resources.
At first I tried creating a custom type to always use as a standard return type in eachh function:
CREATE TYPE app.appresult AS (
success boolean,
error text,
result anyelement
);
Postgres does not allow this however:
[42P16] ERROR: column "result" has pseudo-type anyelement
I then discovered OUT parameters and attempted the following uses:
CREATE OR REPLACE FUNCTION app.get_resource(
IN _username text,
OUT _result app.appresult -- Custom type
-- {success bool, error text}
)
RETURNS SETOF record
AS
$$
BEGIN
IF 1 = 1 THEN -- just a test
_result.success = false;
_result.error = 'ERROR_ERROR';
RETURN NULL;
END IF;
RETURN QUERY(SELECT * FROM app.resources);
END;
$$
LANGUAGE 'plpgsql' VOLATILE;
Postgres doesn't like this either:
[42P13] ERROR: function result type must be app.appresult because of OUT parameters
Also tried a similar function but reversed: Returning a custom app.appresult object and setting the OUT param to "SETOF RECORD". This was also not allowed.
Lastly i looked into Postgres exception handling using
RAISE EXCEPTION 'ERR_MY_ERROR';
So in the example function, i'd just raise this error and return.
This resulted in the driver sending back the error as:
"ERROR: ERR_MY_ERROR\nCONTEXT: PL/pgSQL function app.test(text) line 6 at RAISE\n(P0001)"
This is easy enough to parse but doing things this way feels wrong.
What is the best way to solve this problem?
Is it possible to have a custom AppResult object that i could return?
Something like:
{ success bool, error text, result <whatever type> }
//Edit 1 //
I think I'm leaning more towards #Laurenz Albe solution.
My main goal is simple: Call a stored procedure which can return either an error or some data.
Using RAISE seems to accomplish this and the C++ driver allows easy checking for an error condition returned from a query.
if ([error code returned from the query] == 90100)
{
// 1. Parse out my overly verbose error from the raw driver
// error string.
// 2. Handle the error.
}
I'm also wondering about using custom SQLSTATE codes instead of parsing the driver string.
Throwing '__404' might mean that during the course of my SPs execution, it could not continue because some record needed was not found.
When calling the sql function from my app, i have a general idea of what it failing with a '__404' would mean and how to handle it. This avoids the additional step of parsing driver error string.
I can also see the potential of this being a bad idea.
Bedtime reading:
https://www.postgresql.org/docs/current/static/errcodes-appendix.html
This is slightly opinion based, but I think that throwing an error is the best and most elegant solution. That is what errors are for!
To distinguish various error messages, you could use SQLSTATEs that start with 6, 8 or 9 (these are not used), then you don't have to depend on the wording of the error message.
You can raise such an error with
RAISE EXCEPTION SQLSTATE '90001' USING MESSAGE = 'my own error';
We do something similar to what you're trying to do, but we use TEXT rather than ANYELEMENT, because (almost?) any type can be cast to TEXT and back. So our type looks something like:
(errors our_error_type[], result TEXT)
The function which returns this stores errors in the errors array (it's just some custom error type), and can store the result (cast to text) in the result field.
The calling function knows what type it expects, so it can first check the errors array to see if any errors were returned, and if not it can cast the result value to the expected return type.
As a general observation, I think exceptions are more elegant (possibly because I come from a c# background). The only problem is in plpgsql exception handling is (relatively) slow, so it depends on the context - if you're running something many times in a loop, I would prefer a solution that doesn't use exception handling; if it's a single call, and/or especially when you want it to abort, I prefer raising an exception. In practice we use both at various points throughout our call stacks.
And as Laurenz Albe pointed out, you're not meant to "parse" exceptions, so much as raise an exception with specific values in specific fields, which the function that catches the exception can then extract and act on directly.
As an example:
Setup:
CREATE TABLE my_table (id INTEGER, txt TEXT);
INSERT INTO my_table VALUES (1,'blah');
CREATE TYPE my_type AS (result TEXT);
CREATE OR REPLACE FUNCTION my_func()
RETURNS my_type AS
$BODY$
DECLARE
m my_type;
BEGIN
SELECT my_table::TEXT
INTO m.result
FROM my_table;
RETURN m;
END
$BODY$
LANGUAGE plpgsql STABLE;
Run:
SELECT (m.result::my_table).*
FROM my_func() AS m
Result:
| id | txt |
-------------
| 1 | blah |
I'm looking at this example provided by MS as I'm trying to learn Try...Catch. I understand the syntax and Output (for the most part) but I have one question:
The Output will show the Error_Line as '4'. This is fine but if I remove the line break between GO and BEGIN TRY it'll show the Error_Line as '3'. I just want to understand the logic here.
What I imagine is happening is that SQL Server is counting the lines by beginning the batch immediately after GO, even if that line is blank but I do not know this for certain. Can anyone clarify? If that theory is correct, wouldn't that make finding errors difficult if scripts are written with line breaks like this?
-- Verify that the stored procedure does not already exist.
IF OBJECT_ID ( 'usp_GetErrorInfo', 'P' ) IS NOT NULL
DROP PROCEDURE usp_GetErrorInfo;
GO
-- Create procedure to retrieve error information.
CREATE PROCEDURE usp_GetErrorInfo
AS
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_SEVERITY() AS ErrorSeverity
,ERROR_STATE() AS ErrorState
,ERROR_PROCEDURE() AS ErrorProcedure
,ERROR_LINE() AS ErrorLine
,ERROR_MESSAGE() AS ErrorMessage;
GO
--Line 1
BEGIN TRY --Line 2
-- Generate divide-by-zero error. --Line 3
SELECT 1/0; --Line 4
END TRY
BEGIN CATCH
-- Execute error retrieval routine.
EXECUTE usp_GetErrorInfo;
END CATCH;
You can't really rely on ERROR_LINE(), especially when the error is thrown in internal stored procedure or there is dynamic T-SQL statement which is executed.
But do you really need the exact error line?
in real production code, the fix for the line causing the error may not be so obvious as in your example;
it will be better to debug the stored procedure or the function with the corresponding input parameter in order to reproduce the error
In this way it will be easier to fix an issue. In order to debug a SQL routine:
just script it
remove the drop and create stuff
add declare in front of the input parameters and initialized them with the values causing the error
Basically, instead of the exact error line (which can be easily fine having the correct input parameters and executing the routine) you may found useful two things:
which routing is causing the error (for example, you can add additional parameter to user usp_GetErrorInfo SP which is yielding the SP name as well
the input parameters which are causing the error (this can be done using separated table for logging the errors in the CATCH clause - you simple insert the input parameters in the table and information about the error)
Having this information, it will be easy to reproduce and then fix an issue (in many cases).
I have to make a update function that have multiple conditions like this
BEGIN
OPEN cur3 FOR execute('select id_organigramme from ( select distinct id_personne,id_organigramme,idfax from requpdate where
id_personne= ' || VariableIDpersonne || ' and idfax is null) a where
a.id_organigramme not in (select distinct id_organigramme from
requpdate where id_personne= ' ||VariableIDpersonne || ' and idfax is
not null and a.id_personne=requpdate.id_personne ) ');
LOOP
FETCH cur3 INTO VariableIDorganigrammeFax;
if not found then
--Message here !!!
--Raise notice 'hello word!'
exit;
end if;
I have to show up messages if any condition exists I found out that I can do this with Raise Notice/info ... statement, but I have to make auto export of those messages into a text file when the function finishes.
Is this possible? Otherwise what can I use to make it.
I use PGAdminIII as a client.
What your logging options are depends entirely on your client configuration. But rather than using RAISE NOTICE I would suggest you use the NOTIFY \ LISTEN framework. Basically, in your function you issue a notice to a channel of your choosing (can be any string) and in your client you listen to that same channel, logging the messages as they come in. How exactly the listening and logging works depends on your client.
The code you show can also you use some improvements.
First of all, your query is an incredibly convoluted version of:
SELECT DISTINCT id_organigramme
FROM requpdate
WHERE id_personne = VariableIDpersonne
AND idfax IS NULL;
Secondly, you do not need a dynamic query, you can get by with variable substitution. Assuming id_personne is not a string, it is as simple as stated above, otherwise use quote_literal(VariableIDpersonne).
Lastly, unless there are parts of your function not shown that require a cursor, you can simply do:
FOR VariableIDorganigrammeFax IN [query above]
LOOP
... -- do your processing here
END LOOP;
IF NOT FOUND THEN -- the loop above did not iterate because no records were returned
SELECT pg_notify('logMyFunction', format('%s: No records found', VariableIDpersonne));
END IF;
The pg_notify() function is a wrapper around the NOTIFY command that makes it possible to pass variable strings.
Before you call the function, you should issue the command LISTEN logMyFunction so that your session will receive the notifications from the channel.
I want to use T-SQL to perform a sequence of merges. I understand that if one fails, it rolls back, but I would like to print a message to the effect - so I know I need to go and check it. I can't seem to find any examples of this to start with. My first thought was to put the the transaction in a try, but then I'm not sure what happens with the rollback statement ... it seems weird putting that in the catch. I'm sure others must have had wanted to do this previously. Does anyone have an example of this?
NOTE: I need to use the MERGE command.
Try below code. Try Catch help from msdn
BEGIN TRY
-- Table does not exist; object name resolution
-- error not caught.
SELECT * FROM Table;
END TRY
BEGIN CATCH
Print ERROR_NUMBER() ;
Print ERROR_MESSAGE() ;
END CATCH
You can use OUTPUT with a merge. Sample