function does not exist error in PostgreSQL - postgresql

I'm writing procedure in PostgreSQL While writing am getting error for FN_GETCD like
function fn_getcd(integer) does not exist IS THE NEW VALUE
but i have written function for fn_getcd like this
CREATE OR REPLACE FUNCTION public.fn_getcd(
v_entity_num text)
RETURNS timestamp without time zone
LANGUAGE 'plpgsql'
COST 100
STABLE SECURITY DEFINER PARALLEL UNSAFE
AS $BODY$
DECLARE
O_CBD timestamp;
BEGIN
SELECT
TO_DATE(TO_CHAR(clock_timestamp(),'DD-MON-YYYY'),'DD-MON-YYYY')
INTO STRICT
O_CBD
;
RETURN O_CBD;
EXCEPTION
WHEN OTHERS THEN
RETURN O_CBD;
END;
$BODY$;
please help me to solve this error
tocheck :=cast(P_ENTITY_CODE as TEXT);
RAISE NOTICE 'GOT HERE tocheck :% IS THE NEW VALUE',tocheck;
EXECUTE 'INSERT INTO BENMASTHIST SELECT BENMAST_ENTITY_CODE, BENMAST_CUSTOMER_CODE, BENMAST_BEN_CODE,
FN_GETCD(' ||
tocheck || '),' || W_BEN_HIST_SL ||
', BENMAST_NAME, BENMAST_MOBILE_NO,
BENMAST_EMAIL_ID, BENMAST_PHOTO, BENMAST_SOURCE, BENMAST_CR_BY, BENMAST_CR_ON,
BENMAST_MO_BY, BENMAST_MO_ON, BENMAST_AU_BY, BENMAST_AU_ON, TBA_KEY,BENMAST_FROM
FROM BENMAST WHERE BENMAST_ENTITY_CODE =$1 AND BENMAST_CUSTOMER_CODE =$2 AND BENMAST_BEN_CODE =$3'
USING P_ENTITY_CODE, P_CUST_CODE, P_BEN_CODE;
-- RAISE NOTICE 'GOT HERE BENMASTHIST :% IS THE NEW VALUE',BENMASTHIST;
EXCEPTION
WHEN UNIQUE_VIOLATION THEN
P_SUC_FLAG := 'S';
WHEN OTHERS THEN
--- DBMS_OUTPUT.put_line(SQLERRM);
P_SUC_FLAG := 'F';
P_ERROR := SQLERRM;
RAISE NOTICE 'SQLERRM :% IS THE NEW VALUE',SQLERRM;
P_ERROR := 'BENREG013'; --'Error While Inserting Into Benmast';
-- ROLLBACK;
END;

reference: dollar quote: https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-DOLLAR-QUOTING
Sometimes you don't know how much quote you need.You can first compose the string then do the execute command.
a horse already said that you function can be as simple as return current_date::timestamp;
If you add more quote then you function will work.
DO $$
DECLARE
_vstr text := 'test';
_vtime timestamp;
_sql text;
_sql1 text;
_sql0 text;
BEGIN
_sql := ' select fn_getcd( ' || '''' || _vstr || '''' || ' )';
_sql1 := ' select fn_getcd( ' || $a$ '$a$ || _vstr || $b$' $b$ || ' )';
_sql0 := ' select fn_getcd( ' || _vstr || ' )';
RAISE NOTICE '_sql is %', _sql;
RAISE NOTICE '_sql1 is %', _sql1;
RAISE NOTICE '_sql1 is %', _sql0;
EXECUTE _sql INTO _vtime;
RAISE NOTICE 'v_time is % ', _vtime;
END
$$
LANGUAGE plpgsql;
You string is like _sql0 format. You need use dollar quote or simple put more single quote in it. (_sql or _sql1).

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')
```

PostgreSQL - How to change Length of Different Tables Columns with Cursors

I'm changing column lengths for all necessary tables but I got some errors.
I am using PostgreSQL 10 and pgAdmin4 but I couldn't see error messages.
I guess, because of the pgAdmin version. Firstly, I couldn't declare CURSOR, I don't know why? I had succeeded on Oracle.
Can you help me about this situation? My code as shown below;
do $$
DECLARE
modify_column_cursor CURSOR FOR
SELECT 'ALTER TABLE "schema_name"."' || C.TABLE_NAME || '" ALTER COLUMN'|| C.COLUMN_NAME||' varchar(128)' as alter_sql, TABLE_NAME t_name, COLUMN_NAME c_name, 128 c_length FROM information_schema.columns c WHERE column_name LIKE '%PROD_NUM' and TABLE_NAME not like '%STAGING%' UNION
SELECT 'ALTER TABLE "schema_name"."' || C.TABLE_NAME || '" ALTER COLUMN'|| C.COLUMN_NAME||' varchar(128)' as alter_sql, TABLE_NAME t_name, COLUMN_NAME c_name, 128 c_length FROM information_schema.columns c WHERE column_name LIKE '%PREV_PROD_NUM' and TABLE_NAME not like '%STAGING%';
--.
--.
--.
sql_stmt VARCHAR(800);
c_length numeric;
c_length_db numeric;
flag numeric := 0;
BEGIN
--OPEN modify_column_cursor;
for modify_column in modify_column_cursor LOOP
raise notice 'asd : %', modify_column.ex_name;
sql_stmt := 'SELECT character_maximum_length FROM information_schema.columns WHERE column_name = ''' || modify_column.c_name || ''' and table_name = ''' || modify_column.t_name || ''' and table_schema = ''schema_name''';
EXECUTE sql_stmt INTO c_length_db;
IF c_length_db > modify_column.c_length THEN
sql_stmt := 'select max(length(' || modify_column.c_name || ')) from "schema_name".' || modify_column.t_name;
EXECUTE sql_stmt INTO c_length;
IF c_length > modify_column.c_length THEN
flag := 1;
raise notice '--------------INCONSISTENED FIELD FOUND---------------';
raise notice '% - % - % Not Ok! Default field size in db: %', modify_column.t_name, modify_column.c_name, modify_column.c_length, c_length_db;
raise notice '% - % - % Not Ok! Field has a data with length: %', modify_column.t_name, modify_column.c_name, modify_column.c_length, c_length;
raise notice '-------------------------------------------------------';
raise notice ' ';
ELSE
NULL;
END IF;
ELSE
NULL;
END IF;
END LOOP;
IF flag = 0 THEN
FOR modify_column IN modify_column_cursor
LOOP
EXECUTE modify_column.alter_sql;
END LOOP;
raise notice ' ';
raise notice '-----FIELDS ARE SUCCESSFULLY MODIFIED-----';
ELSE
raise notice ' ';
raise notice '-----ERROR: SOME FIELDS ARE NOT SUITABLE TO ALTER-----';
END IF;
end$$;
I am on PostgreSQL 11 but if I remember well its pretty much the same.
If you want to absolutely use a loop to do that I corrected a little your code and injected a debug table.
You had a missing blank space and a wrong declaration of the cursor. I simply got ride of it.
You can read this excellent article on cursor on postgresql if you want : http://www.postgresqltutorial.com/plpgsql-cursor/
do $$
DECLARE
sql_stmt VARCHAR(800);
c_length numeric;
c_length_db numeric;
flag numeric := 0;
modify_column record;
begin
create table if not exists [your_schema_name].test (query varchar);
for modify_column in
SELECT 'ALTER TABLE "'||[your_schema_name]||'"."' || C.TABLE_NAME || '" ALTER COLUMN '|| C.COLUMN_NAME||' varchar(128)' as alter_sql
, TABLE_NAME t_name
, COLUMN_NAME c_name
, 128 c_length
FROM information_schema.columns c
where table_schema = ''||[your_schema_name]||''
LOOP
--raise notice 'asd : %', modify_column.ex_name;
sql_stmt := 'SELECT character_maximum_length FROM information_schema.columns WHERE column_name = ''' || modify_column.c_name || ''' and table_name = ''' || modify_column.t_name || ''' and table_schema = '''||[your_schema_name]||'''';
insert into [your_schema_name].test values (sql_stmt);
EXECUTE sql_stmt INTO c_length_db;
IF c_length_db > modify_column.c_length THEN
sql_stmt := 'select max(length(' || modify_column.c_name || ')) from "'||[your_schema_name]||'".' || modify_column.t_name;
--EXECUTE sql_stmt INTO c_length;
insert into [your_schema_name].test values (sql_stmt);
IF c_length > modify_column.c_length THEN
flag := 1;
raise notice '--------------INCONSISTENED FIELD FOUND---------------';
raise notice '% - % - % Not Ok! Default field size in db: %', modify_column.t_name, modify_column.c_name, modify_column.c_length, c_length_db;
raise notice '% - % - % Not Ok! Field has a data with length: %', modify_column.t_name, modify_column.c_name, modify_column.c_length, c_length;
raise notice '-------------------------------------------------------';
raise notice ' ';
ELSE
NULL;
END IF;
ELSE
NULL;
END IF;
END LOOP;
IF flag = 0 THEN
--FOR modify_column IN modify_column_cursor
-- LOOP
-- EXECUTE modify_column.alter_sql;
--END LOOP;
raise notice ' ';
raise notice '-----FIELDS ARE SUCCESSFULLY MODIFIED-----';
ELSE
raise notice ' ';
raise notice '-----ERROR: SOME FIELDS ARE NOT SUITABLE TO ALTER-----';
END IF;
end;
$$;

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;

Loop each array's item Postgresql

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'].

Autopartitioning Postgresql for Zabbix

The goal, аutopartitioning for 7 days. And after 14 days to delete the old partitions. In this example, everything works. But, when I try to write data of the form :
insert into history_str (itemid, clock, ns, value) values (40,151,3722, '3.0.3');
I get an error
ERROR: syntax error at or near ".3"
LINE 1: ... istory_str_2018_02_07 values (40,151,3.0.3,3722 ...
                                                    ^
QUERY: INSERT INTO history_str_2018_02_07 values (40,151,3.0.3,3722);
CONTEXT: PL / pgSQL function create_partition_other () line 37 at EXECUTE
Here is the actual code example
CREATE OR REPLACE FUNCTION create_partition() RETURNS trigger AS
$BODY$
DECLARE
partition_name TEXT;
partition_week TEXT;
partitions_names TEXT;
date_search TEXT;
sql_search TEXT;
var_data TEXT;
typeof BOOL;
BEGIN
partition_week := to_char(to_timestamp(NEW.clock),'IW');
RAISE INFO 'Week now: %',partition_week;
partition_name := TG_TABLE_NAME || '_' || to_char(to_timestamp(NEW.clock),'YYYY_MM') || '_' || partition_week;
RAISE INFO 'Master Table: %',TG_TABLE_NAME;
RAISE INFO 'Partit. name: %',partition_name;
IF NOT EXISTS(SELECT relname FROM pg_class WHERE relname = partition_name) THEN
RAISE INFO 'Create table';
EXECUTE 'CREATE TABLE ' || partition_name || ' (check (clock >= ' || quote_literal(NEW.clock) || ' AND clock < ' || quote_literal(NEW.clock + integer '7' * integer '86400') || ')) INHERITS (' || TG_TABLE_NAME || ');';
EXECUTE 'INSERT INTO create_tables_date (name,date) values (' || quote_literal(partition_name) || ',' || quote_literal(to_timestamp(NEW.clock)) || ');';
date_search := quote_literal(date (to_char(to_timestamp(NEW.clock),'YYYY_MM_DD'))-integer '7');
RAISE INFO 'Search data: %',date_search;
sql_search := 'SELECT name FROM create_tables_date WHERE date < ' || date_search || ';';
for partitions_names in EXECUTE sql_search LOOP
IF partitions_names IS NOT NULL THEN
RAISE INFO 'DROP, DELETE: %',partitions_names;
EXECUTE 'DROP TABLE ' || partitions_names || ';';
EXECUTE 'DELETE FROM create_tables_date WHERE name=' || quote_literal(partitions_names) || ';';
END IF;
END LOOP;
END IF;
RAISE INFO 'Value: %',NEW.value;
var_data := 'INSERT INTO ' || partition_name || ' values ' || NEW || ';';
RAISE INFO 'SQL: %',var_data;
EXECUTE var_data;
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
I found out that the problem when writing the values of being in NEW.value.And after replacing the characters [(), \] with _, the problem was solved.
That is, I redefine before an insert NEW.value
NEW.value := quote_literal(regexp_replace(NEW.value,'[(),\ ]','_','g'));
But this is the case if I try to write to a table with a column value, and if there is no other table, I have to write many identical functions for each table. What is bad.
Can you know why this situation arises with these symbols?
PostgreSQL 9.5.9
You could try USING and expand row with asterisk:
var_data := 'INSERT INTO ' || partition_name || ' values ($1.*);';
RAISE INFO 'SQL: %',var_data;
EXECUTE var_data using new;