PostgreSQL: How to set value to variable using 'execute'? - postgresql

I have a query which is assigned to some variable and I want to execute it with setting some values.
Example:
create or replace function funct1(a int)
returns void as
$$
declare
wrclause varchar := '';
sqlq varchar ;
t varchar;
begin
IF (a IS NOT NULL ) THEN
wrclause := 'where C = '|| a ||' AND C IN ('|| a || ')';
END IF;
sqlq := ' t :=select string_agg(''select *, abcd as "D" from '' || table_namess ||, '' Union all '') as namess
from tablescollection2 ud
inner join INFORMATION_SCHEMA.Tables so on ud.table_namess = so.Table_name ' || wrclause;
raise info '%',sqlq;
execute sqlq; /* How to set value to variable f.ex (t varchar output,t output)*/
raise info '%',t;
end;
$$
language plpgsql;
In SQL Server: We can use
exec sp_executesql #sqlq, N'#t nvarchar(max) output', #t OUTPUT;
Note: How can I do in the PostgreSQL?
MyTry:
execute sqlq(t varchar output,t output); /* getting error near varchar */

You can do something like
EXECUTE sqlq INTO target_variable;
for more help plpgsql-statements

Related

ERROR: syntax error at or near "TABLE" LINE 5: SELECT * FROM TABLE(str_common_utils.splitstr(p_...)

CREATE OR REPLACE FUNCTION sql_builder.sql_or_builder (v_field_name text, p_string text, v_operator text DEFAULT '=', p_delimiter text DEFAULT ',') RETURNS varchar AS $body$
DECLARE
cur_fields CURSOR FOR
SELECT * FROM TABLE(str_common_utils.splitstr(p_string, p_delimiter));
v_sql varchar(4000) := '';
BEGIN
v_sql := v_sql || ' (';
FOR r_field IN cur_fields loop
-- FOR DEBUG
-- dbms_output.put_line(r_field.column_value);
v_sql := v_sql || v_field_name || ' ' || v_operator || ' ''' ||
r_field.column_value || '''';
v_sql := v_sql || ' or ';
END LOOP;
v_sql := substr(v_sql, 1, length(v_sql) - 4);
v_sql := v_sql || ') ';
-- FOR DEBUG
-- dbms_output.put_line(v_sql);
RETURN v_sql;
END;
$body$
LANGUAGE PLPGSQL
STABLE;
That TABLE keyword in
SELECT * FROM TABLE(str_common_utils.splitstr(p_string, p_delimiter));
does not exist. Just omit the TABLE() decoration around the function call. If you want a decoration, use FROM ROWS FROM (str_common_utils.splitstr(p_string, p_delimiter)).
the table() operator is not necessary in Postgres to use set returning functions, so you just need:
cur_fields CURSOR FOR
SELECT * FROM str_common_utils.splitstr(p_string, p_delimiter);
But the whole function is way more complicated than it needs to be. As far as I can tell you can simplify it to:
CREATE OR REPLACE FUNCTION sql_or_builder(v_field_name text, p_string text, v_operator text DEFAULT '=', p_delimiter text DEFAULT ',')
RETURNS text
AS
$body$
select concat('(', string_agg(format('%I = %L', v_field_name, r.column_value), ' OR '), ')')
from unnest(string_to_array(p_string, p_delimiter)) as r(column_value);
$body$
language sql
immutable;
e.g.
select sql_or_builder('some_name', 'one,two,three');
sql_or_builder
---------------------------------------------------------------
(some_name = 'one' OR some_name = 'two' OR some_name = 'three')
```

concatenate the contents of two columns of the same table produces a name with double quotes

the situation.
I'm producing a function, and part of it needs to get the name of each table by concatenating schemaname.tablename to iterate.
part of the code:
the part that it´s producing the error:
select schemaname||'.'||tablename as otra from pg_tables where schemaname = 'vigano' and pg_tables.tablename ~ 'ver1920_soja_filtrado$'
The hole function is this one:
CREATE OR REPLACE FUNCTION yield_summary()
RETURNS TABLE (
promedio double precision,
minimo double precision,
maximo double precision
) LANGUAGE plpgsql AS
$$DECLARE
v_sql text := '';
v_sep text := '';
v_tab text;
BEGIN
FOR v_tab IN
select schemaname||'.'||tablename as otra from pg_tables where schemaname = 'vigano' and pg_tables.tablename ~ 'ver1920_soja_filtrado$'
LOOP
v_sql := v_sql || v_sep ||
format(
'select round(avg(masa_de_re)::numeric,3) as promedio, round(min(masa_de_re)::numeric,3) as minimo, round(max(masa_de_re)::numeric,3) as maximo
FROM %I',
v_tab
);
v_sep := ' UNION ALL ';
END LOOP;
RETURN QUERY EXECUTE v_sql;
END;$$;
it produces the right table schemaname.tablename, but..it doesn't recognize it because of the double quotes.
ERROR: no existe la relación «vigano.elcerro_elmolino_ver1920_soja_filtrado»
LINE 2: FROM "vigano.elcerro_elmolino_ver1920_soja_filtrado" UNI...
^
how do I get rid of the double quotes??
if i use quote_indent()
select quote_ident(schemaname||'.'||tablename) as otra from pg_tables where schemaname = 'vigano' and pg_tables.tablename ~ 'ver1920_soja_filtrado$'
it produces this:
ERROR: no existe la relación «"vigano.elcerro_elmolino_ver1920_soja_filtrado"»
LINE 2: FROM """vigano.elcerro_elmolino_ver1920_soja_filtrado""" ^
If I use quote_literal()
select quote_literal(schemaname||'.'||tablename) as otra from pg_tables where schemaname = 'vigano' and pg_tables.tablename ~ 'ver1920_soja_filtrado$'
it produces:
ERROR: no existe la relación «'vigano.elcerro_elmolino_ver1920_soja_filtrado'»
LINE 2: FROM "'vigano.elcerro_elmolino_ver1920_soja_filtrado'" U...
^
thanks
In FORMAT(), %I specifies an identifier, and is always quoted by double quotes...
Try this instead:
CREATE OR REPLACE FUNCTION yield_summary()
RETURNS TABLE (
promedio double precision,
minimo double precision,
maximo double precision
) LANGUAGE plpgsql AS $$
DECLARE
v_sql text := '';
v_sep text := '';
v_tab record;
BEGIN
FOR v_tab IN
SELECT schemaname, tablename FROM pg_tables WHERE schemaname = 'vigano' AND pg_tables.tablename ~ 'ver1920_soja_filtrado$'
LOOP
v_sql := v_sql || v_sep || format(
'SELECT round(avg(masa_de_re)::numeric,3) AS promedio, round(min(masa_de_re)::numeric,3) AS minimo, round(max(masa_de_re)::numeric,3) AS maximo
FROM %I.%I',
v_tab.schemaname, v_tab.tablename
);
v_sep := ' UNION ALL ';
END LOOP;
RETURN QUERY EXECUTE v_sql;
END;
$$;
Don't pass schema name and table name as a single value to format(). Select two columns and pass two parameters.
FOR v_tab IN
select schemaname, tablename as otra from pg_tables where schemaname = 'vigano' and pg_tables.tablename ~ 'ver1920_soja_filtrado$'
LOOP
v_sql := v_sql || v_sep ||
format(
'select round(avg(masa_de_re)::numeric,3) as promedio, round(min(masa_de_re)::numeric,3) as minimo, round(max(masa_de_re)::numeric,3) as maximo
FROM %I.%I',
v_tab.schemaname, v_tab.tablename
);
v_sep := ' UNION ALL ';
END LOOP;
The way you used it, makes the format() function "think" that it's a single identifier that contains a . - which would require quoting.
Unrelated to your question, but you can simplify your code and get rid of the FOR loop completely:
select string_agg(
format('select round(avg(masa_de_re)::numeric,3) as promedio, round(min(masa_de_re)::numeric,3) as minimo, round(max(masa_de_re)::numeric,3) as maximo
FROM %I.%I',
schemaname, tablename), ' UNION ALL ')
into v_sql
from pg_tables
where schemaname = 'vigano'
and pg_tables.tablename ~ 'ver1920_soja_filtrado$';
return query
execute v_sql;

How to use a Function Parameter in a Cursor that's incorporated with Dynamic SQL in Postgres Functions?

Created this Postgres Function which is working fine, but the actual requirement is to pass the input parameter in the function to the Cursor which uses the dynamic SQL as follows,
The below is the Function
CREATE OR REPLACE FUNCTION ssp2_pcat.find_shift_dates (date_to_find date)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
DECLARE
C1 CURSOR FOR
SELECT TABLE_NAME, 'SELECT COUNT(*) FROM ' || TABLE_NAME || ' WHERE ' ||
COLUMN_NAME || ' = '||
'CASE WHEN ' || COLUMN_NAME || ' LIKE ' || '''%START%'''||' THEN
date_to_find ELSE date_to_find-1 END;' SQL_TEXT
FROM (
SELECT TABLE_NAME, COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME IN (SELECT TABLE_NAME FROM RESET_DATES WHERE RESET_IT =
'Y') AND
UPPER(DATA_TYPE) = 'DATE'
AND (COLUMN_NAME LIKE '%START%' OR COLUMN_NAME LIKE '%END%')
AND (COLUMN_NAME NOT LIKE '%TEST%'
AND COLUMN_NAME NOT LIKE '%PCAT%'
AND COLUMN_NAME NOT LIKE '%ORDER%'
AND COLUMN_NAME NOT LIKE '%SEASON%'
AND COLUMN_NAME NOT LIKE '%_AT')
ORDER BY 1, 2) A;
END_COUNT INTEGER := 0;
START_COUNT INTEGER := 0;
TABLENAME VARCHAR(32) := 'ALFU';
l_start TIMESTAMP;
l_end TIMESTAMP;
Time_Taken VARCHAR(20);
BEGIN
l_start := clock_timestamp();
DELETE FROM SHIFT_DATES_COUNT;
FOR I IN C1 LOOP
IF I.TABLE_NAME <> TABLENAME THEN
INSERT INTO SHIFT_DATES_COUNT VALUES (TABLENAME, START_COUNT,
END_COUNT, current_timestamp::timestamp(0));
TABLENAME := I.TABLE_NAME;
END_COUNT := 0;
START_COUNT := 0;
END IF;
IF STRPOS(I.SQL_TEXT, 'END') > 0 THEN
EXECUTE I.SQL_TEXT INTO END_COUNT;
RAISE NOTICE '% ', ('END: ' || I.SQL_TEXT);
ELSE
EXECUTE I.SQL_TEXT INTO START_COUNT;
RAISE NOTICE '% ', ('START: ' || I.SQL_TEXT);
END IF;
END LOOP;
INSERT INTO SHIFT_DATES_COUNT VALUES (TABLENAME, START_COUNT, END_COUNT,
current_timestamp::timestamp(0));
RAISE NOTICE '% ', ('INSERT INTO SHIFT_DATES_COUNT Done...');
l_end := clock_timestamp();
Time_Taken := (l_end-l_start);
RAISE NOTICE '% ', ('FIND_SHIFT_DATES Took: ' || Time_Taken );
END;
$BODY$;
Please let me know how can I use the date_to_find input parameter in the Dynamic SQL in the Cursor in the above Function.
You can use unbound cursor, clause fetch to get data from cursor, and exit when not found to finish, like:
CREATE OR REPLACE FUNCTION example (p_name text) RETURNS void LANGUAGE 'plpgsql' AS $$
DECLARE
C1 refcursor;
res record;
BEGIN
OPEN c1 FOR EXECUTE 'SELECT * FROM pg_database WHERE datname like ''%'||p_name||'%''';
LOOP
FETCH c1 INTO res;
EXIT WHEN not found;
raise notice 'value datname: %',res.datname;
END LOOP;
CLOSE c1;
RETURN;
END; $$;
--in my case
select example ('test')
NOTICE: value datname: test
NOTICE: value datname: test_msmov
NOTICE: value datname: test_resources
NOTICE: value datname: test_load_table
NOTICE: value datname: test_resources2
Total query runtime: 63 msec
1 row retrieved.
You can use EXECUTE clause for open cursor, see the documentation of PostgreSQL
https://www.postgresql.org/docs/10/plpgsql-cursors.html#PLPGSQL-CURSOR-OPENING
Example:
OPEN curs1 FOR EXECUTE format('SELECT * FROM %I WHERE col1 = $1',tabname) USING keyvalue;

Check to see if a record exists postgres function

I am attempting to create a function that will determine if a record exists for our applications developers to help simplify things.
CREATE FUNCTION records_exist(schema_name VARCHAR(255), table_name VARCHAR(255), field_name VARCHAR(255), field_value VARCHAR(255))
RETURNS BOOLEAN
LANGUAGE plpgsql
AS $$
DECLARE
_schema_name ALIAS FOR $1;
_table_name ALIAS FOR $2;
_field_name ALIAS FOR $3;
_field_value ALIAS FOR $4;
_sql_string VARCHAR(5000);
BEGIN
_sql_string= 'SELECT EXISTS(SELECT 1 FROM ' || _schema_name || '.' || _table_name || ' WHERE ' || _field_name || '=' || _field_value || ');';
RETURN BOOLEAN EXECUTE _sql_string;
--RETURN TABLE EXECUTE _sql_string; doesn't work
END
$$;
The following should work, but I keep getting ERROR: syntax error at or near "EXECUTE"
Please let me know the error of my ways.
also, your dynamic string is bad, better use:
select format('select exists from %I.%I where %I = %L',schema_name,table_name,field_name, field_value) into _sql_string;
also, you realize your _field_value has no check for data type?..
so in short, it could be smth similar to:
db=# CREATE or replace FUNCTION records_exist(schema_name VARCHAR(255), table_name VARCHAR(255), field_name VARCHAR(255), field_value VARCHAR(255))
RETURNS BOOLEAN
LANGUAGE plpgsql
AS $$
DECLARE
_sql text;
_b boolean;
BEGIN
_sql := format('select exists (select null from %I.%I where %I = %L)',schema_name,table_name,field_name, field_value);
execute _sql into _b;
return _b;
END
$$;
CREATE FUNCTION
Time: 10.680 ms
db=# select * from records_exist('pg_catalog','pg_database','datname','postgres'); records_exist
---------------
t
(1 row)
Time: 59.472 ms

Postgresql function return multiple select statements

Can any one of you tell me how to approach this:
CREATE OR REPLACE FUNCTION name()
RETURNS ????? AS
$func$
BEGIN
SELECT * FROM tbl_a a;
SELECT * FROM tbl_b b;
END
$func$ LANGUAGE plpgsql;
Both tables have different structures.
You can use cursors but I can hardly imagine why you need such a function.
CREATE OR REPLACE FUNCTION my_multiselect(refcursor, refcursor) RETURNS VOID AS
$func$
BEGIN
OPEN $1 FOR SELECT * FROM information_schema.routines;
OPEN $2 FOR SELECT * FROM information_schema.sequences;
END
$func$ LANGUAGE plpgsql;
BEGIN;
SELECT my_multiselect('first_cursor_to_routines', 'second_cursor_to_sequences');
FETCH ALL IN first_cursor_to_routines;
FETCH ALL IN second_cursor_to_sequences;
COMMIT;
I'm not really sure what you're doing with this, but it sounds like you just want to return a union of these distinct result sets. You can do this with a dynamic query. I'm using Postgres 9.4.
CREATE OR REPLACE FUNCTION make_query(IN p_tables text[])
RETURNS void AS
$BODY$
DECLARE
v_qry text;
v_cols text;
v_types text;
v_as text;
BEGIN
EXECUTE format('
WITH sub AS (
SELECT
table_name,
column_name,
data_type
FROM
information_schema.columns
WHERE
table_name = ANY(%L)
ORDER BY
table_name,
ordinal_position)
,sub2 AS(
SELECT
DISTINCT ON (column_name, data_type)
column_name || '' '' || data_type AS def
FROM
sub
)
SELECT
string_agg(def, '','')
FROM
sub2;
',
p_tables
) INTO v_types;
v_qry := '
CREATE OR REPLACE FUNCTION name()
RETURNS TABLE(' || v_types || ') AS
$func$';
FOR i IN 1..array_upper(p_tables, 1)
LOOP
v_as := 'tbl' || i;
EXECUTE format('
WITH sub AS (
SELECT
table_name,
column_name,
data_type
FROM
information_schema.columns
WHERE
table_name = ANY(%L)
ORDER BY
table_name,
ordinal_position)
,sub2 AS(
SELECT
DISTINCT ON (column_name, data_type)
CASE WHEN table_name = ''%I''
THEN %L || ''.'' || column_name
ELSE ''NULL::'' || data_type
END AS cols
FROM
sub
)
SELECT
string_agg(cols, '','')
FROM
sub2;
',
p_tables,
p_tables[i],
v_as
) INTO v_cols;
IF i > 1 THEN
v_qry := v_qry || '
UNION ALL';
END IF;
v_qry := v_qry || '
SELECT ' || v_cols || ' FROM ' || p_tables[i] || ' AS ' || v_as;
IF i = array_upper(p_tables, 1) THEN
v_qry := v_qry || ';';
END IF;
END LOOP;
v_qry := v_qry || '
$func$ LANGUAGE sql;
';
EXECUTE v_qry;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
Sorry it looks ugly here, but this formatting helps the final product look nicer. If you're shy about executing a dynamic query like this off the bat, just replace EXECUTE v_qry; with RAISE INFO 'v_qry: %', v_qry; and it will simply print the dynamic query out in a message without executing it, so you can review what it will do once executed.
Then execute make_query() with a list of tables you want to display like this:
SELECT make_query(ARRAY['tbl_a', 'tbl_b']);
The result is that you will now have a function called name() which you can call in order to see the results of both tables at the same time, with all the union details already sorted out:
SELECT * FROM name();