Postgresql 8.4 a variable hold set of records returned from another function - postgresql

I am using postgresql 8.4 in backend
I have a postgres function say A() it can return a set of records (3 columns) like:
<A_id>::int,<A_ts_1>::timestamp,<A_ts_2>::timestamp
function A define like this(for example):
CREATE OR REPLACE FUNCTION A()
RETURNS SETOF record AS
$$
DECLARE
BEGIN
RETURN QUERY SELECT DISTINCT ON (A.id) A.id, A.ts_1, A.ts_2 FROM tablea;
END;
$$ LANGUAGE plpgsql;
SQL
function A has been called in another function B. In function B I need a variable to hold what returned from A() then do some query for example:
<variable> = select * from A();
a_id_array = ARRAY(select A_id from <variable>);
a_filtered_array = ARRAY(select A_id from <variable> where A_ts_1 ><a_timestamp> and A_ts_2 < <a_timestamp>);
So My question is what variable I should define to hold the set of records returned from A().
I tried temp table which really not good for multi-session env, it blocks data insertion. postgresql create temp table could block data insertion?
I checked doc for views seems not meet my requirements, however I may wrong so if any of you could give me an idea on how to use view in this case and use view will block data insertion as well?
Thank all!
P.S.
I think the worse case is in function B() I call function A() twice for example:
a_id_array = ARRAY(select A_id from A());
a_filtered_array = ARRAY(select A_id from A() where A_ts_1 ><a_timestamp> and A_ts_2 < <a_timestamp>);
Then my question would slightly change, can I achive this case just using one function call to A()?

PostgreSQL doesn't (yet, as of postgres 10) have table-valued variables backed by a tuplestore. So your best options are:
Return a REFCURSOR and use it from the other function. Can be clumsy to work with as you cannot reuse the resultset easily or FETCH in a subquery. It's not always easy to generate a cursor resultset either, depending on how you're creating the results.
Use temp tables with generated names so they don't collide. Lots of dynamic SQL involved here (EXECUTE format(...)) but it works.
Avoid trying to pass result sets between functions

After researching, found a way to replace using temp table and query returned set of record which is using WITH query.
SELECT c.r_ids, c.a_r_ids into a_id_array, a_filtered_array FROM(
WITH returned_r AS (SELECT * FROM a())
SELECT * from (
SELECT ARRAY( SELECT A_id from returned_r ) as r_ids ) as a
CROSS JOIN (
SELECT ARRAY(SELECT A_id FROM returned_r WHERE A_ts_1 is NOT NULL AND A_ts_2 IS NULL) as a_r_ids
) as b
) as c;

Related

PostgreSQL function returning a table without specifying each field in a table

I'm moving from SQL server to Postgresql. In SQL Server I can define table-based function as an alias for a query. Example:
Create Function Example(#limit int) As
Returns Table As Return
Select t1.*, t2.*, price * 0.5 discounted
From t1
Inner Join t2 on t1.id = t2.id
Where t1.price < #limit;
GO
Select * From Example(100);
It gives me a way to return all fields and I don't need to specify types for them. I can easily change field types of a table, add new fields, delete fields, and then re-create a function.
So the question is how to do such thing in Postgresql? I found out that Postgresql requires to explicitly specify all field names and types when writing a function. May be I need something else, not a function?
Postgres implicitly creates a type for each table. So, if you are just selecting from one table, it's easiest to use that type in your function definition:
CREATE TABLE test (id int, value int);
CREATE FUNCTION mytest(p_id int)
RETURNS test AS
$$
SELECT * FROM test WHERE id = p_id;
$$ LANGUAGE SQL;
You are then free to add, remove, or alter columns in test and your function will still return the correct columns.
EDIT:
The question was updated to use the function parameter in the limit clause and to use a more complex query. I would still recommend a similar approach, but you could use a view as #Bergi recommends:
CREATE TABLE test1 (a int, b int);
CREATE TABLE test2 (a int, c int);
CREATE VIEW test_view as SELECT a, b, c from test1 JOIN test2 USING (a);
CREATE FUNCTION mytest(p_limit int)
RETURNS SETOF test_view AS
$$
SELECT * FROM test_view FETCH FIRST p_limit ROWS ONLY
$$ LANGUAGE SQL;
You aren't going to find an exact replacement for the behavior in SQL Server, it's just not how Postgres works.
If you change the function frequently, I'd suggest to use view instead of a function. Because every time you re-create a function, it gets compiled and it's a bit expensive, otherwise you're right - Postgres requires field name and type in functions:
CREATE OR REPLACE VIEW example AS
SELECT t1.*, t2.*, price * 0.5 discounted
FROM t1 INNER JOIN t2 ON t1.id = t2.id;
then
SELECT * FROM example WHERE price < 100;
You can do something like this
CREATE OR REPLACE FUNCTION Example(_id int)
RETURNS RECORD AS
$$
DECLARE
data_record RECORD;
BEGIN
SELECT * INTO data_record FROM SomeTable WHERE id = _id;
RETURN data_record;
END;
$$
LANGUAGE 'plpgsql';

Execute select statement conditionally

I'm using PostgreSQL 9.6 and I need to create a query that performs a select depending on the logic of an if
Basically I've tried:
DO $$
BEGIN
IF exists ( SELECT 1 FROM TABLE WHERE A = B ) THEN
SELECT *
FROM A
ELSE
SELECT *
FROM B
END IF
END $$
And that returns me an error:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM
instead.
CONTEXT: PL/pgSQL function inline_code_block line 15 at SQL statement
Then I switched "SELECT" for "PERFORM", but that don't actually execute the SELECT statement for me.
I read that I need to call a void function to perform a "dynamic" query, but I couldn't make that work either. I'm new to writing queries on PostgreSQL. Is there any better way of doing that?
DO statements do not take parameters nor return anything. See:
Returning values for Stored Procedures in PostgreSQL
You may want a function instead. Create once:
CREATE FUNCTION foo()
RETURNS SETOF A -- or B, all the same
LANGUAGE plpgsql AS
$func$
BEGIN
IF EXISTS (SELECT FROM ...) THEN -- some meaningful test
RETURN QUERY
SELECT *
FROM A;
ELSE
RETURN QUERY
SELECT *
FROM B;
END IF;
END
$func$
Call:
SELECT * FROM foo();
But the function has one declared return type. So both tables A and B must share the same columns (at least columns with compatible data types in the same order; names are no problem).
The same restriction applies to a plain SQL statement. SQL is strictly typed.
Anonymous code blocks just can't return anything - you would need a function instead.
But I think you don't need pl/pgsql to do what you want. Assuming that a and b have the same count of columns and datatypes, you can use union all and not exists:
select a.* from a where exists (select 1 from mytable where ...)
union all
select b.* from b where not exists (select 1 from mytable where ...)

how to call postgresql stored procs from inside another stored proc and include return values in queries

I have a postgresql function / stored proc that does the following:
1. calls another function and saves the value into a variable.
2. executes another sql statement using the value I got from step one as an argument.
My problem is that the query is not returning any data. No errors are returned either.
I'm just new to postgresql so I don't know the best way to debug... but I added a RAISE NOTICE command right after step 1, like so:
SELECT INTO active_id get_widget_id(widget_desc);
RAISE NOTICE 'Active ID is:(%)', active_id;
In the "Messages" section of the pgadmin3 screen, I see the debug message with the data:
NOTICE: Active ID is:(2)
I'm wondering whether or not the brackets are causing the problem for me.
Here's the sql I'm trying to run in step 2:
SELECT d.id, d.contact_id, d.priority, cp.contact
FROM widget_details d, contact_profile cp, contact_type ct
WHERE d.rule_id=active_id
AND d.active_yn = 't'
AND cp.id=d.contact_id
AND cp.contact_type_id=ct.id
AND ct.name = 'email'
Order by d.priority ASC
You'll notice that in my where clause I am referencing the variable "active_id".
I know that this query should return at least one row because when i run a straight sql select (vs using this function) and substitute the value 2 for the variable "active_id", I get back the data I'm looking for.
Any suggetions would be appreciated.
Thanks.
EDIT 1:
Here's the full function definition:
CREATE TYPE custom_return_type AS (
widgetnum integer,
contactid integer,
priority integer,
contactdetails character varying
);
CREATE OR REPLACE FUNCTION test(widget_desc integer)
RETURNS SETOF custom_return_type AS
$BODY$
DECLARE
active_id integer;
rec custom_return_type ;
BEGIN
SELECT INTO active_id get_widget_id(widget_desc);
RAISE NOTICE 'Active ID is:(%)', active_id;
FOR rec IN
SELECT d.id, d.contact_id, d.priority, cp.contact
FROM widget_details d, contact_profile cp, contact_type ct
WHERE d.rule_id=active_id
AND d.active_yn = 't'
AND cp.id=d.contact_id
AND cp.contact_type_id=ct.id
AND ct.name = 'email'
Order by d.priority ASC
LOOP
RETURN NEXT rec;
END LOOP;
END
$BODY$
That's several levels of too-complicated (edit: as it turns out that Erwin already explained to you last time you posted the same thing). Start by using RETURNS TABLE and RETURN QUERY:
CREATE OR REPLACE FUNCTION test(fmfm_number integer)
RETURNS TABLE (
widgetnum integer,
contactid integer,
priority integer,
contactdetails character varying
) AS
$BODY$
BEGIN
RETURN QUERY SELECT d.id, d.contact_id, d.priority, cp.contact
FROM widget_details d, contact_profile cp, contact_type ct
WHERE d.rule_id = get_widget_id(widget_desc)
AND d.active_yn = 't'
AND cp.id=d.contact_id
AND cp.contact_type_id=ct.id
AND ct.name = 'email'
Order by d.priority ASC;
END
$BODY$ LANGUAGE plpgsql;
at which point it's probably simple enough to be turned into a trivial SQL function or even a view. Hard to be sure, since the function doesn't make tons of sense as written:
You never use the parameter fmfm_number anywhere; and
widget_desc is never defined
so this function could never run. Clearly you haven't shown us the real source code, but some kind of "simplified" code that doesn't match the code you're really having issues with.
There is a difference between:
SELECT INTO ...
[http://www.postgresql.org/docs/current/interactive/sql-selectinto.html]
and
SELECT select_expressions INTO [STRICT] target FROM ...;
[http://www.postgresql.org/docs/current/interactive/plpgsql-statements.html#PLPGSQL-STATEMENTS-SQL-ONEROW]
I think you want:
SELECT get_widget_id(widget_desc) INTO active_id;

Avoid to nest aggregation functions in PostgreSQL 8.3.4

Assuming that my subquery yields a number of rows with the columns (x,y), I would like to calculate the value avg(abs(x-mean)/y). where mean effectively is avg(x).
select avg(abs(x-avg(x))/y) as incline from subquery fails because I cannot nest aggregation functions. Nor can I think of a way to calculate the mean in a subquery while keeping the original result set. An avgdev function as it exists in other dialects would not exactly help me, so here I am stuck. Probably just due to lack of sql knowledge - calculating the value from the result set in postprocessing is easy.
Which SQL construct could help me?
Edit: Server version is 8.3.4. No window functions with WITH or OVER available here.
Not sure I understand you correctly, but you might be looking for something like this:
SELECT avg(x - mean/y)
FROM (
SELECT x,
y,
avg(x) as mean over(partition by your_grouping_column)
FROM your_table
) t
If you do not need to group your results to get the correct avg(x) then simply leave out the "partition by" using an empty over: over()
if your data sets are not too large you could accumulate them into an array and then return the incline from a function:
create type typ as (x numeric, y numeric);
create aggregate array_accum( sfunc = array_append,
basetype = anyelement,
stype = anyarray,
initcond = '{}' );
create or replace function unnest(anyarray) returns setof anyelement
language sql immutable strict as $$
select $1[i] from generate_series(array_lower($1,1), array_upper($1,1)) i;$$;
create function get_incline(typ[]) returns numeric
language sql immutable strict as $$
select avg(abs(x-(select avg(x) from unnest($1)))/y) from unnest($1);$$;
select get_incline((select array_accum((x,y)::typ) from subquery));
sample view for testing:
create view subquery as
select generate_series(1,5) as x, generate_series(1,6) as y;
One option I found is to use a temporary table:
begin;
create temporary table sub on commit drop as (...subquery code...);
select avg(abs(x-mean)/y) as incline from (SELECT x, y, (SELECT avg(x) FROM sub) AS mean FROM sub) as sub2;
commit;
But is that overkill?

Sending one record from cursor to another function Postgres

FYI: I am completely new to using cursors...
So I have one function that is a cursor:
CREATE FUNCTION get_all_product_promos(refcursor, cursor_object_id integer) RETURNS refcursor AS '
BEGIN
OPEN $1 FOR SELECT *
FROM promos prom1
JOIN promo_objects ON (prom1.promo_id = promo_objects.promotion_id)
WHERE prom1.active = true AND now() BETWEEN prom1.start_date AND prom1.end_date
AND promo_objects.object_id = cursor_object_id
UNION
SELECT prom2.promo_id
FROM promos prom2
JOIN promo_buy_objects ON (prom2.promo_id =
promo_buy_objects.promo_id)
LEFT JOIN promo_get_objects ON prom2.promo_id = promo_get_objects.promo_id
WHERE (prom2.buy_quantity IS NOT NULL OR prom2.buy_quantity > 0) AND
prom2.active = true AND now() BETWEEN prom2.start_date AND
prom2.end_date AND promo_buy_objects.object_id = cursor_object_id;
RETURN $1;
END;
' LANGUAGE plpgsql;
SO then in another function I call it and need to process it:
...
--Get the promotions from the cursor
SELECT get_all_product_promos('promo_cursor', this_object_id)
updated := FALSE;
IF FOUND THEN
--Then loop through your results
LOOP
FETCH promo_cursor into this_promotion
--Preform comparison logic -this is necessary as this logic is used in other contexts from other functions
SELECT * INTO best_promo_results FROM get_best_product_promos(this_promotion, this_object_id, get_free_promotion, get_free_promotion_value, current_promotion_value, current_promotion);
...
SO the idea here is to select from the cursor, loop using fetch (next is assumed correct?) and put the record fetched into this_promotion. Then send the record in this_promotion to another function. I can't figure out what to declare the type of this_promotion in get_best_product_promos. Here is what I have:
CREATE OR REPLACE FUNCTION get_best_product_promos(this_promotion record, this_object_id integer, get_free_promotion integer, get_free_promotion_value numeric(10,2), current_promotion_value numeric(10,2), current_promotion integer)
RETURNS...
It tells me: ERROR: plpgsql functions cannot take type record
OK first I tried:
CREATE OR REPLACE FUNCTION get_best_product_promos(this_promotion get_all_product_promos, this_object_id integer, get_free_promotion integer, get_free_promotion_value numeric(10,2), current_promotion_value numeric(10,2), current_promotion integer)
RETURNS...
Because I saw some syntax in the Postgres docs showed a function being created w/ a input parameter that had a type 'tablename' this works, but it has to be a tablename not a function :( I know I am so close, I was told to use cursors to pass records around. So I studied up. Please help.
One possibility would be to define the query you have in get_all_product_promos as a all_product_promos view instead. Then you would automatically have the "all_product_promos%rowtype" type to pass between functions.
That is, something like:
CREATE VIEW all_product_promos AS
SELECT promo_objects.object_id, prom1.*
FROM promos prom1
JOIN promo_objects ON (prom1.promo_id = promo_objects.promotion_id)
WHERE prom1.active = true AND now() BETWEEN prom1.start_date AND prom1.end_date
UNION ALL
SELECT promo_buy_objects.object_id, prom2.*
FROM promos prom2
JOIN promo_buy_objects ON (prom2.promo_id = promo_buy_objects.promo_id)
LEFT JOIN promo_get_objects ON prom2.promo_id = promo_get_objects.promo_id
WHERE (prom2.buy_quantity IS NOT NULL OR prom2.buy_quantity > 0)
AND prom2.active = true
AND now() BETWEEN prom2.start_date AND prom2.end_date
You should be able to verify using EXPLAIN that querying SELECT * FROM all_product_promos WHERE object_id = ? takes the object_id parameter into the two subqueries rather than filtering afterwards. Then from another function you can write:
DECLARE
this_promotion all_product_promos%ROWTYPE;
BEGIN
FOR this_promotion IN
SELECT * FROM all_product_promos WHERE object_id = this_object_id
LOOP
-- deal with promotion in this_promotion
END LOOP;
END
TBH I would avoid using cursors to pass records around in PLPGSQL. In fact, I would avoid using cursors in PLPGSQL full stop- unless you need to pass a whole resultset to another function for some reason. This method of simply looping through the statement is much simpler, with the caveat that the entire resultset is materialised into memory first.
The other disadvantage with this approach is that if you need to add a column to all_product_promos, you will need to recreate all the functions that depend on it, since you can't add columns to a view with 'alter view'. AFAICT this affects named types create with CREATE TYPE too, since ALTER TYPE doesn't seem to allow you to add columns to a type either.
So, you can specify a record format using "CREATE TYPE" to pass between functions. Any relation automagically specifies a type called <relation>%ROWTYPE which you can also use.
The answer:
Select specific fields in the cursor function instead of *
then:
CREATE TYPE get_all_product_promos as (buy_quantity integer, discount_amount numeric(10,2), get_quantity integer, discount_type integer, promo_id integer);
Then I can say:
CREATE OR REPLACE FUNCTION get_best_product_promos(this_promotion get_all_product_promos, this_object_id integer, get_free_promotion integer, get_free_promotion_value numeric(10,2), current_promotion_value numeric(10,2), current_promotion integer)
RETURNS...