PostgreSQL CASE statement syntax issues - postgresql

I receive a syntax error for the following:
CREATE OR REPLACE FUNCTION my_function(old_num INTEGER)
returns INTEGER
language plpgsql
AS
$$
DECLARE new_num INTEGER;
BEGIN
CASE
WHEN (old_num IN (1, 2, 3, 4)) THEN new_num = 10
WHEN (old_num IN (5, 6, 7, 8)) THEN new_num = 20
ELSE new_num = 0
END;
RETURN new_num;
END;
$$;
The error points to the second WHEN. I've tried using all kinds of combinations of parenthesis. What is wrong with this syntax??

A CASE expression results in a value. You want it to result in 10, 20 or 0, which you then want to assign to your variable new_num.
new_num :=
CASE
WHEN (old_num IN (1, 2, 3, 4)) THEN 10
WHEN (old_num IN (5, 6, 7, 8)) THEN 20
ELSE 0
END;

CREATE OR REPLACE FUNCTION my_function(old_num INTEGER)
returns INTEGER
language plpgsql
AS
$$
DECLARE new_num INTEGER;
BEGIN
new_num := (CASE
WHEN old_num in (1, 2, 3, 4) THEN 10
WHEN old_num in (5, 6, 7, 8) THEN 20
ELSE 0
END);
RETURN new_num;
END;
$$;

Alternate solution:
CREATE OR REPLACE FUNCTION public.my_function(old_num integer)
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE new_num INTEGER;
BEGIN
SELECT INTO new_num
CASE
WHEN (old_num IN (1, 2, 3, 4)) THEN 10
WHEN (old_num IN (5, 6, 7, 8)) THEN 20
ELSE 0
END;
RETURN new_num;
END;
$function$
select my_function(1);
my_function
-------------
10
select my_function(6);
my_function
-------------
20
select my_function(12);
my_function
-------------
0

You can convert your function to a SQL function and reduce it to a single Select statement. See Demo.
create or replace function public.my_function(old_num integer)
returns integer
language sql
as $$
select case when (old_num in (1, 2, 3, 4)) then 10
when (old_num in (5, 6, 7, 8)) then 20
else 0
end;
$$;

Related

converting postgresql functions to oracle

I need to support postgresql and oracle in the product that I work in. When we install our db on oracle, we do not give privilege of "create type". So I am unsure as to how I can convert following two cases to oracle:
Function That returns array
create or replace function get_array_of_integers(i_param int)
returns integer[] as
$body$
declare array_of_integers int[];
begin
select
case
when i_param = 1 then
array[1]
when i_param = 2 then
array[1, 2, 3, 4]
when i_param = 3 then
array[5, 6]
else
array[]::integer[]
end into array_of_integers;
return array_of_integers;
end;
$body$
language 'plpgsql' volatile
cost 100;
Second case: function that accepts array of integers:
create or replace function some_function(params int[])
...
You could make use of Oracle's built-in collection type sys.odcinumberlist.
Your function is equivalent to.
CREATE OR REPLACE FUNCTION get_array_of_integers(i_param INT)
RETURN sys.odcinumberlist
AS
array_of_integers sys.odcinumberlist := NEW sys.odcinumberlist() ; --Initialization
BEGIN
IF i_param = 1 THEN
array_of_integers.EXTEND(1);
array_of_integers(1) := 1;
ELSIF i_param = 2 THEN
array_of_integers.EXTEND(4);--appends 4 null elements to a collection
array_of_integers(1) := 1; --assign elements
array_of_integers(2) := 2;
array_of_integers(3) := 3;
array_of_integers(4) := 4;
ELSIF i_param = 3 THEN
array_of_integers.EXTEND(2);
array_of_integers(1) := 5;
array_of_integers(2) := 6;
END IF;
RETURN array_of_integers;
END;
/
You may use TABLE function in your query to select from this table.
SQL> select * FROM TABLE(get_array_of_integers(2));
COLUMN_VALUE
------------
1
2
3
4
SQL> select * FROM TABLE(get_array_of_integers(0));
no rows selected
For Oracle 12.2 and above, you won't need TABLE function, you can directly select from your function.
select * FROM get_array_of_integers(3);
Second case could be something like this.
CREATE OR REPLACE FUNCTION some_function( params sys.odcinumberlist )
RETURN INTEGER
AS
BEGIN
RETURN 1;
END;
/

Trigger on date in postgresql

I'm reading a lot of articles how to create triggers, but I really have no clue how to create trigger depending on date.
I want to add a trigger that:
1. check before add or update if the sum of minutes is higher than 50 on a specific day.
So in example entity:
CREATE TABLE employees(
id int serial primary key,
minutes int NOT NULL,
date date NOT NULL
);
this is a typical data in db:
(1, 40, '2018-01-1')
(2, 30, '2018-01-2')
(3, 20, '2018-01-3')
(4, 10, '2018-01-4')
now if I add:
(6, 20, '2018-01-1')
(7, 40, '2018-01-2')
(8, 20, '2018-01-3')
(9, 20, '2018-01-4')
the final result would've be:
(1, 40, '2018-01-1')
(2, 30, '2018-01-2')
(3, 20, '2018-01-3')
(4, 10, '2018-01-4')
(8, 20, '2018-01-3')
(9, 20, '2018-01-4')
id with 6 and 7 are omitted (not added, because they are higher than 50)
I really appreciate a help with it :)
update: with help from #Laurenz Albe I created function and trigger as below:
CREATE FUNCTION func_check_minutes() RETURNS trigger AS
$$
BEGIN
IF (SELECT sum(minutes) + NEW.minutes FROM employees WHERE date = NEW.date) > 50
THEN RETURN NULL;
END IF;
END;
$$
LANGUAGE 'plpgsql';
CREATE TRIGGER tr_check_minutes
BEFORE INSERT ON employees
FOR EACH ROW
EXECUTE PROCEDURE func_check_minutes();
But this solution fails without a clear reason.
As you have not mentioned what fails, i am assuming that nothing is being written to the table. You will have to add RETURN NEW after the END IF
I have updated the trigger
CREATE FUNCTION func_check_minutes() RETURNS trigger AS
$$
BEGIN
IF (SELECT sum(minutes) + NEW.minutes FROM employees WHERE date = NEW.date) > 50
THEN RETURN NULL;
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
CREATE TRIGGER tr_check_minutes
BEFORE INSERT ON employees
FOR EACH ROW
EXECUTE PROCEDURE func_check_minutes();
The central piece of such a trigger would be:
IF (SELECT sum(minutes) + NEW.minutes
FROM employees
WHERE date = NEW.date) > 50
THEN
<throw an error or return NULL>
END IF;
But you should be aware that there is a race condition that could cause the condition to be violated:
If the triggers from two inserts run concurrently, each of them won't see the effects of the other transaction, and both could report success while the final result after both transactions are committed could exceed the limit.
The only way to get around that would be using SERIALIZABLE transactions.

Postgresql function doesn't work properly

Why is my function returning only half the zip codes?
I mean if i do
insert into address(zip_code) values(99-100)
it's doesn't work properly, because it's adding only "99-".
CREATE FUNCTION normalize_zip_code() RETURNS trigger AS $$
BEGIN
if length(NEW.zip_code)<=4 and length(NEW.zip_code)>=6 then
RAISE EXCEPTION 'Wrong zip code!';
elsif length(NEW.zip_code)=6 and (SUBSTRing(NEW.zip_code, 3, 1)='-') then
NEW.zip_code:= concat((SUBSTRing(NEW.zip_code, 1, 2))::varchar(2),(SUBSTRING(NEW.zip_code, 3, 1))::varchar(1), (SUBSTRING(NEW.zip_code, 3, 5))::varchar(3));
return NEW;
else
NEW.zip_code := concat((SUBSTRing(NEW.zip_code, 1, 2))::varchar(2),'-' (SUBSTRING(NEW.zip_code, 3, 5))::varchar(3));
RETURN NEW;
end if;
END;
$$ language plpgsql;

PL/pgSQL syntax error: 42601

I'm quite new to pl/pgsql and I have one issue: I'm trying to create function that will calculate tax based on pensja in:
create table pracownicy (
id_pracownika int unique
, imie varchar(20)
, nazwisko varchar(20)
, miasto varchar(20)
, pensja real);
insert into pracownicy values
(1, 'John', 'Smith', 'New York', 150)
, (2, 'Ben', 'Johnson', 'New York', 250)
, (3, 'Louis', 'Armstrong', 'New Orleans', 75)
, (4, 'John', 'Lennon', 'London', 300)
, (5, 'Peter', 'Gabriel', 'London', 100);
I've created function:
CREATE OR REPLACE FUNCTION pit(pensja real)
RETURNS real AS
$BODY$
DECLARE
dochod REAL;
podatek REAL;
BEGIN
dochod = 12*pensja;
IF dochod <= 85528 THEN
podatek = dochod*0,18-556,02;
RETURN podatek;
ELSE
podatek = 14839 + 0,32*(dochod - 85528);
RETURN podatek;
END IF;
END;
$BODY$
LANGUAGE plpgsql;
I'm trying to run it with:
select nazwisko, pensja, pit(pensja) from pracownicy;
Receiving:
ERROR: query "SELECT * dochod 0,18-556,02" returned 3 columns
SQL state: 42601
Context: function PL / pgSQL pit (real), line 8 in the assignment
And I'm not quite sure where the error is?
You have to use a dot as decimal separator:
podatek = dochod*0.18-556.02;
Besides the primary issue with the decimal separator that #Jens pointed out, you can also largely simplify your function. Assignments are comparatively more expensive in plpgsql than in other popular languages.
CREATE OR REPLACE FUNCTION pit(pensja real)
RETURNS real AS
$func$
DECLARE
dochod real := 12 * pensja;
BEGIN
IF dochod <= 85528 THEN
RETURN dochod * 0.18 - 556.02;
ELSE
RETURN (dochod - 85528) * 0.32 + 14839;
END IF;
END
$func$ LANGUAGE plpgsql;

How to specify column types for CTE (Common Table Expressions) in PostgreSQL?

Consider
WITH t (f0, f1) as (
values
(1, 10),
(2, 20)
)...
How do I specify that f0 and f1 are of type bigint?
I think you'd have to specify the types inside the VALUES expression in your case:
WITH t (f0, f1) as (
values
(1::bigint, 10::bigint),
(2, 20)
)...
You only need the types on the first set of values, PostgreSQL can infer the rest.
For example, suppose we have two functions:
create function f(bigint, bigint) returns bigint as $$
begin
raise notice 'bigint';
return $1 * $2;
end;
$$ language plpgsql;
create function f(int, int) returns int as $$
begin
raise notice 'int';
return $1 * $2;
end;
$$ language plpgsql;
Then
WITH t (f0, f1) as (
values
(1, 10),
(2, 20)
)
select f(f0, f1) from t;
will give you two int notices whereas
WITH t (f0, f1) as (
values
(1::bigint, 10::bigint),
(2, 20)
)
select f(f0, f1) from t;
would give you two bigint notices.
I don't know if this will work for you, but casting the column you want to specify worked for me in DB2
WITH CTE AS (
SELECT
'Apple' AS STRING1
,CAST('Orange' AS VARCHAR(10000)) AS STRING2
FROM SYSIBM . SYSDUMMY1)
SELECT * FROM CTE