PostgreSQL triggers and exceptions - postgresql

I'm trying to get my first ever trigger and function to work, but how I throw exceptions and return data right way?
PostgreSQL 8.4.1
CREATE TABLE "SHIFTS" (
id integer NOT NULL, -- SERIAL
added timestamp without time zone DEFAULT now() NOT NULL,
starts timestamp without time zone NOT NULL,
ends timestamp without time zone NOT NULL,
employee_id integer,
modified timestamp without time zone,
status integer DEFAULT 1 NOT NULL,
billid integer,
CONSTRAINT "SHIFTS_check" CHECK ((starts < ends))
);
-- Check if given shift time overlaps with existing data
CREATE OR REPLACE FUNCTION
shift_overlaps (integer, timestamp, timestamp)
RETURNS
boolean AS $$
DECLARE
_employeeid ALIAS FOR $1;
_start ALIAS FOR $2;
_end ALIAS FOR $3;
BEGIN
SELECT
COUNT(id) AS c
FROM
"SHIFTS"
WHERE
employee_id = _employeeid AND
status = 1 AND
(
(starts BETWEEN _start AND _end)
OR
(ends BETWEEN _start AND _end)
)
;
-- Return boolean
RETURN (c > 0);
END;
$$
LANGUAGE
plpgsql
;
CREATE OR REPLACE FUNCTION
check_shift()
RETURNS trigger AS '
BEGIN
-- Bill ID is set, do not allow update
IF tg_op = "UPDATE" THEN
IF old.billid IS NOT NULL THEN
RAISE EXCEPTION "Shift is locked"
END IF;
END IF;
-- Check for overlap
IF tg_op = "INSERT" THEN
IF new.employee_id IS NOT NULL THEN
IF shift_overlaps(new.employee_id, new.starts, new.ends) THEN
RAISE EXCEPTION "Given time overlaps with shifts"
END IF;
END IF;
END IF;
-- Check for overlap
IF tg_op = "UPDATE" THEN
IF (new.employee_id IS NOT NULL) AND (new.status = 1) THEN
IF shift_overlaps(new.employee_id, new.starts, new.ends) THEN
RAISE EXCEPTION "Given time overlaps with shifts"
END IF;
END IF;
END IF;
RETURN new;
END
'
LANGUAGE
plpgsql
;
-- Shift checker trigger
CREATE TRIGGER
check_shifts
BEFORE
INSERT OR UPDATE
ON
"SHIFTS"
FOR EACH ROW EXECUTE PROCEDURE
check_shift()
;
shift_overlaps():
SQL error: ERROR: query has no destination for result data
check_shift():
SQL error: ERROR: unrecognized exception condition "Shift is locked"

You've got an error here:
SELECT
COUNT(id) AS c
FROM
"SHIFTS"
WHERE
employee_id = _employeeid AND
status = 1 AND
(
(starts BETWEEN _start AND _end)
OR
(ends BETWEEN _start AND _end)
)
;
Such a select in a plpgsql procedure has to be SELECT INTO... like this:
DECLARE
c INTEGER;
BEGIN
SELECT
COUNT(id)
INTO c
FROM
"SHIFTS"
WHERE
employee_id = _employeeid AND
status = 1 AND
(
(starts BETWEEN _start AND _end)
OR
(ends BETWEEN _start AND _end)
)
;
RETURN (c > 0);
END;
And here you've got to have the semicolon at the end of the line:
enter code here`RAISE EXCEPTION "Shift is locked";

Not sure what you're trying to find out. You're managing to raise your own exceptions, so that's good. I would expect that any error handling would be in the code that evokes this method.
If you want to do something inside the procedure, you need an EXCEPTION section:
[ <> ]
[ DECLARE
declarations ]
BEGIN
statements
EXCEPTION
WHEN condition [ OR condition ... ] THEN
handler_statements
[ WHEN condition [ OR condition ... ] THEN
handler_statements
... ]
END;
But generally I would expect you'd handle it in the calling code.

You have to use SELECT INTO to get a value returned by a query
DECLARE
[...]
c boolean;
SELECT
COUNT(id) INTO c
FROM
"SHIFTS"
WHERE
[...]

Related

debugging psql - session procedure

I am trying to modify an existing sessions procedure to add cycle count.The error I am getting is
SQL Error [42601]: ERROR: syntax error at or near "END"
Position: 3587
--call transactions_packs.tep_session()
CREATE OR REPLACE PROCEDURE transactions_packs.tep_session()
LANGUAGE plpgsql
AS $procedure$
DECLARE
session "transactions_packs"."simple_sessions";
"session_toSearch" TEXT;
"end_timestamp" TIMESTAMP WITH TIME ZONE;
"energy" NUMERIC;
"charge" NUMERIC;
"duration" NUMERIC;
"cycle_count" numeric;
"f" record ;
BEGIN
cycle_count = '0';
-- go to statement fore session reset
FOR session IN SELECT * FROM "transactions_packs"."simple_sessions" WHERE "sessionDuration" IS NULL
LOOP
BEGIN
IF session."sessionType" = 0 THEN
"session_toSearch" := 'Charging';
ELSIF session."sessionType" = 1 THEN
"session_toSearch" := 'Discharging';
END IF;
-- Session_count:Start
EXECUTE FORMAT('
FOR f IN select (current' || '%s' || '), "timestamp"
FROM "transactions_packs"."basic_measurements_packs" a order by a."timestamp" desc
LOOP
BEGIN
IF AVG((current' || '%s' || '))
OVER (ORDER BY "f"."timestamp" ROWS BETWEEN 3 PRECEDING AND CURRENT ROW) > 0.01
then cycle_count = cycle_count + 1;
END IF;
END
END LOOP;',"session_toSearch","session_toSearch")
-- get value from If and else statement to fetch records from charging and discharging col
--Session_count :End
END ;
END LOOP;
end;
$procedure$
;
where -
schema is transactions_packs
tables are -
simple_sessions
basic_measurements_packs
Please let me know if there is any part of query is which cannot be understood.
The variable "hell" sounds like useless here, try something like this :
create or replace procedure "public"."extract"(arg json)
language plpgsql as $$
begin
raise notice 'test:%', arg->'global'->>'packetType';
raise notice 'test1:%', arg-> 'transactional'->>'A';
-- freeze the input
end ;
$$;

Refcursors in Postgresql

Good Morning,
I have a quick question on how to see the data that is fetched in an Output refcursor
I'm executing the below block, its successfully executed but I want to see the data fetched in Output RefCursor, by the way I'm using PGADMIN 4 tool to connect to Postgres Servers,
BEGIN;
SELECT prod_package_list ('G6028|G6026|G6025|G6024|G6022|G6021|G6020', NULL);
FETCH ALL IN vref_cur;
COMMIT;
I found online and according to them I should follow the below format,
BEGIN;
SELECT test_prod_package_list ('G6028|G6026|G6025|G6024|G6022|G6021|G6020', NULL, 'vref_cur');
FETCH ALL IN "vref_cur";
COMMIT;
But the above execution block throws an error
function prod_package_list(unknown, unknown, unknown) does not exist,
The function is as follows,
CREATE OR REPLACE FUNCTION ssp2_pcat.prod_package_list(
prodlist text,
billcodelist text,
OUT vref_cur refcursor)
RETURNS refcursor
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
BEGIN
vref_cur := 'vref_cur';
IF prodlist IS NOT NULL THEN
OPEN vref_cur FOR
SELECT DISTINCT
b.package_id, a.product_id, a.pdct_ctlg_id, a.product_name, a.billing_system, a.billing_code, a.fulfill_system, a.fulfill_code, a.speed_code, a.product_type, a.product_type_value, a.return_auth_applies, a.return_auth_term, a.nrc_waiver, a.nrc_waiver_product_id, a.linked_nrc_product_id, a.national_east_west, a.lata_list, a.state_list, a.product_info_url, a.isp_prov_flags, a.product_coefficient, a.last_updated, a.product_keywords, a.combo_product_list, a.additional_info, a.usoc_info, a.min_billing_days, a.data_nrf_product_id, a.video_nrf_product_id, a.account_line_level, a.web_desc, a.orderguicd, a.prod_startdate, a.prod_enddate, b.pdct_ctlg_id, b.package_name, b.billing_code, b.category_id, b.standalone_cpe, b.standalone_truckroll, b.btn_billed, b.emp_discount, b.package_type, b.last_updated, b.pkg_detail_descr, b.grandfathered AS pkg_grandfathered,
CASE CONCAT_WS('', a.grandfathered, c.pkg_prod_grandfathered)
WHEN 'YY' THEN 'Y'
WHEN 'YN' THEN 'Y'
WHEN 'NY' THEN 'Y'
ELSE 'N'
END AS grandfathered
FROM ssp2_pcat.products AS a, ssp2_pcat.packages AS b, ssp2_pcat.package_products AS c
WHERE strpos(prodlist, CONCAT_WS('', '|', a.product_id, '|')) > 0
AND a.product_id = c.product_id AND c.package_id = b.package_id
AND c.start_date <= LOCALTIMESTAMP AND c.end_date >= LOCALTIMESTAMP
ORDER BY 1;
ELSIF billcodelist IS NOT NULL THEN
OPEN vref_cur FOR
SELECT DISTINCT
b.package_id, a.product_id, a.pdct_ctlg_id, a.product_name, a.billing_system, a.billing_code, a.fulfill_system, a.fulfill_code, a.speed_code, a.product_type, a.product_type_value, a.return_auth_applies, a.return_auth_term, a.nrc_waiver, a.nrc_waiver_product_id, a.linked_nrc_product_id, a.national_east_west, a.lata_list, a.state_list, a.product_info_url, a.isp_prov_flags, a.product_coefficient, a.last_updated, a.product_keywords, a.combo_product_list, a.additional_info, a.usoc_info, a.min_billing_days, a.data_nrf_product_id, a.video_nrf_product_id, a.account_line_level, a.web_desc, a.orderguicd, a.prod_startdate, a.prod_enddate, b.pdct_ctlg_id, b.package_name, b.billing_code, b.category_id, b.standalone_cpe, b.standalone_truckroll, b.btn_billed, b.emp_discount, b.package_type, b.last_updated, b.pkg_detail_descr, b.grandfathered AS pkg_grandfathered,
CASE CONCAT_WS('', a.grandfathered, c.pkg_prod_grandfathered)
WHEN 'YY' THEN 'Y'
WHEN 'YN' THEN 'Y'
WHEN 'NY' THEN 'Y'
ELSE 'N'
END AS grandfathered
FROM ssp2_pcat.products AS a, ssp2_pcat.packages AS b, ssp2_pcat.package_products AS c
WHERE strpos(billcodelist, CONCAT_WS('', '|', a.billing_code, '|')) > 0
AND a.product_id = c.product_id AND c.package_id = b.package_id
AND c.start_date <= LOCALTIMESTAMP AND c.end_date >= LOCALTIMESTAMP
ORDER BY 1;
ELSE
RAISE USING hint = -20001, message = 'Product List and Billing Code Lists are empty.', detail = 'User-defined exception';
END IF;
END;
$BODY$;
ALTER FUNCTION ssp2_pcat.prod_package_list(text, text)
OWNER TO ssp2_pcat;
Please advise,
Use IN parameter to pass a cursor name. Do not forget to RETURN vref_cursor. Working example:
CREATE FUNCTION my_func(arg1 text, arg2 text, vref_cursor refcursor)
RETURNS refcursor AS $$
BEGIN
OPEN vref_cursor FOR SELECT generate_series(1,3);
RETURN vref_cursor;
END;
$$ LANGUAGE plpgsql;
BEGIN;
SELECT my_func('first arg', 'second arg', 'vref_cursor');
FETCH ALL IN vref_cursor;
COMMIT;

Dynamic SELECT in trigger function gives syntax error

Just need help as to debug some syntax errors in the below code.The code is as below and have only couple of syntax errors near keywords like Insert, Select, etc
CREATE OR REPLACE FUNCTION audit_temp() RETURNS TRIGGER LANGUAGE plpgsql AS $BODY$
DECLARE
ri RECORD;
oldValue TEXT;
newValue TEXT;
isColumnSignificant BOOLEAN;
isValueModified BOOLEAN;
BEGIN
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
NEW.record_modified_ = clock_timestamp();
FOR ri IN
-- Fetch a ResultSet listing columns defined for this trigger's table.
SELECT ordinal_position, column_name, data_type
FROM information_schema.columns
WHERE table_schema = quote_ident(TG_TABLE_SCHEMA)
AND table_name = quote_ident(TG_TABLE_NAME)
ORDER BY ordinal_position
LOOP
-- For each column in this trigger's table, copy the OLD & NEW values into respective variables.
-- NEW value
EXECUTE 'SELECT ($1).' || ri.column_name || '::text' INTO newValue USING NEW;
-- OLD value
IF TG_OP = 'INSERT' THEN -- If operation is an INSERT, we have no OLD value, so use an empty string.
oldValue := ''::varchar;
ELSE -- Else operation is an UPDATE, so capture the OLD value.
EXECUTE 'SELECT ($1).' || ri.column_name || '::text' INTO oldValue USING OLD;
END IF;
isColumnSignificant := (position( '_x_' in ri.column_name ) < 1) AND
(ri.column_name <> 'pkey_') AND
(ri.column_name <> 'record_modified_');
IF isColumnSignificant THEN
isValueModified := oldValue <> newValue; -- If this nthField in the table was modified, make history.
IF isValueModified THEN
/*RAISE NOTICE E'Inserting history_ row for INSERT or UPDATE.\n';*/
INSERT INTO audit_temp( operation_, table_oid_, table_name_, uuid_, column_name_, ordinal_position_of_column_, old_value_, new_value_ )
VALUES ( TG_OP, TG_RELID, TG_TABLE_NAME, NEW.pkey_, ri.column_name::VARCHAR, ri.ordinal_position, oldValue::VARCHAR, newValue::VARCHAR);
END IF;
END IF;
END LOOP;
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
/*RAISE NOTICE E'Inserting history_ row for DELETE.\n';*/
-- Similar to INSERT above, but refers to OLD instead of NEW, and passes empty values for last 4 fields.
INSERT INTO audit_temp ( operation_, table_oid_, table_name_, uuid_, column_name_, ordinal_position_of_column_, old_value_, new_value_ )
VALUES ( TG_OP, TG_RELID, TG_TABLE_NAME, OLD.pkey_, ''::VARCHAR, 0, ''::VARCHAR, ''::VARCHAR );
RETURN OLD;
END IF;
/* Should never reach this point. Branching in code above should always reach a call to RETURN. */
RAISE EXCEPTION 'Unexpectedly reached the bottom of this function without calling RETURN.';
END; $BODY$;
The error is as follows & mostly around Select Insert keywords only:
>[Error] Script lines: 1-42 -------------------------
ERROR: syntax error at or near "SELECT"
Any suggestions?????
Syntax error
The offending statement is this (and the others like it):
EXECUTE 'SELECT ($1).' || ri.column_name || '::text' INTO newValue USING NEW;
According to the documentation parameter symbols can only be used for data values — if you want to use dynamically determined table or column names, you must insert them into the command string textually. So the solution would be:
EXECUTE 'SELECT (' || NEW || ').' || ri.column_name || '::text' INTO newValue;
Improvements
You can make a few improvements to your trigger function to make it faster and more efficient:
You should check isColumnSignficant before you populate oldValue and newValue.
When updating, you do not have to record the OLD values: they are already in the audit table when the data was first inserted or later updated.
When deleting, don't store empty strings and 0, just leave the columns NULL.

"Function does not exist" error when creating the function with more parameters

I have dropped a PL/pgSQL function and I try to recreate it with one more parameter (with default value). But I get a strange error:
ERROR: function vytvor_kod_sj(text, integer, integer) does not exist
SQL state: 42883
I would expect such an error while dropping or calling the function, not while creating it. I made sure that the error occurs exactly while trying to create it, not in any of the other functions or triggers I created from the same sql file.
I made a dummy function without the last parameter and it works now, but it is definitely not what I want - not only I don't need the function without the last parameter anymore, I usually call the function only with the first two or three parameter, so I need to drop it just after creating the new version of my function to avoid mismatches. Fortunately, there are no errors while doing this, but it is hardly optimal solution.
So does anyone know how to solve this mysterious problem?
I have PostgreSQL 9.3.4 on Windows 32; I use pgAdmin 1.18.1
My code:
CREATE OR REPLACE FUNCTION vytvor_kod_sj( _akce text, typ_sj integer,
podtyp integer DEFAULT 0, styl integer DEFAULT NULL )
RETURNS text AS $$
DECLARE
--styl integer;
_nazev_seq text;
_min integer;
_max integer;
_nazev text;
_soucasna integer;
BEGIN
IF styl IS NULL THEN
SELECT nomenklatura INTO styl FROM akce WHERE kod_akce = _akce;
END IF;
IF NOT EXISTS( SELECT id_nom FROM pro_nom_pravidlo_sj
WHERE id_nom = styl AND sj = typ_sj AND typ = podtyp ) THEN
IF podtyp = 0 OR NOT EXISTS( SELECT id_nom FROM pro_nom_pravidlo_sj
WHERE id_nom = styl AND sj = typ_sj AND typ = 0 ) THEN
RAISE NOTICE 'Pro daný typ stratigrafické jednotky není vytvořeno žádné pravidlo!';
RETURN NULL;
ELSE
podtyp := 0;
END IF;
END IF;
_nazev_seq := _akce || '_' || typ_sj || '_' || podtyp || '_seq';
IF NOT EXISTS (SELECT 0 FROM pg_class where relname = _nazev_seq ) THEN
SELECT min, max INTO _min, _max FROM pro_nom_pravidlo_sj
WHERE id_nom = styl AND sj = typ_sj AND typ = podtyp;
IF _max IS NOT NULL THEN
EXECUTE format('CREATE SEQUENCE %I MINVALUE %s MAXVALUE %s ', _nazev_seq, _min, _max);
ELSE
EXECUTE format('CREATE SEQUENCE %I MINVALUE %s ', _nazev_seq, _min);
END IF;
END IF;
--IF strict IS TRUE THEN
RETURN (
SELECT predpona FROM pro_nom_pravidlo_sj
WHERE id_nom = styl AND sj = typ_sj AND typ = podtyp
) || CAST(nextval(_nazev_seq) AS TEXT);
--END IF;
END;
$$ LANGUAGE plpgsql
SECURITY DEFINER;
ALTER FUNCTION vytvor_kod_sj( text, integer, integer ) OWNER TO ins_daemon;
The old version had styl variable declared, not as a parameter, and there was no test whether it is null. Otherwise I didn't change anything.
I noticed the cause of the problem just after posting the question:
ALTER FUNCTION vytvor_kod_sj( text, integer, integer ) OWNER TO ins_daemon;
It tries to alter the old function.

Testing error within function in PostgreSQL

How to check the error within the function of PostgreSQL?
My try for the same as shown below in the example.
Example:
create or replace function fun_testing(sn int, na text, gen text, ad text, rn int, flag int)
returns int as
$$
begin
if flag = 1 then
insert into testing(ssn,name,gender,address,rno) values(sn,na,gen,ad,rn);
elsif flag = 2 then
update testing
set ssn=sn,
name=na,
gender=gen,
address=ad,
rno = rn
where ssn=sn;
elsif flag =3 then
delete from testing
where ssn =sn;
end if;
if error <> 0 then /* Error Testing */
return(1);
else
return(0);
end if;
end;
$$
language plpgsql;
ERROR: column "error" does not exist
Note: The same if condition(using ##error) works fine with SQL Server but not getting executed in PostgreSQL.
You need a BEGIN ... EXCEPTION ... END block.
create or replace function fun_testing(sn int, na text, gen text, ad text, rn int,
flag int)
returns int as
$$
begin
if flag = 1 then
insert into testing(ssn,name,gender,address,rno) values(sn,na,gen,ad,rn);
elsif flag = 2 then
update testing
set ssn=sn,
name=na,
gender=gen,
address=ad,
rno = rn
where ssn=sn;
elsif flag =3 then
delete from testing
where ssn =sn;
end if;
exception when others then
raise notice 'The transaction is in an uncommittable state. '
'Transaction was rolled back';
raise notice '% %', SQLERRM, SQLSTATE;
end;
$$ language 'plpgsql';