Postgresql : null values cannot be formatted as an SQL identifier - postgresql

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

Related

Syntac Error when using EXECUTE command in postgresql Function

I'm trying to write a function which executes a dynamically prepared sql query and return the result as table.
I was refering to the SO answer, it mentioned to use language plpgsql, even after using it I'm getting the same syntax error.
Below is the function code provided.
CREATE OR REPLACE FUNCTION public.execute_test(
ddsmappingids text,
totalnumberofrecords bigint,
skiprecords bigint,
pagesize integer,
cid bigint)
RETURNS TABLE(sampletime timestamp without time zone, jsonstring text, rowscount bigint)
LANGUAGE 'plpgsql'
COST 100
VOLATILE PARALLEL UNSAFE
ROWS 1000
AS $BODY$
DECLARE
table_names text:='dynamictable';
sql_query text;
dsids_array int[];
sub_query text;
BEGIN
if cid>=2 then
select 'dynamictable_'||cid into table_names;
end if;
select string_to_array(ddsmappingids, ',')::int[] into dsids_array;
sub_query:='SELECT * , COUNT(*) over() AS row_count FROM (SELECT start_time, cast(jsonb_object_agg(vw.dataseries_name, ROUND(CAST(o.dbl_value as numeric), 4)) as text) AS DataSeriesValue
FROM ' || quote_ident(table_names) ||' o
join vwdds vw on o.ddmid=vw.id    
where ddmid in (' || array_to_string(dsids_array,',') || ')
and o.dbl_value is not null
GROUP BY start_time
ORDER BY start_time desc
limit ' || totalnumberofrecords || ') t offset '|| skiprecords || ' rows fetch next ' || pagesize || ' rows only;';
RAISE NOTICE 'Temporary table created';
RETURN QUERY Execute sub_query;
END
$BODY$;
Error Message when running the function
EXECUTE command, when i try with some simple query it is working fine, but the query mentioned in the code snippet it is giving syntax error.
Please help me where I'm doing wrong.

Postgresql error: cannot open EXECUTE query as cursor

I have written a function to read certain columns from a table below using a dynamic query:
CREATE OR REPLACE FUNCTION select_cols ()
RETURNS SETOF mytable_name
LANGUAGE plpgsql
AS $$
DECLARE
list_of_columns text;
BEGIN
SELECT
string_agg(trim(cols::text, '()'), ', ') INTO list_of_columns
FROM (
SELECT
'mytable_name.' || column_name
FROM
information_schema.columns
WHERE
table_name = 'mytable_name'
AND column_name LIKE 'rm%_b'
OR column_name LIKE 'rm%_s') AS cols;
RETURN query EXECUTE concat(format('select %s from mytable_name', list_of_columns), ' RETURNING *');
END
$$;
Though when I run
select * from select_cols();
it gives me an error : "cannot open EXECUTE query as cursor".
I appreciate if someone can help with this issue
You are not returning a set, but you aggreagte the result set for only one table. So, for only one table you can use:
CREATE OR REPLACE FUNCTION select_colsx ()
RETURNS text
LANGUAGE plpgsql
AS $$
DECLARE
list_of_columns text;
BEGIN
SELECT
'select '||string_agg(trim(cols::text, '()'), ', ') || ' from pg_class RETURNING *'
INTO list_of_columns
FROM (
SELECT
'pg_class.' || column_name
FROM
information_schema.columns
WHERE
table_name = 'pg_class'
AND column_name LIKE 'oid'
OR column_name LIKE 'relacl') AS cols;
RETURN list_of_columns ;
END
$$;
select select_colsx();
DB Fiddle Example
RETURN QUERY EXECUTE was introduced in PostgreSQL 8.4. Upgrade to a less ancient version.

postgresql - null value error on function

I'm trying to figure out how to resolve this ERROR: null values cannot be formatted as an SQL identifier when trying to select my function:
select * from store_keys();
ERROR: null values cannot be formatted as an SQL identifier
CONTEXT: SQL statement "SELECT string_agg(
format('SELECT %1$I, count(email_store_key), email_store_key, form_created_datetime
FROM %1$I where email_store_key=0 GROUP BY 3, 4', tbl_name),
' UNION ') FROM information_schema.tables
WHERE table_schema = 'abc_dev_sch_1234'
AND table_name LIKE 'fact_%'"
The base query it uses, does not produce null values. So where is this coming from??
select count(email_store_key), email_store_key, form_created_datetime
FROM <table_name> where email_store_key=0 GROUP BY email_store_key, form_created_datetime;
Here's my create statement:
DROP FUNCTION store_keys();
CREATE OR REPLACE FUNCTION store_keys()
RETURNS TABLE (tbl_name varchar, count_keys bigint, email_store_key integer, form_created_datetime timestamp)
AS $$
DECLARE
qry text;
BEGIN
SELECT string_agg(
format('SELECT %1$I, count(email_store_key), email_store_key, form_created_datetime
FROM %1$I where email_store_key=0 GROUP BY 3, 4', tbl_name),
' UNION ') INTO qry
FROM information_schema.tables
WHERE table_schema = 'abc_dev_sch_1234'
AND table_name LIKE 'fact_%';
RETURN QUERY EXECUTE qry;
END
$$ LANGUAGE plpgsql;

Get IDs from multiple columns in multiple tables as one set or array

I have multiple tables with each two rows of interest: connection_node_start_id and connection_node_end_id. My goal is to get a collection of all those IDs, either as a flat ARRAY or as a new TABLE consisting of one row.
Example output ARRAY:
result = {1,4,7,9,2,5}
Example output TABLE:
IDS
-------
1
4
7
9
2
5
My fist attempt is somewhat clumsy and does not work properly as the SELECT statement just returns one row. It seems there must be a simple way to do this, can someone point me into the right direction?
CREATE OR REPLACE FUNCTION get_connection_nodes(anyarray)
RETURNS anyarray AS
$$
DECLARE
table_name varchar;
result integer[];
sel integer[];
BEGIN
FOREACH table_name IN ARRAY $1
LOOP
RAISE NOTICE 'table_name(%)',table_name;
EXECUTE 'SELECT ARRAY[connection_node_end_id,
connection_node_start_id] FROM ' || table_name INTO sel;
RAISE NOTICE 'sel(%)',sel;
result := array_cat(result, sel);
END LOOP;
RETURN result;
END
$$
LANGUAGE 'plpgsql';
Test table:
connection_node_start_id | connection_node_end_id
--------------------------------------------------
1 | 4
7 | 9
Call:
SELECT get_connection_nodes(ARRAY['test_table']);
Result:
{1,4} -- only 1st row, rest is missing
For Postgres 9.3+
CREATE OR REPLACE FUNCTION get_connection_nodes(text[])
RETURNS TABLE (ids int) AS
$func$
DECLARE
_tbl text;
BEGIN
FOREACH _tbl IN ARRAY $1
LOOP
RETURN QUERY EXECUTE format('
SELECT t.id
FROM %I, LATERAL (VALUES (connection_node_start_id)
, (connection_node_end_id)) t(id)'
, _tbl);
END LOOP;
END
$func$ LANGUAGE plpgsql;
Related answer on dba.SE:
SELECT DISTINCT on multiple columns
Or drop the loop and concatenate a single query. Probably fastest:
CREATE OR REPLACE FUNCTION get_connection_nodes2(text[])
RETURNS TABLE (ids int) AS
$func$
BEGIN
RETURN QUERY EXECUTE (
SELECT string_agg(format(
'SELECT t.id FROM %I, LATERAL (VALUES (connection_node_start_id)
, (connection_node_end_id)) t(id)'
, tbl), ' UNION ALL ')
FROM unnest($1) tbl
);
END
$func$ LANGUAGE plpgsql;
Related:
Loop through like tables in a schema
LATERAL was introduced with Postgres 9.3.
For older Postgres
You can use the set-returning function unnest() in the SELECT list, too:
CREATE OR REPLACE FUNCTION get_connection_nodes2(text[])
RETURNS TABLE (ids int) AS
$func$
BEGIN
RETURN QUERY EXECUTE (
SELECT string_agg(
'SELECT unnest(ARRAY[connection_node_start_id
, connection_node_end_id]) FROM ' || tbl
, ' UNION ALL '
)
FROM (SELECT quote_ident(tbl) AS tbl FROM unnest($1) tbl) t
);
END
$func$ LANGUAGE plpgsql;
Should work with pg 8.4+ (or maybe even older). Works with current Postgres (9.4) as well, but LATERAL is much cleaner.
Or make it very simple:
CREATE OR REPLACE FUNCTION get_connection_nodes3(text[])
RETURNS TABLE (ids int) AS
$func$
BEGIN
RETURN QUERY EXECUTE (
SELECT string_agg(format(
'SELECT connection_node_start_id FROM %1$I
UNION ALL
SELECT connection_node_end_id FROM %1$I'
, tbl), ' UNION ALL ')
FROM unnest($1) tbl
);
END
$func$ LANGUAGE plpgsql;
format() was introduced with pg 9.1.
Might be a bit slower with big tables because each table is scanned once for every column (so 2 times here). Sort order in the result is different, too - but that does not seem to matter for you.
Be sure to sanitize escape identifiers to defend against SQL injection and other illegal syntax. Details:
Table name as a PostgreSQL function parameter
The EXECUTE ... INTO statement can only return data from a single row:
If multiple rows are returned, only the first will be assigned to the INTO variable.
In order to concatenate values from all rows you have to aggregate them first by column and then append the arrays:
EXECUTE 'SELECT array_agg(connection_node_end_id) ||
array_agg(connection_node_start_id) FROM ' || table_name INTO sel;
You're probably looking for something like this:
CREATE OR REPLACE FUNCTION d (tblname TEXT [])
RETURNS TABLE (c INTEGER) AS $$
DECLARE sql TEXT;
BEGIN
WITH x
AS (SELECT unnest(tblname) AS tbl),
y AS (
SELECT FORMAT('
SELECT connection_node_end_id
FROM %s
UNION ALL
SELECT connection_node_start_id
FROM %s
', tbl, tbl) AS s
FROM x)
SELECT string_agg(s, ' UNION ALL ')
INTO sql
FROM y;
RETURN QUERY EXECUTE sql;
END;$$
LANGUAGE plpgsql;
CREATE TABLE a (connection_node_end_id INTEGER, connection_node_start_id INTEGER);
INSERT INTO A VALUES (1,2);
CREATE TABLE b (connection_node_end_id INTEGER, connection_node_start_id INTEGER);
INSERT INTO B VALUES (100, 101);
SELECT * from d(array['a','b']);
c
-----
1
2
100
101
(4 rows)

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)