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

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;
$$;

Related

function does not exist error in 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).

Update Null columns to Zero dynamically in Redshift

Here is the code in SAS, It finds the numeric columns with blank and replace with 0's
DATA dummy_table;
SET dummy_table;
ARRAY DUMMY _NUMERIC_;
DO OVER DUMMY;
IF DUMMY=. THEN DUMMY=0;
END;
RUN;
I am trying to replicate this in Redshift, here is what I tried
create or replace procedure sp_replace_null_to_zero(IN tbl_nm varchar) as $$
Begin
Execute 'declare ' ||
'tot_cnt int := (select count(*) from information_schema.columns where table_name = ' || tbl_nm || ');' ||
'init_loop int := 0; ' ||
'cn_nm varchar; '
Begin
While init_loop <= tot_cnt
Loop
Raise info 'init_loop = %', Init_loop;
Raise info 'tot_cnt = %', tot_cnt;
Execute 'Select column_name into cn_nm from information_schema.columns ' ||
'where table_name ='|| tbl_nm || ' and ordinal_position = init_loop ' ||
'and data_type not in (''character varying'',''date'',''text''); '
Raise info 'cn_nm = %', cn_nm;
if cn_nm is not null then
Execute 'Update ' || tbl_nm ||
'Set ' || cn_nm = 0 ||
'Where ' || cn_nm is null or cn_nm =' ';
end if;
init_loop = init_loop + 1;
end loop;
End;
End;
$$ language plpgsql;
Issues I am facing
When I pass the Input parameter here, I am getting 0 count
tot_cnt int := (select count(*) from information_schema.columns where table_name = ' || tbl_nm || ');'
For testing purpose I tried hardcode the table name inside proc, I am getting the error amazon invalid operation: value for domain information_schema.cardinal_number violates check constraint "cardinal_number_domain_check"
Is this even possible in redshift, How can I do this logic or any other workaround.
Need Expertise advise here!!
You can simply run an UPDATE over the table(s) using the NVL(cn_nm,0) function
UPDATE tbl_raw
SET col2 = NVL(col2,0);
However UPDATE is a fairly expensive operation. Consider just using a view over your table that wraps the columns in NVL(cn_nm,0)
CREATE VIEW tbl_clean
AS
SELECT col1
, NVL(col2,0) col2
FROM tbl_raw;

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;

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;

In postgres how can I delete all columns that share the same prefix

I have been using the following code for dropping all tables that share the same prefix (in this case delete all tables that their name starts with 'supenh_'):
DO
$do$
DECLARE
_tbl text;
BEGIN
FOR _tbl IN
SELECT quote_ident(table_schema) || '.'
|| quote_ident(table_name) -- escape identifier and schema-qualify!
FROM information_schema.tables
WHERE table_name LIKE 'supenh_' || '%' -- your table name prefix
AND table_schema NOT LIKE 'pg_%' -- exclude system schemas
LOOP
-- RAISE NOTICE '%',
EXECUTE
'DROP TABLE ' || _tbl;
END LOOP;
END
$do$;
Is there a way to amend this code / or to use a different script in order to delete from one specific table all the columns that start with the same prefix (for example, 'patient1_') ?
You could write it as PL/pgSQL function:
CREATE OR REPLACE FUNCTION drop_columns_with_prefix(tbl_name TEXT, column_prefix TEXT) RETURNS VOID AS
$BODY$
DECLARE
_column TEXT;
BEGIN
FOR _column IN
SELECT quote_ident(column_name)
FROM information_schema.columns
WHERE table_name = tbl_name
AND column_name LIKE column_prefix || '%'
AND table_schema NOT LIKE 'pg_%'
LOOP
-- RAISE NOTICE '%',
EXECUTE
'ALTER TABLE ' || tbl_name || ' DROP COLUMN ' || _column;
END LOOP;
END
$BODY$
LANGUAGE plpgsql VOLATILE;
Call it using:
SELECT drop_columns_with_prefix('tbl_name', 'prefix_');
Or if you don't want to use it as a function:
DO
$do$
DECLARE
_column TEXT;
BEGIN
FOR _column IN
SELECT quote_ident(column_name)
FROM information_schema.columns
WHERE table_name = 'tbl_name'
AND column_name LIKE 'prefix_%'
AND table_schema NOT LIKE 'pg_%'
LOOP
-- RAISE NOTICE '%',
EXECUTE
'ALTER TABLE tbl_name DROP COLUMN ' || _column;
END LOOP;
END
$do$