I want to look for data in two different ways in a function, and return as soon as I find a result.
First I want to run a query like;
select * from company where company.id = x
Then if that doesn't return results try a query like this
select company.*
from
company
join
company_alias on company.id = company_alias.company_id
where
company_alias.company_alias_id = x;
At the moment I'm doing this with a union all
create or replace function get_payer(x int) returns company as $$
select * from company where company.id = x
union all
select company.*
from
company
join
company_alias on company.id = company_alias.company_id
where
company_alias.company_alias_id = x;
$$ language sql stable
set search_path from current;
This doesn't seem efficient, as I'm always running two queries. But I'm not sure how to structure a condition in a plpgsql function to handle this.
I've tried variations of the following without any luck
create or replace function payment_claim_payer(x int) returns company as $$
declare found_company company;
begin
select * from company where company.id = x into found_company;
if not exists found_company then
select
company.*
from
company
join
company_alias on company.id = company_alias.company_id
where
company_alias.company_alias_id = x into found_company;
end if;
return found_company;
end;
$$ language plpgsql stable
set search_path from current;
Your last attempt was almost hit. You need a plpgsql (not sql) function and should examine the special variable found:
create or replace function payment_claim_payer(x int)
returns company language plpgsql as $$
declare found_company company;
begin
select *
from company
where company.id = x
into found_company;
if not found then
select company.*
from company
join company_alias on company.id = company_alias.company_id
where company_alias.company_alias_id = x
into found_company;
end if;
return found_company;
end;
$$;
Related
Following functions are created for doing housekeeping within the database (PostgreSQL 11.4).
entity_with_multiple_taskexec: returns a list of entities for which the housekeeping should be done.
row_id_to_delete: returns tuples of id's to delete
Just for completeness, the function which works fine:
CREATE OR REPLACE FUNCTION entity_with_multiple_taskexec()
RETURNS TABLE(entitykey varchar) AS
$func$
BEGIN
RETURN QUERY select distinct task.entitykey from
(select task.entitykey from task where dtype = 'PropagationTask' group by task.entitykey having count(*) > (select count(*) from conninstance)) more_than_one_entry
inner join task on task.entitykey = more_than_one_entry.entitykey
inner join taskexec on taskexec.task_id = task.id order by task.entitykey asc;
END
$func$ LANGUAGE plpgsql;
But which the second function, I'm not able to return a table, created from looping through the results of the entity_with_multiple_taskexec function;
CREATE OR REPLACE FUNCTION row_id_to_delete()
RETURNS TABLE(task_id varchar, taskexec_id varchar) AS
$func$
DECLARE
entityrow RECORD;
resultset RECORD;
BEGIN
FOR entityrow IN SELECT entitykey FROM entity_with_multiple_taskexec() LOOP
insert into resultset select task.id as task_id, taskexec.id as taskexec_id from task
inner join taskexec on taskexec.task_id = task.id where taskexec.entitykey = entityrow.entitykey order by taskexec.enddate desc offset 1
END LOOP;
RETURN resultset;
END
$func$ LANGUAGE plpgsql;
This breaks with the following error
ERROR: syntax error at or near "END"
LINE 12: END LOOP;
I've tried different approaches. What would be a good solution to return the table?
You don't need a loop, just join to the function as if it is a table.
There is also no need to use PL/pgSQL for this, a simple language sql function will be more efficient.
CREATE OR REPLACE FUNCTION row_id_to_delete()
RETURNS TABLE(task_id varchar, taskexec_id varchar) AS
$func$
select task.id as task_id, taskexec.id as taskexec_id
from task
join taskexec on taskexec.task_id = task.id
join entity_with_multiple_taskexec() as mt on mt.entitykey = taskexec.entitykey
order by taskexec.enddate desc
offset 1
$func$
LANGUAGE sql;
I have a postgres function that I'd like to return the result of a query, but I'd like it to return nothing if that query matches more than 1 record.
So, something like:
CREATE OR REPLACE FUNCTION myFunc(_a text, _b text)
RETURNS yy
LANGUAGE plpgsql
STABLE
PARALLEL SAFE
AS $$
BEGIN
RETURN QUERY
SELECT *
FROM yy
WHERE a = x
AND b = y;
END;
$$;
Except, it should return nothing if that query matches more than 1 record.
CREATE OR REPLACE FUNCTION myFunc(_a text, _b text)
RETURNS SETOF yy -- To be able to return "nothing"
LANGUAGE plpgsql
STABLE
PARALLEL SAFE
AS $$
DECLARE
result yy;
BEGIN
SELECT *
INTO STRICT result -- STRICT allows to check that exactly one row returned
FROM yy
WHERE a = x
AND b = y;
RETURN NEXT result; -- RETURN NEXT - return yet another row for "RETURNS SETOF" function
EXCEPTION
WHEN no_data_found OR too_many_rows THEN -- When no data or more then one rows
RETURN; -- Nothing to return, just exit
END;
$$;
i guess this can help you out.
CREATE OR REPLACE FUNCTION database.myFunction(
IN text,IN text)
RETURNS TABLE(firstField, secondField, lastField) AS
$BODY$
--sql string is the variable containing the final sql code
declare sql_string text;
declare regs numeric;
begin
--this is what happens in case count<1
sql_string = 'select 0,0,0';
--now we count them
regs = (select count(firstField) from mytable where a=b)::numeric;
--if >=1, then whe get the whole data
if (regs>=1) then
sql_string = 'select firstField,secondField, lastField from mytable where a=b';
end if;
--and return to you...
return query EXECUTE sql_string;
end;
I try take all records from two columns with different tables and Divide Between each other but i have only result from first row not from all rows How i can fix that?
CREATE FUNCTION human() RETURNS integer AS $$
DECLARE person integer;
size integer;
def integer;
Begin
Select sizex into size from region;
Select defx into def from humans;
osoba = def / size;
return person;
END;
$$ LANGUAGE 'plpgsql';
select human();
Assuming your "humans" table and "region" table both have an ID field. And also assuming your humans table has a regionId field creating a relationship between your two tables, I would suggest doing the following:
CREATE FUNCTION human(arg_humanId int) RETURNS decimal AS $$
DECLARE
div_result decimal;
Begin
SELECT h.defx/r.sizex
INTO result
FROM humans h
JOIN region r on r.ID = h.RegionID
WHERE h.ID = arg_humanId;
RETURN div_result;
END;
$$ LANGUAGE 'plpgsql';
SELECT human(h.ID)
FROM humans h;
Note I've changed the data type from integer to decimal since division usually results in decimal places.
Another option is to return a setof decimals and do all of the logic inside your function:
CREATE FUNCTION human() RETURNS setof decimal AS $$
DECLARE
div_result decimal;
Begin
RETURN QUERY
SELECT h.defx/r.sizex
FROM humans h
JOIN region r on r.ID = h.RegionID;
END;
$$ LANGUAGE 'plpgsql';
SELECT *
FROM human();
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.
Can we able to use SELECT statement within CASE conditional expression in function?
I have tried the following function to accomplish the above task.
Example: I have a function which is used to display table containing some rows.
create or replace function test(n integer)
returns table (name text,city text) as
$body$
begin
case n when 1 then
select * from table1
when 2 then
select * from table2
when 3 then
select * from view1
end;
end;
$body$
language plpgsql;
--Calling function
select * from test(1);
/*have to show details of table1*/
select * from test(2);
/*have to show details of table2*/
select * from test(3);
/*have to display details of view1*/
Actually, there is a plpgsql CASE statement, not to be confused with the SQL CASE expression:
CREATE OR REPLACE function test(n integer)
RETURNS TABLE (name text, city text) AS
$func$
BEGIN
CASE n
WHEN 1 THEN
RETURN QUERY SELECT t.name, t.city FROM table1 t;
WHEN 2 THEN
RETURN QUERY SELECT t.foo, t.bar FROM table2 t;
WHEN 3 THEN
RETURN QUERY SELECT t.bar, t.bamm FROM view1 t;
END CASE;
END
$func$ LANGUAGE plpgsql;
If you declare your function as RETURNS TABLE (name text, city text), then your SELECT statements should have a column list with matching types.
If on the other hand you want to SELECT *, declare the function as RETURNS SETOF table1 accordingly.
When naming columns in the return type, those variable are visible in the function body. Be sure to table-qualify column names that would conflict with same names. So t.name instead of just name.
Column names from queries inside the function are not visible outside. Only the declared return type. So names don't have to match, just data types.
Either way, I suggest to simplify things:
CREATE OR REPLACE function test(n integer)
RETURNS SETOF table1 AS
$func$
SELECT * FROM table1 t WHERE n = 1
UNION ALL
SELECT * FROM table2 t WHERE n = 2
UNION ALL
SELECT * FROM view1 t WHERE n = 3;
$func$ LANGUAGE sql;
Same result. Just as fast. SQL or PL/pgSQL function is a matter of taste and some other details. PL/pgSQL is probably faster for stand-alone calls. SQL can more easily be nested.
For this you need an IF statement and RETURN QUERY, e.g.
create or replace function test(n integer)
returns table (name text,city text) as
$body$
begin
IF n = 1 THEN
RETURN QUERY select * from table1;
ELIF n = 2 THEN
RETURN QUERY select * from table2;
ELIF n = 3 THEN
RETURN QUERY select * from view1;
END IF;
end;
$body$
language plpgsql;
It's a very weird thing to want to do, though.