If we have a tabled with an indexed fields
create table A (
...
indexed_field integer
...
);
create index on A(indexed_field);
create or replace function refer_indexed_table(...other_criterion)
returns table (
...
) as
$func$
begin
return query
select
*
from A
where other_fields match other_criterion;
end
$func$ language plpgsql;
select * from refer_indexed_table(whatever)
where refer_indexed_table.indexed_field = 54;
Would this refer to an indexed_field through the function call still profit from existing index?
No. That would require the function body to be "inlined" and currently that might happen only with language SQL functions.
Related
I'm trying to create function which will accept ARRAY as INPUT and then return SETOF RECORD for each of the parameter in ARRAY.
I have table country_regions which consists of 3 Columns: id int, region_name TEXT, country_name TEXT;
My Functions code looks like this:
CREATE OR REPLACE FUNCTION search1(TEXT[])
RETURNS SETOF RECORD AS $$
DECLARE x RECORD;
BEGIN
FOR x IN
SELECT *
FROM company_regions
WHERE country_name = $1::TEXT
LOOP
RETURN NEXT x;
END LOOP;
END; $$
LANGUAGE plpgSQL;
This Function was created successfully, but when I try to call the function like this:
SELECT * FROM search1(ARRAY ['usa', 'canada']) AS search1(id int, region_name TEXT, country_name text)
it returns table with 0 rows in it.
Can someone tell me what am I doing wrong? I'm completely new to SQL, tried to find answer in other post but I still could not figure out the problem.
You try to compare text value versus text[].
CREATE OR REPLACE FUNCTION search1(text[])
RETURNS SETOF company_regions AS $$
BEGIN
RETURN QUERY SELECT * FROM company_regions
WHERE country_name = ANY($1);
END
$$ LANGUAGE plpgsql STABLE
Attention - functions like this are black box for optimizer. Usually is not too good (from performance perspective) using functions like envelops of one SQL statement. In complex query it can block some optimizations (Mainly if you forget to set correct flag of function - in this case STABLE).
I have a PL/PgSQL function like this:
CREATE FUNCTION get_value(firstval integer) RETURNS SETOF mytable AS
$func$
DECLARE
current mytable;
BEGIN
SELECT fv FROM mytable WHERE fv = fistval INTO current;
IF current IS NULL THEN
INSERT INTO mytable(fv) VALUES (firstval);
END IF;
RETURN current.fv;
END
$func$ LANGUAGE plpgsql;
I need to return a specific row (fv) of the current variable, but this code is not working (it does not return anything), so how I have to do this?
There are lot of issues:
current is declared like record. But you try to assign integer value to this composite variable SELECT INTO.
The return from table functions should be realised by RETURN TABLE statement. There are not any table with name mytable. In Postgres concept - RETURNS SETOF mytable means - returns rows of type like table "mytable".
The test IS NULL is not safe in this case. You want to check FOUND variable.
Maybe you don't want to return table - then there is badly used SETOF clause.
CREATE OR REPLACE FUNCTION get_value(firstval int)
RETURNS mytable AS $$
DECLARE
_r mytable;
BEGIN
SELECT * FROM mytable WHERE fv = firstval INTO _r;
IF NOT FOUND THEN
INSERT INTO mytable(fv) VALUES(firstval);
SELECT * FROM mytable WHERE fv = firstval INTO _r;
END IF;
RETURN _r;
END;
$$ LANGUAGE plpgsql;
Attention! - this code maybe does what you want, but it is not safe against race conditions. Better to use Postgres statement INSERT ON CONFLICT DO.
Please, try to read documentation first - it is designed differently than you are expecting.
Based on this question I would like to know if it is possible to return different table data based on an ID passed to the function.
Something like (pseudocode):
CREATE FUNCTION schemaB.testFunc(p_id INT, select_param INT)
RETURNS setof schemaZ.Table_1
AS
$$
CASE
WHEN select_param = 1 THEN SELECT * FROM schemaZ.Table_1 WHERE id = p_id
WHEN select_param = 2 THEN SELECT * FROM schemaZ.Table_2 WHERE id = p_id
END;
$$
language sql;
Table_1 and Table_2 share no same columns and that invalidates the above RETURNS clause.
This is generally impossible with SQL functions. Even with a polymorphic return type, the actual return type must be determined at call time. But all statements in an SQL function are planned before the function is executed. So you'd always end up with an error message for one of the SELECT statements returning data that doesn't fit the return type.
The same can be done with dynamic SQL in a PL/pgSQL function - with some trickery:
CREATE OR REPLACE FUNCTION f_demo(_tabletype anyelement, _id int)
RETURNS SETOF anyelement LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE
format('SELECT * FROM %s WHERE id = $1', pg_typeof(_tabletype))
USING _id;
END
$func$;
Call (important!):
SELECT * FROM f_demo(null::schemaZ.Table_1, 1);
The "trick" is to cast a null value to the desired table type, thereby defining the return type and choosing from which table to select. Detailed explanation:
Refactor a PL/pgSQL function to return the output of various SELECT queries
Take this as proof of concept. Typically, there are better (safer, less confusing, more performant) solutions ...
Related:
Difference between language sql and language plpgsql in PostgreSQL functions
I have this question, I was doing some migration from SQL Server to PostgreSQL 12.
The scenario, I am trying to accomplish:
The function should have a RETURN Statement, be it with SETOF 'tableType' or RETURN TABLE ( some number of columns )
The body starts with a count of records, if there is no record found based on input parameters, then simply Return Zero (0), else, return the entire set of record defined in the RETURN Statement.
The Equivalent part in SQL Server or Oracle is: They can just put a SELECT Statement inside a Procedure to accomplish this. But, its a kind of difficult in case of PostgreSQL.
Any suggestion, please.
What I could accomplish still now - If no record found, it will simply return NULL, may be using PERFORM, or may be selecting NULL as column name for the returning tableType columns.
I hope I am clear !
What I want is something like -
============================================================
CREATE OR REPLACE FUNCTION public.get_some_data(
id integer)
RETURNS TABLE ( id_1 integer, name character varying )
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
p_id alias for $1;
v_cnt integer:=0;
BEGIN
SELECT COUNT(1) FROM public.exampleTable e
WHERE id::integer = e.id::integer;
IF v_cnt= 0 THEN
SELECT 0;
ELSE
SELECT
a.id, a.name
public.exampleTable a
where a.id = p_id;
END;
$BODY$;
If you just want to return a set of a single table, using returns setof some_table is indeed the easiest way. The most basic SQL function to do that would be:
create function get_data()
returns setof some_table
as
$$
select *
from some_table;
$$
language sql;
PL/pgSQL isn't really necessary to put a SELECT statement into a function, but if you need to do other things, you need to use RETURN QUERY in a PL/pgSQL function:
create function get_data()
returns setof some_table
as
$$
begin
return query
select *
from some_table;
end;
$$
language plpgsql;
A function as exactly one return type. You can't have a function that sometimes returns an integer and sometimes returns thousands of rows with a dozen columns.
The only thing you could do, if you insist on returning something is something like this:
create function get_data()
returns setof some_table
as
$$
begin
return query
select *
from some_table;
if not found then
return query
select (null::some_table).*;
end if;
end;
$$
language plpgsql;
But I would consider the above an extremely ugly and confusing (not to say stupid) solution. I certainly wouldn't let that pass through a code review.
The caller of the function can test if something was returned in the same way I implemented that ugly hack: check the found variable after using the function.
One more hack to get as close as possible to what you want. But I will repeat what others have told you: You cannot do what you want directly. Just because MS SQL Server lets you get away poor coding does not mean Postgres is obligated to do so. As the link by #a_horse_with_no_name implies converting code is easy, once you migrate how you think about the problem in the first place. The closest you can get is return a tuple with a 0 id. The following is one way.
create or replace function public.get_some_data(
p_id integer)
returns table ( id integer, name character varying )
language plpgsql
as $$
declare
v_at_least_one boolean = false;
v_exp_rec record;
begin
for v_exp_rec in
select a.id, a.name
from public.exampletable a
where a.id = p_id
union all
select 0,null
loop
if v_exp_rec.id::integer > 0
or (v_exp_rec.id::integer = 0 and not v_at_least_one)
then
id = v_exp_rec.id;
name = v_exp_rec.name;
return next;
v_at_least_one = true;
end if;
end loop ;
return;
end
$$;
But that is still just a hack and assumes there in not valid row with id=0. A much better approach would by for the calling routing to check what the function returns (it has to do that in one way or another anyway) and let the function just return the data found instead of making up data. That is that mindset shift. Doing that you can reduce this function to a simple select statement:
create or replace function public.get_some_data2(
p_id integer)
returns table ( id integer, name character varying )
language sql strict
as $$
select a.id, a.name
from public.exampletable a
where a.id = p_id;
$$;
Or one of the other solutions offered.
I have spent good amount of time trying to figure it out and I haven't been able to resolve it. So, I need your help please.
I am trying to write a PL/pgSQL function that returns multiple rows. The function I wrote is shown below. But it is not working.
CREATE OR REPLACE FUNCTION get_object_fields()
RETURNS SETOF RECORD
AS
$$
DECLARE result_record keyMetrics;
BEGIN
return QUERY SELECT department_id into result_record.visits
from fact_department_daily
where report_date='2013-06-07';
--return result_record;
END
$$ LANGUAGE plpgsql;
SELECT * FROM get_object_fields;
It is returning this error:
ERROR: RETURN cannot have a parameter in function returning set;
use RETURN NEXT at or near "QUERY"
After fixing the bugs #Pavel pointed out, also define your return type properly, or you have to provide a column definition list with every call.
This call:
SELECT * FROM get_object_fields()
... assumes that Postgres knows how to expand *. Since you are returning anonymous records, you get an exception:
ERROR: a column definition list is required for functions returning "record"
One way (of several) to fix this is with RETURNS TABLE (Postgres 8.4+):
CREATE OR REPLACE FUNCTION get_object_fields()
RETURNS TABLE (department_id int) AS
$func$
BEGIN
RETURN QUERY
SELECT department_id
FROM fact_department_daily
WHERE report_date = '2013-06-07';
END
$func$ LANGUAGE plpgsql;
Works for SQL functions just the same.
Related:
PostgreSQL: ERROR: 42601: a column definition list is required for functions returning "record"
I see more bugs:
first, a SET RETURNING FUNCTIONS call has following syntax
SELECT * FROM get_object_fields()
second - RETURN QUERY forwards query result to output directly. You cannot store this result to variable - it is not possible ever in PostgreSQL now.
BEGIN
RETURN QUERY SELECT ....; -- result is forwarded to output directly
RETURN; -- there will not be any next result, finish execution
END;
third - these simple functions is better to implement in SQL languages
CREATE OR REPLACE FUNCTION get_object_fields()
RETURNS SETOF RECORD AS $$
SELECT department_id WHERE ...
$$ LANGUAGE sql STABLE;
Here's one way
drop function if exists get_test_type();
drop type if exists test_comp;
drop type if exists test_type;
drop type if exists test_person;
create type test_type as (
foo int,
bar int
);
create type test_person as (
first_name text,
last_name text
);
create type test_comp as
(
prop_a test_type[],
prop_b test_person[]
);
create or replace function get_test_type()
returns test_comp
as $$
declare
a test_type[];
b test_person[];
x test_comp;
begin
a := array(
select row (m.message_id, m.message_id)
from message m
);
-- alternative 'strongly typed'
b := array[
row('Bob', 'Jones')::test_person,
row('Mike', 'Reid')::test_person
]::test_person[];
-- alternative 'loosely typed'
b := array[
row('Bob', 'Jones'),
row('Mike', 'Reid')
];
-- using a select
b := array (
select row ('Jake', 'Scott')
union all
select row ('Suraksha', 'Setty')
);
x := row(a, b);
return x;
end;
$$
language 'plpgsql' stable;
select * from get_test_type();
CREATE OR REPLACE FUNCTION get_object_fields()
RETURNS table (department_id integer)
AS
$$
DECLARE result_record keyMetrics;
BEGIN
return QUERY
SELECT department_id
from fact_department_daily
where report_date='2013-06-07';
--return result_record;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM get_object_fields()