Select [COLUMN_NAME] AS, self referencing table - postgresql

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;

Related

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

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;

Function in PostgreSQL to insert from one table to another?

I have the following:
I got the tables:
Equipos (Teams)
Partidos (Matches)
The columns num_eqpo_loc & num_eqpo_vis from the table partidos reference to the table equipo. They reference to the num_eqpo column.
As you can see here:
create table equipos
(num_eqpo serial,
ciudad varchar (30),
num_gpo int,
nom_equipo varchar (30),
primary key (num_eqpo),
foreign key (num_gpo) references grupos (num_gpo))
create table partidos
(semana int,
num_eqpo_loc int,
num_eqpo_vis int,
goles_loc int,
goles_vis int, primary key (semana,num_eqpo_loc,num_eqpo_vis),
foreign key (num_eqpo_loc) references equipos (num_eqpo),
foreign key (num_eqpo_vis) references equipos (num_eqpo))
I want to get the following output:
In one hand, I created a table called general:
CREATE TABLE general
(
equipo character varying(30) NOT NULL,
partidos_jug integer,
partidos_gana integer,
partidos_emp integer,
partidos_perd integer,
puntos integer,
goles_favor integer,
CONSTRAINT general_pkey PRIMARY KEY (equipo)
)
In the other, I have the function:
CREATE OR REPLACE FUNCTION sp_tablageneral () RETURNS TABLE (
equipo character varying(30)
, partidos_jug int
, partidos_gana int
, partidos_emp int
, partidos_perd int
, puntos int
, goles_favor int) AS
$BODY$
DECLARE cont int:= (SELECT count(num_eqpo)FROM equipos);
r partidos%ROWTYPE;
BEGIN
while cont>0
LOOP
SELECT INTO equipo nom_equipo FROM equipos AS E WHERE E.num_eqpo=cont;
SELECT INTO partidos_jug COUNT(*) FROM partidos as P WHERE (P.num_eqpo_loc=cont OR P.num_eqpo_vis=cont);
SELECT INTO partidos_gana COUNT(*) FROM partidos AS P WHERE (P.num_eqpo_loc=cont AND P.goles_loc>P.goles_vis OR P.num_eqpo_vis=cont AND P.goles_vis>P.goles_loc);
SELECT INTO partidos_emp COUNT(*) FROM partidos AS P WHERE (P.num_eqpo_loc=cont AND P.goles_loc=P.goles_vis OR P.num_eqpo_vis=cont AND P.goles_loc=P.goles_vis);
SELECT INTO partidos_perd COUNT(*) FROM partidos as P WHERE ( (P.num_eqpo_loc=cont AND P.goles_loc<P.goles_vis) OR (P.num_eqpo_vis=cont AND P.goles_loc>P.goles_vis));
SELECT INTO puntos partidos_emp*1 + partidos_gana*3;
SELECT INTO goles_favor SUM(goles_loc) FROM partidos as P WHERE P.num_eqpo_loc=cont + (SELECT SUM(goles_vis) FROM partidos as P WHERE P.num_eqpo_vis=cont);
cont:= cont - 1;
END LOOP;
RETURN NEXT ;
END;
$BODY$ LANGUAGE plpgsql STABLE;
I want the function to show my desired Output & I also want the table 'General' to have the same values from the desired output.
With this function I just get:
I don't know how to see the desired content as I just get the first row of data.
I also wonder how to Insert from the table returned by the fuction to the existing table called General.
Edit: I have also tried with:
CREATE OR REPLACE FUNCTION sp_tablageneral () RETURNS TABLE (
equipo character varying(30)
, partidos_jug int
, partidos_gana int
, partidos_emp int
, partidos_perd int
, puntos int
, goles_favor int) AS
$BODY$
DECLARE cont int:= (SELECT count(num_eqpo)FROM equipos);
r partidos%ROWTYPE;
BEGIN
while cont>0
LOOP
SELECT INTO equipo nom_equipo FROM equipos AS E WHERE E.num_eqpo=cont;
SELECT INTO partidos_jug COUNT(*) FROM partidos as P WHERE (P.num_eqpo_loc=cont OR P.num_eqpo_vis=cont);
SELECT INTO partidos_gana COUNT(*) FROM partidos AS P WHERE (P.num_eqpo_loc=cont AND P.goles_loc>P.goles_vis OR P.num_eqpo_vis=cont AND P.goles_vis>P.goles_loc);
SELECT INTO partidos_emp COUNT(*) FROM partidos AS P WHERE (P.num_eqpo_loc=cont AND P.goles_loc=P.goles_vis OR P.num_eqpo_vis=cont AND P.goles_loc=P.goles_vis);
SELECT INTO partidos_perd COUNT(*) FROM partidos as P WHERE ( (P.num_eqpo_loc=cont AND P.goles_loc<P.goles_vis) OR (P.num_eqpo_vis=cont AND P.goles_loc>P.goles_vis));
SELECT INTO puntos partidos_emp*1 + partidos_gana*3;
SELECT INTO goles_favor SUM(goles_loc) FROM partidos as P WHERE P.num_eqpo_loc=cont + (SELECT SUM(goles_vis) FROM partidos as P WHERE P.num_eqpo_vis=cont);
SELECT equipo, partidos_jug , partidos_gana, partidos_emp , partidos_perd , puntos , goles_favor INTO equipo,partidos_jug,partidos_gana,partidos_emp,partidos_perd,puntos,goles_favor FROM general;
cont:= cont - 1;
END LOOP;
RETURN NEXT ;
END;
$BODY$ LANGUAGE plpgsql STABLE;
But I get:
ERROR: the reference to the column "equipo" is ambiguous
LINE 1: SELECT equipo , partidos_jug, partidos_gana, partidos_emp ...
            ^
********** Error **********
ERROR: the reference to the column "equipo" is ambiguous
SQL state: 42702
Detail: It could refer either to a variable PL / pgSQL as a column in a table.
Context: PL / pgSQL sp_tablageneral () function on line 17 in SQL statement
Any help would be amazing.
Thanks in advance!
You can solve this issue in pure SQL, you don't need a function for this.
The best thing is to break the collection of statistics into two distinct queries, one for when the team plays at home, one when they play away. For each game calculate the points and the goals scored. Then UNION those two queries and use that as a sub-query to calculate the overall stats:
SELECT
eq.nom_equipo AS equipo,
COUNT(p.*) AS partidos_jug,
SUM(CASE WHEN p.puntos = 3 THEN 1 ELSE 0 END) partidos_gana,
SUM(CASE WHEN p.puntos = 1 THEN 1 ELSE 0 END) partidos_emp,
SUM(CASE WHEN p.puntos = 0 THEN 1 ELSE 0 END) partidos_perd,
SUM(p.puntos) AS puntos,
SUM(p.goles) AS goles_favor
FROM equipos eq
JOIN (
-- Playing at home
SELECT
num_eqpo_loc AS eqpo,
CASE WHEN (goles_loc > goles_vis) THEN 3
WHEN (goles_loc = goles_vis) THEN 1
ELSE 0
END AS puntos,
goles_loc AS goles
FROM partidos
UNION
-- Playing away
SELECT
num_eqpo_vis AS eqpo,
CASE WHEN (goles_vis > goles_loc) THEN 3
WHEN (goles_vis = goles_loc) THEN 1
ELSE 0
END AS puntos,
goles_vis AS goles
FROM partidos) AS p ON p.eqpo = eq.num_eqpo
GROUP BY equipo
ORDER BY puntos DESC, partidos_jug ASC, goles_favor DESC;
This is not particularly fast due to the CASE statements, but it will be faster than using a procedure and a loop.
Instead of putting the result of this query into a table, I would suggest that you CREATE VIEW general AS ... with the above query. In that case you always get the latest results when you SELECT * FROM general and you don't have to TRUNCATE the general table before running the query (adding new results with data in the table will violate the PK constraint). If you really need the table then use SELECT ... INTO general FROM ... in the query above.

Syntax error on postgresql function. impossible syntax

I try to correct this function but is impossible!!
I declare one integer "var_id", and insert in id_val the value of frist query,
if is null the tag name is inserted in a table and var_id = last insert id, otherwise do the last insert...
CREATE OR REPLACE FUNCTION public."InsertVideoTag"
(
IN in_idvideo integer,
IN in_tagname VARCHAR(25)
)
RETURNS bigint AS
$$
DECLARE var_id bigint DEFAULT NULL;
SELECT var_id := IDTag FROM Tag WHERE TagName = in_tagname;
IF var_id IS NULL
THEN
INSERT INTO tag ( TagName )
VALUES( in_tagname );
var_id := SELECT CURRVAL(pg_get_serial_sequence('public.tag','idtag'));
END IF;
INSERT INTO video_has_tag
(
IDVideo,
IDTag
)
VALUES
(
in_idvideo,
var_id
);
SELECT CURRVAL(pg_get_serial_sequence('public.video','idvideo'));
$$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER;
The function can be converted into a pure SQL one, which will make it much better performing one. Also I've noted, that the current functionality will create duplicate entries in the video_has_tag table if called multiple times with the same arguments.
I've changed the function to be idempotent.
First table structure that I've assumed:
CREATE TABLE tag (
idTag serial,
tagName text);
CREATE TABLE video_has_tag (
idVideo integer,
idTag integer);
And then the function itself:
CREATE OR REPLACE FUNCTION insVideoTag(in_idvideo integer, in_tagname text)
RETURNS integer STRICT AS $insVideoTag$
WITH
new_tag AS (
INSERT INTO tag (tagName)
SELECT $2
WHERE NOT EXISTS (SELECT 1 FROM tag WHERE tagName = $2)
RETURNING idTag, tagName
), tag_data AS (
SELECT * FROM new_tag
UNION
SELECT idTag, tagName FROM tag
WHERE tagName = $2
), new_target AS (
INSERT INTO video_has_tag(idVideo, idTag)
SELECT $1, td.idTag
FROM tag_data td
WHERE NOT EXISTS (SELECT 1 FROM video_has_tag
WHERE idVideo=$1 AND idTag=td.idTag)
RETURNING idVideo, idTag
)
SELECT idVideo FROM (
SELECT * FROM new_target
UNION
SELECT * FROM video_has_tag
WHERE idVideo=$1 AND idTag=(SELECT idTag FROM tag_data)
) s;
$insVideoTag$ LANGUAGE sql;
No need for if statements, a where clause is enough:
selecting the current value is not suitable as a return value (or to be entered into an other tables FK) : it could have been bumped after the first insert.
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE tag
( idtag SERIAL NOT NULL PRIMARY KEY
, tagname varchar
);
CREATE TABLE video_has_tag
( idvideo INTEGER NOT NULL
, idtag INTEGER NOT NULL REFERENCES tag (idtag)
);
CREATE OR REPLACE FUNCTION tmp.insertvideotag
( in_idvideo integer , in_tagname VARCHAR )
RETURNS bigint AS
$$
DECLARE var_id bigint DEFAULT NULL;
BEGIN
INSERT INTO tag (tagname )
SELECT in_tagname
WHERE NOT EXISTS (
SELECT * FROM tag
WHERE tagname = in_tagname
);
INSERT INTO video_has_tag (idvideo,idtag)
SELECT in_idvideo, tg.idtag
FROM tag tg
WHERE tg.tagname = in_tagname
RETURNING idtag
INTO var_id
;
RETURN var_id;
END;
$$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER;
SELECT insertvideotag(11, 'Eleven');
SELECT insertvideotag(12, 'Eleven');
SELECT insertvideotag(13, 'Thirteen');
SELECT tv.idvideo
,tv.idtag, tg.tagname
FROM video_has_tag tv
JOIN tag tg ON tg.idtag = tv.idtag
;
Result:
NOTICE: CREATE TABLE will create implicit sequence "tag_idtag_seq" for serial column "tag.idtag"
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "tag_pkey" for table "tag"
CREATE TABLE
CREATE TABLE
CREATE FUNCTION
insertvideotag
----------------
1
(1 row)
insertvideotag
----------------
2
(1 row)
idvideo | idtag | tagname
---------+-------+----------
11 | 1 | Eleven
12 | 1 | Eleven
13 | 2 | Thirteen
(2 rows)
CREATE OR REPLACE FUNCTION public."InsertVideoTag"
(
IN in_idvideo integer,
IN in_tagname VARCHAR(25)
)
RETURNS bigint AS
$$
DECLARE var_id bigint DEFAULT NULL;
begin
var_id := (select IDTag FROM Tag WHERE TagName = in_tagname);
IF var_id IS NULL
THEN
INSERT INTO tag ( TagName )
VALUES( in_tagname );
var_id := (SELECT CURRVAL(pg_get_serial_sequence('public.tag','idtag')));
END IF;
INSERT INTO video_has_tag
(
IDVideo,
IDTag
)
VALUES
(
in_idvideo,
var_id
);
return (SELECT CURRVAL(pg_get_serial_sequence('public.video','idvideo')));
end;
$$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER;
I've found it's much easier to work with lower case columns & table names, etc. with postgres, so I've made a few changes to your existing code :
CREATE OR REPLACE FUNCTION public."insertvideotag"
(
IN in_idvideo integer,
IN in_tagname VARCHAR(25)
)
RETURNS bigint AS
$$
DECLARE var_id bigint DEFAULT NULL;
BEGIN
--SELECT var_id := IDTag FROM Tag WHERE TagName = in_tagname;
SELECT idtag into var_id FROM tag WHERE tagname = in_tagname;
IF var_id IS NULL
THEN
INSERT INTO tag ( TagName )
VALUES( in_tagname );
--var_id := SELECT CURRVAL(pg_get_serial_sequence('public.tag','idtag'));
SELECT CURRVAL(pg_get_serial_sequence('public.tag','idtag')) into var_id;
END IF;
INSERT INTO video_has_tag
(
idvideo,
idtag
)
VALUES
(
in_idvideo,
var_id
);
SELECT CURRVAL(pg_get_serial_sequence('public.video','idvideo'));
END
$$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER;
The remaining potential issues are the sequence names, and perhaps what value you'd like to return.