Postgres Function returning no results but when the same query returns results outside function - postgresql

I am running a simple postgres function to return the count of rows. I am able to run the same query outside function with the output of raise option , but the function doesn't return any rows. I have tried different ways to produce results but unable to. Please find my function below,
CREATE OR REPLACE FUNCTION my_schema.usp_spellcheck3(SearchORItems_WithPipe varchar, site varchar, lan varchar, rows_display integer)
RETURNS TABLE (docmnt int) AS $BODY$
DECLARE
arrSearchTerms text[];
NewTerm varchar;
i integer;
AltSearch_withComma varchar;
AltSearch_withPipe varchar;
strDidYouMean varchar;
dpDidYouMean double precision;
txtDidYouMean Text;
SearchORItems_withComma varchar;
SearchORItems varchar;
SearchORItem varchar;
ws varchar;
arrSearchORItems_withComma varchar[];
BEGIN
strDidYouMean = 'DidYouMeanRow';
dpDidYouMean = 0.0;
txtDidYouMean = 'DidYouMeanRow';
ws = '''' || '%' || site || '%' || '''' ;
RAISE NOTICE '%', ws;
SearchORItems = REPLACE(SearchORItems_WithPipe, '|', ',');
SELECT regexp_split_to_array(SearchORItems, ',') INTO arrSearchORItems_withComma;
RAISE NOTICE '%', SearchORItems;
FOR i IN 1 .. coalesce(array_upper(arrSearchORItems_withComma, 1), 1) LOOP
IF (i = 1) THEN
SearchORItems_withComma = '''' || arrSearchORItems_withComma[i] || '''';
ELSE
SearchORItems_withComma = SearchORItems_withComma||','||'''' || arrSearchORItems_withComma[i] || '''';
END IF;
END LOOP;
RAISE NOTICE '%',SearchORItems_withComma;
SELECT COUNT(*) INTO res_count
FROM (
SELECT 1 FROM my_schema.features f , my_schema.documents d
WHERE term IN (SearchORItems_withComma)
AND d.docid = f.docid
AND d.url LIKE ws
GROUP BY f.docid, d.url) t;
RAISE NOTICE '%', res_count;
SearchORItem = 'SELECT COUNT(*) INTO res_count
FROM (SELECT 1 FROM my_schema.features f , my_schema.documents d
WHERE term IN ('||SearchORItems_withComma||')
AND d.docid = f.docid AND d.url LIKE ' || ws ||'
GROUP BY f.docid, d.url) t';
RAISE NOTICE '%',SearchORItem;
END;
$BODY$ LANGUAGE SQL VOLATILE;
this is my query output :
NOTICE: '%uni%'
NOTICE: daniel,data
NOTICE: 'daniel','data'
NOTICE: 0
NOTICE: select count(*) into res_count
from ( select 1 from my_schema.features f , my_schema.documents d
where term in ('daniel','data')
and d.docid=f.docid and d.url like '%uni%'
group by f.docid,d.url)t
Total query runtime: 16 ms.
0 rows retrieved.
I dont know where I'm going wrong, any help would be appreciated .. Thanks..

The simple reason that nothing is returned is that you have no RETURN statements in your code. When a function RETURNS TABLE you need to explicitly put one or more RETURN NEXT or RETURN QUERY statements in the body of your code, with a final RETURN statement to indicate the end of the function. See the documentation here: http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html#PLPGSQL-STATEMENTS-RETURNING. What exactly you want to return is not clear but likely candidates are res_count and d.docid.
Other than that, your code could use a real clean-up reducing clutter like:
ws := '''%' || site || '%''' ;
instead of:
ws = '''' || '%' || site || '%' || '''' ;
and:
SELECT COUNT(*) INTO res_count
FROM my_schema.features f,
JOIN my_schema.documents d ON d.docid = f.docid
WHERE term IN (SearchORItems_withComma)
AND d.url LIKE ws
GROUP BY f.docid, d.url;
instead of:
SELECT COUNT(*) INTO res_count
FROM (
SELECT 1 FROM my_schema.features f , my_schema.documents d
WHERE term IN (SearchORItems_withComma)
AND d.docid = f.docid
AND d.url LIKE ws
GROUP BY f.docid, d.url) t;
And you should use the assignment operator (:=) instead of the equality operator in any plpgsql statement that is not a SQL statement.

Related

Doesn't find variable when passing query as parameter

I have a function with a static output that works. (Postgres v.10)
This returns the Amount of users per Server.
Here is the code:
CREATE OR REPLACE FUNCTION public.test()
RETURNS SETOF record
LANGUAGE plpgsql
AS $function$
DECLARE
var_req TEXT;
var_error text;
rec_key record;
cur_key CURSOR FOR Select s.srv,s.host,s.port from public.connections() s where s.online = true;
BEGIN
open cur_key;
loop
fetch cur_key into rec_key;
EXIT WHEN NOT FOUND;
var_req :=
'Select * from dblink(
''host=' || rec_key.host || '
port=' || rec_key.port || '
user=**
password=**
dbname=mydb'',
''
select '''''|| rec_key.srv ||''''' as srv ,count (*) as total from users '') as (srv varchar,total integer)
';
return query execute var_req;
end loop;
close cur_key;
END
$function$
;
Output =
srv total
rp1 50
sr2 41
xy 100
To be able to use reuse this query i want to move out the sql part so that i can pass it as parameter.
CREATE OR REPLACE FUNCTION public.test2(text)
RETURNS SETOF record
LANGUAGE plpgsql
AS $function$
DECLARE
var_req TEXT;
var_error text;
rec_key record;
cur_key CURSOR FOR Select s.srv,s.host,s.port from public.connections() s where s.online = true;
BEGIN
open cur_key;
loop
fetch cur_key into rec_key;
EXIT WHEN NOT FOUND;
var_req :=
'Select * from dblink(
''host=' || rec_key.host || '
port=' || rec_key.port || '
user=**
password=**
dbname=**'',
''
' || $1 || '
';
return query execute var_req;
end loop;
close cur_key;
END
$function$
;
Now when i try to make exact the same query with the dynamic function i don't get it to work.
Like this i am pretty close to my goal but instead of using what's inside the rec_key.srv variable it returns '''''|| rec_key.srv ||''''' :(
select * from public.test2('select ''''''''''''|| rec_key.srv ||'''''''''''' as srv ,count (*) as total from users '') as (srv varchar,total integer)') as (srv varchar,total integer)
Output =
srv total
'''''|| rec_key.srv ||''''' 50
'''''|| rec_key.srv ||''''' 41
'''''|| rec_key.srv ||''''' 100
Can someone explain me how i can call what's inside the variable rec_key.srv with the new function?
Boils down to just this: (!)
SELECT s.srv, t.*
FROM public.connections() s
, dblink('host=' || s.host || ' port=' || s.port || ' user=** password=** dbname=**'
, 'SELECT count(*) FROM users') AS t(total integer);
No wrapper function, no dynamic SQL, no cursor.
Just execute dblink() in an implicit CROSS JOIN LATERAL.
I also added srv to the result like you have in your first function. No need to pipe that through dblink.
See:
What is the difference between LATERAL JOIN and a subquery in PostgreSQL?
Passing arbitrary queries is open to SQL injection. Be sure to understand possible implications and only execute it with trusted input. See:
Demonstrate SQL injection in PL/pgSQL
https://bobby-tables.com/

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;

using variable as part of the statement

I want to use a variable as part of the statement, but it says that "tableref" doesn't exist.
CREATE OR REPLACE FUNCTION ff(tipo_acta integer, hasta date)
RETURNS void AS
$BODY$
DECLARE
tableref varchar;
r record;
BEGIN
if tipo_acta = 1 then
tableref = 't1';
elsif tipo_acta = 2 then tableref = 't2';
else tableref = 't3';
end if;
for r select id from tableref where somedate >= hasta loop
--
end loop;
I tried to use EXECUTE 'select id from ' || tableref || ' where....' but doesn't work either
I thought to get the record first with select id into r from t1 where .. and then use it in the loop, but there seems to be no way to use a record in a loop like that:
FOR r LOOP
....
END LOOP;
You need to use dynamic sql for that. You need to use execute command to do that in PLPG/SQL.
In your code it should be something like:
EXECUTE 'SELECT id FROM ' || tableref || ' WHERE somedate >= $1'
INTO c
USING hasta;

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

Postgres COPY command escapes new lines and tabs

I have a sql function:
CREATE OR REPLACE FUNCTION dump_func()
RETURNS void AS
$BODY$
DECLARE
r record;
loc varchar(100);
BEGIN
for r in (SELECT f.oid as oid, proname, pg_get_functiondef(f.oid) as src
FROM pg_catalog.pg_proc f
INNER JOIN pg_catalog.pg_namespace n ON (f.pronamespace = n.oid)
WHERE n.nspname = 'public') loop
loc = '/Users/raman/Desktop/functions/' || r.proname || '.sql';
raise notice '% - %', r.oid, loc;
raise notice 'func: %', r.src;
EXECUTE format('COPY (
SELECT pg_get_functiondef(f.oid)
FROM pg_catalog.pg_proc f
INNER JOIN pg_catalog.pg_namespace n ON (f.pronamespace = n.oid)
WHERE n.nspname = ''public'' and f.oid=%s
) TO ''%s'' ', r.oid, loc);
end loop;
end
$BODY$
LANGUAGE plpgsql;
which exports each db functions in postgres into a separate file using the COPY command. Everything works except that in the exported sql files new lines and tabs are escaped, for example:
CREATE OR REPLACE FUNCTION public.is_numeric(text)\n RETURNS boolean\n
LANGUAGE plpgsql\n IMMUTABLE\nAS $function$\nDECLARE x
NUMERIC;\nBEGIN\n x = $1::NUMERIC;\n RETURN TRUE;\nEXCEPTION
WHEN others THEN\n RETURN FALSE;\nEND;\n$function$\n
As you can see new lines and tabs are escaped, how to fix that?
Actually, this can be done. Hacky though it is you simply use regexp_split_to_table and split on the \n character. I was just trying to work out the same problem myself. Here's the code I wound up with:
DO $$
DECLARE
v_row record;
v_proname text;
v_sql text;
BEGIN
FOR v_row IN (
SELECT f.proname
FROM pg_catalog.pg_proc AS f
INNER JOIN pg_catalog.pg_namespace AS n ON (f.pronamespace = n.oid)
WHERE n.nspname = 'public' AND f.proisagg = false
) LOOP
SELECT v_row.proname::text INTO v_proname;
EXECUTE FORMAT('COPY (
SELECT regexp_split_to_table(regexp_replace(pg_get_functiondef(oid), ''\t'', '' '', ''g''), ''\n'')
FROM pg_catalog.pg_proc WHERE proname = ''%s'')
TO ''C:\pg\%s.sql'';'
, v_proname, v_proname);
END LOOP;
END $$
Andrew's answer is correct, helped me format it as well.
I came up with a similar function but to print all user defined functions in PSQL. Hope it helps to anyone who needs it:
CREATE OR REPLACE FUNCTION printFunctions(stage VARCHAR, ischema VARCHAR, file_path VARCHAR)
RETURNS VARCHAR AS
$$
DECLARE
func VARCHAR;
file_location TEXT;
BEGIN
FOR func IN SELECT procname FROM listfunctions()
LOOP
SELECT (file_path || '/' || func || '_' || stage || '.sql') INTO file_location;
IF (SELECT regexp_matches(func, '(pldb|plpg)')) IS NULL
THEN
EXECUTE 'COPY ' ||
'(SELECT regexp_split_to_table(regexp_replace(pg_get_functiondef(oid), ''\t'', '' '', ''g''),''\n'') from pg_proc where proname=' ||
'''' || func || '''' ||
' and pronamespace=(select oid from pg_namespace where nspname=' ||
'''' || ischema || ''''
')) TO ' || '''' || file_location || '''';
END IF;
END LOOP;
RETURN stage;
END
$$
LANGUAGE 'plpgsql';
You can call it with:
SELECT * FROM printFunctions('dev', 'public', '/tmp');