How can I catch exceptions within a postgresql function? - sql

I am initializing functions as part of an ETL pipeline; however, one of the functions is dependant to operate on a certain table, which has not yet been created by the time this function has been initialized.
Illustration:
CREATE OR REPLACE FUNCTION some_schema.my_func(parameter_1 BIGINT)
RETURNS TEXT
AS
$$
SELECT foo
FROM non_existent_schema.non_existent_schema AS my_table -- will cause error as this relation does not yet exist
WHERE my_table.bar = parameter_1
;
$$ LANGUAGE sql;
Section 42.6.8 in the documentation (Trapping Errors) discusses exception handling but using BEGIN statements (I am not sure where to include a BEGIN or if it is relevant to my case).
My question is, how can I avoid having this error, and if I would want to silence that Exception, what is the right way to do it.
note: I am a beginner with writing functions in Postgres.

You cannot do that in an SQL function, because SQL does not have procedural code. You need to use a different procedural language, for example PL/pgSQL:
CREATE FUNCTION some_schema.my_func(parameter_1 BIGINT) RETURNS TEXT
LANGUAGE plpgsql AS
$$BEGIN
RETURN (SELECT foo
FROM non_existent_schema.non_existent_schema AS my_table
WHERE my_table.bar = parameter_1);
EXCEPTION
WHEN undefined_table THEN
NULL; -- ignore
END;$$;

Related

Struggling to create a "stored procedure" beyond INSERT

Whenever I try to call a stored procedure in PostgreSQL that goes beyond inserting data, it takes forever to run, and it isn't that the query is complicated or the dataset is huge. The dataset is small. I cannot return a table from a stored procedure and I cannot even return 1 row or 1 data point from a stored procedure. It says it is executing the query for a very long time until I finally stop the query from running. It does not give me a reason. I can't let it run for hours. Any ideas on what might be happening? I have included stored procedures that I have tried to call.
Non-working example #1:
CREATE PROCEDURE max_duration(OUT maxD INTERVAL)
LANGUAGE plpgsql AS $$
DECLARE maxD INTERVAL;
BEGIN
SELECT max(public.bikeshare3.duration)
INTO maxD
FROM public.bikeshare3;
END;
$$ ;
CALL max_duration(NULL);
Non-working example #2:
CREATE PROCEDURE getDataByRideId2(rideId varchar(16))
LANGUAGE SQL
AS $$
SELECT rideable_type FROM bikeshare3
WHERE ride_id = rideId
$$;
CALL getDataByRideId2('x78900');
Working example
The only one that worked when called is an insert procedure:
CREATE OR REPLACE PROCEDURE genre_insert_data(GenreId integer, Name_b character varying)
LANGUAGE SQL
AS $$
INSERT INTO public.bikeshare3 VALUES (GenreId, Name_b)
$$;
CALL genre_insert_data(1, 'testName');
FUNCTION or PROCEDURE?
The term "stored procedure" has been a widespread misnomer for the longest time. That got more confusing since Postgres 11 added CREATE PROCEDURE.
You can create a FUNCTION or a PROCEDURE in Postgres. Typically, you want a FUNCTION. A PROCEDURE mostly only makes sense when you need to COMMIT in the body. See:
How to return a value from a stored procedure (not function)?
Nothing in your question indicates the need for a PROCEDURE. You probably want a FUNCTION.
Question asked
Adrian already pointed out most of what's wrong in his comment.
Your first example could work like this:
CREATE OR REPLACE PROCEDURE max_duration(INOUT _max_d interval = NULL)
LANGUAGE plpgsql AS
$proc$
BEGIN
SELECT max(b.duration) INTO _max_d
FROM public.bikeshare3 b;
END
$proc$;
CALL max_duration();
Most importantly, your OUT parameter is visible inside the procedure body. Declaring another instance as variable hides the parameter. You could access the parameter by qualifying with the function name, max_duration.maxD in your original. But that's a measure of last resort. Rather don't introduce duplicate variable names to begin with.
I also did away with error-prone mixed-case identifiers in my answer. See:
Are PostgreSQL column names case-sensitive?
I made the parameter INOUT max_d interval = NULL. By adding a default value, we don't have to pass a value in the call (that's not used anyway). But it must be INOUT instead of OUT for this.
Also, OUT parameters only work for a PROCEDURE since Postgres 14. The release notes:
Stored procedures can now return data via OUT parameters.
While using an OUT parameter, this advise from the manual applies:
Arguments must be supplied for all procedure parameters that lack
defaults, including OUT parameters. However, arguments matching OUT
parameters are not evaluated, so it's customary to just write NULL
for them. (Writing something else for an OUT parameter might cause
compatibility problems with future PostgreSQL versions.)
Your second example could work like this:
CREATE OR REPLACE PROCEDURE get_data_by_ride_id2(IN _ride_id text
, INOUT _rideable_type text = NULL) -- return type?
LANGUAGE sql AS
$proc$
SELECT b.rideable_type
FROM public.bikeshare3 b
WHERE b.ride_id = _ride_id;
$proc$;
CALL get_data_by_ride_id2('x78900');
If the query returns multiple rows, only the first one is returned and the rest is discarded. Don't go there. This only makes sense while ride_id is UNIQUE. Even then, a FUNCTION still seems more suitable ...

How to write script instead of function in pl/pgsql?

I know how to define functions in pl/pgsql ... but (for testing purposes) I would now like to write pl/pgsql as a script. (That is, the code should not be enclosed in a function.) Somehow this does not seem possible. I get syntax errors for things I know are correct (inside a pl/pgsql-function), for example:
declare v_test character varying;
Even this simple one-line script fails.
How can I write a pl/pgsql script?
PostgreSQL parser doesn't support PLpgSQL. PLpgSQL can be parsed (executed) only inside functions (procedures) or inside anonymous block - statement DO
DO $$
DECLARE x int DEFAULT 10;
BEGIN
RAISE NOTICE '%', x;
END;
$$;
There are not any other possibility.

syntax to return value for error while preforming postgresql function

I am brand new to Postgresql and am trying to write a function that calls another function, and then returns -1 if that function had any errors. I am coming from a T-SQL background and essentially want the equivalent of
CREATE PROCEDURE [dbo].[p_newproc]
AS
BEGIN
EXEC p_seed_table
IF ##ERROR <> 0
RETURN -1
...
END
So far I have found that I am likely going to use something like the following:
CREATE OR REPLACE FUNCTION public.fn_newfunc()
RETURNS void
AS
$$
BEGIN
PERFORM fn_seed_table();
EXCEPTION
WHEN
SQLSTATE <> '00000'
RAISE EXCEPTION '-1';
End;
$$
LANGUAGE 'plpgsql';
Any advice would be appreciated. Thanks.
This is too long for a comment.
Both SQL Server and Postgres support exception handling. You chose not to use exception handling in the SQL Server code. You should make a conscious and informed decision about using exceptions (in either database).
SQL Server stored procedures return integers by default, and these can be used for status. Postgres doesn't have stored procedures, "only" stored functions, so you need to be a bit more explicit about the return values. The closest equivalent in Postgres to a stored procedure is a function that returns an integer.
There are pros and cons to switching over to exceptions rather than handling exceptions locally using return values (and be extra careful if you have transactions that span code blocks). I think exception handling is more robust in Postgres: SQL Server misses some types of exceptions.
However, you can implement either method in either scripting language. If you switch to using exceptions, think about the issues before doing so. That is, don't just reflexively make the switch. If you do switch to exception handling, also change the SQL Server code -- assuming you are maintaining both code bases.

Creating a function in postgresql what return value to use

I'm new to PostgresSQL programming and am trying to create a function called update_docket so that I can call it from an existing stored procedure.
With the help of some great StackOverflow folks I was able to build my update statement as follows:
UPDATE incode_warrants iw
SET warn_docket_no = iv.viol_docket_no
FROM incode_warrantvs iwvs
JOIN incode_violations iv ON iv.viol_citation_no = iwvs.warnv_citation_no
AND iv.viol_viol_no = iwvs.warnv_viol_no
WHERE iw.warn_rid = iwvs.warnv_rid;
In Navicat (My Postgresql interface) I run this query on the database and I get a return message of Affected Rows with a count.
Now I want to take this query and build a function but I'm unsure as to what I should use as the return value. I was guessing at int4, and I tried to build it with return value void but nothing seems to work.
Can someone point me in the right direction of how to create this function so that I can call it from within a very large stored procedure?
I've looked at the Postgresql manual and am admittedly a bit confused and intimidated as I'm not much of a SQL guy.
I believe in the function I need a BEGIN and END for the transaction and I've even tried return 1; but so far I haven't had any luck in successfully saving this function.
Thanks!
Thank you but I am not sure what code you are running. But, if you are doing a generic stored procedure in postgres, you could do something like this:
CREATE OR REPLACE FUNCTION my_function([parameters to be used in stored procedure, if any])
RETURNS boolean AS -- [or you can have it return any data type such as integer]
$BODY$
DECLARE
some_variable text;
some_other_variable integer;
another_variable timestamp;
BEGIN
/*
Put stored procedure here, in this case it would be your update statement
*/
RETURN true; -- or you can return whatever you want based on what you declare above under RETURNS
END; -- closes the BEGIN clause
$BODY$ -- this is simply a delimiter to open and close the actual procedure that runs
LANGUAGE plpgsql;
Hopefully that gives you some idea.... But it would be helpful if you posted whatever you were trying to run.

Difference between language sql and language plpgsql in PostgreSQL functions

Am very new in Database development so I have some doubts regarding my following example:
Function f1() - language sql
create or replace function f1(istr varchar)
returns text as $$
select 'hello! '::varchar || istr;
$$ language sql;
Function f2() - language plpgsql
create or replace function f2(istr varchar)
returns text as $$
begin select 'hello! '::varchar || istr; end;
$$ language plpgsql;
Both functions can be called like select f1('world') or select f2('world').
If I call select f1('world') the output will be:
`hello! world`
And output for select f2('world'):
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function f11(character varying) line 2 at SQL statement
********** Error **********
I wish to know the difference and in which situations I should use language sql or language plpgsql.
Any useful link or answers regarding functions will much appreciated.
SQL functions
... are the better choice:
For simple scalar queries. Not much to plan, better save any overhead.
For single (or very few) calls per session. Nothing to gain from plan caching via prepared statements that PL/pgSQL has to offer. See below.
If they are typically called in the context of bigger queries and are simple enough to be inlined.
For lack of experience with any procedural language like PL/pgSQL. Many know SQL well and that's about all you need for SQL functions. Few can say the same about PL/pgSQL. (Though it's rather simple.)
A bit shorter code. No block overhead.
PL/pgSQL functions
... are the better choice:
When you need any procedural elements or variables that are not available in SQL functions, obviously.
For any kind of dynamic SQL, where you build and EXECUTE statements dynamically. Special care is needed to avoid SQL injection. More details:
Postgres functions vs prepared queries
When you have computations that can be reused in several places and a CTE can't be stretched for the purpose. In an SQL function you don't have variables and would be forced to compute repeatedly or write to a table. This related answer on dba.SE has side-by-side code examples for solving the same problem using an SQL function / a plpgsql function / a query with CTEs:
How to pass a parameter into a function
Assignments are somewhat more expensive than in other procedural languages. Adapt a programming style that doesn't use more assignments than necessary.
When a function cannot be inlined and is called repeatedly. Unlike with SQL functions, query plans can be cached for all SQL statements inside a PL/pgSQL functions; they are treated like prepared statements, the plan is cached for repeated calls within the same session (if Postgres expects the cached (generic) plan to perform better than re-planning every time. That's the reason why PL/pgSQL functions are typically faster after the first couple of calls in such cases.
Here is a thread on pgsql-performance discussing some of these items:
Re: pl/pgsql functions outperforming sql ones?
When you need to trap errors.
For trigger functions.
When including DDL statements changing objects or altering system catalogs in any way relevant to subsequent commands - because all statements in SQL functions are parsed at once while PL/pgSQL functions plan and execute each statement sequentially (like a prepared statement). See:
Why can PL/pgSQL functions have side effect, while SQL functions can't?
Also consider:
PostgreSQL Stored Procedure Performance
To actually return from a PL/pgSQL function, you could write:
CREATE FUNCTION f2(istr varchar)
RETURNS text AS
$func$
BEGIN
RETURN 'hello! '; -- defaults to type text anyway
END
$func$ LANGUAGE plpgsql;
There are other ways:
Can I make a plpgsql function return an integer without using a variable?
The manual on "Returning From a Function"
PL/PgSQL is a PostgreSQL-specific procedural language based on SQL. It has loops, variables, error/exception handling, etc. Not all SQL is valid PL/PgSQL - as you discovered, for example, you can't use SELECT without INTO or RETURN QUERY. PL/PgSQL may also be used in DO blocks for one-shot procedures.
sql functions can only use pure SQL, but they're often more efficient and they're simpler to write because you don't need a BEGIN ... END; block, etc. SQL functions may be inlined, which is not true for PL/PgSQL.
People often use PL/PgSQL where plain SQL would be sufficient, because they're used to thinking procedurally. In most cases when you think you need PL/PgSQL you probably actually don't. Recursive CTEs, lateral queries, etc generally meet most needs.
For more info ... see the manual.
just make the select query you wrote inside the function as the returned value:
create or replace function f2(istr varchar)
returns text as $$
begin return(select 'hello! '::varchar || istr); end;
$$ language plpgsql;