PL/pgSQL syntax error: 42601 - postgresql

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;

Related

How avoid extra quotes added within a loop in postgresql

When performing a loop on a table in which a value has ", I get those quotes doubled such as:
initial input: 'test AND "test"'
output in the loop: 'test AND ""test""'
How to reproduce:
CREATE TABLE roles(
id int,
criteria VARCHAR (255)
);
INSERT INTO roles (id, criteria) VALUES (1, 'test AND "test"');
CREATE OR REPLACE FUNCTION test_quote()
RETURNS VARCHAR
LANGUAGE plpgsql
AS $$
DECLARE
sample varchar;
rec record;
BEGIN
FOR rec IN
SELECT * FROM roles
LOOP
sample:= rec;
END LOOP;
RETURN sample;
END
$$;
SELECT * FROM test_quote();
Expected result: (1,"test AND "test"")
Actual result: (1,"test AND ""test""")
Does anyone have an idea how to get the expected behaviour here?
Found it, indeed, the issue is from the conversion from record to varchar.
I can directly pass the column from the rec like:
sample:= rec.criteria;
(I didn't know that we could do that)
So, that gives:
CREATE TABLE roles(
id int,
criteria VARCHAR (255)
);
INSERT INTO roles (id, criteria) VALUES (1, 'test AND "test"');
CREATE OR REPLACE FUNCTION test_quote()
RETURNS VARCHAR
LANGUAGE plpgsql
AS $$
DECLARE
sample varchar;
rec record;
BEGIN
FOR rec IN
SELECT * FROM roles
LOOP
sample := rec.criteria;
END LOOP;
RETURN sample;
END
$$;
SELECT * FROM test_quote();
Thx guys!

How do i create a column 1-10 with a loop?

This is an example but I want only a column with 1-10 values without other text columns.
CREATE OR REPLACE FUNCTION somefun_recordset(param_numcount integer)
RETURNS SETOF record AS
$$
DECLARE
result text := '';
searchsql text := '';
var_match record;
BEGIN
searchsql := 'SELECT n || '' down'' As countdown, n as integer
FROM generate_series(' || CAST(param_numcount As text) || ', 1, -1) As n ';
FOR var_match IN EXECUTE(searchsql) LOOP
RETURN NEXT var_match;
END LOOP;
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE;
SELECT r.n , r.countdown
FROM somefun_recordset(10)
As r(countdown text, n integer)
ORDER BY r.n;
How do I create a loop in postgres?
From your current description, you seem to be over-complicating this.
As described in the Postgres manual, the generate_series function can generate a column of values from 10 to 1, like so:
SELECT generate_series(10, 1, -1) as n;
Or using it as a table alias rather than a column alias:
SELECT * FROM generate_series(10, 1, -1) as n;
If you wanted to wrap this in a custom function with only one parameter, it would look like this:
CREATE FUNCTION countdown(param_numcount INT) RETURNS SETOF INT LANGUAGE SQL AS $$
SELECT generate_series(param_numcount, 1, -1);
$$;
Then you would run it as:
SELECT countdown(10) as n;

PostgreSQL compare two jsonb objects

With PostgreSQL(v9.5), the JSONB formats give awesome opportunities. But now I'm stuck with what seems like a relatively simple operation;
compare two jsonb objects; see what is different or missing in one document compared to the other.
What I have so far
WITH reports(id,DATA) AS (
VALUES (1,'{"a":"aaa", "b":"bbb", "c":"ccc"}'::jsonb),
(2,'{"a":"aaa", "b":"jjj", "d":"ddd"}'::jsonb) )
SELECT jsonb_object_agg(anon_1.key, anon_1.value)
FROM
(SELECT anon_2.key AS KEY,
reports.data -> anon_2.KEY AS value
FROM reports,
(SELECT DISTINCT jsonb_object_keys(reports.data) AS KEY
FROM reports) AS anon_2
ORDER BY reports.id DESC) AS anon_1
Should return the difference of row 1 compared to row 2:
'{"b":"bbb", "c":"ccc", "d":null}'
Instead it returns also duplicates ({"a": "aaa"}). Also; there might be a more elegant approach in general!
UPDATED
CREATE OR REPLACE FUNCTION jsonb_diff_val(val1 JSONB,val2 JSONB)
RETURNS JSONB AS $$
DECLARE
result JSONB;
v RECORD;
BEGIN
result = val1;
FOR v IN SELECT * FROM jsonb_each(val2) LOOP
IF result #> jsonb_build_object(v.key,v.value)
THEN result = result - v.key;
ELSIF result ? v.key THEN CONTINUE;
ELSE
result = result || jsonb_build_object(v.key,'null');
END IF;
END LOOP;
RETURN result;
END;
$$ LANGUAGE plpgsql;
Query:
SELECT jsonb_diff_val(
'{"a":"aaa", "b":"bbb", "c":"ccc"}'::jsonb,
'{"a":"aaa", "b":"jjj", "d":"ddd"}'::jsonb
);
jsonb_diff_val
---------------------------------------
{"b": "bbb", "c": "ccc", "d": "null"}
(1 row)
I have created similar function that would scan the object recursively and will return the difference between new object and old object. I was not able to find a 'nicer' way to determine if jsonb object 'is empty' - so would be grateful for any suggestion how to simplify that. I plan to use it to keep track of updates made to the jsonb objects, so I store only what have changed.
Here is the function:
CREATE OR REPLACE FUNCTION jsonb_diff_val(val1 JSONB,val2 JSONB)
RETURNS JSONB AS $$
DECLARE
result JSONB;
object_result JSONB;
i int;
v RECORD;
BEGIN
IF jsonb_typeof(val1) = 'null'
THEN
RETURN val2;
END IF;
result = val1;
FOR v IN SELECT * FROM jsonb_each(val1) LOOP
result = result || jsonb_build_object(v.key, null);
END LOOP;
FOR v IN SELECT * FROM jsonb_each(val2) LOOP
IF jsonb_typeof(val1->v.key) = 'object' AND jsonb_typeof(val2->v.key) = 'object'
THEN
object_result = jsonb_diff_val(val1->v.key, val2->v.key);
-- check if result is not empty
i := (SELECT count(*) FROM jsonb_each(object_result));
IF i = 0
THEN
result = result - v.key; --if empty remove
ELSE
result = result || jsonb_build_object(v.key,object_result);
END IF;
ELSIF val1->v.key = val2->v.key THEN
result = result - v.key;
ELSE
result = result || jsonb_build_object(v.key,v.value);
END IF;
END LOOP;
RETURN result;
END;
$$ LANGUAGE plpgsql;
Then simple query looks like this:
SELECT jsonb_diff_val(
'{"a":"aaa", "b":{"b1":"b","b2":"bb","b3":{"b3a":"aaa","b3c":"ccc"}}, "c":"ccc"}'::jsonb,
'{"a":"aaa", "b":{"b1":"b1","b3":{"b3a":"aaa","b3c":"cccc"}}, "d":"ddd"}'::jsonb
);
jsonb_diff_val
-------------------------------------------------------------------------------
{"b": {"b1": "b1", "b2": null, "b3": {"b3c": "cccc"}}, "c": null, "d": "ddd"}
(1 row)
Here is a solution without creating a new function;
SELECT
json_object_agg(COALESCE(old.key, new.key), old.value)
FROM json_each_text('{"a":"aaa", "b":"bbb", "c":"ccc"}') old
FULL OUTER JOIN json_each_text('{"a":"aaa", "b":"jjj", "d":"ddd"}') new ON new.key = old.key
WHERE
new.value IS DISTINCT FROM old.value
The result is;
{"b" : "bbb", "c" : "ccc", "d" : null}
This method only compares first level of json. It does NOT traverse the whole object tree.
My solution is not recursive but you can use it for detecting common key/values:
-- Diff two jsonb objects
CREATE TYPE jsonb_object_diff_result AS (
old jsonb,
new jsonb,
same jsonb
);
CREATE OR REPLACE FUNCTION jsonb_object_diff(in_old jsonb, in_new jsonb)
RETURNS jsonb_object_diff_result AS
$jsonb_object_diff$
DECLARE
_key text;
_value jsonb;
_old jsonb;
_new jsonb;
_same jsonb;
BEGIN
_old := in_old;
_new := in_new;
FOR _key, _value IN SELECT * FROM jsonb_each(_old) LOOP
IF (_new -> _key) = _value THEN
_old := _old - _key;
_new := _new - _key;
IF _same IS NULL THEN
_same := jsonb_build_object(_key, _value);
ELSE
_same := _same || jsonb_build_object(_key, _value);
END IF;
END IF;
END LOOP;
RETURN (_old, _new, _same);
END;
$jsonb_object_diff$
LANGUAGE plpgsql;
Result can look like this:
SELECT * FROM jsonb_object_diff(
'{"a": 1, "b": 5, "extra1": "woo", "old_null": null, "just_null": null}'::jsonb,
'{"a": 1, "b": 4, "extra2": "ahoj", "new_null": null, "just_null": null}'::jsonb);
-[ RECORD 1 ]--------------------------------------
old | {"b": 5, "extra1": "woo", "old_null": null}
new | {"b": 4, "extra2": "ahoj", "new_null": null}
same | {"a": 1, "just_null": null}
This does basically the same as what other folks have done, but reports changes in a right/left "delta" format.
Example:
SELECT jsonb_delta(
'{"a":"aaa", "b":"bbb", "c":"ccc"}'::jsonb,
'{"a":"aaa", "b":"jjj", "d":"ddd"}'::jsonb
);
Resolves to:
{"b": {"left": "bbb", "right": "jjj"}}
code:
CREATE OR REPLACE FUNCTION jsonb_delta(
IN json_left JSONB
, IN json_right JSONB
, OUT json_out JSONB
) AS
$$
BEGIN
IF json_left IS NULL OR json_right IS NULL THEN
RAISE EXCEPTION 'Non-null inputs required';
END IF
;
WITH
base as
(
SELECT
key
, CASE
WHEN a.value IS DISTINCT FROM b.value THEN jsonb_build_object('left', a.value, 'right', b.value)
ELSE NULL
END as changes
FROM jsonb_each_text(json_left) a
FULL OUTER JOIN jsonb_each_text(json_right) b using (key)
)
SELECT
jsonb_object_agg(key,changes)
INTO json_out
FROM base
WHERE
changes IS NOT NULL
;
json_out := coalesce(json_out, '{}');
END;
$$
LANGUAGE PLPGSQL
IMMUTABLE
PARALLEL SAFE
;
I achieved this by using the hstore extension.
CREATE EXTENSION hstore;
CREATE OR REPLACE FUNCTION log_history() RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
hs_new hstore;
hs_old hstore;
BEGIN
IF (TG_OP = 'DELETE') THEN
INSERT INTO history(item_id, old_values, new_values)
VALUES(OLD.id, row_to_json(OLD)::jsonb, NULL);
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO history(item_id, old_values, new_values)
VALUES(NEW.id, NULL, row_to_json(NEW)::jsonb);
ELSIF (TG_OP = 'UPDATE' AND NEW.* IS DISTINCT FROM OLD.*) THEN
hs_new := hstore(NEW);
hs_old := hstore(OLD);
INSERT INTO history(item_id, old_values, new_values)
VALUES(NEW.id, (hs_old - hs_new - 'updated_at'::text)::jsonb, (hs_new - hs_old - 'updated_at'::text)::jsonb);
END IF;
RETURN NULL;
END;
$$;
Notice that I used it to log history of any change happens on a specific table, and also I'm removing updated_at from the diff object.

Postgresql Syntax Error for Trigger

here is my Trigger and when i try to execute this I am getting error
Trigger:
CREATE OR REPLACE FUNCTION vqm_minoutline_cp_trg()
RETURNS trigger AS
$BODY$ DECLARE
--TYPE RECORD IS REFCURSOR;
Cur_Defaults RECORD;
v_M_Product_ID VARCHAR(32); --OBTG:varchar2--
v_VQM_Parameter_ID VARCHAR(32); --OBTG:varchar2--
vqm_minoutline_parameter_id VARCHAR(32);
v_count NUMERIC;
BEGIN
IF AD_isTriggerEnabled()='N' THEN IF TG_OP = 'DELETE' THEN RETURN OLD; ELSE RETURN NEW; END IF;
END IF;
-- Default Quality Parameter for Product
IF (TG_OP = 'INSERT') THEN
FOR Cur_Defaults IN
(
SELECT VQM_Parameter_ID, Description, Criteria, MinValue, MaxValue, TextValue
FROM VQM_Product_Parameter VPP
WHERE VPP.M_PRODUCT_ID=new.M_PRODUCT_ID
)
LOOP
/*
Creating quality lines for Purchase Order Line
*/
SELECT * INTO vqm_minoutline_parameter_id FROM Ad_Sequence_Next('vqm_minoutline_parameter', Cur_Defaults.VQM_Parameter_ID);
INSERT INTO vqm_minoutline_parameter //here line 65 and so on
(vqm_minoutline_parameter_id, m_inoutline_id, VQM_PARAMETER_ID, AD_Client_ID, AD_Org_ID,
IsActive, Created, CreatedBy, Updated, UpdatedBy, Criteria, MaxValue, MinValue,
TextValue, Description)
VALUES
(vqm_minoutline_parameter_id, new.m_inoutline_id, Cur_Defaults.VQM_PARAMETER_ID,
new.AD_Client_ID, new.AD_Org_ID, 'Y', TO_DATE(NOW()), new.CreatedBy, TO_DATE(NOW()), new.UpdatedBy,
Cur_Defaults.Criteria, Cur_Defaults.MaxValue, Cur_Defaults.MinValue, Cur_Defaults.TextValue,
Cur_Defaults.Description);
END LOOP;
ELSIF (TG_OP = 'UPDATE') THEN
IF new.M_PRODUCT_ID != old.M_PRODUCT_ID THEN
DELETE FROM vqm_minoutline_parameter WHERE m_inoutline_id = new.m_inoutline_id;
FOR Cur_Defaults IN
(
SELECT VQM_Parameter_ID, Description, Criteria, MinValue, MaxValue, TextValue
FROM VQM_Product_Parameter VPP
WHERE VPP.M_PRODUCT_ID=new.M_PRODUCT_ID
)
LOOP
/*
Creating quality lines for Purchase Order Line
*/
SELECT * INTO vqm_minoutline_parameter_id FROM Ad_Sequence_Next('vqm_minoutline_parameter', Cur_Defaults.VQM_Parameter_ID);
INSERT INTO vqm_minoutline_parameter
(
vqm_minoutline_parameter_id, m_inoutline_id, VQM_PARAMETER_ID, AD_Client_ID, AD_Org_ID,
IsActive, Created, CreatedBy, Updated, UpdatedBy, Criteria, MaxValue, MinValue,
TextValue, Description
)
VALUES
(
vqm_minoutline_parameter_id, new.m_inoutline_id, Cur_Defaults.VQM_PARAMETER_ID,
new.AD_Client_ID, new.AD_Org_ID, 'Y', TO_DATE(NOW()), new.CreatedBy, TO_DATE(NOW()), new.UpdatedBy,
Cur_Defaults.Criteria, Cur_Defaults.MaxValue, Cur_Defaults.MinValue, Cur_Defaults.TextValue,
Cur_Defaults.Description
);
END LOOP;
END IF;
END IF;
IF TG_OP = 'DELETE' THEN RETURN OLD; ELSE RETURN NEW; END IF;
END
; $BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION vqm_minoutline_cp_trg()
OWNER TO saksham27;
This is error:
ERROR: syntax error at or near "$1"
LINE 1: INSERT INTO vqm_minoutline_parameter ( $1 , m_inoutline_id, ...
^
QUERY: INSERT INTO vqm_minoutline_parameter ( $1 , m_inoutline_id, VQM_PARAMETER_ID, AD_Client_ID, AD_Org_ID, IsActive, Created, CreatedBy, Updated, UpdatedBy, Criteria, MaxValue, MinValue, TextValue, Description) VALUES ( $1 , $2 , $3 , $4 , $5 , 'Y', TO_DATE(NOW()), $6 , TO_DATE(NOW()), $7 , $8 , $9 , $10 , $11 , $12 )
CONTEXT: SQL statement in PL/PgSQL function "vqm_minoutline_cp_trg" near line 66
********** Error **********
ERROR: syntax error at or near "$1"
SQL state: 42601
Context: SQL statement in PL/PgSQL function "vqm_minoutline_cp_trg" near line 66
In this query:
INSERT INTO vqm_minoutline_parameter //here line 65 and so on
(vqm_minoutline_parameter_id, m_inoutline_id, VQM_PARAMETER_ID, AD_Client_ID, AD_Org_ID,
IsActive, Created, CreatedBy, Updated, UpdatedBy, Criteria, MaxValue, MinValue,
TextValue, Description)
VALUES
(vqm_minoutline_parameter_id, new.m_inoutline_id, Cur_Defaults.VQM_PARAMETER_ID,
new.AD_Client_ID, new.AD_Org_ID, 'Y', TO_DATE(NOW()), new.CreatedBy, TO_DATE(NOW()), new.UpdatedBy,
Cur_Defaults.Criteria, Cur_Defaults.MaxValue, Cur_Defaults.MinValue, Cur_Defaults.TextValue,
Cur_Defaults.Description);
You're using vqm_minoutline_parameter_id both as a column name and a plpgsql variable name. As a result, plpgsql replaces the column name with $1 which leads to an incorrect query.
The workaround is to change the variable name to something that does not clash with any column name.
The documentation mentions the problem in Variable Substitution, with this note:
Note: PostgreSQL versions before 9.0 would try to substitute the
variable in all three cases, leading to syntax errors.
Presumably you're using a pre-9.0 version otherwise the error wouldn't occur. In theory column names are no longer substituted with more recent versions.

PostgreSQL & division_by_zero exception

I can't quite get this right.. my function looks like:
create or replace function myfunc(integer, varchar(25), varchar(25), integer, integer) returns numeric as $$
declare var_return numeric;
begin
select sum(a+ b) / sum(a + b + c)
from mytable
where col1 = $1
and col2 = $2
and col3 = $3
and col4 between $4 AND $5
into var_return;
exception when division_by_zero then return 0.0;
return coalesce(var_return, 0.0);
end;
$$ language plpgsql;
But when I do select myfunc(123, 'foo', 'bar', 1, 10); I see:
ERROR: control reached end of function without RETURN
CONTEXT: PL/pgSQL function "myfunc"
Why does this happen? Clearly I want to catch when the select statement encounters a case where a + b + c equals 0 and return 0.
The EXCEPTION clause pertains to the entire BEGIN block, and runs until the END. That is, Postgres understands you to have written this:
create or replace function myfunc(...) returns ... as $$
declare var_return numeric;
begin
select ...
into var_return; -- but no RETURN statement in this block
exception when division_by_zero then
return 0.0;
return coalesce(var_return, 0.0); -- this is part of the exception handler
end;
$$ language plpgsql;
Move your "RETURN COALESCE..." above the EXCEPTION line, and try again.
You can vastly simplify your function:
CREATE OR REPLACE FUNCTION myfunc(int, varchar(25), varchar(25), int, int)
RETURNS numeric AS
$BODY$
SELECT CASE WHEN abc = 0 THEN 0::numeric ELSE (ab/abc)::numeric END
FROM (
SELECT sum(a + b) AS ab, sum(a + b + c) AS abc
FROM mytable
WHERE col1 = $1
AND col2 = $2
AND col3 = $3
AND col4 BETWEEN $4 AND $5
) x;
$BODY$ language SQL;
Major points
This is language SQL, not a PL/pgSQL function. You can use either, but for a simple case like this you might as well stick with the simpler tool.
#pilcrow diagnosed the cause of your error accurately. The way you have it, the RETURN statement is part of the exception handler and is only ever reached if an exception occurs.
If the data type of a, b, c should be integer or bigint, you probably want to cast to numeric before the division or the results will be coerced to integers.
Use a CASE statement to avoid division by zero. This is much cheaper than exception handling.
Are you sure that you handle the right exception ?
I guess that you have another exception.
See a list of exception you can trap.
I would try first ( I always put exceptions at the end of the block, easier to read.)
create or replace function myfunc(integer, varchar(25), varchar(25), integer, integer) returns numeric as $$
declare var_return numeric;
begin
select sum(a+ b) / sum(a + b + c)
from mytable
where col1 = $1
and col2 = $2
and col3 = $3
and col4 between $4 AND $5
into var_return;
return coalesce(var_return, 0.0);
exception
when division_by_zero then
return 0.0;
when others then
RAISE NOTICE 'caught others';
return 0.0;
end;
$$ language plpgsql;