CREATE TABLE AS is not allowed in a non-volatile function in Postgresql - postgresql

I have wrote this method on postgresql 10 :
create or replace function get_users()
returns TABLE(user_idd uuid, device_idd text, shapee text , datee timestamp) AS
$$
begin
create temp table lines as
SELECT DISTINCT user_id, device_id from olocations;
select uuid_generate_v4(),
o1.user_id,
o1.device_id,
st_astext(ST_Collect(o1.shape)),
date(o1.creation_date_time) as date
from olocations o1
inner join lines on o1. device_id = lines.device_id and o1.user_id = lines.user_id
where o1.user_id = 'd0edfc59-9923-44c3-9c34-ef5aad3cb810'
and o1.device_id = '89984320001811791540'
group by o1.user_id, o1.device_id, date
order by date ASC ;
DROP TABLE lines;
end
$$
LANGUAGE 'plpgsql'
IMMUTABLE
SECURITY DEFINER
COST 100;
After create method without any problem, when i call my method:
select * from get_users();
I got this error:
sql> select from get_users()
[2018-09-30 17:23:23] [0A000] ERROR: CREATE TABLE AS is not allowed in a non-volatile function
[2018-09-30 17:23:23] Where: SQL statement "create temp table lines as
[2018-09-30 17:23:23] SELECT DISTINCT user_id, device_id from olocations"
[2018-09-30 17:23:23] PL/pgSQL function get_users() line 3 at SQL statement
I think i can not create table in method? right?

The function cannot be IMMUTABLE, define it as VOLATILE.
Per the documentation:
Any function with side-effects must be labeled VOLATILE, so that calls to it cannot be optimized away.
In this case this side-effect is the table creation.
Update.
Use return query to return rows generated by the query:
...
return query
select uuid_generate_v4(),
o1.user_id,
o1.device_id,
st_astext(ST_Collect(o1.shape)),
date(o1.creation_date_time) as date
...

Related

Return entire row from table and columns from other tables

I'm using postgresql 14 and trying to return an entire record from a table in addition to columns from different tables. The catch is, that I don't want to write all the titles in the record. I tried working with the guide lines here [1], but I'm getting different errors. Can anyone tell me what I'm doing wrong?
CREATE OR REPLACE FUNCTION public.get_license_by_id(license_id_ integer)
RETURNS TABLE(rec tbl_licenses, template_name character varying, company_name character varying)
LANGUAGE 'plpgsql'
COST 100
VOLATILE SECURITY DEFINER PARALLEL UNSAFE
ROWS 1000
AS $BODY$
DECLARE
BEGIN
CREATE TEMPORARY TABLE tempTable AS (
SELECT
(rec).*, B.company_name, C.template_name
-- Tried using A instead of (rec).* with error: column "a" has pseudo-type record
FROM
(
(SELECT * FROM tbl_licenses) A
LEFT JOIN
(SELECT * FROM tbl_customers) B on A.customer_id = B.customer_id
LEFT JOIN
(SELECT * FROM tbl_templates) C on A.template_id = C.template_id
)
);
UPDATE tempTable
SET license = '1'
WHERE tempTable.license IS NOT NULL;
RETURN QUERY (
SELECT * FROM tempTable
);
DROP TABLE tempTable;
RETURN;
END;
$BODY$;
I'm calling the function like SELECT rec FROM get_license_by_id(1);
but getting:
ERROR: structure of query does not match function result type
DETAIL: Returned type integer does not match expected type tbl_licenses in column 1.
You need to cast the A alias to the correct record type. However the nested derived tables are not necessary for the other tables. If you use a coalesce() for the license column in the SELECT, then you get get rid of the inefficient creation and update of the temp table as well.
CREATE OR REPLACE FUNCTION get_license_by_id(license_id_ integer)
RETURNS TABLE(rec tbl_licenses, template_name character varying, company_name character varying)
LANGUAGE sql
STABLE
AS $BODY$
SELECT a::tbl_licenses, -- this is important
B.company_name, C.template_name
FROM (
select license_id, customer_id, ... other columns ...,
coalesce(license, '1') as license -- makes the temp table unnecessary
from tbl_licenses A
) a
LEFT JOIN tbl_customers B on A.customer_id = B.customer_id
LEFT JOIN tbl_templates C on A.template_id = C.template_id
where a.license_id = license_id_;
$BODY$
;

no function matches the given name and argument types. you might need to add explicit type casts. INSERT on PREPARE

I have this PL/pgSQL function.
CREATE OR REPLACE FUNCTION add_employee_att(emp_id INT, att_time TIMESTAMP)
RETURNS void AS $$
BEGIN
IF NOT EXISTS (SELECT FROM employee_att WHERE employee_id = emp_id AND time_stamp = att_time) THEN
PREPARE prep_att (INT, TIMESTAMP)
AS INSERT INTO employee_att (employee_id, time_stamp) VALUES ($1, $2);
EXECUTE prep_att (emp_id, att_time);
END IF;
END;
$$ LANGUAGE plpgsql;
Then if i execute this:
SELECT add_employee_att(35, '2019-08-29 00:00:25'::timestamp);
I got this error:
Error in query: ERROR: function prep_att(integer, timestamp without time zone) does not exist
LINE 1: SELECT prep_att(emp_id, att_time)
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
QUERY: SELECT prep_att(emp_id, att_time)
CONTEXT: PL/pgSQL function add_employee_attendant(integer,timestamp without time zone) line 6 at EXECUTE
But if replace the PREPARE with this INSERT:
INSERT INTO employee_att (employee_id, time_stamp) VALUES (emp_id, att_time);
It works fine. Any idea what's wrong on the PREPARE stuff ?
Don't use PREPARE in plpgsql. It has not sense. Any embedded SQL is already prepared (execution plan is reused).
So write just
CREATE OR REPLACE FUNCTION add_employee_att(emp_id INT, att_time TIMESTAMP)
RETURNS void AS $$
BEGIN
IF NOT EXISTS (SELECT FROM employee_att
WHERE employee_id = emp_id
AND time_stamp = att_time)
THEN
INSERT INTO employee_att (employee_id, time_stamp) VALUES (emp_id, att_time);
END IF;
END;
$$ LANGUAGE plpgsql;
Attention: this test is example of typical code that is partially useless. It is not protection against duplicates (emp_id, att_time). Only unique index is it. You cannot to know ever, if you see last data. Every time, when you are using SQL, you are working with snapshot of database. But data inside database can be little bit different already.

Postgres: Function check duplicates in table

I have table with multiple duplicates and I want to make from these a function. Could you please help me to make a function from this code? thanks.
SELECT id_member,id_service,amount,date, count(*) as number_of_duplicates
from evidence
GROUP BY id_member,id_service,amount,date
HAVING COUNT(*) > 1;
CREATE OR REPLACE FUNCTION check_for_duplicates()
RETURNS VOID AS
$BODY$
BEGIN
SELECT id_member,id_service,amount,date, count(*) as number_of_duplicates
from evidence
GROUP BY id_member,id_service,amount,date
HAVING COUNT(*) > 1;
END;
$BODY$
LANGUAGE ‘plpgsql‘;
If a function should return a result set it needs to be declared as returns table () or returns setof
You also don't need a PL/pgSQL function for that, a simple SQL function will do and is more efficient:
CREATE OR REPLACE FUNCTION check_for_duplicates()
RETURNS table (id_member integer, id_service integer, amount numeric, date date, number_of_duplicates bigint)
$BODY$
SELECT id_member,id_service,amount,date, count(*) as number_of_duplicates
from evidence
GROUP BY id_member,id_service,amount,date
HAVING COUNT(*) > 1;
$BODY$
LANGUAGE sql;
You didn't show us the definition of the table evidence so I had to guess the data type of the columns. You will need to adjust the types in the returns table (...) part to match the types from the table.
Having said that: I would create a view for things like that, not a function.
Unrelated, but: date is a horrible name for a column. For one because it's also a keyword but more importantly it doesn't document what the column contains: a release date? An expiration date? A due date?

Execute a dynamic crosstab query

I implemented this function in my Postgres database: http://www.cureffi.org/2013/03/19/automatically-creating-pivot-table-column-names-in-postgresql/
Here's the function:
create or replace function xtab (tablename varchar, rowc varchar, colc varchar, cellc varchar, celldatatype varchar) returns varchar language plpgsql as $$
declare
dynsql1 varchar;
dynsql2 varchar;
columnlist varchar;
begin
-- 1. retrieve list of column names.
dynsql1 = 'select string_agg(distinct '||colc||'||'' '||celldatatype||''','','' order by '||colc||'||'' '||celldatatype||''') from '||tablename||';';
execute dynsql1 into columnlist;
-- 2. set up the crosstab query
dynsql2 = 'select * from crosstab (
''select '||rowc||','||colc||','||cellc||' from '||tablename||' group by 1,2 order by 1,2'',
''select distinct '||colc||' from '||tablename||' order by 1''
)
as ct (
'||rowc||' varchar,'||columnlist||'
);';
return dynsql2;
end
$$;
So now I can call the function:
select xtab('globalpayments','month','currency','(sum(total_fees)/sum(txn_amount)*100)::decimal(48,2)','text');
Which returns (because the return type of the function is varchar):
select * from crosstab (
'select month,currency,(sum(total_fees)/sum(txn_amount)*100)::decimal(48,2)
from globalpayments
group by 1,2
order by 1,2'
, 'select distinct currency
from globalpayments
order by 1'
) as ct ( month varchar,CAD text,EUR text,GBP text,USD text );
How can I get this function to not only generate the code for the dynamic crosstab, but also execute the result? I.e., the result when I manually copy/paste/execute is this. But I want it to execute without that extra step: the function shall assemble the dynamic query and execute it:
Edit 1
This function comes close, but I need it to return more than just the first column of the first record
Taken from: Are there any way to execute a query inside the string value (like eval) in PostgreSQL?
create or replace function eval( sql text ) returns text as $$
declare
as_txt text;
begin
if sql is null then return null ; end if ;
execute sql into as_txt ;
return as_txt ;
end;
$$ language plpgsql
usage: select * from eval($$select * from analytics limit 1$$)
However it just returns the first column of the first record :
eval
----
2015
when the actual result looks like this:
Year, Month, Date, TPV_USD
---- ----- ------ --------
2016, 3, 2016-03-31, 100000
What you ask for is impossible. SQL is a strictly typed language. PostgreSQL functions need to declare a return type (RETURNS ..) at the time of creation.
A limited way around this is with polymorphic functions. If you can provide the return type at the time of the function call. But that's not evident from your question.
Refactor a PL/pgSQL function to return the output of various SELECT queries
You can return a completely dynamic result with anonymous records. But then you are required to provide a column definition list with every call. And how do you know about the returned columns? Catch 22.
There are various workarounds, depending on what you need or can work with. Since all your data columns seem to share the same data type, I suggest to return an array: text[]. Or you could return a document type like hstore or json. Related:
Dynamic alternative to pivot with CASE and GROUP BY
Dynamically convert hstore keys into columns for an unknown set of keys
But it might be simpler to just use two calls: 1: Let Postgres build the query. 2: Execute and retrieve returned rows.
Selecting multiple max() values using a single SQL statement
I would not use the function from Eric Minikel as presented in your question at all. It is not safe against SQL injection by way of maliciously malformed identifiers. Use format() to build query strings unless you are running an outdated version older than Postgres 9.1.
A shorter and cleaner implementation could look like this:
CREATE OR REPLACE FUNCTION xtab(_tbl regclass, _row text, _cat text
, _expr text -- still vulnerable to SQL injection!
, _type regtype)
RETURNS text
LANGUAGE plpgsql AS
$func$
DECLARE
_cat_list text;
_col_list text;
BEGIN
-- generate categories for xtab param and col definition list
EXECUTE format(
$$SELECT string_agg(quote_literal(x.cat), '), (')
, string_agg(quote_ident (x.cat), %L)
FROM (SELECT DISTINCT %I AS cat FROM %s ORDER BY 1) x$$
, ' ' || _type || ', ', _cat, _tbl)
INTO _cat_list, _col_list;
-- generate query string
RETURN format(
'SELECT * FROM crosstab(
$q$SELECT %I, %I, %s
FROM %I
GROUP BY 1, 2 -- only works if the 3rd column is an aggregate expression
ORDER BY 1, 2$q$
, $c$VALUES (%5$s)$c$
) ct(%1$I text, %6$s %7$s)'
, _row, _cat, _expr -- expr must be an aggregate expression!
, _tbl, _cat_list, _col_list, _type);
END
$func$;
Same function call as your original version. The function crosstab() is provided by the additional module tablefunc which has to be installed. Basics:
PostgreSQL Crosstab Query
This handles column and table names safely. Note the use of object identifier types regclass and regtype. Also works for schema-qualified names.
Table name as a PostgreSQL function parameter
However, it is not completely safe while you pass a string to be executed as expression (_expr - cellc in your original query). This kind of input is inherently unsafe against SQL injection and should never be exposed to the general public.
SQL injection in Postgres functions vs prepared queries
Scans the table only once for both lists of categories and should be a bit faster.
Still can't return completely dynamic row types since that's strictly not possible.
Not quite impossible, you can still execute it (from a query execute the string and return SETOF RECORD.
Then you have to specify the return record format. The reason in this case is that the planner needs to know the return format before it can make certain decisions (materialization comes to mind).
So in this case you would EXECUTE the query, return the rows and return SETOF RECORD.
For example, we could do something like this with a wrapper function but the same logic could be folded into your function:
CREATE OR REPLACE FUNCTION crosstab_wrapper
(tablename varchar, rowc varchar, colc varchar,
cellc varchar, celldatatype varchar)
returns setof record language plpgsql as $$
DECLARE outrow record;
BEGIN
FOR outrow IN EXECUTE xtab($1, $2, $3, $4, $5)
LOOP
RETURN NEXT outrow
END LOOP;
END;
$$;
Then you supply the record structure on calling the function just like you do with crosstab.
Then when you all the query you would have to supply a record structure (as (col1 type, col2 type, etc) like you do with connectby.

How to create PL/pgSQL function returning several rows

I'm trying to create a PL/pgSQL function, which should populate a temporary table and then return all rows from it (it will be a join later), but I don't know which return type to specify for it:
create or replace function pref_daily_misere() returns void as $BODY$
begin
create temporary table temp_best (id varchar not null) on commit drop;
insert into temp_best (id) select id from pref_money where
yw = to_char(current_timestamp - interval '1 week', 'IYYY-IW')
order by money desc limit 10;
select id from temp_best;
end;
$BODY$ language plpgsql;
The statements above work on their own, but give me the error
# select pref_daily_misere();
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 "pref_daily_misere" line 7 at SQL statement
when I try to call it in my PostgreSQL 8.4.11 database.
This is probably because I have wrongly specified the returns void above, but I don't know which return type to use instead and omitting the return type is a compile error.
You want to use a setof varchar return type and then return query ... inside the function. From the fine manual:
39.6.1.2. RETURN NEXT and RETURN QUERY
RETURN NEXT expression;
RETURN QUERY query;
RETURN QUERY EXECUTE command-string [ USING expression [, ... ] ];
When a PL/pgSQL function is declared to return SETOF sometype, the procedure to follow is slightly different. In that case, the individual items to return are specified by a sequence of RETURN NEXT or RETURN QUERY commands, and then a final RETURN command with no argument is used to indicate that the function has finished executing.
I think you want something more like this:
create or replace function pref_daily_misere() returns setof varchar as $BODY$
begin
create temporary table temp_best (id varchar not null) on commit drop;
insert into temp_best (id)
select id
from pref_money
where yw = to_char(current_timestamp - interval '1 week', 'IYYY-IW')
order by money
desc limit 10;
return query select id from temp_best;
return;
end;
$BODY$ language plpgsql;
However, the temp table is pointless here:
Note: The current implementation of RETURN NEXT and RETURN QUERY stores the entire result set before returning from the function, as discussed above.
So PostgreSQL is computing the entire result set and caching it by itself. You could do this:
create or replace function pref_daily_misere() returns setof varchar as $BODY$
begin
return query
select id
from pref_money
where yw = to_char(current_timestamp - interval '1 week', 'IYYY-IW')
order by money
desc limit 10;
return;
end;
$BODY$ language plpgsql;
I'm pretty sure the temp table is going to be dropped at the end of your function anyway so you should get rid of it.