I am trying to call this function
CREATE or replace FUNCTION get_column_names(param text)
RETURNS text AS $get_column_names$
DECLARE
return_value text;
x record;
y int;
begin
return_value := '';
y := 0;
for x in SELECT *
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = param
ORDER BY ordinal_position
loop
if (y = 0)
then
return_value = x.column_name;
else
return_value := return_value ||','|| x.column_name;
end if;
Y = Y+1;
end loop;
return return_value;
END;
$get_column_names$ LANGUAGE plpgsql;
Which this function works here
select get_column_names('users');
Results
first_name,middle_name,last_name,gender,locale,auth_user_id,identity_id,active,date_created,provisioned,user_id,timezone,last_seen
However when I use it in another function I get an error.
Here is the other function I am calling it from.
CREATE OR REPLACE FUNCTION process_users_audit() RETURNS TRIGGER AS $users_audit$
BEGIN
--
-- Create a row in users_audit to reflect the operation performed on users
--
IF (TG_OP = 'DELETE') THEN
INSERT INTO users_audit (user_audit_id, stamp, operation, db_user, get_column_names(TG_TABLE_NAME)) values (uuid_generate_v4(), now(), 'D', user, OLD.*);
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO users_audit (user_audit_id, stamp, operation, db_user, get_column_names(TG_TABLE_NAME)) values (uuid_generate_v4(), now(), 'U', user, NEW.*);
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO users_audit (user_audit_id, stamp, operation, db_user, get_column_names(TG_TABLE_NAME)) values (uuid_generate_v4(), now(), 'I', user, NEW.*);
RETURN NEW;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$users_audit$ LANGUAGE plpgsql;
The error I am receiving is this (same as title).
SQL Error [42601]: ERROR: syntax error at or near "("
Position: 406
UPDATE
#Ed Brook's answer worked except for had to change how NEW was being used. Will remove this update once he has updated his answer but here is what worked.
CREATE OR REPLACE FUNCTION process_audit_table() RETURNS TRIGGER AS $audit_table$
BEGIN
--
-- Create a row in the requesting table's audit to reflect the operation performed on users,
-- make use of the special variable TG_OP to work out the operation.
--
IF (TG_OP = 'DELETE') then
EXECUTE 'INSERT INTO ' || concat(TG_TABLE_NAME, '_audit') || ' (' || get_primary_key_name(concat(TG_TABLE_NAME, '_audit')) || ', stamp, operation, db_user, ' || get_column_names(TG_TABLE_NAME) || ') values (uuid_generate_v4(), now(), ''D'', user, $1.*);'
USING OLD;
ELSIF (TG_OP = 'UPDATE') then
EXECUTE 'INSERT INTO ' || concat(TG_TABLE_NAME, '_audit') || ' (' || get_primary_key_name(concat(TG_TABLE_NAME, '_audit')) || ', stamp, operation, db_user, ' || get_column_names(TG_TABLE_NAME) || ') values (uuid_generate_v4(), now(), ''U'', user, $1.*);'
USING NEW;
ELSIF (TG_OP = 'INSERT') then
EXECUTE 'INSERT INTO ' || concat(TG_TABLE_NAME, '_audit') || ' (' || get_primary_key_name(concat(TG_TABLE_NAME, '_audit')) || ', stamp, operation, db_user, ' || get_column_names(TG_TABLE_NAME) || ') values (uuid_generate_v4(), now(), ''I'', user, $1.*);'
USING NEW;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$audit_table$ LANGUAGE plpgsql;
Also added some extra bits in there like dynamically getting the audit table name and then get_primary_key_name below is that function.
CREATE OR REPLACE FUNCTION get_primary_key_name(table_name text)
RETURNS text AS $primary_key_name$
DECLARE
return_value text;
BEGIN
SELECT pg_attribute.attname INTO return_value
FROM pg_index, pg_class, pg_attribute, pg_namespace
WHERE pg_class.oid = table_name::regclass
AND indrelid = pg_class.oid
AND nspname = 'public'
AND pg_class.relnamespace = pg_namespace.oid
AND pg_attribute.attrelid = pg_class.oid
AND pg_attribute.attnum = any(pg_index.indkey)
AND indisprimary;
RETURN return_value;
END
$primary_key_name$ LANGUAGE plpgsql;
You may need to use EXECUTE, as I don't think you can have function calls in the column list?
ref: https://www.postgresql.org/docs/current/plpgsql-statements.html#PLPGSQL-STATEMENTS-EXECUTING-DYN
So, something like this?
EXECUTE 'INSERT INTO users_audit (user_audit_id, stamp, operation, db_user, ' || get_column_names(TG_TABLE_NAME) || ') values (uuid_generate_v4(), now(), ''U'', user, NEW.*)';
Related
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;
I'm writing a function where I can store specific fields from a table before to delete the record, to keep an historical.
The insertion is working well, but the Delete sometimes is returning Delete 0, even if I have hardcode the id field that I want to delete.
This is my function:
create or replace function deletefromtable(_schema text, _table text, _filter text, _userid int)
returns json as
$func$
DECLARE _record json;
DECLARE target text;
DECLARE mykey TEXT;
DECLARE newvalue TEXT;
DECLARE oldvalue TEXT;
DECLARE columnname TEXT;
begin
SET session_replication_role = replica;
execute format ('select row_to_json(t) from (select * from ' || _schema ||'.' || _table || ' WHERE ' || _filter || ' ) t') into _record;
raise notice 'record: %', _record;
FOR target IN SELECT col from track_settings(_table) LOOP
with vw_listing (new_record) as ( values
(_record::jsonb)
)
SELECT (new_record ->> target)::text INTO newvalue
FROM vw_listing LIMIT 1;
raise notice 'newvalue: %', newvalue;
execute format ('insert into track_history (created_at, table_name, column_name, table_id, user_id, new_val, old_val, pg_user)
values (''' || Now() || ''', ''' || _table || ''', ''' || target || ''', ''' || _filter || ''' ,' || _userid || ', null, ''' || newvalue || ''', current_user )');
END LOOP;
execute ('DELETE FROM public.user WHERE id = 1 ;'); -- THIS LINE IS NOT EXECUTED
SET session_replication_role = default;
RETURN _record;
COMMIT;
end
$func$ language plpgsql;
I tried to enable SET session_replication_role = replica; or SET session_replication_role = default; but is still not working.
The function hasn't any errors and is executing all the statements.
Can someone help me to fix it?
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;
I created a table partition that will create a table if it is not yet existing the table names are on a monthly basis. I need this function to return the inserted ID but I'm getting this error of column "partition" does not exist it seems that my schema(partition) is considered column in this code
CREATE OR REPLACE FUNCTION partition.itinerary_partition_function()
RETURNS TRIGGER AS
$BODY$
DECLARE
reflowId bigint;
_tablename text;
_startyear text;
_startmonth text;
_fulltablename text;
BEGIN
--Takes the current inbound "time" value and determines when midnight is for the given date
_startyear := to_char(now(), 'YYYY');
_startmonth := to_char(now(), 'MM');
_tablename := 'itinerary_'||_startyear || '_' || _startmonth;
_fulltablename := 'partition.' || _tablename;
-- Check if the partition needed for the current record exists
PERFORM 1
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
AND c.relname = _tablename
AND n.nspname = 'partition';
-- If the partition needed does not yet exist, then we create it:
-- Note that || is string concatenation (joining two strings to make one)
IF NOT FOUND THEN
EXECUTE 'CREATE TABLE partition.' || quote_ident(_tablename) || '()INHERITS (partition.itinerary)';
-- Table permissions are not inherited from the parent.
-- If permissions change on the master be sure to change them on the child also.
EXECUTE 'ALTER TABLE partition.' || quote_ident(_tablename) || ' OWNER TO postgres';
-- Indexes are defined per child, so we assign a default index that uses the partition columns
EXECUTE 'CREATE INDEX ' || quote_ident(_tablename||'_indx1') || ' ON partition.' || quote_ident(_tablename) || ' (id)';
END IF;
BEGIN
EXECUTE format('INSERT INTO %I SELECT $1.*', "partition." || _tablename)
USING NEW;
RETURN NEW;
END;
END;
$BODY$
LANGUAGE plpgsql;
After this code I am calling it in another insert function
CREATE OR REPLACE FUNCTION partition.insert_data(username text,jsonData jsonb) RETURNS bigint AS
$$
DECLARE reflowId bigint;
BEGIN
INSERT INTO reflow_partition.itinerary(username, data)
VALUES (username, jsonData) RETURNING id;
END;
$$
LANGUAGE plpgsql;
Try changing this:
EXECUTE format('INSERT INTO %I SELECT $1.*', "partition." || _tablename)
to this:
EXECUTE format('INSERT INTO %1$I.%2$I SELECT $1.*', 'partition', _tablename)
I have two tables
Table A
col1 col2 col3
Table B
table_name column_name new_value old_value
if any update happened on table A it will insert the data on table B
the output of table B is==>
table_name column_name new_value old_value
---------------- ------------------ -------------- -----------
A {col1}
A {col1,col2} {col1.new_value, {col1.old_value,
col2.new_value} col2.old_value},
so anyone can tell me how to capture the column_names and it store data target table like arrays
Try this
Use Trigger Function
CREATE OR REPLACE FUNCTION update_history()
RETURNS trigger AS
$BODY$
DECLARE col_name VARCHAR[];
DECLARE od_value VARCHAR[];
DECLARE ne_value VARCHAR[];
DECLARE each_column RECORD;
DECLARE each_entity RECORD;
DECLARE column_name VARCHAR;
DECLARE old_value VARCHAR;
DECLARE new_value VARCHAR;
FOR each_column IN
select c.column_name --- Get the all column names in affected table
from information_schema.columns c
where(table_name = tg_relname And c.TABLE_SCHEMA = TG_TABLE_SCHEMA)
LOOP
FOR each_entity IN --- Its used to get old and new columns value
EXECUTE 'SELECT text((' || quote_literal(OLD.*) || '::"' || tg_table_schema || '"."' || tg_relname || '")."' || each_column.column_name || '") as old_val,
text((' || quote_literal(NEW.*) || '::"' || tg_table_schema || '"."' || tg_relname || '")."' || each_column.column_name || '")
AS new_val
FROM "' || tg_table_schema || '"."' || tg_relname || '";'
LOOP
old_value = each_entity.old_val;
new_value = each_entity.new_val;
IF old_value != new_value THEN
i=i+1;
col_name[i]=each_column.column_name;
od_value[i]=old_value;
ne_value[i]=new_value;
END IF;
END LOOP;
END LOOP;
INSERT INTO B
(
tablename,
columnnames,
oldvalues,
newvalues
)
VALUES
(
tg_relname,
col_name,
od_value,
ne_value
);
End if;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
I thinks it's a good case to use hstore extension of PostgreSQL:
create or replace function history_trigger_func()
returns trigger AS
$$
begin
insert into TableB
select
tg_relname,
case when tg_op in ('UPDATE', 'INSERT') then hstore(new) end,
case when tg_op in ('UPDATE', 'DELETE') then hstore(old) end;
return null;
end;
$$
language plpgsql;
create trigger tr_history_trigger after insert or update or delete on TableA
for each row execute procedure history_trigger_func();
sql fiddle demo
You can extend this further by removing columns which are not changing, or, if you're using PostgreSQL 9.3, you can use JSON instead of hstore.