PostgreSQL CASE usage in functions - postgresql

Can't we use CASE condition outside SQL SELECT statements?
E.g.:
CASE
WHEN old.applies_to = 'admin' THEN _applies_to = 'My Self'
ELSE _applies_to = initcap(old.applies_to)
END
_summary = _summary || '<li>Apply To: ' || _applies_to || '</li>';
I get the following error:
ERROR: syntax error at or near "_summary"
LINE 86: _summary = _summary || '<li>Apply To: ' || _applies ...

This concerns the conditional control structure CASE of the procedural language PL/pgSQL, to be used in plpgsql functions or procedures or DO statements.
Not to be confused with CASE expression of SQL. Different language! Subtly different syntax rules.
While SQL CASE can be embedded in SQL expressions inside PL/pgSQL code (which is mostly just glue for SQL commands), you cannot have stand-alone SQL CASE expressions (would be nonsense).
-- inside a plpgsql code block:
CASE
WHEN old.applies_to = 'admin' THEN
_applies_to := 'My Self';
ELSE
_applies_to := initcap(old.applies_to);
END CASE;
You have to use fully qualified statements, terminated with semicolon (;) and END CASE to close it.
Answer to additional question in comment
According to documentation the ELSE keyword of a CASE statement is not optional. I quote from the link above:
If no match is found, the ELSE statements are executed;
but if ELSE is not present, then a CASE_NOT_FOUND exception is raised.
However, you can use an empty ELSE:
CASE
WHEN old.applies_to = 'admin' THEN
_applies_to := 'My Self';
ELSE
-- do nothing
END CASE;
This is different from SQL CASE expressions where ELSE is optional, but if the keyword is present, an expression has to be given, too!

Related

C-like compound assignment operator in Postgresql

In postgresql documentation, there is the entry regarding the "CREATE OPERATOR". I'm literally desperate for a compound assignment operator, C or C# like style, in my PL/PGSQL code. Ideally it would work like this
v_my_text += 'some more text to be added to existing value of v_my_text';
This would of course be the equivalent of
v_my_text := v_my_text || 'some more text...';
I was not able to find any example around the assignment operators. Is something like this possible?
According to feedbacks I was getting, creating += operator is currently not possible. Yet, #Bergi gave me an idea with inout parameters which can simplify my code.
create or replace procedure add_line
(
p_text_body inout text,
p_new_text in text
)
language plpgsql
as $$
begin
p_text_body := p_text_body || p_next_text || chr(10) || chr(13);
end; $$
It would be used like call add_line(v_output_report, 'New text line here...');
Any better ideas are welcome. See comments on question for more context. Regards.
There is no need for procedure with INOUT parameter (or starting with v14 an OUT parameter). Instead just build an SQL function. (see demo)
create or replace function add_line
( p_text_body in text
, p_new_text in text
)
returns text
language sql
as $$
select concat(p_text_body, E'\n', p_new_text);
$$;
Notes:
Use E'\n' (4.1. Lexical Structure: 4.1.2.2. String Constants with C-Style Escapes) rather than chr(10) || chr(13). It adjusts to the proper code for the operating system.
The normal assignment operator in Postgres is just =. The += is strictly to maintain compatibility with Oracle plsql.
The concat function is perhaps better than the concatenation operator (||) here as it handles NULLs. concat('A', null) or concat(null, 'A' ) both return 'A' where as 'A' || null or null || 'A' both return null.

Why are TRIM and SUBSTRING not working in this PL/pgSQL function?

I wrote this very simple PL/pgSQL function in order to manage date formats in my database (datetuple is a custom type being a tuple of int's):
CREATE FUNCTION date2datetuple (_date text)
RETURNS datetuple AS
$BODY$
BEGIN
IF _date ~ '[0-9]{4}' THEN
RETURN (_date::int,
_date::int);
ELSIF _date ~ '[0-9]{4}ca' THEN
RETURN (trim(trailing 'ca' from _date)::int,
trim(trailing 'ca' from _date)::int);
ELSE
RETURN (substring(_date from '^[0-9]{4}')::int - substring(_date from '[0-9]{2}$')::int,
substring(_date from '^[0-9]{4}')::int + substring(_date from '[0-9]{2}$')::int);
END IF;
END;
$BODY$
LANGUAGE plpgsql;
The function gets defined without any error, but when I input e.g. '1980ca' then it throws me this error:
ERROR: invalid input syntax for type integer: "1950ca"
CONTEXT: PL/pgSQL function date2datetuple(text) line 4 at RETURN
SQL state: 22P02
Upon further investigation it seems that any argument just gets passed on without being modified, which throws an error since '1950ca' cannot be cast as int. Why are the functions not doing anything?
According to the documentation:
Unlike LIKE patterns, a regular expression is allowed to match anywhere within a string, unless the regular expression is explicitly anchored to the beginning or end of the string.
You should anchor the pattern in the first and (probably) second comparison:
...
IF _date ~ '^[0-9]{4}$' THEN
...
ELSIF _date ~ '^[0-9]{4}ca$' THEN
...

How to pass variable to a do block with sqlx?

I am trying to run the following do block script in sql through sqlx. As do block doesn't accept parameters, I tried to prepare it beforehand.
func LinkSessionUser(sessionId string, userId string, result *bool) error {
db := database.GetDB()
statement, err := db.Preparex(`
do
$$
begin
if (exists(select id from "session" where id = $1) and exists(select id from "user" where id = $2)) then
return insert into "session_user" (session_id, user_id) values ($1, $2) on conflict do nothing;
else
raise exception "Either session(id=%) or user(id=%) doesn't exist" $1, $2;
end if;
end
$$;
`)
if err != nil {
return err
}
return statement.Get(result, sessionId, userId)
}
But as I run it, I got the following errors:
sql: expected 0 arguments, got 2
How can I fix this issue? Should I be using Preparex to replace prepare in sql?
"Should I be using Preparex to replace prepare in sql?" -- Yes, however the problem you've encountered has nothing to do with Preparex, or the Go language itself, for that matter.
The code block of the DO command is a string literal, this string literal will not be, and has no reason to be, parsed during the PREPARE command execution, which is why the resulting prepared statement will have no defined parameters. As you correctly point out, DO does not support input parameters, but trying to get around that limitation by wrapping DO in a prepared statement will simply not produce the result you were hoping for.
If you need to conditionally execute parameterized SQL statements then you should use the CREATE FUNCTION command and explicitly execute the resulting function.

How to add strings any type of string into PostgreSQL text field

I have a Postgres database where I have a column that stores text. This text is user input and can have all types of strange characters empirically for example both ' and ".
But in in principle there is no limit.
How can I format my update strings such that I can input them with the following query type:
update article
set text = <formated_text>
where article.name=<name>;
There are two techniques:
use a prepared statement:
PREPARE ins(text, text) AS
UPDATE article SET text = $1 WHERE article.name = $2;
Then you can call it like
EXECUTE ins('with '' quote', 'more '' quotes''');
and you can be sure that no SQL injection can happen.
This example is using server side prepared statements, but your client API will provide prepared statements too.
properly escape the strings:
DO
$$DECLARE
v_text = 'with '' quote';
v_name = 'more '' quotes''';
BEGIN
EXECUTE 'UPDATE article SET text = '
|| quote_literal(v_text)
|| ' WHERE article.name = '
|| quote_literal(v_name);
END;$$;
This example uses PL/pgSQL, but there should be similar quoting functions in your client API as well.
If you have a chioce, prepared statements are better.

Postgres - ERROR: syntax error at or near "IF"

I have a function and I need to do different select based on inputid. so I have added the condition as below.
....
begin
IF(iv_binrocess = 1 AND IsNull(iv_binno,'') != '') THEN
...//some process here
END If;
...
End
When I run the function, I got the error "
ERROR: syntax error at or near "IF"
I referred many sites and tutorials, nothing helped me.
Can anyone help me to fix this issue?
Thanks
WAG, since you deleted most of the important information: You're trying to create a function with a PL/pgsql body, but declaring it SQL.
craig=> CREATE FUNCTION test() RETURNS void LANGUAGE sql AS
$$ BEGIN IF TRUE THEN PERFORM 1; END IF; END; $$ ;
ERROR: syntax error at or near "IF"
LINE 1: ...TION test() RETURNS void LANGUAGE sql AS $$ BEGIN IF TRUE TH...
Declare PL/PgSQL functions as LANGUAGE plpgsql.
If you instead want to use an SQL function, use a CASE expression instead of IF.
CREATE FUNCTION test2() RETURNS integer LANGUAGE sql
AS $$ SELECT CASE WHEN TRUE THEN 1 ELSE 0 END; $$;
(Note, I haven't bothered to format these readably. Don't write functions all in one line like this in real code.)