Refcursors in Postgresql - 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;

Related

How to select into multiple variables inside a trigger function?

This is what I'd like to achieve:
CREATE FUNCTION f() RETURNS trigger AS $$
BEGIN
SELECT COUNT(*) AS total_num, SUM(width) AS total_width
FROM some_table WHERE foo = NEW.foo;
IF total_num > 0 AND total_width > 100
THEN
RAISE EXCEPTION 'this is bad';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
But it's not yet syntactically correct.
I've read I first need to DECLARE the variables (in this case total_num and total_width) so I can use those and use SELECT INTO but I've seen examples with a single variable / SELECT statement only. What if I have more of them?
You can list multiple variables in the into part. And the declare section needs to come before the first begin:
CREATE FUNCTION f() RETURNS trigger
AS $$
declare
total_num bigint;
total_width bigint;
BEGIN
SELECT COUNT(*), SUM(width)
into total_num, total_width
FROM some_table
WHERE foo = NEW.foo;
IF total_num > 0 AND total_width > 100 THEN
RAISE EXCEPTION 'this is bad';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Edit: I'm not sure whether the emphasis here is on the use of variables or the actual IF. This is meant as an answer on the latter:
You can do this without variables using HAVING and EXISTS.
IF EXISTS (SELECT ''
FROM some_table
WHERE foo = new.foo
HAVING count(*) > 0
AND sum(width) > 100) THEN
RAISE EXCEPTION 'this is bad';
END IF;

I tried to execute a query and gave me an error and i can't understand why?

DROP FUNCTION IF EXISTS top_5(customers.customerid%TYPE, products.prod_id%TYPE, orderlines.quantity%TYPE) CASCADE;
CREATE OR REPLACE FUNCTION top_5(c_id customers.customerid%TYPE, p_id products.prod_id%TYPE, quant orderlines.quantity%TYPE)
RETURNS orders.orderid%TYPE AS $$
DECLARE
top_prod CURSOR IS
SELECT inv.prod_id
FROM inventory AS inv, products AS prod
WHERE inv.prod_id=prod.prod_id
ORDER BY inv.quan_in_stock desc, inv.sales
limit 5;
ord_id orders.orderid%TYPE;
ord_date orders.orderdate%TYPE:= current_date;
ordln_id orderlines.orderlineid%TYPE:=1;
BEGIN
SELECT nova_orderid() INTO ord_id;
INSERT INTO orders(orderid, orderdate,customerid,netamount,tax,totalamount) VALUES(ord_id,ord_date,c_id,0,0,0);
PERFORM compra(c_id, p_id, 1::smallint, ord_id, ordln_id, ord_date);
IF (p_id = top_prod) THEN
UPDATE orders
SET totalamount = totalamount - (totalamount*0.2)
WHERE ord_id = (SELECT MAX(ord_id) FROM orders);
END IF;
END;
$$ LANGUAGE plpgsql;
I have the following code and when i try to execute this
SELECT top_5(1,1,'2');
i have this error
ERROR: operator does not exist: integer = refcursor
LINE 1: SELECT (p_id = top_prod)
You need to get the 'prod_id' value from the cursor 'top_prod'.
You cannot compare two types.
Try this,
DECLARE
top_prod_id top_prod%ROWTYPE;
BEGIN
OPEN top_prod;
LOOP
FETCH top_prod INTO top_prod_id;
EXIT WHEN top_prod %NOTFOUND;
IF (p_id = top_prod_id) THEN
UPDATE orders
SET totalamount = totalamount - (totalamount*0.2)
WHERE ord_id = (SELECT MAX(ord_id) FROM orders);
END IF;
END LOOP;
CLOSE top_prod;
END;

PostgreSQL log trigger optimalization

I spent a lot of time trying to optimize our pgsql log trigger which started to be a problem. I did huge progress (from 18min to 2.5min by inserting 3M rows) but I would like to know if some pgSql masters will be able to do it even better.
CREATE OR REPLACE FUNCTION table_log_trig()
RETURNS trigger AS
$BODY$
DECLARE
col TEXT; -- Single column name to save
newVal TEXT; -- New value for column
oldVal TEXT; -- Old value for column
colLimit TEXT[]; -- Columns that should be logged
BEGIN
IF TG_ARGV[0] IS NOT NULL THEN
-- Trigger specifies columns to log
SELECT array_agg(unnest)
FROM unnest(string_to_array(TG_ARGV[0], ','))
INTO colLimit;
ELSE
-- Trigger with no params. Log all columns
SELECT array_agg(json_object_keys)
FROM json_object_keys(row_to_json(NEW))
WHERE json_object_keys NOT IN ('id', 'created_at', 'updated_at') -- Exceptions
INTO colLimit;
END IF;
-- Loop over columns that should be saved in log
FOREACH col IN ARRAY colLimit
LOOP
-- INSERT & UPDATE
EXECUTE 'SELECT ($1).' || col || '::text' INTO newVal USING NEW;
-- UPDATE
IF TG_OP = 'UPDATE' THEN
EXECUTE 'SELECT ($1).' || col || '::text' INTO oldVal USING OLD;
END iF;
-- Add only new or changed data
IF
newVal != oldVal OR
(oldVal IS NULL AND newVal IS NOT NULL) OR
(oldVal IS NOT NULL AND newVal IS NULL)
THEN
INSERT INTO tab_logs (record_id, field_name, old_value, new_value, created_at, created_by, action)
VALUES (NEW.id, col, oldVal, newVal, NOW(), 999, 'O');
END IF;
END LOOP;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
row_to_json() returns both column names and values; you may as well make use of these values, rather than extracting them later via dynamic SQL.
I haven't thoroughly tested this, let alone benchmarked it, but here's the gist of it:
CREATE OR REPLACE FUNCTION table_log_trig() RETURNS trigger AS
$$
DECLARE
OldJson JSONB = NULL;
BEGIN
IF TG_OP <> 'INSERT' THEN
OldJson := to_jsonb(old);
END IF;
INSERT INTO tab_logs (record_id, field_name, old_value, new_value, created_at, created_by, action)
SELECT new.id, key, OldValues.value, NewValues.value, now(), 999, 'O'
FROM jsonb_each(to_jsonb(new)) NewValues
LEFT JOIN jsonb_each(OldJson) OldValues USING (key)
WHERE
(
(TG_ARGV[0] IS NULL AND key NOT IN ('id', 'created_at', 'updated_at')) OR
(TG_ARGV[0] IS NOT NULL AND key = ANY(string_to_array(TG_ARGV[0], ',')))
) AND
OldValues.value::text IS DISTINCT FROM NewValues.value::text;
RETURN NULL;
END
$$
LANGUAGE plpgsql VOLATILE;

Syntax error while creating function in postgresql

I got a syntax error while creating a procedure in postgresql.Here I attached my code.I got a error syntax error near "Continue"
create function patient_form_values() RETURNS void AS
$$ begin
DECLARE columnName varchar(200) ;
DECLARE done boolean default true;
DECLARE CONTINUE handler for not found set done = false;
DECLARE cur1 cursor for select distinct COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = 'currentdiagnosis';
open cur1;
read_loop : loop
fetch from cur1 into columnName;
if done then leave read_loop;
end if;
set #insertValues := concat('INSERT INTO patient_form_temp(patient_id, form_template_id, creator_id, created_date)
SELECT c.patient_id as patient_id, 41 AS form_template_id, 2 AS creator_id, c.created_date AS created_date
FROM currentdiagnosis c
WHERE c.', columnName,' IS NOT NULL GROUP BY c.patient_id, c.created_date');
select #insertValues;
prepare stmt from #insertValues;
execute stmt;
end loop;
close cur1;
end ;
$$ LANGUAGE plpgsql
You are trying to use a MySQL (or other DB?) function in PostgreSQL. There is no concept of CONTINUE HANDLER in PostgreSQL, so you have to convert the function into PostgreSQL format.
drop FUNCTION if exists migratePartnerAdvertiser();
CREATE OR REPLACE FUNCTION migratePartnerAdvertiser() RETURNS int4 AS '
DECLARE r RECORD;
BEGIN
FOR r IN select distinct COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = ''currentdiagnosis'' and table_schema=''public'' LOOP
EXECUTE concat(''INSERT INTO patient_form_temp(patient_id, form_template_id, creator_id, created_date) SELECT c.patient_id as patient_id, 41 AS form_template_id, 2 AS creator_id, c.reg_date AS created_date FROM currentdiagnosis c WHERE c.'' , r.column_name , '' IS NOT NULL GROUP BY c.patient_id, c.reg_date'');
END LOOP;
return 1;
END;
' LANGUAGE plpgsql;

PostgreSQL triggers and exceptions

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
[...]