Optimizing pgSQL function performance - postgresql

I really need advice. I'm trying to use the function : pivot_subrecords_set_table_1
In the following query:
SELECT rec.id AS key,
rec.primaryurn AS urn,
rec.mx_syscreatedby AS creared_by,
(SELECT mf_mxdate_str_to_date (epi.mx_epizoddate)) AS date_of_creation,
epi.mx_epizodenotes AS notes,
epi.mx_effectscountry AS country,
epi.mx_effectsregion AS region,
epi.mx_effectscity AS city,
(SELECT get_epizod_subrecords (epi.id)) AS epizod_type
(pivot_subrecords_set_table_1 (epi.id)).idx,
(pivot_subrecords_set_table_1 (epi.id)).upn_1 AS upn_1,
(pivot_subrecords_set_table_1 (epi.id)).status_1
FROM mxt_recordheader AS rec, mt_epizod AS epi, mt_epizod_subrecord AS eps
WHERE rec.entitytype = 'epizod'
AND rec.logicalserverprefix = 'EA'
AND epi.id = rec.id;
The function looks like this:
CREATE OR REPLACE FUNCTION public.pivot_subrecords_set_table_1(id bigint,
OUT idx bigint, OUT upn_1 text, OUT status_1 text, OUT upn_2 text, OUT
status_2 text, OUT upn_3 text, OUT status_3 text, OUT upn_4 text, OUT
status_4 text, OUT upn_5 text, OUT status_5 text, OUT upn_6 text, OUT
status_6 text, OUT upn_7 text, OUT status_7 text, OUT upn_8 text, OUT
status_8 text, OUT upn_9 text, OUT status_9 text, OUT upn_10 text, OUT
status_10 text)
RETURNS SETOF record
LANGUAGE plpgsql
AS $function$
DECLARE
key ALIAS FOR $1;
BEGIN
RETURN QUERY
WITH sub_upn AS (SELECT * FROM crosstab
('SELECT id, mx_osobastatus, mx_osobaupn FROM mt_epizod_subrecord
WHERE mt_epizod_subrecord.mx_syscategory = ''tableepizodinfo''
AND mt_epizod_subrecord.id = '|| key ||' GROUP BY id, mx_osobaupn,
mx_osobastatus
ORDER BY id') AS ct
(id bigint, UPN_1 text, UPN_2 text, UPN_3 text, UPN_4 text, UPN_5 text, UPN_6
text, UPN_7 text, UPN_8 text ,UPN_9 text, UPN_10 text)
),
sub_stat AS
(SELECT * FROM crosstab
('SELECT id, mx_osobaupn, mx_osobastatus FROM mt_epizod_subrecord
WHERE mt_epizod_subrecord.mx_syscategory = ''tableepizodinfo''
AND mt_epizod_subrecord.id = '|| key ||' GROUP BY 1,2,3 ORDER BY id ')
AS cn (id bigint, status_1 text, status_2 text, status_3 text, status_4 text,
status_5 text, status_6 text, status_7 text, status_8 text,
status_9 text, status_10 text)
)
SELECT
supn.id,
supn.UPN_1,
sust.status_1,
supn.UPN_2,
sust.status_2,
supn.UPN_3,
sust.status_3,
supn.UPN_4,
sust.status_4,
supn.UPN_5,
sust.status_5,
supn.UPN_6,
sust.status_6,
supn.UPN_7,
sust.status_7,
supn.UPN_8,
sust.status_8,
supn.UPN_9,
sust.status_9,
supn.UPN_10,
sust.status_10
FROM sub_upn supn
JOIN sub_stat sust ON supn.id = sust.id;
END;
$function$
But the performance of the function is very low.
How can I increase the performance of a function or can I rewrite the query in which it is used?

Use plprofiler to figure out what is slow in your function and optimize that.

Related

Postgres EXECUTE is slower than Query

I am facing a problem, I have developed a function with dynamic whare clause.
When every I run the query separatly it returns the value much faster while running with EXECUTE it give allot of time. "cv."SessionId" = ''f0d1c0e1-b13c-4aff-b1f7-432183b06d10'' AND cv."CampusId" = ''4f4310a3-9c36-49c2-8456-5604cb7e9a62'' AND cv."ProgramId"=''e50efffa-40a3-4eb9-a65b-65106967add7'' AND cv."ShiftId"=''414de073-ca8b-4a84-80bc-52c2030b4eeb'' AND cv."ProgramDetailId"=''8d1ce321-d015-4336-ba92-dcd2c8b6443b'' AND cv."ClassId"=''8931d744-acc9-4776-a03a-2b705038ea48'' AND cv."SectionId"=''83c507d0-8f61-4a28-b221-88625482953f'' AND cv."CollectorId"=''e58d2fbf-839a-49a1-b1ce-92e7c26a305b'' AND cv."FeeHeadId"=''5721bc8f-4e9c-49d0-a992-6b846fafb9db'' AND cv."PaidDate" >= ''2020-07-01'' AND cv."PaidDate" <= ''2020-08-07'' ORDER BY cv."ChallanNo" ASC" this is where clause parameter which change every time. some time two some time four or five. the following is my function which i want to optimize.
declare feeheadrow record;
declare paiddate date;
declare rollno text;
declare admisformid uuid;
declare feeheadname text;
declare instNo int4;
declare stdname text;
declare fathername text;
declare refno text;
declare classid uuid;
declare campusprogid uuid;
declare stdid uuid;
declare challano text;
declare campusid uuid;
declare sessionid uuid;
declare progdetailid uuid;
declare campusname text;
declare descrip text;
declare shiftname text;
declare genderid uuid;
declare shiftid uuid;
declare remarks text;
declare sessionname text;
declare totalamount int4;
declare programid uuid;
declare collectorid uuid;
BEGIN
DROP TABLE IF EXISTS "FeeReport";
DROP TABLE IF EXISTS "FeeHeads";
create temp table "FeeReport"("NewID" uuid, "PaidDate" Date, "RollNo" text, "AdmissionFormId" uuid, "FeeHeadName" text, "InstallmentNo" int4, "StudentName" text, "FatherName" text, "RefferenceNo" text, "ClassId" uuid, "CampusProgramId" uuid, "FeeHeadId" uuid,
"FeeAmount" int4, "PayableAmount" int4, "StudentId" uuid, "ChallanNo" text, "CampusId" uuid, "SessionId" uuid,
"ProgramDetailId" uuid, "CampusName" text, "Description" text, "ShiftName" text, "GenderId" uuid,
"ShiftId" uuid,"Remarks" text, "ScholarshipCriteriaId" uuid, "ScholarshipCriteriaName" text, "SessionName" text, "TotalAmount" int4
, "ProgramId" uuid, "CollectorId" uuid
) on commit DROP;
create temp table "FeeHeads"("FeeHeadId" UUID, "FullName" TEXT) on commit drop;
insert into "FeeReport"
SELECT * FROM "Query"('cv.*', '"Fee"."VWFeeStatement2" AS cv', whereclause) AS
"FeeRecords" ("NewID" uuid, "PaidDate" date, "RollNo" text, "AdmissionFormId" uuid, "FeeHeadName" text, "InstallmentNo" int4, "StudentName" text, "FatherName" text, "RefferenceNo" text, "ClassId" uuid, "CampusProgramId" uuid, "FeeHeadId" uuid, "FeeAmount" int4, "PayableAmount" int4, "StudentId" uuid, "ChallanNo" text, "CampusId" uuid, "SessionId" uuid, "ProgramDetailId" uuid, "CampusName" text, "Description" text, "ShiftName" text, "GenderId" uuid, "ShiftId" uuid, "Remarks" text, "ScholarshipCriteriaId" uuid, "ScholarshipCriteriaName" text, "SessionName" text, "TotalAmount" int4,"ProgramId" uuid,"CollectorId" uuid);
insert into "FeeReport"
SELECT * FROM "Query"('cv.*', '"Fee"."VWFeeStatementOther2" AS cv', whereclause) AS
"FeeRecords" ("NewID" uuid, "PaidDate" date, "RollNo" text, "AdmissionFormId" uuid, "FeeHeadName" text, "InstallmentNo" int4, "StudentName" text, "FatherName" text, "RefferenceNo" text, "ClassId" uuid, "CampusProgramId" uuid, "FeeHeadId" uuid, "FeeAmount" int4, "PayableAmount" int4, "StudentId" uuid, "ChallanNo" text, "CampusId" uuid, "SessionId" uuid, "ProgramDetailId" uuid, "CampusName" text, "Description" text, "ShiftName" text, "GenderId" uuid, "ShiftId" uuid, "Remarks" text, "ScholarshipCriteriaId" uuid, "ScholarshipCriteriaName" text, "SessionName" text, "TotalAmount" int4,"ProgramId" uuid,"CollectorId" uuid);
insert into "FeeHeads"
SELECT DISTINCT fh."FeeHeadId", fh."FullName" FROM "Fee"."FeeHead" AS fh, "FeeReport" fr
WHERE fr."FeeHeadId" = fh."FeeHeadId";
FOR vwfeestatementrow in select distinct f."AdmissionFormId"
from "FeeReport" f
LOOP
select vw."PaidDate", vw."RollNo", vw."AdmissionFormId", vw."InstallmentNo", vw."StudentName", vw."FatherName" , vw."RefferenceNo", vw."ClassId",
vw."CampusProgramId", vw."StudentId", vw."ChallanNo", vw."CampusId" , vw."SessionId",
vw."ProgramDetailId", vw."CampusName", vw."Description", vw."ShiftName", vw."GenderId",vw."ShiftId", vw."SessionName", vw."TotalAmount",vw."ProgramId",vw."CollectorId"
INTO paiddate,rollno,
admisformid,instNo,stdname,fathername,refno,classid,
campusprogid,stdid,challano,campusid,sessionid,progdetailid,campusname,descrip,shiftname,genderid,shiftid,sessionname,totalamount,programid,collectorid
from "FeeReport" vw where vw."AdmissionFormId"=vwfeestatementrow."AdmissionFormId" limit 1;
FOR feeheadrow IN SELECT fh."FeeHeadId",fh."FullName" FROM "FeeHeads" fh WHERE (fh."FeeHeadId" NOT IN (
SELECT ins."FeeHeadId" FROM "FeeReport" AS ins WHERE ins."AdmissionFormId" = vwfeestatementrow."AdmissionFormId"))
LOOP
INSERT INTO "FeeReport"("NewID","PaidDate","RollNo","AdmissionFormId","FeeHeadName","InstallmentNo","StudentName",
"FatherName","RefferenceNo","ClassId", "CampusProgramId","FeeHeadId",
"FeeAmount", "PayableAmount", "StudentId", "ChallanNo", "CampusId", "SessionId",
"ProgramDetailId", "CampusName", "Description", "ShiftName", "GenderId",
"ShiftId","Remarks", "ScholarshipCriteriaId", "ScholarshipCriteriaName", "SessionName", "TotalAmount","ProgramId",
"CollectorId"
)
VALUES(uuid_generate_v1(),paiddate,rollno,vwfeestatementrow."AdmissionFormId",
feeheadrow."FullName",instNo,stdname,fathername
,refno,classid,campusprogid,feeheadrow.
"FeeHeadId",0,0,stdid,challano,campusid,
sessionid,progdetailid,campusname,descrip,shiftname,genderid,shiftid,'',null,'',
sessionname,totalamount,programid,collectorid
);
END LOOP;
END LOOP;
-- DELETE FROM "Student" AS st WHERE st."AdmissionFormId" IN (SELECT af."AdmissionFormId" FROM "Fee"."StudentFeeStructure" AS sf, "Admission"."AdmissionForm" AS af WHERE sf."AdmissionFormId" = af."AdmissionFormId" AND af."CampusProgramId" = campusprogramid AND sf."ConcessionDetailId" IS NOT NULL);
RETURN query Select fr.* from "FeeReport" as fr ORDER BY fr."ChallanNo", fr."FeeHeadName";
END```

Generate dynamic columns for CROSSTAB in postgresql?

I have this table in postgres
CREATE TABLE ct(id SERIAL, rowid TEXT, attribute TEXT, value TEXT);
INSERT INTO ct(rowid, attribute, value) VALUES('test1','att1','val1');
INSERT INTO ct(rowid, attribute, value) VALUES('test1','att2','val2');
INSERT INTO ct(rowid, attribute, value) VALUES('test1','att3','val3');
INSERT INTO ct(rowid, attribute, value) VALUES('test1','att4','val4');
INSERT INTO ct(rowid, attribute, value) VALUES('test2','att1','val5');
INSERT INTO ct(rowid, attribute, value) VALUES('test2','att2','val6');
INSERT INTO ct(rowid, attribute, value) VALUES('test2','att3','val7');
INSERT INTO ct(rowid, attribute, value) VALUES('test2','att4','val8');
I want to generate a dynamic crosstab query using this table.
Till now I have created the static query by following the example on the official postgres documentation page.
select * from crosstab
('select rowid, attribute, value from ct order by 1,2')
as final_result(rowid text, att1 text, att2 text, att3 text, att4 text)
Now I want this part to be dynamic
as final_result(rowid text, att1 text, att2 text, att3 text, att4 text)
I tried few things such as
Creating a query which generate the column name with their types and passing that query in as final_result(query), but it doesn't work as here,
SELECT 'rowid text, '
|| string_agg(Distinct attribute, ' text, ') as name
FROM ct;
select * from crosstab
('select rowid, attribute, value from ct order by 1,2')
as final_result(SELECT 'rowid text, '
|| string_agg(Distinct attribute, ' text, ') as name
FROM ct;)
OR
select * from crosstab
('select rowid, attribute, value from ct order by 1,2',
SELECT 'rowid text, '
|| string_agg(Distinct attribute, ' text, ')) as name
FROM ct;)
Both of these queries doesn't work.
I searched stackoverflow found this link, but it also doesn't have a proper acceptable answer here,
Dynamically generate columns for crosstab in PostgreSQL
Any idea how this can be done.

CREATE TABLE is not allowed in a non-volatile function

I have the following three tables :
create table drugs(
id integer,
name varchar(20),
primary key(id)
);
create table prescription(
id integer,
drug_id integer,
primary key(id),
foreign key(drug_id) references drugs(id)
);
create table visits(
patient_id varchar(10),
prescription_id integer,
primary key( patient_id , prescription_id),
foreign key(prescription_id) references prescription(id)
);
I wrote the following function on these tables to show me a patient's drugs list(the patient id is parameter):
CREATE OR REPLACE FUNCTION public.patients_drugs(
patientid character varying)
RETURNS TABLE(drug_id integer, drug_name character varying)
LANGUAGE 'plpgsql'
COST 100
STABLE STRICT
ROWS 1000
AS $BODY$
begin
create temporary table result_table(
drug_id integer,
drug_name varchar(20)
);
return query select distinct drug.id , drug.name
from visits join prescription
on visits.patient_id = patientID;
end;
$BODY$;
However, it gives me this error:
CREATE TABLE is not allowed in a non-volatile function
You don't need to create a table in order to be able to "return a table". Just get rid of the CREATE TABLE statement.
But your query isn't correct either, as you are selecting columns from the drug table, but you never include that in the FROM clause. You can also get rid of the distinct clause if you don't use a join, but an EXISTS condition:
CREATE OR REPLACE FUNCTION public.patients_drugs(p_patientid character varying)
RETURNS TABLE(drug_id integer, drug_name character varying)
LANGUAGE plpgsql
AS $BODY$
begin
return query
select d.*
from drugs d
where exists (select *
from prescription p
join visits v on v.prescription_id = p.id
where d.id = p.drug_id
and v.patientid = p_patientid);
end;
$BODY$;
Or better, use a simple SQL function:
CREATE OR REPLACE FUNCTION public.patients_drugs(p_patientid character varying)
RETURNS TABLE(drug_id integer, drug_name character varying)
LANGUAGE sql
AS
$BODY$
select d.*
from drugs d
where exists (select *
from prescription p
join visits v on v.prescription_id = p.id
where d.id = p.drug_id
and v.patientid = p_patientid);
$BODY$;

PostgreSQL reports a key violation, but that is not possible

I have wrote a stored-procedure in PostgreSQL as an API to create contract and user in a same time (by postgrest).
CREATE OR REPLACE FUNCTION
auth.create_contracts(
username text,
password text,
services json,
province text,
region text,
type user_group,
telephone text,
mobile text,
email text,
start_date text,
end_date text,
organization_name text,
contract_number text,
name text,
economy_code INTEGER,
t_name text,
t_phone text,
t_ip text,
t_email text,
t_telephone text,
t_prefer_contract text,
area_state text,
area_city text,
area_county text
) RETURNS json
LANGUAGE plpgsql
AS $$
DECLARE
_user_id INTEGER;
_res json;
_now DOUBLE PRECISION;
_app_token text;
_username text;
BEGIN
SELECT random_string(64) INTO _app_token;
INSERT INTO auth.users(
username,
password,
role,
services,
app_token,
province,
region,
type,
phone,
organization_name,
mobile,
email
)
VALUES (
username,
password,
username,
services,
_app_token,
province,
region,
type,
telephone,
organization_name,
mobile,
email
);
SELECT username INTO _username;
SELECT id FROM auth.users WHERE users.username=_username
INTO _user_id;
INSERT INTO auth.contracts(
user_id,
start_date,
end_date,
organization_name,
contract_number,
economy_code,
t_name,
t_phone,
t_ip,
t_email,
t_telephone,
t_prefer_contract,
name,
area_state,
area_city,
area_county
)
VALUES(
_user_id,
start_date::TIMESTAMP,
end_date::TIMESTAMP,
organization_name,
contract_number,
economy_code,
t_name,
t_phone,
t_ip,
t_email,
t_telephone,
t_prefer_contract,
name,
area_state,
area_city,
area_county
);
SET TIME ZONE 'UTC-3:32';
PERFORM (
SELECT auth.contract_redis_function(end_date::TIMESTAMP, now()::TIMESTAMP,
_app_token, username, 100000)
);
SELECT json_build_object(
'status', 'ok',
'app_token', _app_token
)
INTO _res;
RETURN _res;
END;
$$;
users definition:
create table auth.users
(
id serial not null
constraint users_pkey
primary key,
username text,
password text,
role text,
services json,
app_token text,
province text,
region text,
type user_group,
contract_id integer,
phone text,
picture text,
is_first_login boolean default true,
is_locked boolean default false,
is_active boolean default true,
organization_name text,
mobile text,
email text,
prefer_contact text
)
;
create unique index users_username_uindex
on users (username);
contracts definition:
create table auth.contracts
(
user_id integer
constraint unique_contract
unique
constraint contract_user_01
references auth.users,
start_date timestamp,
end_date timestamp,
organization_name text,
contract_number text,
economy_code integer,
t_name text,
t_phone text,
t_ip text,
t_email text,
t_telephone text,
t_prefer_contract text,
area_state text,
area_city text,
area_county text,
name text,
id serial not null,
is_active boolean default true
);
I have a unique constraint on a key of users relation (id that is auto incremented)
sometimes when call the end-point raise an exception that key exists, but doesn't, and create record.
I don't know why!?
I have a similar problem when creating policy in a stored-procedure.
username is not unique in auth.users, so there is no guarantee that this query gets the correct id:
SELECT id FROM auth.users WHERE users.username=_username
INTO _user_id;
You will get the id of one of the rows with that username.
If there are several such rows, you could get the wrong one, and the UNIQUE constraint on contracts will be violated when you insert the same number a second time.
The solution is to always get the correct id like this:
INSERT INTO auth.users (...) VALUES (...)
RETURNING id INTO _user_id;
Then you will always get the id of the record you just inserted.

Troubles with Crosstab

I have a postgres table with the following format:
I want add two (or more) columns to right (and maybe to left) like this:
But if I try to do that I get this:
ERROR: invalid return type
DETAIL: Query-specified return tuple has 15 columns but crosstab returns 14.
CONTEXT: PL/pgSQL function> carranza(date,date) line 8 at RETURN QUERY
This is my code:
Can anyone help me please?
CREATE OR REPLACE FUNCTION carranza(
IN p_fecha_inicio date,
IN p_fecha_fin date)
RETURNS TABLE(r_sucursal text, r_cliente text, r_nombre_cliente character varying, r_fecha_pago text, r_fecha_inicio text, r_fecha_fin text, r_fecha_proceso text, r_proceso text, r_nomina text, r_periodo text, r_desc_periodo text, r_fecha_factura text, r_factura text, r_cia text, r_servicio text, r_total text, r_costo_nt text, r_costo_sc text) AS
$BODY$
DECLARE
v_registros record;
BEGIN
RETURN QUERY
SELECT
(SELECT sucursal_name FROM "Facturacion_facturaprevia" WHERE factura_generada_id = obj_concentrado."factura"::integer)::text,
obj_concentrado."id cliente",
(SELECT cliente FROM "Clientes_cliente" WHERE id = obj_concentrado."id cliente"::integer),
obj_concentrado."fecha_pago",
obj_concentrado."fecha_inicio",
obj_concentrado."fecha_fin",
obj_concentrado."fecha_proceso",
obj_concentrado."proceso",
obj_concentrado."num_nomin",
obj_concentrado."periodo",
(SELECT descripcion_corta FROM "Catalogos_tiponomina" WHERE id_primario = obj_concentrado."descrip periodo")::text,
(SELECT fecha::date FROM "Facturacion_facturagenerada" WHERE id = obj_concentrado."factura"::integer)::text,
obj_concentrado."factura",
(SELECT cia FROM "Facturacion_facturagenerada" WHERE id = obj_concentrado."factura"::integer)::text,
(SELECT desc_corta FROM "Catalogos_servicio" WHERE id = obj_concentrado."servicio_id"::integer)::text,
(SELECT total_factura FROM "Facturacion_facturagenerada" WHERE id = obj_concentrado."factura"::integer)::text,
obj_concentrado."cia_NT",
obj_concentrado."cia_SC"
FROM (
SELECT * FROM crosstab (
'SELECT
proceso::text,
num_nomina::text,
(periodo || ''-'' || periodo_esp) as periodo,
tipo_periodo,
fecha_inicio::text,
fecha_fin::text,
fecha_pago::text,
fecha_proceso::text,
cliente_id::text,
servicio_id::text,
factura::text,
tipo_compania::text,
sum(activas)::text,
sum(canceladas)::text
FROM (
SELECT
factura,
cliente_id,
CASE
WHEN nat_concepto_id = 1 AND estatus = true
THEN importe
WHEN nat_concepto_id = 2 AND estatus = true
THEN importe * -1
WHEN nat_concepto_id = 3 AND estatus = true
THEN importe
END activas,
CASE
WHEN nat_concepto_id = 1 AND estatus = false
THEN importe
WHEN nat_concepto_id = 2 AND estatus = false
THEN importe * -1
WHEN nat_concepto_id = 3 AND estatus = false
THEN importe
END canceladas,
tipo_compania,
fecha_inicio,
fecha_fin,
fecha_pago,
fecha_proceso,
num_nomina,
servicio_id,
proceso,
periodo,
periodo_esp,
tipo_periodo
FROM "Facturacion_detallenomina"
WHERE (proceso || ''-'' || num_nomina) IN (
SELECT proceso || ''-'' || nomina
FROM (
SELECT distinct(proceso), nomina
FROM "Facturacion_facturapreviadataregistros"
WHERE facturaprevia_id IN (
SELECT id
FROM "Facturacion_facturaprevia"
WHERE factura_generada_id IN (select id FROM "Facturacion_facturagenerada" where num_factura<>'''')
)
) obj_data
)AND NOT rubro_id = ANY(ARRAY[0,10,14])
)obj_importes
GROUP BY tipo_compania, factura, cliente_id, fecha_pago, fecha_fin, fecha_inicio, fecha_proceso, num_nomina, proceso, periodo, periodo_esp, tipo_periodo, servicio_id
ORDER BY factura',
'select m from generate_series(1,2) m'
)as (
"proceso" text,
"num_nomin" text,
"periodo" text,
"descrip periodo" text,
"fecha_inicio" text,
"fecha_fin" text,
"fecha_pago" text,
"fecha_proceso" text,
"id cliente" text,
"servicio_id" text,
"factura" text,
"cia_NT" text,
"cia_SC" text,
"cia_nt_can" text,
"cia_sc_can" text
)
)obj_concentrado;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
ALTER FUNCTION carranza(date, date)
OWNER TO curso;