Different ways to make "Create or replace" function in PLpgSQL - sql

I am very new to database systems, SQL,PLpgsql,postgresql etc. I am learning about create and replace, and i can only think of one way to make the create and replace function for example:
RETURNS trigger AS $$
--Begins function
BEGIN
perform phone, email from contact ;
if (new.phone) is null OR (new.email) is null then RAISE EXCEPTION 'Bitte geben Sie mindestens eine der folgenden Informationen an: 1. Handy-Nummer 2. E-Mail Adresse' ;
END IF;
Return new;
END;
$$
How can I write this same function differently something that is much shorter, is that possible, if possible can someone show me how it could be don, the possible ways to rewrite this function?

The PERFORM, which actually executes a sequential scan of the complete table and then discards the result, is totally unnecessary, and you should remove it.
NEW is automatically populated in FOR EACH ROW triggers on INSERT OR UPDATE, so the second line alone is good enough. Use a BEFORE trigger for performance.

Related

unterminated dollar-quoted string postgress for creating function

this question might ask many here but I have tried their advice, but it still doesn't work on me, so I am going to ask this
I am trying to create a function where to trigger update_at automatically when i update something on tables
here is my syntax on .sql file:
CREATE OR REPLACE FUNCTION trigger_set_timestamp()
RETURNS TRIGGER AS $BODY$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$BODY$ LANGUAGE plpgsql;
i always got error:
unterminated dollar-quoted string at or near "$BODY$
BEGIN
NEW.updated_at = NOW()"
I have tried to change it to $$ but still doesn't work,
I am using Go for this case with the migration tool called sql-migrate
can anyone has a solution for this? I have tried from yesterday and I get really stuck now for this
It might come from your client, that does not recognize dollar quoting.
A typical workaround is to use regular single quotes. This requires double quoting all embedded single quotes, but fortunately you code has none, so:
CREATE OR REPLACE FUNCTION trigger_set_timestamp()
RETURNS TRIGGER AS '
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
' LANGUAGE plpgsql;

Can't use LOOP PostgreSQL

I'm facing an issue from yesterday and I can't understand why my SQL is not working..
This may be a simple error since i'm a beginner in SQL but I can't find where it is.
Here is what I try to do:
CREATE FUNCTION test() RETURN integer AS $$
BEGIN
FOR i IN 1..5 LOOP
SELECT * from result WHERE id=i;
end loop;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
This is just a simple loop as I can find in the documentation but I have this error:
Error report -
ERROR: syntax error at or near "RETURN" (this is the first RETURN statement in the function)
The database is in PostgreSQL and the version is 9.4.5
Why it's not working ?
There are several problems, apart from the fact that the function isn't doing anything useful:
It must be RETURNS integer, not RETURN integer.
That't what causes the error.
The SELECT has no destination. Either add an INTO clause or discard the result with
PERFORM * from result WHERE id=i;
You should indent the code correctly, so that you can read and understand it.

Does Oracle 12 have problems with local collection types in SQL?

To make a long story short I propose to discuss the code you see below.
When running it:
Oracle 11 compiler raises
"PLS-00306: wrong number or types of arguments tips in call to 'PIPE_TABLE'"
"PLS-00642: Local Collection Types Not Allowed in SQL Statement"
Oracle 12 compiles the following package with no such warnings, but we have a surprise in runtime
when executing the anonymous block as is - everything is fine
(we may pipe some rows in the pipe_table function - it doesn't affect)
now let's uncomment the line with hello; or put there a call to any procedure, and run the changed anonumous block again
we get "ORA-22163: left hand and right hand side collections are not of same type"
And the question is:
Does Oracle 12 allow local collection types in SQL?
If yes then what's wrong with the code of PACKAGE buggy_report?
CREATE OR REPLACE PACKAGE buggy_report IS
SUBTYPE t_id IS NUMBER(10);
TYPE t_id_table IS TABLE OF t_id;
TYPE t_info_rec IS RECORD ( first NUMBER );
TYPE t_info_table IS TABLE OF t_info_rec;
TYPE t_info_cur IS REF CURSOR RETURN t_info_rec;
FUNCTION pipe_table(p t_id_table) RETURN t_info_table PIPELINED;
FUNCTION get_cursor RETURN t_info_cur;
END buggy_report;
/
CREATE OR REPLACE PACKAGE BODY buggy_report IS
FUNCTION pipe_table(p t_id_table) RETURN t_info_table PIPELINED IS
l_table t_id_table;
BEGIN
l_table := p;
END;
FUNCTION get_cursor RETURN t_info_cur IS
l_table t_id_table;
l_result t_info_cur;
BEGIN
OPEN l_result FOR SELECT * FROM TABLE (buggy_report.pipe_table(l_table));
RETURN l_result;
END;
END;
/
DECLARE
l_cur buggy_report.t_info_cur;
l_rec l_cur%ROWTYPE;
PROCEDURE hello IS BEGIN NULL; END;
BEGIN
l_cur := buggy_report.get_cursor();
-- hello;
LOOP
FETCH l_cur INTO l_rec;
EXIT WHEN l_cur%NOTFOUND;
END LOOP;
CLOSE l_cur;
dbms_output.put_line('success');
END;
/
In further experiments we found out that problems are even deeper than it's been assumed.
For example, varying elements used in the package buggy_report we can get an ORA-03113: end-of-file on communication channel
when running the script (in the question). It can be done with changing the type of t_id_table to VARRAY or TABLE .. INDEX BY ... There are a lot of ways and variations leading us to different exceptions, which are off topic to this post.
The one more interesting thing is that compilation time of buggy_report package specification can take up to 25 seconds,
when normally it takes about 0.05 seconds. I can definitely say that it depends on presence of TYPE t_id_table parameter in the pipe_table function declaration, and "long time compilation" happen in 40% of installation cases. So it seems that the problem with local collection types in SQL latently appear during the compilation.
So we see that Oracle 12.1.0.2 obviously have a bug in realization of using local collection types in SQL.
The minimal examples to get ORA-22163 and ORA-03113 are following. There we assume the same buggy_report package as in the question.
-- produces 'ORA-03113: end-of-file on communication channel'
DECLARE
l_cur buggy_report.t_info_cur;
FUNCTION get_it RETURN buggy_report.t_info_cur IS BEGIN RETURN buggy_report.get_cursor(); END;
BEGIN
l_cur := get_it();
dbms_output.put_line('');
END;
/
-- produces 'ORA-22163: left hand and right hand side collections are not of same type'
DECLARE
l_cur buggy_report.t_info_cur;
PROCEDURE hello IS BEGIN NULL; END;
BEGIN
l_cur := buggy_report.get_cursor;
-- comment `hello` and exception disappears
hello;
CLOSE l_cur;
END;
/
Yes, in Oracle 12c you are allowed to use local collection types in SQL.
Documentation Database New Features Guide says:
PL/SQL-Specific Data Types Allowed Across the PL/SQL-to-SQL Interface
The table operator can now be used in a PL/SQL program on a collection whose data type is declared in PL/SQL. This also allows the data type to be a PL/SQL associative array. (In prior releases, the collection's data type had to be declared at the schema level.)
However, I don't know why your code is not working, maybe this new feature has still a bug.
I fiddled around your example. The trick how Oracle 12c can use PL/SQL collections in SQL statements is that Oracle creates surrogate schema object types with compatible SQL type attributes and uses these surrogate types in a query. Your case looks like a bug. I traced the execution and the surrogate types are created only once if not exist. So the effective type doesn't change nor recompile (don't know if implicit recompilation are done using ALTER statement) during execution of pipelined function. And the issue only occurs if you use the p parameter in pipe_table function. If you don't call l_table := p; the code executes successfully even with enabled method call.

PL/SQL Validate/Insert/Update Status - retval vs. exception

I'm always looking to improve and applying best practices. I read quite a bit about refactoring in the last weeks. I have to work with a lot of awful code and I produced some not so nice stuff too but I'm trying to change that. Thats no problem for most languages but I'm pretty new to PL/SQL so I just copied the style of the already written code.
After reading some tutorials I realized that a lot of our code is pretty much more C style code using retval instead exceptions etc.
We have a lot of functions like open cursor, loop through it, validate the data, trim it or make some string manipulation and insert it into another table, update the status etc. I wonder what a best practice solution would look like on something like this. Atm most functions look like this:
LOOP
FETCH C_ABC INTO R_ABC;
EXIT WHEN C_ABC%NOTFOUND OR C_ABC%NOTFOUND IS NULL;
SAVEPOINT SAVE_LOOP;
retval := plausibilty_check(r_ABC);
IF (retval = STATUS_OK) THEN
retval := convert_ABC_TO_XYZ(r_ABC, r_XYZ);
END IF;
IF (retval = STATUS_OK) THEN
retval := insert_XYZ(r_XYZ);
END IF;
retval := update_ABC(r_ABC.PK_Id, retval);
END LOOP;
If i was using exceptions I guess I had to raise them inside the functions so I can handle them in the main function, if not everyone would have to crawl to every sub function to understand the program and where the updates happen etc. So I guess I would have to use another PL/SQL block inside the loop? Like:
LOOP
FETCH C_ABC INTO R_ABC;
EXIT WHEN C_ABC%NOTFOUND OR C_ABC%NOTFOUND IS NULL;
SAVEPOINT SAVE_LOOP;
BEGIN
plausibilty_check(r_ABC);
convert_ABC_TO_XYZ(r_ABC, r_XYZ);
insert_XYZ(r_XYZ);
update_ABC(r_ABC.PK_Id, STATUS_OK);
EXCEPTION
WHEN ERROR_CODE_XYZ THEN
update_ABC(r_ABC.PK_Id, ERROR_CODE_XYZ);
END
END LOOP;
I guess that function handles a pretty common problem but I still havn't found any tutorial covering something like this. Maybe someone more experienced with PL/SQL might gimme a hint what a best practice on a task like that would look like.
I like to use exceptions to jump out of a block of code if an exceptional event (e.g. an error) occurs.
The problem with the "retval" method of detecting error conditions is that it introduces a second layer of semantics on what a function is and for.
In principle, a function should be used to perform a calculation and return a result; in this sense, a function doesn't do anything, i.e. it makes no changes to any state - it merely returns a value.
If it cannot for some reason calculate that value, that would be an exceptional circumstance, so I'd want the function to raise an exception so that the calling program will not blindly continue on its merry way, thinking it got a valid value from the function.
On the other hand, a procedure is a method by which action is done - something is changed, something is validated, or something is sent. The normal expected path is that the procedure is executed, it does its thing, then it finishes. If an error occurs, I want it to raise an exception so that the calling program will not blindly continue thinking that the procedure has successfully done its thing.
Thus, the original intent of the fundamental difference between "procedures" and "functions" is preserved.
In languages like C, there are no procedures - everything is a function in a sense (even functions that return "void") - but in addition, there is no real concept of an "exception" - so these semantics don't apply. It's for this reason that the C style of returning an error/success flag don't translate well into languages like PL/SQL.
In your example, I'd consider doing it something like this:
BEGIN
LOOP
FETCH c_ABC INTO r_ABC;
EXIT WHEN c_ABC%NOTFOUND;
IF record_is_plausible(r_ABC) THEN
r_XYZ := convert_ABC_TO_XYZ(r_ABC);
insert_or_update_XYZ(r_XYZ);
ELSE
update_as_implausible(r_ABC);
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
-- log the error or something, then:
RAISE;
END;
So where the semantics of the operation is doing some validation or something, and returning a result, I converted plausibilty_check into a function record_is_plausible that returns a boolean.
I'd pull the call to update_ABC out of the BEGIN block and make it common at the bottom of the loop, as in:
DECLARE
nFinal_status NUMBER;
BEGIN
LOOP
FETCH C_ABC INTO R_ABC;
EXIT WHEN C_ABC%NOTFOUND OR C_ABC%NOTFOUND IS NULL;
SAVEPOINT SAVE_LOOP;
nFinal_status := nSTATUS_OK;
BEGIN
plausibilty_check(r_ABC);
convert_ABC_TO_XYZ(r_ABC, r_XYZ);
insert_XYZ(r_XYZ);
EXCEPTION
WHEN excpERROR_CODE_XYZ THEN
nFinal_status := nERROR_CODE_XYZ;
END;
update_ABC(r_ABC.PK_Id, nFinal_status);
END LOOP;
END;
You might want to have each of the procedures throw its own exception so you could more easily identify where the issue(s) are coming from - or use different exceptions/error codes for each possible issue (for example, the plausibility check might raise different exceptions depending on what implausible condition it found). However, in my experience plausibility checks will often uncover multiple conditions (if the data's bad it's often really bad :-), so it might be nice to tabularize the errors and relate them to the base ABC data through a foreign key, thus allowing each individual error to be identified with just one pass through the data. Then the 'status' field on ABC becomes moot; you either have errors associated with the ABC row or you don't. If errors exist, do whatever is needed. If no errors, proceed with 'normal' processing.
Just a thought.
Share and enjoy.

IF function in PostgreSQL as in MySQL

I am trying to replicate the IF function from MySQL into PostgreSQL.
The syntax of IF function is IF(condition, return_if_true, return_if_false)
I created following formula:
CREATE OR REPLACE FUNCTION if(boolean, anyelement, anyelement)
RETURNS anyelement AS $$
BEGIN
CASE WHEN ($1) THEN
RETURN ($2);
ELSE
RETURN ($3);
END CASE;
EXCEPTION WHEN division_by_zero THEN
RETURN ($3);
END;
$$ LANGUAGE plpgsql;
It works well with most of the things like if(2>1, 2, 1) but it raises an error for:
if( 5/0 > 0, 5, 0)
fatal error division by zero.
In my program I can't check the denominator as the condition is provided by user.
Is there any way around? Maybe if we can replace first parameter from boolean to something else, as in that case the function will work as it will raise and return the exception.
PostgreSQL is following the standard
This behaviour appears to be specified by the SQL standard. This is the first time I've seen a case where it's a real problem, though; you usually just use a CASE expression or a PL/PgSQL BEGIN ... EXCEPTION block to handle it.
MySQL's default behaviour is dangerous and wrong. It only works that way to support older code that relies on this behaviour. It has been fixed in newer versions when strict mode is active (which it absolutely always should be) but unfortunately has not yet been made the default. When using MySQL, always enable STRICT_TRANS_TABLES or STRICT_ALL_TABLES.
ANSI-standard zero division is a pain sometimes, but it'll also protect against mistakes causing data loss.
SQL injection warning, consider re-design
If you're executing expressions from the user then you quite likely have SQL injection problems. Depending on your security requirements you might be able to live with that, but it's pretty bad if you don't totally trust all your users. Remember, your users could be tricked into entering the malicious code from elsewhere.
Consider re-designing to expose an expression builder to the user and use a query builder to create the SQL from the user expressions. This would be much more complicated, but secure.
If you can't do that, see if you can parse the expressions the user enters into an abstract syntax, validate it before execution, and then produce new SQL expressions based on the parsed expression. That way you can at least limit what they can write, so they don't slip any nasties into the expression. You can also rewrite the expression to add things like checks for zero division. Finding (or writing) parsers for algebraic expressions isn't likely to be hard, but it'll depend on what kinds of expressions you need to let users write.
At minimum, the app needs to be using a role ("user") that has only SELECT privileges on the tables, is not a superuser, and does not own the tables. That'll minimise the harm any SQL injection will cause.
CASE won't solve this problem as written
In any case, because you currently don't validate and can't inspect the expression from the user, you can't use the SQL-standard CASE statement to solve this. For if( a/b > 0, a, b) you'd usually write something like:
CASE
WHEN b = 0 THEN b
ELSE CASE
WHEN a/b=0 THEN a
ELSE b
END
END
This explicitly handles the zero denominator case, but is only possible when you can break the expression up.
Ugly workaround #1
An alternative solution would be to get Pg to return a placeholder instead of raising an exception for division by zero by defining a replacement division operator or function. This will only solve the divide-by-zero case, not others.
I wanted to return 'NaN' as that's the logical result. Unfortunately, 'NaN' is greater than numbers not less then, and you want a less-than or false-like result.
regress=# SELECT NUMERIC 'NaN' > 0;
?column?
----------
t
(1 row)
This means we have to use the icky hack of returning NULL instead:
CREATE OR REPLACE FUNCTION div_null_on_zero(numeric,numeric) returns numeric AS $$
VALUES (CASE WHEN $2 = 0 THEN NULL ELSE $1/$2 END)
$$ LANGUAGE 'SQL' IMMUTABLE;
CREATE OPERATOR #/# (
PROCEDURE = div_null_on_zero(numeric,numeric),
LEFTARG = numeric,
RIGHTARG = numeric
);
with usage:
regress=# SELECT 5 #/# 0, 5 #/# 0>0, CASE WHEN 5 #/# 0 > 0 THEN 5 ELSE 0 END;
?column? | ?column? | case
----------+----------+------
| | 0
(1 row)
Your app can rewrite '/' in incoming expressions into #/# or whatever operator name you choose pretty easily.
There's one pretty critical problem with this approach, and that's that #/# will have different precedence to / so expressions without explicit parentheses may not be evaluated as you expect. You might be able to get around this by creating a new schema, defining an operator named / in that schema that does your null-on-error trick, and then adding that schema to your search_path before executing user expressions. It's probably a bad idea, though.
Ugly workaround #2
Since you can't inspect the denominator, all I can think of is to wrap the whole thing in a DO block (Pg 9.0+) or PL/PgSQL function and catch any exceptions from the evaluation of the expression.
Erwin's answer provides a better example of this than I did, so I've removed this. In any case, this is an awful and dangerous thing to do, do not do it. Your app needs to be fixed.
With a boolean argument, a division by zero will always throw an exception (and that's a good thing), before your function is even called. There is nothing you can do about it. It's already happened.
CREATE OR REPLACE FUNCTION if(boolean, anyelement, anyelement)
RETURNS anyelement LANGUAGE SQL AS
$func$
SELECT CASE WHEN $1 THEN $2 ELSE $3 END
$func$;
I would strongly advise against a function named if to begin with. IF is a keyword in PL/pgSQL. If you use user defined functions written in PL/pgSQL this will be very confusing.
Just use the standard SQL expression CASE directly.
The alternative would be to take a text argument and evaluate it with dynamic SQL.
Proof of concept
What you ask for would work like this:
CREATE OR REPLACE FUNCTION f_if(_expr text
, _true anyelement
, _else anyelement
, OUT result anyelement)
RETURNS anyelement LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE '
SELECT CASE WHEN (' || _expr || ') THEN $1 ELSE $2 END' -- !! dangerous !!
USING _true, _else
INTO result;
EXCEPTION WHEN division_by_zero THEN
result := _else;
-- possibly catch more types of exceptions ...
END
$func$;
Test:
SELECT f_if('TRUE' , 1, 2) --> 1
,f_if('FALSE' , 1, 2) --> 2
,f_if('NULL' , 1, 2) --> 2
,f_if('1/0 > 0', 1, 2); --> 2
This is a big security hazard in the hands of untrusted users. Read #Craig's answer about making this more secure.
However, I fail to see how it can be made bulletproof and would never use it.