PostgreSQL Immutable function usage - postgresql

I'm trying to fasten posgresql selection using a function by means of saying it's immutable or stable, so I have a function
CREATE OR REPLACE FUNCTION get_data(uid uuid)
RETURNS integer AS $$
BEGIN
RAISE NOTICE 'UUID %', $1;
-- DO SOME STUFF
RETURN 0;
END;
$$ LANGUAGE plpgsql IMMUTABLE STRICT;
When I call it like:
SELECT get_data('3642e529-b098-4db4-b7e7-6bb62f8dcbba'::uuid)
FROM table
WHERE true LIMIT 100;
I have 100 results and only one notice raised
When I call it this way:
SELECT get_data(table.hash)
FROM table
WHERE 1 = 1 AND table.hash = '3642e529-b098-4db4-b7e7-6bb62f8dcbba' LIMIT 100;
I have 100 result and 100 notices raised
the condition (table.hash = '3642e529-b098-4db4-b7e7-6bb62f8dcbba') added to make sure that the in param is the same
table.hash is uuid type
The questions is:
So how can force PG to some how cache the result of the function? ( if it's possibe )
I want to have only one notice ( function call ) be raised in the second case...

In your first example get_data('3642e529-b098-4db4-b7e7-6bb62f8dcbba'::uuid) is a constant, independent of table rows, so it is evaluated once.
In the second example get_data(table.hash) is functionally depending on a column value, therefore it is evaluated once per row.
If you want to evaluate the function once, it cannot depend on a value from a column (when more than one row is processed).
After discussion in comments, here is an example how to call function only once per hash:
SELECT *, get_data(x.hash) AS some_data_once_per_hash
FROM (
SELECT hash, count(*) AS ct
FROM table
WHERE table.hash = '3642e529-b098-4db4-b7e7-6bb62f8dcbba'
GROUP BY 1
) x

If Erwin's answer is not good for your case you can either create a materialized view or a trigger to update a "computed column"

Related

PostgreSQL - Declaring a variable that can store multiple values

I need a way to declare a variable that can store multiple values. My first attempt was to declare a variable using the TABLE type:
DECLARE __id TABLE(results_id integer);
However this didnt go as planned, giving me type-declaration errors. My next attempt was to make an integer[] type
DECLARE __id integer[];
but it ended up giving me an error of that values needs to be inserted using curly braces whenever i attempted to insert them with a select function.
SELECT p.id FROM files.main p
WHERE p.reference = __reference
AND p.platform = __platform_id
INTO __id;
I wonder if there is any way to solve this problem?
If you have a table name t you can declare a variable of the type t
create or replace function tf1() returns int as
$BODY$
DECLARE
var public.t;
BEGIN
select * from public.t into var limit 1;
return var.id;
END;
$BODY$
LANGUAGE plpgsql ;
select * from tf1();
i need an array to use them afterwards, where every result will be run in a function and the results will be inserted into a table object of same type – Crated
While you can do this with arrays and variables, it's simpler and faster to do it in a single query using an insert into select.
INSERT INTO some_other_table (some_column)
SELECT your_function(p.id)
FROM files.main p
WHERE p.reference = __reference
AND p.platform = __platform_id
This will run each matching p.id through your_function and insert the results as some_column in some_other_table.

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

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;

Postgres - Return function in Case When Statement

Hey I want to create a random number. When this random number already exist i want to call the randomnumber function again and again. In that case i need to return the randomnumber function inside in CASE WHEN statement.
It does not work. Still get error that number already exist. I want to create a random number for unique column:
CREATE OR REPLACE FUNCTION getrandomnumber(integer, integer)
RETURNS integer AS
$BODY$
DECLARE
start_int ALIAS FOR $1;
end_int ALIAS FOR $2;
name int;
BEGIN
name = trunc(random() * (end_int-start_int) + start_int);
CASE WHEN (select count(alias) from drivers where alias = name limit 1) = 0
THEN RETURN name;
ELSE RETURN getrandomnumber(start_int, end_int);
END CASE;
END;
$BODY$
LANGUAGE plpgsql VOLATILE STRICT
COST 100;
ALTER FUNCTION getrandomnumber(integer, integer)
OWNER TO postgres;
Your function works correctly for me when run without concurrency, i.e. by one user at a time.
With:
CREATE TABLE drivers (alias integer);
INSERT INTO drivers(alias) VALUES (1),(2);
CREATE OR REPLACE FUNCTION ...;
then
INSERT INTO drivers(alias) VALUES (getrandomnumber(1, 5));
works twice, then fails with infinite recursion.
Your function will not work correctly if it is called at the same time from multiple sessions. You must LOCK TABLE drivers IN EXCLUSIVE MODE or be prepared to handle unique violation errors.
I think what you really want is something more like a "random sequence" that doesn't repeat, e.g. the pseudo-encrypt function.
BTW, while:
(select count(alias) from drivers where alias = name limit 1) = 0
will work, you should probably try:
exists (select 1 from drivers where alias = name limit 1)
as it's generally faster.

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;

Select multiple ids from a PostgreSQL sequence

Is there a concise way to select the nextval for a PostgreSQL sequence multiple times in 1 query? This would be the only value being returned.
For example, I would like to do something really short and sweet like:
SELECT NEXTVAL('mytable_seq', 3) AS id;
And get:
id
-----
118
119
120
(3 rows)
select nextval('mytable_seq') from generate_series(1,3);
generate_series is a function which returns many rows with sequential numbers, configured by it's arguments.
In above example, we don't care about the value in each row, we just use generate_series as row generator. And for each row we can call nextval. In this case it returns 3 numbers (nextvals).
You can wrap this into function, but I'm not sure if it's really sensible given how short the query is.
There is a great article about this exact problem: "getting multiple values from sequences".
If performance is not an issue, for instance when using the sequence values dwarfs the time used to get them or n is small, then the SELECT nextval('seq') FROM generate_series(1,n) approach is the simplest and most appropriate.
But when preparing data for bulk loads the last approach from the article of incrementing the sequence by n from within a lock is appropriate.
CREATE OR REPLACE FUNCTION foo() RETURNS SETOF INT AS $$
DECLARE
seqval int; x int;
BEGIN
x := 0;
WHILE x < 100 LOOP
SELECT into seqval nextval('f_id_seq');
RETURN NEXT seqval;
x := x+1;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql STRICT;
Of course, if all you're trying to do is advance the sequence, there's setval().
You could also have the function take a parameter for how many times to loop:
CREATE OR REPLACE FUNCTION foo(loopcnt int) RETURNS SETOF INT AS $$
DECLARE
seqval int;
x int;
BEGIN
x := 0;
WHILE x < loopcnt LOOP
SELECT into seqval nextval('f_id_seq');
RETURN NEXT seqval;x := x+1;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql STRICT;
Unless you really want three rows returned I would set the sequence to 'INCREMENT BY 3' for each select. Then you can simple add 1 and 2 to the result have have your three sequence numbers.
I tried to add a link to the postgresql docs, but apparenty I am not allowed to post links.
My current best solution is:
SELECT NEXTVAL('mytable_seq') AS id
UNION ALL
SELECT NEXTVAL('mytable_seq') AS id
UNION ALL
SELECT NEXTVAL('mytable_seq') AS id;
Which will correctly return 3 rows... but I would like something that is minimal SQL for even as much as 100 or more NEXTVAL invocations.