PostgreSQL 9.0 issues with function - postgresql

Here is the function I am working with:
CREATE OR REPLACE FUNCTION f_multiply_row(_tbl regclass
, _idname text
, _minid int
, _maxid int)
RETURNS void AS
$func$
BEGIN
EXECUTE (
SELECT format('INSERT INTO %1$s (%2$I, %3$s)
SELECT g.g, %3$s
FROM (SELECT * FROM %1$s LIMIT 1) t
,generate_series($1, $2) g(g)'::text
, _tbl
, _idname
, string_agg(quote_ident(attname), ', ')
)
FROM pg_attribute
WHERE attrelid = _tbl
AND attname <> _idname -- exclude id column
AND NOT attisdropped -- no dropped (dead) columns
AND attnum > 0 -- no system columns
)
USING _minid, _maxid;
END
$func$ LANGUAGE plpgsql;
SELECT f_multiply_row('campaign', 'campaign_id', 50, 500);
Obviously PSQL 9.0 does NOT have the format method (introduced in 9.1), so I am trying to convert the method to work in 9.0 and am having the hardest time getting it to work.
Error : ERROR: function format(text, regclass, text, text) does not exist
LINE 2: SELECT format('INSERT INTO %1$s (%2$I, %3$s)
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
So, I attempted to rewrite it in 9.0 style:
CREATE OR REPLACE FUNCTION f_multiply_row()
RETURNS void AS
$func$
DECLARE
_tbl regclass := quote_ident('campaign');
_idname text := 'campaign_id';
_minid int := 50;
_maxid int := 500;
BEGIN
EXECUTE (
'SELECT INSERT INTO ' || _tbl || ' ($1, $2)
SELECT g.g $3 FROM (SELECT * FROM ' || _tbl || ' LIMIT 1) t, generate_series($1, $2) g(g)'
USING _tbl, _idname, string_agg(quote_ident(attname, ', '))
FROM pg_attribute
WHERE attrelid = _tbl
AND attname <> _idname -- exclude id column
AND NOT attisdropped -- no dropped (dead) columns
AND attnum > 0 -- no system columns
);
END
$func$ LANGUAGE plpgsql;
SELECT f_multiply_row();
But, the above causes these two errors:
Error : ERROR: syntax error at or near "USING"
LINE 15: USING _tbl, _idname, string_agg(quote_ident(attname, ', ')...
^
AND
Error : ERROR: function f_multiply_row() does not exist
LINE 1: SELECT f_multiply_row()
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.

Related

Postgresql : null values cannot be formatted as an SQL identifier

how to resolve this ERROR: null values cannot be formatted as an SQL identifier when trying to select my function:
select ws_sls_core.ars_pricing_test()
ERROR: null values cannot be formatted as an SQL identifier
CONTEXT: SQL statement "select string_agg(distinct format('(props ->> %L) as %I', w_order_line_d.matl_grp2_desc, w_order_line_d.matl_grp2_desc), ', ')
from ws_sls_core.w_support_pricing_d
left join ws_sls_core.w_order_line_d
on w_support_pricing_d.svc_pricing_type = w_order_line_d.matl_grp2_cd"
PL/pgSQL function ars_pricing_test() line 6 at SQL statement
I have checked and the query in use doesn't produce any NULL
select * from ws_sls_core.w_support_pricing_d where svc_pricing_type is
null
I have tried the below-mentioned code without JOIN and it works fine, I need an additional column and have to use join, and I am only seeing this error.
Here is my complete code
CREATE OR REPLACE FUNCTION ws_sls_core.ars_pricing_test(
)
RETURNS boolean
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
declare
l_sql text;
l_columns text;
begin
select string_agg(distinct format('(props ->> %L) as %I', w_order_line_d.matl_grp2_desc, w_order_line_d.matl_grp2_desc), ', ')
into l_columns
from ws_sls_core.w_support_pricing_d
left join ws_sls_core.w_order_line_d
on w_support_pricing_d.svc_pricing_type = w_order_line_d.matl_grp2_cd;
-- and A.svc_pricing_type is not null;
l_sql :=
'create or replace view ars_pricing_test as
select w_support_pricing_d.item_num, '||l_columns||'
from (
select w_support_pricing_d.item_num, json_object_agg(w_order_line_d.matl_grp2_desc,w_support_pricing_d.mnth_maint_price) as props
from ws_sls_core.w_support_pricing_d
left join ws_sls_core.w_order_line_d
on w_support_pricing_d.svc_pricing_type = w_order_line_d.matl_grp2_cd
group by w_support_pricing_d.item_num
) t';
execute l_sql;
return true;
end;
$BODY$;
ALTER FUNCTION ws_sls_core.ars_pricing_test()

PostgreSQL: dealing with variables and apostrophes in dblink query

I am trying to execute dblink, and into it's query I need to put variables, but the problem is that dblink executes string and before it's execution "converts" variables into that string.
p_int int := 1;
p_text text;
p_apostroph text := '''';
p_sql text;
p_text := (select columnXY from table2 where id =1);
p_sql: =
'insert into table (column1,column2,column3,column4)
select 1
' || p_int || ',
' || p_apostroph || p_text || p_apostroph || ',
''text''
';
dblink_exec('connection' , p_sql);
As seen in the code problem comes with text variables (and nightmare with arrays), because I already have to manually put my text variable p_text between apostrophes, and by nature I do not know what it contains (I fill it from another table or user interface or any other source I do not control). So every time this variable contains and apostrophe it ends up with error because string get's broken. So this means I would have to come up with some complicated string analyse to counter all possibilities.
Is there any other way how to put variables into dblink query without putting them into string?
PostgreSQL 11.6 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5
20150623 (Red Hat 4.8.5-39), 64-bit
as suggested by #a_horse_with_no_name I tried Dollar-Quoted String
create table table1 (column1 int, column2 int, column3 text, column4 text);
create table table2 (column1 text, id int);
insert into table2 values ('unsafe ''',1);
insert into table2 values ('safe ',2);
create or replace function test (p_id int) returns integer as
$body$
declare
p_int int := 1;
p_text text;
p_apostroph text := '''';
p_sql text;
begin
p_text := (select column1 from table2 where id = p_id);
p_sql:=
$$
insert into table1(column1,column2,column3,column4)
select
1,
$$ || p_int || $$,
$$ || p_apostroph || p_text || p_apostroph || $$,
'textz'
$$
;
raise notice '%', p_sql;
perform dblink_exec('connection' , p_sql );
return 1;
end;
$body$ LANGUAGE plpgsql
select * from test (2); -- no problem
select * from test (1); -- error
[Code: 0, SQL State: 42601] ERROR: unterminated quoted string at or
near "' " Kde: while executing command on unnamed dblink connection
SQL statement "SELECT dblink_exec('connection' , p_sql )" PL/pgSQL
function s5_text_apostrop(integer) line 22 at PERFORM
You forgot a comma in the INSERT statement.
Your code is also vulnerable to SQL injection. Use format to avoid that and make your code more readable:
p_sql: = format(
'insert into atable (column1, column2, column3, column4)'
'select 1, %s, %L, %L',
p_int, p_text, 'text'
);

POSTGRESQL: Function result with Concat value

I'm trying to create function with result of concatenated value.
see below:
CREATE OR REPLACE FUNCTION select_name()
RETURNS TABLE(name text ) AS
$BODY$
BEGIN
RETURN QUERY
select
cast(first_name as text) ||' ' || cast( middle_name as text) ||' ' || cast(last_name as text) as name
from table_name;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
However upon querying
select * from select_name();
it shows error:
ERROR: relation "select_name" does not exist
LINE 8: select * from select_name
^
********** Error **********
ERROR: relation "select_name" does not exist
SQL state: 42P01
Character: 159
I'm stuck here.
Please help.
I tried to used below statement, and it worked. ^_^
select
cast( first_name ||' ' || middle_name ||' ' || last_name as text) as name
from table_name;
Try this out this will go like a fly.
CREATE OR REPLACE FUNCTION select_name()
RETURNS TABLE(name text ) AS
$$
BEGIN
RETURN QUERY
select
select
cast(first_name as text) ||' ' || cast( middle_name as text) ||' ' || cast(last_name as text) as name
from table_name;
END;
$$ LANGUAGE plpgsql;

Query string argument of EXECUTE is null

EDIT
It seems my issue is when this select statement returns null (which is the case I'm trying to handle - when it returns null, I want my new value to be -999). How can I go about doing this if it errors out whenever a null is found?
ORIGINAL
I have read every other SO post I could find regarding this error, but none of which seemed to address the root of my issue.
The error is pretty straightforward - one of my arguments within my EXECUTE statement is null. Great. However, I print out each of the values that make up my EXECUTE statement right before it gets called, and I can clearly see that none of the values are null.
Code:
CREATE FUNCTION inform_icrm_prob_flow_query(tablename text, location_id int,
product_date_str text, lead_time_start int,
lead_time_end int, first_member_id int,
last_member_id int, dest_file text)
RETURNS void AS $$
DECLARE
count int;
product_date TIMESTAMPTZ;
interval_lead_time_start text;
interval_lead_time_end text;
curr_value double precision;
query text;
BEGIN
product_date := product_date_str::TIMESTAMPTZ;
count := first_member_id;
curr_value := 0;
interval_lead_time_start := ''''|| product_date ||'''::timestamptz +
interval '''||lead_time_start||' hours''';
interval_lead_time_end := ''''|| product_date ||'''::timestamptz +
interval '''||lead_time_end||' hours'' -
interval ''6 hours''';
--create our temporary table and populate it's date column
EXECUTE 'CREATE TEMPORARY TABLE temp_table_icrm_prob_flow AS
SELECT * FROM generate_series('||interval_lead_time_start || ',' ||
interval_lead_time_end || ', ''6 hours'')
AS date_valid';
LOOP
EXIT WHEN count > last_member_id;
IF NOT EXISTS(
SELECT 'date_valid'
FROM information_schema.columns
WHERE table_name='temp_table_icrm_prob_flow'
and column_name='value'||count||'')
THEN
EXECUTE 'ALTER TABLE temp_table_icrm_prob_flow ADD COLUMN value' || count
|| ' double precision DEFAULT -999';
END IF;
raise notice 'tablename: %', tablename;
raise notice 'location_id: %', location_id;
raise notice 'product_date: %', product_date;
raise notice 'count: %', count;
query := 'SELECT value FROM '|| tablename ||'
INNER JOIN temp_table_icrm_prob_flow
ON (temp_table_icrm_prob_flow.date_valid = '|| tablename ||'.date_valid)
WHERE '|| tablename ||'.id_location = '|| location_id ||'
AND '|| tablename ||'.date_product = '''|| product_date ||'''
AND '|| tablename ||'.id_member = '|| count ||'';
EXECUTE query INTO curr_value;
EXECUTE 'UPDATE temp_table_icrm_prob_flow
SET value'|| count ||' = COALESCE('|| curr_value ||', -999)';
count := count + 1;
END LOOP;
EXECUTE 'ALTER TABLE temp_table_icrm_prob_flow DROP COLUMN date_valid';
EXECUTE 'COPY temp_table_icrm_prob_flow TO '''||dest_file||''' DELIMITER '','' CSV';
EXECUTE 'DROP TABLE temp_table_icrm_prob_flow';
END;
$$ LANGUAGE plpgsql;
Output:
NOTICE: tablename: inform_tseries_data_basin_proc_fcst_prob_flow
NOTICE: location_id: 38
NOTICE: product_date: 2015-02-05 12:00:00+00
NOTICE: count: 1
ERROR: query string argument of EXECUTE is null
CONTEXT: PL/pgSQL function inform_icrm_prob_flow_query(text,integer,text,integer,integer,integer,integer,text) line 38 at EXECUTE
If none of the variables I am passing in are null, and the only other thing referenced is a temp table that I know exists, what could be causing this error?
Note: when changing my query to:
query := 'SELECT value FROM '|| tablename ||' WHERE '|| tablename ||'.id_location = '|| location_id ||' AND '|| tablename ||'.date_product = '''|| product_date ||''' AND '|| tablename ||'.id_member = '|| count ||' AND temp_table_icrm_prob_flow.date_va lid = '|| tablename ||'.date_valid';
I get the following error:
NOTICE: tablename: inform_tseries_data_basin_proc_fcst_prob_flow
NOTICE: location_id: 38
NOTICE: product_date: 2015-02-05 12:00:00+00
NOTICE: count: 1
ERROR: missing FROM-clause entry for table "temp_table_icrm_prob_flow"
LINE 1: ..._data_basin_proc_fcst_prob_flow.id_member = 1 AND temp_table...
^
QUERY: SELECT value FROM inform_tseries_data_basin_proc_fcst_prob_flow WHERE inform_tseries_data_basin_proc_fcst_prob_flow.id_location = 38 AND inform_tseries_data_basin_proc_fcst_prob_flow.date_product = '2015-02-05 12:00:00+00' AND inform_tseries_data_basin_proc_fcst_prob_flow.id_member = 1 AND temp_table_icrm_prob_flow.date_valid = inform_tseries_data_basin_proc_fcst_prob_flow.date_valid
CONTEXT: PL/pgSQL function inform_icrm_prob_flow_query(text,integer,text,integer,integer,integer,integer,text) line 35 at EXECUTE
Sorry for small offtopic. Your code is pretty unreadable (and SQL injecttion vulnerable). There are some techniques, that you can use:
Use clause USING of EXECUTE statement for usual parameters.
DO $$
DECLARE
tablename text := 'mytab';
from_date date := CURRENT_DATE;
BEGIN
EXECUTE 'INSERT INTO ' || quote_ident(tablename) || ' VALUES($1)'
USING from_date;
END
$$;
This code will be safe (due using quote_ident function), little bit faster (due using binary value of from_date variable - removed multiple string<->date conversions and little bit more readable (because string expression is shorter).
Use function format. The building query string will be shorter and more readable (table aliases helps too):
query := format('
SELECT value
FROM %I _dtn
INNER JOIN temp_table_icrm_prob_flow t ON t.date_valid = _dtn.date_valid
WHERE _dtn.id_location = $1
AND _dtn.date_product = $2
AND _dtd.id_member = $3'
, tablename);
EXECUTE query INTO curr_value USING location_id, product_date, count;
Using variables named like important SQL keywords and identifier is wrong idea - names count, values are wrong.
The error message is clean - you are using the identifier temp_table_icrm_prob_flow.date_valid, but the table temp_table_icrm_prob_flow is not mentioned in query. The query missing JOIN part.

Postgresql: ERROR: structure of query does not match function result type Using DbLink

So I wrote this method that aims at querying to another remote database with the same structure using dblink (inspired from this post Specify dblink column definition list from a local existing type and this one Refactor a PL/pgSQL function to return the output of various SELECT queries)
CREATE OR REPLACE FUNCTION select_remote(_table anyelement)
RETURNS SETOF anyelement
AS $func$
DECLARE
_dblink_schema text;
_cols text;
_server text := 'host=ngrok.com port=45790 user=postgres password=postgres dbname=backup-28-08';
_table_name text := pg_typeof(_table);
BEGIN
SELECT nspname INTO _dblink_schema
FROM pg_namespace n, pg_extension e
WHERE e.extname = 'dblink' AND e.extnamespace = n.oid;
SELECT array_to_string(array_agg(column_name || ' ' || udt_name), ', ') INTO _cols
FROM (select column_name, udt_name from information_schema.columns
WHERE table_name = _table_name
order by ordinal_position) as sub;
RETURN QUERY EXECUTE format('SELECT * FROM %I.dblink(%L, %L) AS remote (%s)',
_dblink_schema,
_server,
format('SELECT * FROM %I', _table_name),
_cols
);
END;
$func$ LANGUAGE plpgsql;
But when I do select * from select_remote(NULL::my_table) I receive this error:
ERROR: structure of query does not match function result type
DETAIL: Returned type character varying does not match expected type character varying(255) in column 2.
CONTEXT: PL/pgSQL function select_remote(anyelement) line 18 at RETURN QUERY
********** Erreur **********
ERROR: structure of query does not match function result type
État SQL :42804
Détail :Returned type character varying does not match expected type character varying(255) in column 2.
Contexte : PL/pgSQL function select_remote(anyelement) line 18 at RETURN QUERY
Which drives me mad, because remote table and local table do have the same structure.
Eg. If I only return the query string, I can UNION it to the local table and it works very well:
SELECT * FROM public.dblink('host=ngrok.com port=45790 user=postgres password=postgres dbname=backup-28-08', 'SELECT * FROM my_table') AS remote (id int4, fname varchar, lname varchar, email varchar, slug varchar)
UNION
SELECT * FROM my_table
What am I doing wrong? How can I force anyelement to accept this data even if it comes from remote table? Or return something different to make it work?
Thanks
Following builds on the accepted answer to my question:
CREATE OR REPLACE FUNCTION select_remote(_table anyelement)
RETURNS SETOF anyelement
AS $func$
DECLARE
_dblink_schema text;
_cols text;
_server text := 'host=ngrok.com port=45790 user=postgres password=postgres dbname=backup-28-08';
_table_name text := pg_typeof(_table);
BEGIN
SELECT nspname INTO _dblink_schema
FROM pg_namespace n, pg_extension e
WHERE e.extname = 'dblink' AND e.extnamespace = n.oid;
SELECT array_to_string(array_agg(column_name || ' ' || udt_name), ', ') INTO _cols
FROM (select column_name, udt_name from information_schema.columns
WHERE table_name = _table_name
order by ordinal_position) as sub;
RETURN QUERY EXECUTE format('SELECT (remote::%I).* FROM %I.dblink(%L, %L) AS remote (%s)',
_table_name,
_dblink_schema,
_server,
format('SELECT * FROM %I', _table_name),
_cols
);
END;
$func$ LANGUAGE plpgsql;
Mind that the selected table/columns of "remote" of the dblink call are cast to the local table at
SELECT (remote::%I).* FROM %I.dblink(%L, %L) AS remote (%s)