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$;
Related
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).
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.
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;
The following is my function get_reportees performed on the self referencing table emp_tabref1
CREATE OR REPLACE FUNCTION get_reportees4(IN id integer)
RETURNS TABLE(e_id integer, e_name character varying, e_manager integer, e_man_name character varying) AS
$$
BEGIN
RETURN QUERY
WITH RECURSIVE manger_hierarchy(e_id, e_name, m_id, m_name) AS
(
SELECT e.emp_id, e.emp_name, e.mgr_id, e.emp_name AS man_name
FROM emp_tabref1 e WHERE e.emp_id = id
UNION
SELECT rp.emp_id, rp.emp_name, rp.mgr_id, rp.emp_name AS man_name
FROM manger_hierarchy mh INNER JOIN emp_tabref1 rp ON mh.e_id = rp.mgr_id
)
SELECT * from manger_hierarchy;
END;
$$ LANGUAGE plpgsql VOLATILE
Table structure of emp_tabref1:
CREATE TABLE **emp_tabref1**
(
emp_id integer NOT NULL,
emp_name character varying(50) NOT NULL,
mgr_id integer,
CONSTRAINT emp_tabref_pkey PRIMARY KEY (emp_id),
CONSTRAINT emp_tabref_mgr_id_fkey FOREIGN KEY (mgr_id)
REFERENCES emp_tabref (emp_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
What I want returned is the hierarchy (both above and below) of the id that we are passing which will have the emp_name, emp_id, mgr_id and mgr_name.
But my function is returning like this:
select * from get_reportees4(9)
e_id e_name e_manager e_man_name
1 9 "Emp9" 10 "Emp9"
2 5 "Emp5" 9 "Emp5"
3 6 "Emp6" 9 "Emp6"
where my expected output is
e_id e_name e_manager e_man_name
1 9 "Emp9" 10 "Emp10"
2 5 "Emp5" 9 "Emp9"
3 6 "Emp6" 9 "Emp9"
The function should return the manager name and not the employee name. Please help!
Found a solution! By creating a new join between the temporary manger_hierarchy table and the emp_tabref1 table using mgr_id and emp_id
CREATE OR REPLACE FUNCTION get_reportees4(IN id integer)
RETURNS TABLE(e_id integer, e_name character varying, e_manager integer, e_man_name character varying) AS
$$
BEGIN
RETURN QUERY
WITH RECURSIVE manger_hierarchy(e_id, e_name, m_id, m_name) AS
(
SELECT e.emp_id, e.emp_name, e.mgr_id, e.emp_name AS man_name
FROM emp_tabref1 e WHERE e.emp_id = id
UNION
SELECT rp.emp_id, rp.emp_name, rp.mgr_id, rp.emp_name AS man_name
FROM manger_hierarchy mh INNER JOIN emp_tabref1 rp ON mh.e_id = rp.mgr_id
)
SELECT manger_hierarchy.e_id, manger_hierarchy.e_name, manger_hierarchy.m_id, emp_tabref1.emp_name
FROM manger_hierarchy LEFT JOIN emp_tabref1 ON manger_hierarchy.m_id = emp_tabref1.emp_id;
END;
$$ LANGUAGE plpgsql VOLATILE
SELECT manger_hierarchy.e_id, manger_hierarchy.e_name, manger_hierarchy.m_id, emp_tabref1.emp_name
FROM manger_hierarchy LEFT JOIN emp_tabref1 ON manger_hierarchy.m_id = emp_tabref1.emp_id;
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.