PostgreSQL Function: Capture exception and go next in LOOP - postgresql

I am trying to implement a function which essentially computes different aggregated values and inserts these values as a row in a new table.
Something like this:
CREATE OR REPLACE FUNCTION myfunction() RETURNS void AS
$$
DECLARE
table_name CHARACTER VARYING(100);
loop_var RECORD;
var1 INT;
var2 INT;
var3 INT;
BEGIN
FOR loop_var IN (SELECT user FROM users) LOOP
SELECT COUNT(*) INTO var1 FROM another_table WHERE user = registro.user;
SELECT COUNT(*) INTO var2 FROM another_table WHERE user = registro.user;
SELECT COUNT(*) INTO var3 FROM another_table WHERE user = registro.user;
INSERT INTO new_table VALUES(loop_var.user,var1,var2,var3);
END LOOP;
END
$$ language 'plpgsql'
;
QUESTION:
I would like to capture the exceptions when there is a problem in any of my select statements or in any of my inserts. I would like to capture the exception, skip that user (if there is any issue with it) and continue the loop.
I have tried using EXCEPTION WHEN raise_exception THEN '% : %', SQLSTATE, SQLERRM; but it did not work. Also, if I use RAISE EXCEPTION, the loop does not continue.
Could you please help me on the sintaxi I should follow in order to achieve the above goals?

Related

stored procedure - pass multiple values for IN clause

I'm writing a stored procedure for Postgres, just do select * from table where id in (value1, value2, ...).
These values will be getting from the variable.
My code:
CREATE OR REPLACE PROCEDURE record_example(v_name varchar(100), v_id int)
LANGUAGE plpgsql
AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN select id, updated from mytable where names in (v_name) and id=v_id
LOOP
RAISE INFO 'id = % and updated = %', rec.id, rec.updated;
END LOOP;
END;
$$;
This actually works if I use single value for v_name.
Ex:
call record_example('myname',101);
But if I do multiple values, its not working.
HELP 1 :
call record_example('myname, your_name',101);
It just returned CALL, that's it. Nothing happened.
HELP 2:
Sometimes the v_id variable is optional, so that time the FOR loop should inclue the id=v_id
Ex:
FOR rec IN select id, updated from mytable where names in (v_name)
LOOP
RAISE INFO 'id = % and updated = %', rec.id, rec.updated;
END LOOP;

PL/PGSQL: Store the result of a loop in a table

I want to store the result of the following loop in a table:
DO $$
DECLARE rec RECORD;
BEGIN
FOR rec IN SELECT c1 FROM t1
LOOP
SELECT foo(rec.c1);
END LOOP;
END; $$
LANGUAGE 'plpgsql';
How can I do that?
You don't need a loop for that at all. In fact you don't even need a function for that:
insert into some_table (some_column)
select foo(c1)
from t1
Or if you want to create the table based on the query:
create table_some
as
select foo(c1) as some_column
from t1;

Using a row as a table in a query within a function PLpgSQL

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.

Only perform update if column exists

Is it possible to execute an update conditionally if a column exists?
For instance, I may have a column in a table and if that column exists I want that update executed, otherwise, just skip it (or catch its exception).
You can do it inside a function. If you don't want to use the function later you can just drop it afterwards.
To know if a column exists in a certain table, you can try to fetch it using a select(or a perform, if you're gonna discard the result) in information_schema.columns.
The query bellow creates a function that searches for a column bar in a table foo, and if it finds it, updates its value. Later the function is run, then droped.
create function conditional_update() returns void as
$$
begin
perform column_name from information_schema.columns where table_name= 'foo' and column_name = 'bar';
if found then
update foo set bar = 12345;
end if;
end;
$$ language plpgsql;
select conditional_update();
drop function conditional_update();
With the following table as example :
CREATE TABLE mytable (
idx INT
,idy INT
);
insert into mytable values (1,2),(3,4),(5,6);
you can create a custom function like below to update:
create or replace function fn_upd_if_col_exists(_col text,_tbl text,_val int) returns void as
$$
begin
If exists (select 1
from information_schema.columns
where table_schema='public' and table_name=''||_tbl||'' and column_name=''||_col||'' ) then
execute format('update mytable set '||_col||'='||_val||'');
raise notice 'updated';
else
raise notice 'column %s doesn''t exists on table %s',_col,_tbl;
end if;
end;
$$
language plpgsql
and you can call this function like:
select fn_upd_if_col_exists1('idz','mytable',111) -- won't update raise "NOTICE: column idz deosnt exists on table mytables"
select fn_upd_if_col_exists1('idx','mytable',111) --will upadate column idx with value 1111 "NOTICE: updated"

Postgres how to evaluate expression from query to variables in the function

I would like to be able to get values of function variables whose names are queried from a table
Edited to show querying a table instead of query from static values:
create table __test__
(
_col text
);
insert into __test__
(_col)
values('_a');
create or replace function __test()
returns void
language 'plpgsql' as
$$
declare
_r record;
_a int;
_b int;
_sql text;
begin
_a = 1;
_b = 0;
for _r in select _col as _nam from __test__ a loop
-- query returns one row valued "_a"
_sql = 'select ' || _r._nam ;
execute _sql into _b;
end loop;
raise info 'value of _b %', _b;
end;
$$;
select __test()
when function executes so that _b = 1. Is it possible?
same error ...
ERROR: column "_a" does not exist
LINE 1: select _a
^
QUERY: select _a
CONTEXT: PL/pgSQL function "__test" line 15 at EXECUTE statement
You could create a temporary table, insert your variable names and values in it, and then execute a select against that. Just clean up after. I have used approaches like that before. It works ok. It does have extra overhead though.
Edit: adding an example
CREATE FUNCTION switch (in_var text) RETURNS text
LANGUAGE PLPGSQL VOLATILE AS $$
declare t_test text;
switch_vals text[];
BEGIN
CREATE TEMPORARY TABLE switch_values (var text, value text);
EXECUTE $e$ INSERT INTO switch_values VALUES
('a', '1'), ('b', '2'), ('c', '3') $e$;
EXECUTE $e$ SELECT value FROM switch_values WHERE var = $e$ || quote_literal(in_var)
INTO t_test;
DROP TABLE switch_values;
RETURN t_test;
END; $$;
postgres=# select switch('a');
switch
--------
1
(1 row)
Let's try to reframe the question: what you're after would be the equivalent of Perl eval()
function, with its ability to execute a dynamically generated piece of code for which "any outer lexical variables are visible to it". In your example, the variable would be _a, but as you can see from the error message, it can't be interpolated by a dynamic SQL statement. The reason is that the SQL interpreter has no visibility on the current pl/pgsql variables, or even the knowledge that such variables exist. They are confined to pl/pgsql.
What would be needed here is a context-aware dynamically-generated pl/pgsql statement, but this language does not have this feature. It's doubtful that a trick could be found to achieve the result without this feature. For all its ability to interface nicely with SQL, other than that it's a fairly static language.
On the other hand, this would be no problem for pl/perl.