I have the following postgres stored procedure:
CREATE OR REPLACE PROCEDURE
schema.MyProcedure()
AS $$
DECLARE
RowCount int;
BEGIN
SELECT cnt INTO RowCount
FROM (
SELECT COUNT(*) AS cnt
FROM MySchema.MyTable
) AS sub;
RAISE NOTICE 'RowCount: %', RowCount;
END;
$$
LANGUAGE plpgsql;
which "prints" out the row count of the static table MySchema.MyTable. How can it make it so I pass the Table and Schema name as an input.
eg:
CREATE OR REPLACE PROCEDURE
schema.MyProcedure(MySchema_In varchar, MyTable_In varchar)
AS $$
DECLARE
RowCount int;
BEGIN
SELECT cnt INTO RowCount
FROM (
SELECT COUNT(*) AS cnt
FROM || **MySchema_In** || . || **MyTable_In** ||
) AS sub;
RAISE NOTICE 'RowCount: %', RowCount;
END;
$$
LANGUAGE plpgsql;
You should use format() instead of concatenating the strings with || and then EXECUTE ... INTO to get the query's result, e.g.
CREATE OR REPLACE PROCEDURE MyProcedure(MySchema_In varchar, MyTable_In varchar)
AS $$
DECLARE RowCount int;
BEGIN
EXECUTE FORMAT('SELECT count(*) FROM %I.%I',$1,$2) INTO RowCount;
RAISE NOTICE 'RowCount: %', RowCount;
END;
$$
LANGUAGE plpgsql;
Hi,
the function F_B() returns in bracket "(i,m)" instead of 2 columns "i" and "m"!
CREATE FUNCTION F_A
(
i_productionsitename IN TEXT,
i_printjobid IN TEXT,
i_isnewlycreated IN INT,
o_returncode OUT TEXT,
o_message OUT TEXT
) AS $$
DECLARE
BEGIN
Select 'i', 'm' into o_ReturnCode, o_message;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM F_A('DRM', '42',1);
=> i & m
CREATE FUNCTION F_B
(
i_productionsitename IN TEXT,
i_printjobid IN TEXT,
i_isnewlycreated IN INT,
o_returncode OUT TEXT,
o_message OUT TEXT
) AS $$
DECLARE
rec RECORD;
l_ReturnCode TEXT;
l_Message TEXT;
BEGIN
o_ReturnCode := 'l_OK';
o_Message := 'l_Kein Problem';
Select F_A (i_ProductionSiteName, i_PrintJobID , i_isnewlycreated) INTO l_ReturnCode, l_Message ; --rec ;
o_ReturnCode := l_ReturnCode; --' rec.o_message;
o_Message := l_Message;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM F_B('DRM', '42',1);
=> gives (i,m) & null
Why is second column empty, but first has the 2 values?
What is wrong?
Thanks
I am attempting to create a function that will determine if a record exists for our applications developers to help simplify things.
CREATE FUNCTION records_exist(schema_name VARCHAR(255), table_name VARCHAR(255), field_name VARCHAR(255), field_value VARCHAR(255))
RETURNS BOOLEAN
LANGUAGE plpgsql
AS $$
DECLARE
_schema_name ALIAS FOR $1;
_table_name ALIAS FOR $2;
_field_name ALIAS FOR $3;
_field_value ALIAS FOR $4;
_sql_string VARCHAR(5000);
BEGIN
_sql_string= 'SELECT EXISTS(SELECT 1 FROM ' || _schema_name || '.' || _table_name || ' WHERE ' || _field_name || '=' || _field_value || ');';
RETURN BOOLEAN EXECUTE _sql_string;
--RETURN TABLE EXECUTE _sql_string; doesn't work
END
$$;
The following should work, but I keep getting ERROR: syntax error at or near "EXECUTE"
Please let me know the error of my ways.
also, your dynamic string is bad, better use:
select format('select exists from %I.%I where %I = %L',schema_name,table_name,field_name, field_value) into _sql_string;
also, you realize your _field_value has no check for data type?..
so in short, it could be smth similar to:
db=# CREATE or replace FUNCTION records_exist(schema_name VARCHAR(255), table_name VARCHAR(255), field_name VARCHAR(255), field_value VARCHAR(255))
RETURNS BOOLEAN
LANGUAGE plpgsql
AS $$
DECLARE
_sql text;
_b boolean;
BEGIN
_sql := format('select exists (select null from %I.%I where %I = %L)',schema_name,table_name,field_name, field_value);
execute _sql into _b;
return _b;
END
$$;
CREATE FUNCTION
Time: 10.680 ms
db=# select * from records_exist('pg_catalog','pg_database','datname','postgres'); records_exist
---------------
t
(1 row)
Time: 59.472 ms
I would like to know if and there is some way to get rid code like this:
CREATE FUNCTION parent_function (json json, OUT response_status integer, OUT response json) RETURNS record
LANGUAGE plpgsql
AS $$
DECLARE
BEGIN
if <condition> then
<function_call1(json,response_status,response)>;
return;
elsif <condition> then
<function_call2(json,response_status,response)>;
return;
elsif <condition> then
<function_call3(json,response_status,response)>;
return;
end if;
END;
$$
I would like to move these conditions into separate functions where I would like based on what the function returns(there are some output parameters I need) terminate parent function without throwing exceptions and catch them in parent function?
What I would like to achive code like this:
CREATE FUNCTION parent_function (json json, OUT response_status integer, OUT response json) RETURNS record
LANGUAGE plpgsql
AS $$
DECLARE
BEGIN
<check_function1(json,response_status,response)>;
<check_function2(json,response_status,response)>;
<check_function3(json,response_status,response)>;
END;
$$
After calling message and output parameters set from function terminate further processing. Is it somehow posible?
Thanks,
Lukas
you want something like this example?
CREATE OR REPLACE FUNCTION aa_test_check1( IN test_value integer)
RETURNS TABLE( problem boolean , problemmessage text ) AS
$BODY$
begin
problem := false;
if $1 <10 then
problem := true;
problemmessage := 'integer is smaller than 10';
end if;
return next;
end;
$BODY$ LANGUAGE plpgsql IMMUTABLE;
CREATE OR REPLACE FUNCTION aa_test_check2( IN test_value integer)
RETURNS TABLE( problem boolean , problemmessage text ) AS
$BODY$
begin
problem := false;
if $1 >20 then
problem := true;
problemmessage := 'integer is greater than 20';
end if;
return next;
end;
$BODY$ LANGUAGE plpgsql IMMUTABLE;
CREATE OR REPLACE FUNCTION aa_test_check3( IN test_value integer)
RETURNS TABLE( problem boolean , problemmessage text ) AS
$BODY$
begin
problem := false;
if $1 =10 then
problem := true;
problemmessage := 'something is wrong, it cant be 10';
end if;
return next;
end;
$BODY$ LANGUAGE plpgsql IMMUTABLE;
CREATE OR REPLACE FUNCTION aa_test_main( IN test_value integer)
RETURNS TABLE( status_id integer , problemmessage text ) AS
$BODY$
declare
temp_problem boolean;
temp_problemmessage text;
begin
status_id := 0;
problemmessage := 'no problem, none at all';
select * from aa_test_check1($1)
into temp_problem, temp_problemmessage;
if temp_problem then
status_id := 1;
problemmessage := temp_problemmessage;
return next;
return;
end if;
select * from aa_test_check2($1)
into temp_problem, temp_problemmessage;
if temp_problem then
status_id := 2;
problemmessage := temp_problemmessage;
return next;
return;
end if;
select * from aa_test_check3($1)
into temp_problem, temp_problemmessage;
if temp_problem then
status_id := 3;
problemmessage := temp_problemmessage;
return next;
return;
end if;
return next;
end;
$BODY$
LANGUAGE plpgsql IMMUTABLE
COST 100
ROWS 100;
select * from aa_test_main(10)
I've written 3 functions to log transactions to designated tables:
CREATE OR REPLACE FUNCTION log_sites() RETURNS TRIGGER AS $body$
DECLARE
target_row sites%ROWTYPE;
BEGIN
IF (TG_OP = 'DELETE') THEN
-- No NEW row
target_row = OLD;
ELSE
target_row = NEW;
END IF;
INSERT INTO sites_history (transaction_type,
transaction_time,
site_id,
address,
name,
shared_key)
VALUES (TG_OP,
NOW(),
target_row.site_id,
target_row.address,
target_row.name,
target_row.shared_key);
RETURN target_row;
END;
$body$
LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION log_licenses() RETURNS TRIGGER AS $body$
DECLARE
target_row licenses%ROWTYPE;
BEGIN
IF (TG_OP = 'DELETE') THEN
target_row = OLD;
ELSE
target_row = NEW;
END IF;
INSERT INTO licenses_history (transaction_type,
transaction_time,
license_id,
start_date,
expiration_date,
site_id)
VALUES (TG_OP,
NOW(),
target_row.license_id,
target_row.start_date,
target_row.expiration_date,
target_row.site_id);
RETURN target_row;
END;
$body$
LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION log_clients() RETURNS TRIGGER AS $body$
DECLARE
target_row clients%ROWTYPE;
BEGIN
IF (TG_OP = 'DELETE') THEN
target_row = OLD;
ELSE
target_row = NEW;
END IF;
INSERT INTO clients_history (transaction_type,
transaction_time,
mac_address,
hardware,
license_id,
site_id)
VALUES (TG_OP,
NOW(),
target_row.mac_address,
target_row.hardware,
target_row.license_id,
target_row.site_id);
RETURN target_row;
END;
$body$
LANGUAGE plpgsql;
This results in a big ugly block of PL/pgSQL, which nobody at my job is especially familiar with, myself included. A coworker suggested it'd be nice to consolidate/DRYify all this stuff, but for the life of me, I'm not sure how, especially considering each table needs a separate trigger, and the way triggers pass data to their functions. Any suggestions?
ETA:
1) Here are the triggers:
CREATE TRIGGER sites_log
AFTER INSERT OR UPDATE OR DELETE
ON sites
FOR EACH ROW EXECUTE PROCEDURE log_transactions();
CREATE TRIGGER licenses_log
AFTER INSERT OR UPDATE OR DELETE
ON licenses
FOR EACH ROW EXECUTE PROCEDURE log_transactions();
CREATE TRIGGER clients_log
AFTER INSERT OR UPDATE OR DELETE
ON clients
FOR EACH ROW EXECUTE PROCEDURE log_transactions();
Here's what I've got now, after quite a bit of messing around:
CREATE OR REPLACE FUNCTION log_transactions() RETURNS TRIGGER LANGUAGE plpgsql AS $body$
DECLARE
target_row RECORD;
target_cols text[];
col_name RECORD;
col_name_str text;
right_now timestamp without time zone;
q_str text;
BEGIN
right_now := now();
target_cols := '{}';
FOR col_name IN SELECT column_name::text FROM information_schema.columns WHERE table_name = TG_TABLE_NAME AND table_schema = TG_TABLE_SCHEMA LOOP
col_name_str := col_name.column_name::text;
target_cols = ARRAY_APPEND(target_cols, col_name_str);
END LOOP;
RAISE NOTICE 'target_cols: %', target_cols;
IF (TG_OP = 'DELETE') THEN
target_row := OLD;
ELSE
target_row := NEW;
END IF;
RAISE NOTICE 'target_row: %', target_row;
EXECUTE format('INSERT INTO %I_history (transaction_time, transaction_type) VALUES (%L, %L)', TG_TABLE_NAME, right_now, TG_OP);
q_str := format('UPDATE %I_history SET (%s) = ', TG_TABLE_NAME, array_to_string(target_cols, ', ')) || '$1' || format(' WHERE transaction_type = %L AND transaction_time = %L', TG_OP, right_now);
EXECUTE q_str USING target_row;
RETURN target_row;
END;
$body$;
This doesn't work either, and it's spiraling out of control, complexity-wise.
Personally, I use a set of home-grown functions Below for auditing any table I want. All I do is run audit.enable() on any table I want to audit and it's stored in the general tables I have here. Yes, it's not exactly what you're doing, but it's the most "DRY" think there is really, I do it once and never again-- ever.
CREATE TABLE audit.audit_log
(
audit_log_seq serial primary key,
schema_name text NOT NULL,
table_name text NOT NULL,
db_username text,
user_seq bigint,
logged_ip inet,
log_timestamp TIMESTAMP NOT NULL DEFAULT (now()),
action TEXT NOT NULL CHECK (action IN ('I','D','U')),
comment varchar(500),
old_data hstore,
new_data hstore,
query text
) WITH
(
fillfactor=100
);
CREATE INDEX audit_log_schema_table_idx ON audit.audit_log(schema_name,table_name);
CREATE INDEX audit_log_timestamp_utc_idx ON audit.audit_log(log_timestamp);
CREATE INDEX audit_log_uname on audit.audit_log(user_seq);
-- generic function for all tables
CREATE OR REPLACE FUNCTION audit.log_func() RETURNS TRIGGER AS $body$
DECLARE
v_old_data hstore;
v_new_data hstore;
v_query text;
v_comment varchar;
-- v_old_data TEXT;
-- v_new_data TEXT;
BEGIN
v_query=current_query();
IF (TG_OP = 'UPDATE') THEN
v_old_data := hstore(OLD.*);
v_new_data := hstore(NEW.*);
v_comment=v_new_data -> 'audit_comment';
ELSIF (TG_OP = 'DELETE') THEN
v_old_data := hstore(OLD.*);
ELSIF (TG_OP = 'INSERT') THEN
v_new_data := hstore(NEW.*);
v_comment=v_new_data -> 'audit_comment';
ELSE
RAISE WARNING '[audit.log_func] - Other action occurred: %, at %',TG_OP,now();
RETURN NULL;
END IF;
INSERT INTO audit.audit_log (schema_name,table_name,db_username,user_seq,logged_ip,action,old_data,new_data,query, comment)
VALUES (TG_TABLE_SCHEMA::TEXT,
TG_TABLE_NAME::TEXT,
session_user::TEXT,
coalesce(current_setting('mvc.user_seq'),'0')::bigint, --current user
inet_client_addr(),
substring(TG_OP,1,1),
v_old_data,
v_new_data,
v_query,
v_comment);
IF (TG_OP = 'DELETE') THEN
RETURN OLD;
END IF;
RETURN NEW;
EXCEPTION
WHEN data_exception THEN
RAISE WARNING '[audit.log_func] - UDF ERROR [DATA EXCEPTION] - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM;
RETURN NULL;
WHEN unique_violation THEN
RAISE WARNING '[audit.log_func] - UDF ERROR [UNIQUE] - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM;
RETURN NULL;
--WHEN OTHERS THEN
-- RAISE WARNING '[audit.log_func] - UDF ERROR [OTHER] - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM;
-- RETURN NULL;
END;
$body$ language plpgsql security definer;
CREATE OR REPLACE FUNCTION audit.enable(p_table_name text,p_schema_name text DEFAULT 'dallas') RETURNS VOID as $body$
DECLARE
BEGIN
EXECUTE 'create trigger trg_audit_'||p_table_name||' BEFORE INSERT OR UPDATE OR DELETE ON '||p_schema_name||'.'||p_table_name|| ' FOR EACH ROW EXECUTE PROCEDURE audit.log_func()';
exception when duplicate_object then null;
END;
$body$ language plpgsql security definer;