I am having trouble debugging some syntax issues with my postgresql function. The error doesn't indicate a line that the issue is on and I am too new to Postgres to recognize what's wrong. I am using SQLFiddle for testing. SQLFiddle link is http://sqlfiddle.com/#!15/266ef
Function I am using
CREATE OR REPLACE FUNCTION updateSalary() RETURNS VOID AS
$BODY$
DECLARE
sum INTEGER := 0;
dep CURSOR FOR SELECT Dno FROM Department;
dep_row Department%ROWTYPE;
emp CURSOR(dept_Dno) CURSOR FOR
SELECT Dno, Salary FROM Employee WHERE Dno = dept_Dno;
emp_row Employee%ROWTYPE;
BEGIN
open dep;
LOOP
FETCH dep into dep_row;
exit when NOT FOUND;
open emp(dep_row.Dno);
LOOP
FETCH emp into emp_row;
exit when NOT FOUND;
SET sum := sum + emp_row.salary;
END LOOP;
UPDATE department SET total_sal = sum WHERE department.dno = emp_row.dno;
close emp;
SET sum := 0;
END LOOP;
close dep;
END;
$BODY$
LANGUAGE plpgsql;
Error I am receiving
Schema Creation Failed: ERROR: missing data type declaration at or near ")":
Cursor argument must have a type declared (and remove second word 'cursor'):
...
emp CURSOR(dept_Dno integer) FOR
SELECT Dno, Salary FROM Employee WHERE Dno = dept_Dno;
...
Assignments without keyword 'set':
sum := sum + emp_row.salary;
Related
I am trying to write a plpgsql function that loops through a table. On each loop, it pulls a row from the table, stores it in a record, then uses that record in the join clause of a query. Here is my code:
CREATE OR REPLACE FUNCTION "testfncjh2" () RETURNS int
IMMUTABLE
SECURITY DEFINER
AS $dbvis$
DECLARE
counter int;
tablesize int;
rec1 record;
tablename text;
rec2 record;
BEGIN
counter = 0;
for rec1 in SELECT * FROM poilocations_sridconv loop
raise notice 'here';
execute $$ select count(*) from $$||rec1||$$ $$ into tablesize;
while counter < tablesize loop
counter = counter + 1;
raise notice 'hi';
execute $$ select count(*) from cities_sridconv $$ into tablesize;
end loop;
end loop;
return counter;
END;
$dbvis$ LANGUAGE plpgsql;
Each time I run this, I get the following error:
ERROR: could not find array type for data type record
Is there a way to use the row as a table in the query within the nested loops?
My end goal is to build a function that loops through a table, pulling a row from that table on each loop. In each loop, a number COUNTER is computed using the row, then a query is executed depending on the row and COUNTER. Knowing that this code is currently very flawed, I am posting it below to give an idea of what I am trying to do:
CREATE OR REPLACE FUNCTION "testfncjh" () RETURNS void
IMMUTABLE
SECURITY DEFINER
AS $dbvis$
DECLARE
counter int;
tablesize int;
rec1 record;
tablename text;
rec2 record;
BEGIN
for rec1 in SELECT * FROM poilocations_sridconv loop
counter = 0;
execute $$ select count(*)
from $$||rec1||$$ a
join
cities_srid_conv b
on right(a.geom_wgs_pois,$$||counter||$$) = right(b.geom_wgs_pois,$$||counter||$$) $$ into tablesize;
raise notice 'got through first execute';
while tablesize = 0 loop
counter = counter + 1;
execute $$ select count(*)
from '||rec1||' a
join
cities_srid_conv b
on right(a.geom_wgs_pois,'||counter||') = right(b.geom_wgs_pois,'||counter||') $$ into tablesize;
raise notice 'hi';
end loop;
EXECUTE
'select
poiname,
name as cityname,
postgis.ST_Distance(postgis.ST_GeomFromText(''POINT(poilat poilong)''),
postgis.ST_GeomFromText(''POINT(citylat citylong)'')
) as distance
from (select a.poiname,
a.latitude::text as poilat,
a.longitude::text as poilong,
b.geonameid,
b.name,
b.latitude as citylat,
b.longitude as citylong
from '||rec1||' a
join cities_srid_conv b
on right(a.geom_wgs_pois,'||counter||') = right(b.geom_wgs_pois,'||counter||'))
) x
order by distance
limit 1'
poi_cities_match (poiname, cityname, distance); ------SQL STATEMENT TO INSERT CLOSEST CITY TO TABLE POI_CITIES_MATCH
end loop;
END;
$dbvis$ LANGUAGE plpgsql;
I am running on a PostgreSQL 8.2.15 database.
Also, sorry for reposting. I had to remove some data from the original.
I think you should be able to use composite types for what you want. I simplified your top example and used composite types in the following way.
CREATE OR REPLACE FUNCTION "testfncjh2" () RETURNS int
IMMUTABLE
SECURITY DEFINER
AS $dbvis$
DECLARE
counter int;
tablesize int;
rec1 poilocations_sridconv;
tablename text;
rec2 record;
BEGIN
counter = 0;
for rec1 in SELECT * FROM poilocations_sridconv loop
raise notice 'here';
select count(*) FROM (select (rec1).*)theRecord into counter;
end loop;
return counter;
END;
$dbvis$ LANGUAGE plpgsql;
The main changes being the rec1 poilocations_sridconv; line and using (select (rec1).*)
Hope it helps.
EDIT: I should note that the function is not doing the same thing as it does in the question above. This is just as an example of how you could use a record as a table in a query.
You have a few issues with your code (apart, perhaps, from your logic).
Foremost, you should not use a record as a table source in a JOIN. Instead, filter the second table for rows that match some field from the record.
Second, you should use the format() function instead of assembling strings with the || operator. But you can't because you are using the before-prehistoric version 8.2. This is from the cave-painting era (yes, it's that bad). UPGRADE!
Thirdly, don't over-complicate your queries. The sub-query is not necessary here.
Put together, the second dynamic query from your real code would reduce to this:
EXECUTE format(
'SELECT b.name,
postgis.ST_Distance(postgis.ST_SetSRID(postgis.ST_MakePoint(%1$I.longitude, %1$I.latitude), 4326),
postgis.ST_SetSRID(postgis.ST_MakePoint(b.longitude, b.latitude), 4326))
FROM cities_srid_conv b
WHERE right(%1$I.geom_wgs_pois, %2$L) = right(b.geom_wgs_pois, %2$L)
ORDER BY distance
LIMIT 1', rec1, counter) INTO cityname, distance;
poi_cities_match (rec1.poiname, cityname, distance); ------SQL STATEMENT TO INSERT CLOSEST CITY TO TABLE POI_CITIES_MATCH
Here %1$I refers to the first parameter after the string, which is an idenifier: rec1; %2$L is the second parameter, being a literal value: counter. I leave it to yourself to re-work this to a pre-8.4 string concatenation. The results from the query are stored in a few additional variables which you can then use in the following function call.
Lastly, you had longitude and latitude reversed. In PostGIS longitude always comes first.
i'm coding this trigger in postgreSQL
CREATE OR REPLACE FUNCTION fn_trg_viabilidad_fila()
RETURNS trigger AS
$BODY$
BEGIN
PERFORM S.*
FROM MontoMinimo M, SolicitudPresupuesto S, Cantidad C, Producto P
WHERE P.idProducto=C.idProducto
and C.idPresupuesto=S.idPresupuesto
and M.idMonto=S.idMonto;
IF (C.cantidad < P.canMinExp OR P.exportable = FALSE)
THEN
UPDATE SolicitudPresupuesto
SET viable = FALSE
WHERE idPresupuesto = OLD.idPresupuesto;
RETURN NEW;
END IF;
END
$BODY$
LANGUAGE plpgsql
CREATE TRIGGER trg_viabilidad_fila BEFORE INSERT
OR UPDATE ON SolicitudPresupuesto
FOR EACH ROW EXECUTE PROCEDURE
fn_trg_viabilidad_fila() ;
I can't solve this error..
An error has occurred: ERROR: missing FROM-clause entry for table "c"
LINE 1: SELECT C.cantidad < P.canminexp OR P.exportable = FALSE ^
QUERY: SELECT C.cantidad < P.canminexp OR P.exportable = FALSE
CONTEXT: PL/pgSQL function fn_trg_viabilidad_fila() line 9 at IF
I will be very grateful to any help. Sorry for my bad english
You can't access the columns of a query outside of the query (or the block where you use the query). You need to store the result of the select somewhere. Additionally you shouldn't run an UPDATE on the triggered table, you need to assign the value to the NEW record.
CREATE OR REPLACE FUNCTION fn_trg_viabilidad_fila()
RETURNS trigger AS
$BODY$
DECLARE
l_result boolean;
BEGIN
SELECT (c.cantidad < p.canMinExp OR p.exportable = FALSE)
INTO l_result
FROM MontoMinimo M
JOIN SolicitudPresupuesto s ON m.idMonto = s.idMonto
JOIN Cantidad c ON c.idPresupuesto = s.idPresupuesto
JOIN Producto p ON p.idProducto = c.idProducto;
IF l_result THEN
new.viable := false;
END IF;
RETURN NEW;
END
$BODY$
LANGUAGE plpgsql;
It would be possible to "inline" the query into the IF statement but this way it resembles the structure of your current code better. Also note that I replaced the old, outdated implicit joins by an explicit and more robust JOIN operator.
The assigment new.viable assumes that idpresupuesto is the PK in the table solicitudpresupuesto (because you used that in the WHERE clause of the UPDATE statement)
I have the code:
DECLARE
cliente_cursor CURSOR FOR SELECT * FROM cliente;
cliente cliente.id_clie%TYPE;
nom cliente.nom_clie%TYPE;
BEGIN
OPEN cliente_cursor;
FETCH cliente_cursor INTO cliente, nom;
But I cannot run it. The following error appears:
ERROR: syntax error at or near "cliente"
LINE 3: cliente cliente.id_clie% TYPE;
^
I have the table "cliente" which has:
I want to create a cursor that shows only the content of the columns: id_clie, nom_clie of the previous table.
I have also used:
create or replace function facturas_cliente()
returns table (a int, b character varying(40))
language plpgsql as $$
declare
x cliente%rowtype ;
cliente_cursor cursor for select id_clie, nom_clie from cliente
order by id_clie;
begin
for x in cliente_cursor loop
return next x;
end loop;
end $$;
But I get the error:
ERROR: RETURN NEXT can not have parameters in a function with OUT parameters
LINE 9: return next x;
^
What am I doing wrong?
First issue is strange. I tested on PostgreSQL 9.5 (but same code should to work on 9.2 and newer):
CREATE TABLE cliente(id_clie int, tel_clie varchar(15), dir_clie varchar(15));
DO $$
DECLARE
cliente_cursor CURSOR FOR SELECT * FROM cliente;
cliente cliente.id_clie%TYPE;
tel cliente.tel_clie%TYPE;
BEGIN
OPEN cliente_cursor;
END;
$$;
And it works without any issue. It looks like some mistyped error.
Second issue is clear. When function has a OUT variables or is declared as TABLE function, then RETURN NEXT has to be without expression. Returned composite value is based on actual context of OUT variables (columns declared in TABLE clause are OUT variables too). Your code should to be:
create or replace function facturas_cliente()
returns table (a int, b character varying(40))
language plpgsql as $$
declare
cliente_cursor cursor for select id_clie, nom_clie
from cliente
order by id_clie;
r record;
begin
for r in cliente_cursor -- a,b ~ OUT var declared in TABLE() clause
loop
a := r.id_clie; b := r.nom_clie;
return next; -- not: return next x;
end loop;
end $$;
This code can be reduced in PL/pgSQL two ways:
use a SQL function
create or replace function facturas_cliente()
returns table (a int, b character varying(40))
language sql as $$
select id_clie, nom_clie from cliente order by id_clie;
$$ language plpgsql;
use a RETURN QUERY statement in plpgsql:
create or replace function facturas_cliente()
returns table (a int, b character varying(40))
language sql as $$
BEGIN
RETURN QUERY SELECT id_clie, nom_clie
FROM cliente
ORDER BY id_clie;
RETURN;
END;
$$ language plpgsql;
Attention: these function can block a SQL optimizer if you use it in some complex query than trivial. Personally I don't like it. Use a view instead. It works like you need and there is no risk with optimization.
CREATE VIEW facturas_cliente
AS SELECT id_clie, nom_clie
FROM cliente
ORDER BY id_clie;
I'm trying to create this function with Goose using a postgres (pq lib) database.
My code is as follows:
CREATE OR REPLACE FUNCTION add_userlocation(user_id INT, location_id INT) RETURNS VOID AS
$BODY$
BEGIN
LOOP
UPDATE userslocations SET count = count+1 WHERE userid = user_id AND locationid = location_id;
IF found THEN
RETURN;
END IF;
BEGIN
INSERT INTO userslocations(userid,locationid, count) VALUES (user_id, location_id, 1);
RETURN;
EXCEPTION WHEN unique_violation THEN
END;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql;
When I try to goose up it provides an error:
(pq: unterminated dollar-quoted string at or near "$BODY$
BEGIN
LOOP
-- first try to update the key
UPDATE userslocations SET count = count+1 WHERE userid = user_id AND locationid = location_id;
"), quitting migration.
Goose basically echo's the pq library error, so I dont think it's in Goose, but rather the pq-library. Query runs succesful on pgAdmin III.
According to the goose documentation, complex statements that include semicolons must be annotated with -- +goose StatementBegin and -- +goose StatementEnd
Your statement contains semicolons embedded within it so you need to use these annotations. Otherwise goose mangles the SQL so that libpq gives errors.
I have a stored procedure in which I need to query for a set of ids and then use that set in an UPDATE statement's WHERE clause. I'm using PostgresQL 9.0+. I'd rather not use a loop over the set of ids and issue multiple UPDATE statements -- that's not very efficient.
Here's a simple example:
CREATE OR REPLACE FUNCTION test_it()
RETURNS VOID AS $$
DECLARE
cur_time TIMESTAMP;
ids a%ROWTYPE;
BEGIN
SELECT id FROM a INTO ids;
UPDATE b
SET state = 'foobar', updated_at = cur_time
WHERE id IN ids;
END;
$$ LANGUAGE plpgsql;
This doesn't even compile.
I've also tried SELECT-ing the ids like so...
CREATE OR REPLACE FUNCTION test_it()
RETURNS VOID AS $$
DECLARE
cur_time TIMESTAMP;
ids a%ROWTYPE;
BEGIN
SELECT id FROM a INTO ids;
UPDATE b
SET state = 'foobar', updated_at = cur_time
WHERE id IN (SELECT ids);
END;
$$ LANGUAGE plpgsql;
This throws an error: HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
My actual stored proc is a lot more complicated because the initial query for the set of ids is actually a dynamic query.
The actual error output is this (just more context...):
ERROR: operator does not exist: integer = task_responses
LINE 3: WHERE id IN (SELECT task_response_ids)
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
QUERY: UPDATE task_responses
SET state = state, updated_at = cur_time, rejected_at = cur_time
WHERE id IN (SELECT task_response_ids)
CONTEXT: PL/pgSQL function "reject_submissions_with_comment" line 38 at SQL statement
At some point during the execution of my SP I needed to loop over results anyway, so I opted to keep track of the IDs I was visiting them and use them later in a dynamic query to do a single update.
FWIW: I could never get #Steve's suggestion to work because task_response_ids was a result set, not a table expression. If I embedded the query that would've worked, but then I would have needed to run the same query multiple times in my use case, because I had multiple updates (different tables) to do.
Here's the (fake) code based on my needs as outlined above and in the original question:
CREATE OR REPLACE FUNCTION test_it() RETURNS VOID AS $$
DECLARE
cur_time TIMESTAMP;
state varchar(20);
a_response RECORD;
ids bigint[];
other_ids bigint[];
s_ids varchar(4000);
s_other_ids varchar(4000);
BEGIN
state := 'foobar';
cur_time := CURRENT_TIMESTAMP;
FOR a_response IN (SELECT id,other_id FROM a) LOOP
ids[id_index] := a_response.id;
other_ids[id_index] := a_response.other_id;
id_index := id_index + 1;
-- do other stuff with the current record
END LOOP;
s_ids := array_to_string(ids, ',');
s_other_ids := array_to_string(other_ids, ',');
EXECUTE '
UPDATE b
SET state = $1, updated_at = $2
WHERE id IN (' || s_ids || ')'
USING state, cur_time;
EXECUTE '
UPDATE c
SET state = $1, updated_at = $2
WHERE id IN (' || s_other_ids || ')'
USING state, cur_time;
END;
$$ LANGUAGE plpgsql;
This code is pretty fictitious, but it demonstrates the things I needed to accomplish.
You want an UPDATE ... FROM ... style query, as per http://www.postgresql.org/docs/9.1/static/sql-update.html
UPDATE b
SET state = 'foobar', updated_at = cur_time
FROM
ids i
WHERE
b.id = i.id;