Run text as a query - postgresql

I have a table with 2 columns . The first a serial and the 2nd is a query that stored as a text.
To more simple the question , at the end: I wish to create a function which I will enter the number of the serial as a parameter and the function will return me the result of the query which is found in the 2nd column.
I know I have to use the command 'execute' from other questions I saw here on stack.
Even before the end result, I made this simple function:
CREATE OR REPLACE FUNCTION public.try1()
RETURNS TABLE(datery timestamp without time zone)
LANGUAGE plpgsql
AS $function$
declare
stmt text;
BEGIN
stmt :='SELECT b FROM chks where a=4';
RETURN QUERY
execute stmt ;
END
$function$
The result of this query select b form chks where a=4 is:
'select now()::timestamp without time zone'
When I am running the function I get cast error which tells me the reutrn of th fuction is varchar and not timestamp and if I change the return type to varchar I get as a result the query itself and not the result of the query.
What am I missing here?
In any case, is there a more simple way to do this?
I am asking this both for my simple function and for the function with the parameter I have mentioned before.

Try something like this:
CREATE OR REPLACE FUNCTION public.try1(query_number numeric)
RETURNS TABLE(datery timestamp without time zone)
AS $function$
DECLARE
stmt text;
BEGIN
SELECT b INTO stmt
FROM chks
WHERE a=query_number;
RETURN QUERY EXECUTE stmt;
END
$function$
LANGUAGE plpgsql STRICT;

Related

issue with create function in PostgreSQL

I am trying to get year from orderdate function
type
orderdate date
create or replace function getyearfromdate(year date)returns
table
as
$$
begin
return QUERY execute (
'select extract (year from orderdate) FROM public.orderalbum'
);
end;
$$
language plpgsql;
I write a logic but not able to create a function
I want to return year from the orderdate.
I want to pass a orderdate and return year from the function
I am facing below error
ERROR: syntax error at or near "as"
LINE 3: as
^
SQL state: 42601
Character: 70
Based on your comments, it seems you only want a wrapper around the extract() function. In that case you do not want a set returning function. And you don't need PL/pgSQL or even dynamic SQL for this:
create or replace function getyearfromdate(p_date_value date)
returns int --<< make this a scalar function!
as
$$
select extract(year from p_date_value)::int;
$$
language sql;
Note that I renamed your parameter as I find a parameter named year for a date value highly confusing.
That function can then be used as part of a SELECT list:
SELECT ..., getyearfromdate(orderdate)
FROM public.orderalbum
GROUP BY ...
Original answer based on the question before comments clarified it.
As documented in the manual returns table requires a table definition.
Your use of dynamic SQL is also useless.
create or replace function getyearfromdate(year date)
returns table (year_of_month int)
as
$$
begin
return QUERY
select extract(year from orderdate)::int
FROM public.orderalbum;
end;
$$
language plpgsql;
I am not sure why you are passing a parameter to the function that you never use.

pl/pgsql does not return all the results using record

I am trying to create a complicated pl/pgsql function that gathers some results from a query and then checks each one and returns it or not.
This is my code so far. The record and loop part confuse me.
CREATE FUNCTION __a_search_creator(creator text, ordertype integer, orderdate integer, areaid bigint) RETURNS record
AS $$
DECLARE
fromText text;
whereText text;
usingText text;
firstrecord record;
areageom geometry;
BEGIN
IF areaid IS NOT NULL
THEN
EXECUTE format('SELECT area.geom FROM area WHERE area.id=$1')
INTO areageom
USING areaid;
FOR firstrecord IN
EXECUTE format(
'SELECT place.id, person.name, place.geom
FROM '||fromText||'
WHERE '||whereText)
USING creator, ordertype , orderdate
LOOP
--return only data that the place.geom is inside areageom using PostGIS
END LOOP;
END IF;
RETURN firstrecord;
END;
$$
LANGUAGE plpgsql;
I plan to do some extra checks in the loop, as you can see and RETURN only data that the place.geom (lon/lat) is inside areageom. But since I am new to pl/pgsql, I am creating now the first step, just gathering all the data, put them in a record and return.
My problem is that, no matter what I try I keep getting only one result back. I call select __a_search_creator('johnson',8, 19911109, 20); and I get 1,"seth johnson", 65485,84545 but I know I should be getting another row of results. Is there overwrite happening?
I tried putting RETURN NEXT firstrecord; I tried something like
select place.id from place INTO placeid;
select person.name from person INTO personname;
select place.geom from place INTO placegeom;
firstrecord.id := placeid;
firstrecord.name := personname;
firstrecord.geom := placegeom;
That still brings back just one result, I tried testing just this RAISE NOTICE '%', firstrecord.id; that still brings back just one full set of result.
I don't know how to proceed, please advice.
You declared your function with RETURNS record, so it returns a single record (of unknown type).
Use RETURNS SETOF record to return a set of rows. The manual:
The SETOF modifier indicates that the function will return a set of items, rather than a single item.
Better yet, use RETURNS TABLE with a proper table definition. Like you already had it in your recent questions:
Syntax error in dynamic SQL in pl/pgsql function
Using text in pl/pgsql brings empty set of results
Related (with detailed explanation and links to the manual):
How to return result of a SELECT inside a function in PostgreSQL?
Return setof record (virtual table) from function
Return a query from a function?
This is my solution on the problem
IF areaid IS NOT NULL
THEN
EXECUTE format('SELECT area.geom FROM area WHERE area.id=$1')
INTO areageom
USING areaid;
FOR firstrecord IN
EXECUTE format(
'SELECT place.id, person.name, place.geom FROM '||fromText||' WHERE '||whereText)
USING creator, ordertype, orderdate
LOOP
IF ST_Within(firstrecord.geom , areageom)
THEN
RETURN QUERY VALUES(firstrecord.id, firstrecord.name, firstrecord.geom);
END IF;
END LOOP;
END IF;
RETURN;
Thanks to #Erwin Brandstetter for the useful links.

How to return multiple rows from PL/pgSQL function?

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()

Create a function to get column from multiple tables in PostgreSQL

I'm trying to create a function to get a field value from multiple tables in my database. I made script like this:
CREATE OR REPLACE FUNCTION get_all_changes() RETURNS SETOF RECORD AS
$$
DECLARE
tblname VARCHAR;
tblrow RECORD;
row RECORD;
BEGIN
FOR tblrow IN SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname='public' LOOP /*FOREACH tblname IN ARRAY $1 LOOP*/
RAISE NOTICE 'r: %', tblrow.tablename;
FOR row IN SELECT MAX("lastUpdate") FROM tblrow.tablename LOOP
RETURN NEXT row;
END LOOP;
END LOOP;
END
$$
LANGUAGE 'plpgsql' ;
SELECT get_all_changes();
But it is not working, everytime it shows this error
tblrow.tablename" not defined in line "FOR row IN SELECT MAX("lastUpdate") FROM tblrow.tablename LOOP"
Your inner FOR loop must use the FOR...EXECUTE syntax as shown in the manual:
FOR target IN EXECUTE text_expression [ USING expression [, ... ] ] LOOP
statements
END LOOP [ label ];
In your case something along this line:
FOR row IN EXECUTE 'SELECT MAX("lastUpdate") FROM ' || quote_ident(tblrow.tablename) LOOP
RETURN NEXT row;
END LOOP
The reason for this is explained in the manual somewhere else:
Oftentimes you will want to generate dynamic commands inside your PL/pgSQL functions, that is, commands that will involve different tables or different data types each time they are executed. PL/pgSQL's normal attempts to cache plans for commands (as discussed in Section 39.10.2) will not work in such scenarios. To handle this sort of problem, the EXECUTE statement is provided[...]
Answer to your new question (mislabeled as answer):
This can be much simpler. You do not need to create a table just do define a record type.
If at all, you would better create a type with CREATE TYPE, but that's only efficient if you need the type in multiple places. For just a single function, you can use RETURNS TABLE instead :
CREATE OR REPLACE FUNCTION get_all_changes(text[])
RETURNS TABLE (tablename text
,"lastUpdate" timestamp with time zone
,nums integer) AS
$func$
DECLARE
tblname text;
BEGIN
FOREACH tblname IN ARRAY $1 LOOP
RETURN QUERY EXECUTE format(
$f$SELECT '%I', MAX("lastUpdate"), COUNT(*)::int FROM %1$I
$f$, tblname)
END LOOP;
END
$func$ LANGUAGE plpgsql;
A couple more points:
Use RETURN QUERY EXECUTE instead of the nested loop. Much simpler and faster.
Column aliases would only serve as documentation, those names are discarded in favor of the names declared in the RETURNS clause (directly or indirectly).
Use format() with %I to replace the concatenation with quote_ident() and %1$I to refer to the same parameter another time.
count() usually returns type bigint. Cast the integer, since you defined the column in the return type as such: count(*)::int.
Thanks,
I finally made my script like:
CREATE TABLE IF NOT EXISTS __rsdb_changes (tablename text,"lastUpdate" timestamp with time zone, nums bigint);
CREATE OR REPLACE FUNCTION get_all_changes(varchar[]) RETURNS SETOF __rsdb_changes AS /*TABLE (tablename varchar(40),"lastUpdate" timestamp with time zone, nums integer)*/
$$
DECLARE
tblname VARCHAR;
tblrow RECORD;
row RECORD;
BEGIN
FOREACH tblname IN ARRAY $1 LOOP
/*RAISE NOTICE 'r: %', tblrow.tablename;*/
FOR row IN EXECUTE 'SELECT CONCAT('''|| quote_ident(tblname) ||''') AS tablename, MAX("lastUpdate") AS "lastUpdate",COUNT(*) AS nums FROM ' || quote_ident(tblname) LOOP
/*RAISE NOTICE 'row.tablename: %',row.tablename;*/
/*RAISE NOTICE 'row.lastUpdate: %',row."lastUpdate";*/
/*RAISE NOTICE 'row.nums: %',row.nums;*/
RETURN NEXT row;
END LOOP;
END LOOP;
RETURN;
END
$$
LANGUAGE 'plpgsql' ;
Well, it works. But it seems I can only create a table to define the return structure instead of just RETURNS SETOF RECORD. Am I right?
Thanks again.

I want to have my pl/pgsql script output to the screen

I have the following script that I want output to the screen from.
CREATE OR REPLACE FUNCTION randomnametest() RETURNS integer AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT * FROM my_table LOOP
SELECT levenshtein('mystring',lower('rec.Name')) ORDER BY levenshtein;
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
I want to get the output of the levenshein() function in a table along with the rec.Name. How would I do that? Also, it is giving me an error about the line where I call levenshtein(), saying that I should use perform instead.
Assuming that you want to insert the function's return value and the rec.name into a different table. Here is what you can do (create the table new_tab first)-
SELECT levenshtein('mystring',lower(rec.Name)) AS L_val;
INSERT INTO new_tab (L_val, rec.name);
The usage above is demonstrated below.
I guess, you can use RAISE INFO 'This is %', rec.name; to view the values.
CREATE OR REPLACE FUNCTION randomnametest() RETURNS integer AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT * FROM my_table LOOP
SELECT levenshtein('mystring',lower(rec.Name))
AS L_val;
RAISE INFO '% - %', L_val, rec.name;
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
Note- the FROM clause is optional in case you select from a function in a select like netxval(sequence_name) and don't have any actual table to select from i.e. like SELECT nextval(sequence_name) AS next_value;, in Oracle terms it would be SELECT sequence_name.nextval FROM dual; or SELECT function() FROM dual;. There is no dual in postgreSQL.
I also think that the ORDER BY is not necessary since my assumption would be that your function levenshtein() will most likely return only one value at any point of time, and hence wouldn't have enough data to ORDER.
If you want the output from a plpgsql function like the title says:
CREATE OR REPLACE FUNCTION randomnametest(_mystring text)
RETURNS TABLE (l_dist int, name text) AS
$BODY$
BEGIN
RETURN QUERY
SELECT levenshtein(_mystring, lower(t.name)), t.name
FROM my_table t
ORDER BY 1;
END;
$$ LANGUAGE plpgsql;
Declare the table with RETURNS TABLE.
Use RETURN QUERY to return records from the function.
Avoid naming conflicts between column names and OUT parameters (from the RETURNS TABLE clause) by table-qualifying column names in queries. OUT parameters are visible everywhere in the function body.
I made the string to compare to a parameter to the function to make this more useful.
There are other ways, but this is the most effective for the task. You need PostgreSQL 8.4 or later.
For a one-time use I would consider to just use a plain query (= function body without the RETURN QUERY above).