Postgres Stored Procedure issue - how do I solve this? - postgresql

I am creating a stored procedure that looks like this:
CREATE FUNCTION get_components(_given_user_id integer) RETURNS TABLE (id integer, name varchar, active boolean, parent integer, type smallint, description varchar, email varchar, user_id integer, component_id integer, flag smallint) AS $body$
BEGIN
RETURN QUERY SELECT s.* FROM st_components s LEFT JOIN (SELECT a.* FROM st_users_components_perms a WHERE a.user_id=_given_user_id) ON a.component_id=s.id ORDER BY name;
END;
$body$ LANGUAGE plpgsql;
The problem is, ON a.component_id=s.id ORDER BY name doesn't work because a.component_id is out of scope at this point. Is there a way to declare "a" as st_users_components_perms outside of the query? How is this solved? Thank you very much for any insight!

SELECT
s.*
FROM
st_components s
LEFT JOIN
(SELECT
a.*
FROM
st_users_components_perms a
WHERE
a.user_id = <CONSTANT>
) AS x
ON
x.component_id = s.id
ORDER BY
name;
Though the query seems pointless but let's assume it's a simplified version of your real query.

Related

Postgresql - return a record[] from a plpgsql function

I have an employer and an employee table. I have a join table that joins them.
CREATE TABLE employer (id int primary key, name text);
CREATE TABLE employee (id int primary key, name text);
CREATE TABLE employer_employee_join(
employer_id int REFERENCES employer(id),
employee_id int REFERENCES employee(id)
);
INSERT INTO employer (id, name) VALUES (1, 'the boss');
INSERT INTO employee (id, name) VALUES (1, 'employee1');
INSERT INTO employee (id, name) VALUES (2, 'employee2');
INSERT INTO employer_employee_join (employer_id, employee_id) VALUES(1, 1);
INSERT INTO employer_employee_join (employer_id, employee_id) VALUES(1, 2);
My sql query returns employer and aggregates employee returning an array of records (record[]).
SELECT
employer.id, employer.name, array_agg((employee.id, employee.name))
FROM employer
LEFT JOIN employer_employee_join
ON employer_employee_join.employer_id = employer.id
LEFT JOIN employee
ON employee.id = employer_employee_join.employee_id
GROUP BY employer.id;
This works fine.
But when I put it in a PL/PGSQL function it fails:
CREATE OR REPLACE FUNCTION _test()
RETURNS table(id integer, name text, agg record[])
LANGUAGE 'plpgsql'
AS $BODY$
begin
SELECT
employer.id, employer.name, array_agg((employee.id, employee.name))
FROM employer
LEFT JOIN employer_employee_join
ON employer_employee_join.employer_id = employer.id
LEFT JOIN employee
ON employee.id = employer_employee_join.employee_id
GROUP BY employer.id;
end;
$BODY$
The error is
ERROR: PL/pgSQL functions cannot accept type record[]
SQL state: 0A000
How can I get a plpgsql function to return an array of records?
(I don't really want to use json_agg() because of another layer in the system outside of postgresql and my control)
Thanks to #Bergi. It was a composite type I needed.
create type employee_agg as (id int, name text);
CREATE OR REPLACE FUNCTION _test()
RETURNS table(id integer, name text, agg employee_agg[])
LANGUAGE plpgsql
AS $BODY$
begin
return query
SELECT
employer.id,
employer.name,
array_agg(row(employee.id, employee.name)::employee_agg)
FROM employer
LEFT JOIN employer_employee_join
ON employer_employee_join.employer_id = employer.id
LEFT JOIN employee
ON employee.id = employer_employee_join.employee_id
GROUP BY employer.id;
end;
$BODY$;
Use a VIEW
Just do this...
CREATE VIEW _test
AS
SELECT
employer.id,
employer.name,
array_agg(row(employee.id, employee.name)::employee_agg)
FROM employer
LEFT JOIN employer_employee_join
ON employer_employee_join.employer_id = employer.id
LEFT JOIN employee
ON employee.id = employer_employee_join.employee_id
GROUP BY employer.id;
Your function has no arguments, it's just a query. This is a much better idea.
Alternatively, you can use a SQL function (but the view is better).

structure of query does not match function result type postgresql

I have this function which takes a Varchar as an input and it throws a table as an output.
create or replace function historial_reproductivo_vaca(hierro_v varchar) returns table(hierro_toro varchar, sexo varchar, fecha_nacimiento varchar, peso_nacimiento numeric, peso_destete numeric, clasificacion varchar, estado varchar)
as $$
declare
estado varchar;
begin
create temporary table temp_table AS
select hijos.hierro_padre as toro, hijos.sexo as s, hijos.fecha_nacimiento as nacimiento, hijos.hierro as hierro, pesos.peso_nacimiento, pesos.peso_12_meses, hijos.clasificacion FROM
((select animales.hierro, animales.sexo, animales.fecha_nacimiento, animales.hierro_madre, animales.hierro_padre, animales.clasificacion from animales)
union (select defuncion.hierro, defuncion.sexo, defuncion.fecha_nacimiento, defuncion.hierro_madre, defuncion.hierro_padre, defuncion.clasificacion from defuncion)
union (select venta_carne.hierro, venta_carne.sexo, venta_carne.fecha_nacimiento, venta_carne.hierro_madre, venta_carne.hierro_padre, venta_carne.clasificacion from venta_carne)
union (select venta_finca.hierro, venta_finca.sexo, venta_finca.fecha_nacimiento, venta_finca.hierro_madre, venta_finca.hierro_padre, venta_finca.clasificacion from venta_finca))as hijos
JOIN pesos ON pesos.hierro = hijos.hierro;
alter table temp_table add estado varchar;
--call update_temp_table(temp_table.hierro) from temp_table;
return QUERY SELECT * from temp_table;
end;
$$ language plpgsql;
But there is not the problem, the problem is when I execute Select historial_reproductivo_vaca('anything') then I get this message: structure of query does not match function result type
I wonder if anyone could help me please. Thanks guys
The error you get stems from the fact that your query returns 8 columns, but your function is defined to return 7.
Your query to create the temp table returns 7 columns and then you add another column to the table. So your select * returns 8 columns that are matched like this:
selected column (from temp table) declared output column
---------------------------------------------------------------
toro hierro_toro
s sexo
nacimiento fecha_nacimiento
hierro peso_nacimiento
peso_nacimiento peso_destete
peso_12_meses clasificacion
clasificacion estado
estado (added column) ?????
Given the result column names and the select column names, it seems you simply forgot to add the (selected) column hierro to the result columns of the function.

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$;

how to extecute multiple select queries with different returning data in postgresql?

I'm using postgreql and i have two tables and i want to execute two select queries on them. the data that returning from each select are varying!
the data that returns from first table is:
id integer, first_name varchar, last_name varchar, email varchar, company varchar,positions varchar,address varchar,phone varchar
and the second table return:
group_contact_id integer, contact_id integer, group_id integer
i want to do it in a function like this:
create function findcontactbyid(id integer) returns table (id integer, first_name varchar, last_name varchar, email varchar, company varchar,positions varchar,address varchar,phone varchar, group_contact_id integer, contact_id integer, group_id integer) as $$
select * from cms_contact where id = $1
UNION ALL
select * from cms_groups_contacts where contact_id = $1
$$ language 'sql'
but i get errors
$1 mentions to (id integer) and it exists in both tables
I rather wonder about your function! What do you really need here.
Comeback to the issues:
I think it will raise an error because PostgreSQL have an ambiguous between id (INPUT), id (RETURN) and id of tables in query. So, if you can, you can named it different to each other and then check it again.
The second think, I have some worry about the UNION QUERY. In my knowledge, when you want to UNION 2 query to each other, the result of both query must be the same column and the same type of column.
So, I think after you fix the first issue, the function is also can not run. But you can try first.

PostgreSQL: subquery must return one column

Looking obvious error, still see no chance to find it. I've made to localize error in this function:
CREATE OR REPLACE FUNCTION findRecipientsByQuestion(questionId BIGINT)
RETURNS SETOF BIGINT AS $$
DECLARE
question questionObject;
BEGIN
question := (
SELECT "a"."id", "a"."body", "a"."author_id", "a"."category_id", "a"."urgent", "a"."created_at", "a"."locale_id", "a"."lat", "a"."lng", "a"."radius"
FROM "question" "a"
WHERE "a"."id"=questionId
LIMIT 1
);
RETURN QUERY SELECT "a"."id"
FROM "user" "a" INNER JOIN "notifications" "b" ON ("a"."id"="b"."user_id")
WHERE ("b"."category_id"=question.category_id OR "b"."urgent") AND
isGeoMatch("a"."lat", "a"."lng", "a"."radius", question.lat, question.lng, question.radius);
END
$$LANGUAGE plpgsql;
Which uses this type:
CREATE TYPE questionObject AS (
id BIGINT,
body VARCHAR,
author_id BIGINT,
category_id BIGINT,
urgent BOOLEAN,
created_at TIMESTAMP,
locale_id BIGINT,
lat DOUBLE PRECISION,
lng DOUBLE PRECISION,
radius INTEGER
);
And I'm getting this error in runtime:
Error: subquery must return only one column
I would just get rid off all the complexity and make it plain sql:
create or replace function findrecipientsbyquestion (
_questionid bigint
) returns setof bigint as $$
select a.id
from
user a
inner join
notifications b on a.id = b.user_id
inner join (
select categoty_id, lat, lng, radius
from question
where id = _questionid
limit 1
) q on q.category_id = b.category_id or b.urgent
where isgeomatch(a.lat, a.lng, a.radius, q.lat, q.lng, q.radius);
$$ language sql;
with my type
CREATE TYPE map.get_near_link AS
(link_id integer,
distance integer,
direction integer,
geom public.geometry(4));
I do
sRow map.get_near_link;
SELECT i.Link_ID, i.Distance, i.Direction, i.geom into sRow
FROM
index_query i;