How to change "EXECUTE sql_string" to "RETURN QUERY" in a function declaration, but there is a where array which must be used.
In other words, how to change this
p_sql := 'SELECT *
FROM tbl
WHERE ' || array_to_string(where_arr, ' AND ');
FOR item IN EXECUTE p_sql
LOOP
RETURN NEXT item;
END LOOP;
to this
RETURN QUERY
SELECT *
FROM tbl
WHERE || array_to_string(where_arr, ' AND '); -- this is the place
I want my editors (phpstorm, notepad++) to see SQL as SQL and not as string.
https://www.postgresql.org/docs/current/static/plpgsql-control-structures.html#PLPGSQL-STATEMENTS-RETURNING
return query execute $$
select *
from tbl
where $$ || array_to_string(where_arr, ' and ');
Related
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;
this is my function:
CREATE OR REPLACE FUNCTION SANDBOX.DAILYVERIFY_DATE(TABLE_NAME regclass, DATE_DIFF INTEGER)
RETURNS void AS $$
DECLARE
RESULT BOOLEAN;
DATE DATE;
BEGIN
EXECUTE 'SELECT VORHANDENES_DATUM AS DATE, CASE WHEN DATUM IS NULL THEN FALSE ELSE TRUE END AS UPDATED FROM
(SELECT DISTINCT DATE VORHANDENES_DATUM FROM ' || TABLE_NAME ||
' WHERE DATE > CURRENT_DATE -14-'||DATE_DIFF|| '
) A
RIGHT JOIN
(
WITH compras AS (
SELECT ( NOW() + (s::TEXT || '' day'')::INTERVAL )::TIMESTAMP(0) AS DATUM
FROM generate_series(-14, -1, 1) AS s
)
SELECT DATUM::DATE
FROM compras)
B
ON DATUM = VORHANDENES_DATUM'
INTO date,result;
RAISE NOTICE '%', result;
INSERT INTO SANDBOX.UPDATED_TODAY VALUES (TABLE_NAME, DATE, RESULT);
END;
$$ LANGUAGE plpgsql;
It is supposed to upload rows into the table SANDBOX.UPDATED_TODAY, which contains table name, a date and a boolean.
The boolean shows, whether there was an entry for that date in the table. The whole part, which is inside of EXECUTE ... INTO works fine and gives me those days.
However, this code only inserts the first row of the query's result. What I want is that all 14 rows get inserted. Obviously, I need to change it into something like a loop or something completely different, but how exactly would that work?
Side note: I removed some unnecessary parts regarding those 2 parameters you can see. It does not have to do with that at all.
Put the INSERT statement inside the EXECUTE. You don't need the result of the SELECT for anything other than inserting it into that table, right? So just insert it directly as part of the same query:
CREATE OR REPLACE FUNCTION SANDBOX.DAILYVERIFY_DATE(TABLE_NAME regclass, DATE_DIFF INTEGER)
RETURNS void AS
$$
BEGIN
EXECUTE
'INSERT INTO SANDBOX.UPDATED_TODAY
SELECT ' || QUOTE_LITERAL(TABLE_NAME) || ', VORHANDENES_DATUM, CASE WHEN DATUM IS NULL THEN FALSE ELSE TRUE END
FROM (
SELECT DISTINCT DATE VORHANDENES_DATUM FROM ' || TABLE_NAME ||
' WHERE DATE > CURRENT_DATE -14-'||DATE_DIFF|| '
) A
RIGHT JOIN (
WITH compras AS (
SELECT ( NOW() + (s::TEXT || '' day'')::INTERVAL )::TIMESTAMP(0) AS DATUM
FROM generate_series(-14, -1, 1) AS s
)
SELECT DATUM::DATE
FROM compras
) B
ON DATUM = VORHANDENES_DATUM';
END;
$$ LANGUAGE plpgsql;
The idiomatic way to loop through dynamic query results would be
FOR date, result IN
EXECUTE 'SELECT ...'
LOOP
INSERT INTO ...
END LOOP;
I have a function in which I want to loop throw each array's item. I get a string in input like 'tab1#tab2#tab3'...Each item of the string must be splitted (by #) in order to obtain tab1, tab2, tab3 into myArray. My function is:
CREATE OR REPLACE FUNCTION funcA(
myUid integer,
mytable_name varchar,
state varchar)
RETURNS void AS
$BODY$
declare
TABarray varchar[];
indx int;
BEGIN
select REGEXP_REPLACE('{'||myTABLE_NAME||'}','#','','g') into TABarray;
for indx in 1..array_length(TABarray, 1) loop
execute 'update ' || TABarray(indx) || ' set CODE_STATO = ''' || state || ''' where uid = ' || myUid || 'and CODE_STATO <> ''N'' ';
raise notice 'i: %', TABarray[ indx ];
end loop;
END; $BODY$
LANGUAGE plpgsql stable
As a result I expect 3 splitted string such as:
-tab1
-tab2
-tab3
Right now myFunction print {tab1tab2tab3}.
select oms_write_stato (10, 'tab1#tab2#tab3', '')
What I am doing wrong?
Thank you in advance!
You could use string_to_array to split the string into array. Also, you were using () to refer to index elements instead of []
CREATE OR replace FUNCTION funca( myuid integer, mytable_name varchar, state varchar)
returns void AS
$BODY$
DECLARE
tabarray VARCHAR[];
indx int;
BEGIN
SELECT string_to_array(mytable_name ,'#')
INTO tabarray;
for indx IN 1..array_length(tabarray, 1)
LOOP
--check the o/p of this notice below to see if update statement is correct
--raise notice '%', 'update ' || tabarray[indx] || ' set CODE_STATO = ''' || state || ''' where uid = ' || myuid || 'and CODE_STATO <> ''N'' ';
execute 'update ' || tabarray[indx] || ' set CODE_STATO = ''' || state || ''' where uid = ' || myUid || ' and CODE_STATO <> ''N'' ';
raise notice 'i: %', tabarray[ indx ];
END LOOP;
END;
$BODY$ language plpgsql stable;
PL/pgSQL has FOREACH IN ARRAY statement for this purpose:
You task can be written some like:
-- Don't use case mixed identifiers (prohibit camel notation)
create or replace function funca(uid integer,
tablenames varchar,
state varchar)
returns void as $$
declare tablename text;
begin
foreach tablename in array string_to_array(tablenames, '#')
loop
execute format('update %I set code_stato = $1 where uid = $2 and code_state <>'N',
tablename)
using state, uid;
end loop;
end;
$$ language plpgsql;
Notes:
don't mix upper lower chars in identifiers
don't mix upper / lower keywords - there are some variants - keywords by upper cases, or all by lower cases, but mix is bad for reading
when you use dynamic SQL, then sanitize your data before you use it in dynamic query - use quote_ident,quote_literal functions, or function format with secure placeholders and when it is possible, pass with USING clause.
postgres has array types - using str1#str2#str3#str4 is little bit obscure in Postgres - use native arrays like ARRAY['str1','str2','str3','str4'].
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();
I want to execute query with ltree param in plpgsql function... but i can`t understand how to use quotes in this functions...
CREATE OR REPLACE FUNCTION f_select(BIGINT) RETURNS setof categories AS
$$
DECLARE
s_cat ALIAS FOR $1;
queryText TEXT;
result categories%ROWTYPE;
BEGIN
queryText := 'SELECT * FROM categories WHERE cat_tree ~ \'*.\'|| s_cat::text ||\'.*\'';
FOR result IN EXECUTE queryText
LOOP
RETURN NEXT result;
END LOOP;
RETURN;
END
$$
LANGUAGE plpgsql;
How to do this ???
After executing this code in psql I get error:
ERROR: syntax error at or near "."
LINE 10: ... := 'SELECT * FROM categories WHERE cat_tree ~ \'*.\'|| s_ca...
Final working verison:
CREATE OR REPLACE FUNCTION f_select(BIGINT) RETURNS setof categories AS
$$
DECLARE
s_cat ALIAS FOR $1;
queryText TEXT;
result categories%ROWTYPE;
BEGIN
queryText := 'SELECT * FROM categories WHERE cat_tree ~ ''' || ('*.'|| s_cat::text || '.*')::lquery || '''';
FOR result IN EXECUTE queryText
LOOP
RETURN NEXT result;
END LOOP;
RETURN;
END
$$
LANGUAGE plpgsql;
The problem seems to be misusing the backslash, but anyway putting this query into a text variable shouldn't be needed in the first place.
What about this form:
FOR result IN SELECT * FROM categories WHERE cat_tree ~ '*.'|| s_cat::text || '.*'
LOOP
RETURN NEXT result;
END LOOP;
And if the LOOP just has to return the results, you may just as well avoid it and return the query directly:
RETURN QUERY SELECT * FROM categories WHERE cat_tree ~ '*.'|| s_cat::text || '.*';
EDIT:
Since the operator is ltree ~ lquery and ~ binds tighter than ||, the right operand should be parenthesized and cast to lquery:
RETURN QUERY SELECT * FROM categories
WHERE cat_tree ~ ('*.'|| s_cat::text || '.*')::lquery;