PostgreSQL 9.5: Return columns based on input parameter - postgresql

I have the following table.
Table:
CREATE TABLE tblTest
(
Column1 int,
Column2 int,
Column3 int,
Column11 int,
Column111 int,
Column1111 int,
Column22 int,
Column222 int,
Column33 int
);
Records:
INSERT INTO tblTest VALUES(1,2,3,11,111,1111,22,222,33);
I am writting FUNCTION to return the result from above table based on passed parameter.
The parameter p_ColumnName is used to pass the column name. Based on column name the list of columns needs to be display.
Function:
CREATE OR REPLACE FUNCTION ufn_test
(
p_ColumnName text
)
RETURNS -- ? How to specify the dynamic return type or dynamic column list ?
AS
$BODY$
DECLARE v_ColumnsList text;
v_query text;
BEGIN
IF p_ColumnName = 'Column1'
THEN
v_ColumnsList := 'Column11,Column111,Column1111';
ELSIF p_ColumnName = 'Column2'
THEN
v_ColumnsList := 'Column22,Column222';
ELSIF p_ColumnName = 'Column3'
THEN
v_ColumnsList := 'Column33';
END IF;
v_query := 'SELECT '|| v_ColumnsList ||' FROM tblTest';
RETURN QUERY EXECUTE v_query;
END;
$BODY$
LANGUAGE PLPGSQL;

I suggest you are using a type RefCursor in Returns.
Base on the language you are using (SQL, Java ...), you can get data from this Cursor.
So, your function will become to
CREATE OR REPLACE FUNCTION ufn_test( p_ColumnName text )
RETURNS refcursor AS
$BODY$
DECLARE
v_ColumnsList text;
v_query text;
ref_cursor refcursor;
BEGIN
IF (p_ColumnName = 'Column1') THEN
v_ColumnsList := 'Column11,Column111,Column1111';
ELSIF (p_ColumnName = 'Column2') THEN
v_ColumnsList := 'Column22,Column222';
ELSIF (p_ColumnName = 'Column3') THEN
v_ColumnsList := 'Column33';
END IF;
v_query := 'SELECT '|| v_ColumnsList ||' FROM tblTest';
OPEN ref_cursor FOR EXECUTE (v_query);
RETURN ref_cursor;
END;
$BODY$
LANGUAGE plpgsql;
Hopefully it will help you.

Related

Save dynamic query to variable in postgres stored procedure

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;

Postgres calling Function in functio does not what I expect

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

Check to see if a record exists postgres function

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

Removing else-if-elseif.. in postgresql function

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)

DRY postgresql 9.4 trigger functions

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;