Postgres return array from stored function - postgresql-9.4

I am trying to return array from stored function and use it's output in a where clause , for a query. But it is not working for unknown reason. I really do not have clue. Please help.
Using below function.
CREATE OR REPLACE FUNCTION terep_cstm.testdays(
v_in_date date,
v_n integer)
RETURNS text[] AS
$BODY$
declare
listndays text = '';
arrndays text[];
BEGIN
for i in 0..v_n
loop
listndays = listndays||''||(v_in_date - i*interval '1 day')::date::text||',';
end loop;
listndays = trim(trailing ',' from listndays);
arrndays = string_to_array(listndays,',');
return arrndays;
END
$BODY$
LANGUAGE plpgsql VOLATILE;
Executing this query , with stored function.
select retl_code,price_2, attr_1,start_date,price,printedprice
from tessttable abc
where abc.attr_1 = '011' and abc.price_2 = '034' and abc.retl_code = '00068247'
and abc.start_date = ANY (select testdays('2017-05-31'::date,7)) --'2017-05-31'
--and abc.start_date = ANY (ARRAY['2017-05-31','2017-05-30','2017-05-29','2017-05-28','2017-05-27','2017-05-26','2017-05-25','2017-05-24'])
--and abc.start_date in ('2017-05-31','2017-05-30','2017-05-29','2017-05-28','2017-05-27','2017-05-26','2017-05-25','2017-05-24');
This is not working for ANY operator , with error message,
ERROR: operator does not exist: text = text[];
Please let me know where i am making mistake. The abc. start_date datatype is text and it is platform configured. Unfortunately we have no liberty to convert this column datatype and have to carry this one only.
It seems to work with
and abc.start_date = ANY (testdays('2017-05-31'::date,7));
But query gets hanged for a long time.As i could see the different in explain plan as well.
Query:
select retl_code,price_2, attr_1,start_date,price,printedprice
from tessttable abc
where abc.attr_1 = '011' and abc.price_2 = '034' and abc.retl_code = '00068247'
and abc.start_date in ('2017-05-31','2017-05-30','2017-05-29','2017-05-28','2017-05-27','2017-05-26','2017-05-25','2017-05-24');
Explain Plan:
"Index Scan using tessttable_comp_date on tessttable abc (cost=0.70..41.70 rows=1 width=43)"
" Index Cond: ((start_date = ANY ('{2017-05-31,2017-05-30,2017-05-29,2017-05-28,2017-05-27,2017-05-26,2017-05-25,2017-05-24}'::text[])) AND (prd_price_attr_1 = '011'::text) AND (price_2 = '034'::text) AND (retl_code = '00068247': (...)"
Query:
select retl_code,price_2, attr_1,start_date,price,printedprice
from tessttable abc
where abc.attr_1 = '011' and abc.price_2 = '034' and abc.retl_code = '00068247'
and abc.start_date = ANY (testdays('2017-05-31'::date,7)) ;
Explain Plan:
"Index Scan using tessttable_comp_date on tessttable abc (cost=0.70..10285844.48 rows=4 width=43)"
" Index Cond: ((prd_price_attr_1 = '011'::text) AND (price_2 = '034'::text) AND (retl_code = '00068247'::text))"
" Filter: (start_date = ANY (just_n_days('2017-05-31'::date, 7)))"

Get rid of the select, simply use the function as an expression:
and abc.start_date = ANY ( testdays('2017-05-31'::date,7) )
If abc.start_date is defined as date then you shouldn't compare a date value with an array of strings. Make the function return a date[] instead.
The function can also be simplified to a simple SQL function, there is no need for PL/pgSQL:
CREATE OR REPLACE FUNCTION testdays(v_in_date date, v_n integer)
RETURNS date[] AS
$BODY$
select array_agg(x::date)
from generate_series(current_date, current_date + 6, interval '1' day) x;
$BODY$
LANGUAGE sql;
To stick with the wrong text values for dates, just change what you aggregate
CREATE OR REPLACE FUNCTION testdays(v_in_date date, v_n integer)
RETURNS text[] AS
$BODY$
select array_agg(to_char(x, 'yyyy-mm-dd'))
from generate_series(current_date, current_date + 6, interval '1' day) x;
$BODY$
LANGUAGE sql;
Note that I changed the return type to date[] so that you will compare dates with dates.
However, I don't see why you need a function in the first place:
and abc.start_date between date '2017-05-31' and date '2017-05-31' + 7
would achieve exactly the same thing and will be faster in most of the cases.

Related

postgres function returns operator doesn't exist integer - timestamp

Hi I have a function that when calling it to test returns the error in the question. i know it is to do with how i insert the variable into part of the query but i cannot work out why, i've tried changing the type castings around on it but no luck. any help would be appreciated
create or replace function ageing_balance_calc(start_date text, client_id text) returns void
AS $BODY$
declare
_absd date;
_client_id text;
req text;
BEGIN
_absd:=(to_date(start_date,'YYYY-MM-DD'));
_client_id:= client_id;
with cte as
(
Select * from crosstab('
select * from (
select absd,
case when a.days_ago between 0 and 30 then ''0-30''
when a.days_ago between 31 and 60 then ''31-60''
when a.days_ago between 61 and 90 then ''61-90''
when a.days_ago >90 then ''90+''
else ''not due'' end as bucket, sum(a.item_amount)
from (select date_part(''day'',((due_date::timestamp) - ('||_absd||' ::text::timestamp)) )as days_ago, item_amount, '||_absd||'::text::date as absd from fdw_test) a
group by bucket, absd)b order by 1,2') as (absd date,
bucket0_30 numeric,
bucket31_60 numeric,
bucket61_90 numeric,
bucket90_plus numeric,
not_due numeric)
)
update ddva.kpi_calc_results
set
absd = a.absd,
bucket0_30 = a.bucket0_30,
bucket31_60 = a.bucket31_60,
bucket61_90 = a.bucket61_90,
bucket90_plus = a.bucket90_plus,
not_due = a.not_due from (
select absd, bucket0_30, bucket31_60, bucket61_90, bucket90_plus, not_due from cte) as a
where ddva.kpi_calc_results.client_id = _client_id ;
END;
$BODY$
LANGUAGE plpgsql
update
the date_part works if i remove it to its own query and then use $date to insert the date as a string that way. if i remove the to_date part at the start of the function i still get the same error as before. does '||x||' do anything to the data type?
the problem is here:
the result query looks like this: (2021-07-07::text::timestamp) and results in error: iteger - timestamp.
You must add more quotes: ('''||_absd||'''::text::timestamp)
Well:
date_part(''day'',((due_date::timestamp)
is going to return an integer:
select date_part('day', now());
27
I'm guessing you want something more like:
select now()::date; 2021-07-27
UPDATE
This part:
'''||_absd||'''::text::timestamp
won't work either:
select '''||_absd||'''::text::timestamp;
ERROR: invalid input syntax for type timestamp: "'||_absd||'"
the below line:
(select date_part(''day'',((due_date::timestamp) - ('||_absd||' ::text::timestamp)) )as days_ago, item_amount, '||_absd||'::text::date as absd from fdw_test)
was changed slightly to this:
(select date_part(''day'',((due_date::timestamp) - ('''||_absd||'''::timestamp)) )as days_ago, item_amount, '''||_absd||'''::date as absd from fdw_test)
the function now works as expected

Set-returning functions are not allowed in UPDATE when using Postgres 11

I have a Postgresql 9.6 (and postgis 2.5) function that calculates a json with parameters based on a geometry of a record.
I created a json field and wrote the json calculation output there, and all was well.
UPDATE public.recordtable
SET polygoncalc = public.fn_calculate_polygon(5)
WHERE id = 5;
When I try to run it on Postgres 11.2 , it returns a error:
SQL Error [0A000]:ERROR: set-returning functions are not allowed in UPDATE
Based on this link I understand there has been a change in postgresql, but honestly,
how can I write the json into the field in Postgres 11.2 without getting the error ?
Thank you in advance.
EDIT: I changed the output field to type json and added the function text
the function:
CREATE OR REPLACE FUNCTION public.fn_create_jsn_point(pid double precision)
RETURNS TABLE(jsn json)
LANGUAGE plpgsql
AS $function$
DECLARE
p_id double precision = pid;
myjson json = null ;
begin
return QUERY
select row_to_json(finaljson)
from(
select
ST_x(a.wgs_84_dump_point) as X,
ST_y(a.wgs_84_dump_point) as Y
(
select (st_transform(st_centroid(t.geom),4326)) wgs_84_dump_point
from baserecords t where base_id = p_id
) a ) finaljson;
END;
$function$
;
The function is returning a table. No matter how many rows are returned, it is returning a table, i.e a set.
Since you know a single value will be returned, you can change the function to restrict it:
CREATE OR REPLACE FUNCTION public.fn_create_jsn_point(pid double precision)
RETURNS json -- <---------------------------- Return a single value
LANGUAGE plpgsql
AS $function$
DECLARE
p_id double precision = pid;
myjson json = null ;
begin
return ( -- <---------------------------------- return a single value
select row_to_json(finaljson)
from(
select
ST_x(a.wgs_84_dump_point) as X,
ST_y(a.wgs_84_dump_point) as Y
(
select (st_transform(st_centroid(t.geom),4326)) wgs_84_dump_point
from baserecords t where base_id = p_id
) a ) finaljson
);
END;
$function$
;

Recursive with cursor on psql, nothing data found

How to use a recursive query and then using cursor to update multiple rows in postgresql. I try to return data but no data is found. Any alternative to using recursive query and cursor, or maybe better code please help me.
drop function proses_stock_invoice(varchar, varchar, character varying);
create or replace function proses_stock_invoice
(p_medical_cd varchar,p_post_cd varchar, p_pstruserid character varying)
returns void
language plpgsql
as $function$
declare
cursor_data refcursor;
cursor_proses refcursor;
v_medicalCd varchar(20);
v_itemCd varchar(20);
v_quantity numeric(10);
begin
open cursor_data for
with recursive hasil(idnya, level, pasien_cd, id_root) as (
select medical_cd, 1, pasien_cd, medical_root_cd
from trx_medical
where medical_cd = p_pstruserid
union all
select A.medical_cd, level + 1, A.pasien_cd, A.medical_root_cd
from trx_medical A, hasil B
where A.medical_root_cd = B.idnya
)
select idnya from hasil where level >=1;
fetch next from cursor_data into v_medicalCd;
return v_medicalCd;
while (found)
loop
open cursor_proses for
select B.item_cd, B.quantity from trx_medical_resep A
join trx_resep_data B on A.medical_resep_seqno = B.medical_resep_seqno
where A.medical_cd = v_medicalCd and B.resep_tp = 'RESEP_TP_1';
fetch next from cursor_proses into v_itemCd, v_quantity;
while (found)
loop
update inv_pos_item
set quantity = quantity - v_quantity, modi_id = p_pstruserid, modi_id = now()
where item_cd = v_itemCd and pos_cd = p_post_cd;
end loop;
close cursor_proses;
end loop;
close cursor_data;
end
$function$;
but nothing data found?
You have a function with return void so it will never return any data to you. Still you have the statement return v_medicalCd after fetching the first record from the first cursor, so the function will return from that point and never reach the lines below.
When analyzing your function you have (1) a cursor that yields a number of idnya values from table trx_medical, which is input for (2) a cursor that yields a number of v_itemCd, v_quantity from tables trx_medical_resep, trx_resep_data for each idnya, which is then used to (3) update some rows in table inv_pos_item. You do not need cursors to do that and it is, in fact, extremely inefficient. Instead, turn the whole thing into a single update statement.
I am assuming here that you want to update an inventory of medicines by subtracting the medicines prescribed to patients from the stock in the inventory. This means that you will have to sum up prescribed amounts by type of medicine. That should look like this (note the comments):
CREATE FUNCTION proses_stock_invoice
-- VVV parameter not used
(p_medical_cd varchar, p_post_cd varchar, p_pstruserid varchar)
RETURNS void AS $function$
UPDATE inv_pos_item -- VVV column repeated VVV
SET quantity = quantity - prescribed.quantity, modi_id = p_pstruserid, modi_id = now()
FROM (
WITH RECURSIVE hasil(idnya, level, pasien_cd, id_root) AS (
SELECT medical_cd, 1, pasien_cd, medical_root_cd
FROM trx_medical
WHERE medical_cd = p_pstruserid
UNION ALL
SELECT A.medical_cd, level + 1, A.pasien_cd, A.medical_root_cd
FROM trx_medical A, hasil B
WHERE A.medical_root_cd = B.idnya
)
SELECT B.item_cd, sum(B.quantity) AS quantity
FROM trx_medical_resep A
JOIN trx_resep_data B USING (medical_resep_seqno)
JOIN hasil ON A.medical_cd = hasil.idnya
WHERE B.resep_tp = 'RESEP_TP_1'
--AND hacil.level >= 1 Useless because level is always >= 1
GROUP BY 1
) prescribed
WHERE item_cd = prescribed.item_cd
AND pos_cd = p_post_cd;
$function$ LANGUAGE sql STRICT;
Important
As with all UPDATE statements, test this code before you run the function. You can do that by running the prescribed sub-query separately as a stand-alone query to ensure that it does the right thing.

Month and year instead of the date variable

I work with PostgreSQL and I have three variables in a function and one of them is of type date:
CREATE OR REPLACE FUNCTION public.fn_reporte_venta_mes(
IN p_fech = date,
IN p_client integer,
IN p_comprobante character
The parameter p_fech serves to enter a date from PHP in format '1111-11-11'. Instead, I want to only insert date a month and year format '1111-11'. But I do not know how to change the data type of the p_fech variable since when I put only the day and month in PHP I get compatibility error.
My complete function:
CREATE OR REPLACE FUNCTION public.fn_reporte_venta(
IN p_fech date,
IN p_client integer,
IN p_comprobante character)
RETURNS TABLE(nro integer, fecha date, tipo character varying, cliente text, porc_igv numeric, st numeric, igv numeric, total numeric) AS
$BODY$
begin
return query
select v.numero_venta,
v.fecha_venta,
tc.descripcion,
concat(apellido_paterno, ' ', apellido_materno, ' ', nombres) as client,
v.porcentaje_igv,
v.sub_total,
v.igv,
v.total
from venta v
inner join cliente cl on v.codigo_cliente = cl.codigo_cliente
inner join tipo_comprobante tc on v.codigo_tipo_comprobante = tc.codigo_tipo_comprobante
where v.estado = 'E' and
(
case when p_fech = '11-11-1111' then
1 = 1
else
v.fecha_venta = p_fech
end
)
and
(
case when p_client = 0 then
1 = 1
else
cl.codigo_cliente = p_client
end
) and
(
case when p_comprobante = '00' then
1 = 1
else
tc.codigo_tipo_comprobante = p_comprobante
end
)
order by 2;
end
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
Use a character type parameter (text or varchar, not character!) and the function to_date() to convert the input to a date.
Your function, simplified and fixed:
CREATE OR REPLACE FUNCTION public.fn_reporte_venta(p_fech text
, p_client integer
, p_comprobante text)
RETURNS TABLE(nro integer, fecha date, tipo varchar, cliente text
, porc_igv numeric, st numeric, igv numeric, total numeric) AS
$func$
DECLARE
v_fech date := to_date(p_fech, 'YYYY-MM-DD'); -- transform to date
BEGIN
RETURN QUERY
SELECT v.numero_venta,
v.fecha_venta,
t.descripcion,
concat_ws(' ', c.apellido_paterno, c.apellido_materno, c.nombres) AS client,
v.porcentaje_igv,
v.sub_total,
v.igv,
v.total
FROM venta v
JOIN cliente c USING (codigo_cliente)
JOIN tipo_comprobante t USING (codigo_tipo_comprobante)
WHERE v.estado = 'E'
AND (v_fech = '1111-11-11' OR v.fecha_venta = v_fech) -- var goes here
AND (p_client = 0 OR c.codigo_cliente = p_client)
AND (p_comprobante = '00' OR t.codigo_tipo_comprobante = p_comprobante)
ORDER BY 2;
END
$func$ LANGUAGE plpgsql STABLE ROWS 1000;
Always use the ISO-8601 format for date literals (YYYY-MM-DD), which is unambiguous with any locale setting. Don't fall for local syntax dialects, they break if a session should run with a different locale.
I am using a date variable in the plpgsql function, which can be assigned at declaration right away.
This expression is very versatile:
to_date(p_fech, 'YYYY-MM-DD');
to_date() allows to omit elements to the right to 1 for missing data. All of these are valid and result in '2016-01-01':
SELECT to_date('2016-01-01', 'YYYY-MM-DD')
, to_date('2016-1-1' , 'YYYY-MM-DD')
, to_date('2016-01' , 'YYYY-MM-DD')
, to_date('2016-' , 'YYYY-MM-DD')
, to_date('2016' , 'YYYY-MM-DD');
So you can pass in full dates or truncated to month or even year. You always specify a single day this way.
Or to always cover a whole month:
...
v_fech date := to_date(p_fech, 'YYYY-MM'); -- no "-DD" truncates to month
...
AND (v_fech = '1111-11-01' -- also truncated to 1st of month!
OR v.fecha_venta >= v_fech
AND v.fecha_venta < v_fech + interval '1 month')
...
Why not data type character? Because that's an outdated, largely useless type, typically a misunderstanding:
Best way to check for "empty or null value"
Any downsides of using data type "text" for storing strings?
Also note how I replaced your verbose CASE statements with simple OR expressions.
And some other minor improvements ...

Why postgres function so slow but single query is fast?

I have function to get employee in 'Create' status.
CREATE OR REPLACE FUNCTION get_probation_contract(AccountOrEmpcode TEXT, FromDate DATE,
ToDate DATE)
RETURNS TABLE("EmpId" INTEGER, "EmpCode" CHARACTER VARYING,
"DomainAccount" CHARACTER VARYING, "JoinDate" DATE,
"ContractTypeCode" CHARACTER VARYING, "ContractTypeName" CHARACTER VARYING,
"ContractFrom" DATE, "ContractTo" DATE, "ContractType" CHARACTER VARYING,
"Signal" CHARACTER VARYING) AS $$
BEGIN
RETURN QUERY
EXECUTE 'SELECT
he.id "EmpId",
rr.code "EmpCode",
he.login "DomainAccount",
he.join_date "JoinDate",
contract_type.code "ContractTypeCode",
contract_type.name "ContractTypeName",
contract.date_start "ContractFrom",
contract.date_end "ContractTo",
CASE WHEN contract_group.code = ''1'' THEN ''Probation''
WHEN contract_group.code IN (''3'', ''4'', ''5'') THEN ''Official''
WHEN contract_group.code = ''2'' THEN ''Collaborator'' END :: CHARACTER VARYING "ContractType",
''CREATE'' :: CHARACTER VARYING "Signal"
FROM
hr_employee he
INNER JOIN resource_resource rr
ON rr.id = he.resource_id
INNER JOIN hr_contract contract
ON contract.employee_id = he.id AND contract.date_start = (
SELECT max(date_start) "date_start"
FROM hr_contract cc
WHERE cc.employee_id = contract.employee_id
)
INNER JOIN hr_contract_type contract_type
ON contract_type.id = contract.type_id
INNER JOIN hr_contract_type_group contract_group
ON contract_group.id = contract_type.contract_type_group_id
WHERE
contract_group.code = ''1''
AND
($1 IS NULL OR $1 = '''' OR rr.code = $1 OR
he.login = $1)
AND (
(he.join_date BETWEEN $2 AND $3)
OR (he.join_date IS NOT NULL AND (contract.date_start BETWEEN $2 AND $3))
OR (he.create_date BETWEEN $2 AND $3 AND he.create_date > he.join_date)
)
AND rr.active = TRUE
'using AccountOrEmpcode, FromDate, ToDate ;
END;
$$ LANGUAGE plpgsql;
It took 37 second to execute
SELECT *
FROM get_probation_contract('', '2014-01-01', '2014-06-01');
When I use single query
SELECT
he.id "EmpId",
rr.code "EmpCode",
he.login "DomainAccount",
he.join_date "JoinDate",
contract_type.code "ContractTypeCode",
contract_type.name "ContractTypeName",
contract.date_start "ContractFrom",
contract.date_end "ContractTo",
CASE WHEN contract_group.code = '1' THEN 'Probation'
WHEN contract_group.code IN ('3', '4', '5') THEN 'Official'
WHEN contract_group.code = '2' THEN 'Collaborator' END :: CHARACTER VARYING "ContractType",
'CREATE' :: CHARACTER VARYING "Signal"
FROM
hr_employee he
INNER JOIN resource_resource rr
ON rr.id = he.resource_id
INNER JOIN hr_contract contract
ON contract.employee_id = he.id AND contract.date_start = (
SELECT max(date_start) "date_start"
FROM hr_contract
WHERE employee_id = he.id
)
INNER JOIN hr_contract_type contract_type
ON contract_type.id = contract.type_id
INNER JOIN hr_contract_type_group contract_group
ON contract_group.id = contract_type.contract_type_group_id
WHERE
contract_group.code = '1'
AND (
(he.join_date BETWEEN '2014-01-01' AND '2014-06-01')
OR (he.join_date IS NOT NULL AND (contract.date_start BETWEEN '2014-01-01' AND '2014-01-06'))
OR (he.create_date BETWEEN '2014-01-01' AND '2014-01-06' AND he.create_date > he.join_date)
)
AND rr.active = TRUE
It take 5 second to complete
How to optimize the function above.
and why function is slow than single query so much even I use execute 'select ...' in function.
Indexing in field id each table.
Possible reason is a blind optimization for prepared statements (embedded SQL). It is little bit better in new PostgreSQL releases, although it can be the issue there too. Execution plan in embedded SQL in PL/pgSQL is reused for more calls - and it is optimized for more often value (not for really used value). Sometimes this difference can make really big slowdowns.
Then you can use dynamic SQL - EXECUTE statement. Dynamic SQL uses only once executed plans and it uses real parameters. It should to fix this issue.
Example of embedded SQL with reused prepared plans.
CREATE OR REPLACE FUNCTION fx1(_surname text)
RETURNS int AS $$
BEGIN
RETURN (SELECT count(*) FROM people WHERE surname = _surname)
END;
Example with dynamic SQL:
CREATE OR REPLACE FUNCTION fx2(_surname text)
RETURNS int AS $$
DECLARE result int;
BEGIN
EXECUTE 'SELECT count(*) FROM people WHERE surname = $1' INTO result
USING _surname;
RETURN result;
END;
$$ LANGUAGE plpgsql;
Second function can be faster if your dataset contains some terrible often surname - then common plan will be seq scan, but lot of time you will ask some other surname, and you will want to use index scan. Dynamical query parametrization (like ($1 IS NULL OR $1 = '''' OR rr.code = $1 OR) has same effect.
Your queries are not the same.
The first one has
WHERE cc.employee_id = contract.employee_id
where the second one has:
WHERE employee_id = he.id
And also:
($1 IS NULL OR $1 = '''' OR rr.code = $1 OR
he.login = $1)
Please test again with identical queries and identical values.