I want to get the count of benches I have overall. I have multiple schema as classroom and each clsssroom has multiple tables one of them is benches. I want to count number of benches I have in all the schema. So its like classroom A has 6-chairs and 7-benches Classroom B has 2-chairs and 8 benches so I have 15 benches.
I have two queries already
SELECT classroom_name FROM public.classroom
SELECT COUNT(DISTINCT id) FROM <classroom_name>.benches;
I want to get all the values from query 1 and loop them in query 2. Solution I came close to is
CREATE OR REPLACE FUNCTION all_customers_dynamic() RETURNS SETOF classroom AS $$
DECLARE
schema RECORD;
BEGIN
FOR schema IN EXECUTE
format(
'SELECT classroom_name FROM public.classroom'
)
LOOP
RETURN QUERY EXECUTE
format('SELECT COUNT(DISTINCT id) FROM %I.benches;', schema.classroom_name);
END LOOP;
END;
$$ LANGUAGE plpgsql;
Sample Data for testing:
CREATE SCHEMA classroom1;
CREATE SCHEMA classroom2;
CREATE TABLE classroom1.benches (
id int4 NULL
);
INSERT INTO classroom1.benches (id) VALUES(1);
INSERT INTO classroom1.benches (id) VALUES(2);
INSERT INTO classroom1.benches (id) VALUES(3);
INSERT INTO classroom1.benches (id) VALUES(4);
INSERT INTO classroom1.benches (id) VALUES(5);
INSERT INTO classroom1.benches (id) VALUES(6);
CREATE TABLE classroom2.benches (
id int4 NULL
);
INSERT INTO classroom2.benches (id) VALUES(1);
INSERT INTO classroom2.benches (id) VALUES(2);
INSERT INTO classroom2.benches (id) VALUES(3);
INSERT INTO classroom2.benches (id) VALUES(4);
INSERT INTO classroom2.benches (id) VALUES(5);
INSERT INTO classroom2.benches (id) VALUES(6);
INSERT INTO classroom2.benches (id) VALUES(7);
INSERT INTO classroom2.benches (id) VALUES(8);
CREATE TABLE public.classroom (
classroom_name varchar NULL
);
INSERT INTO public.classroom (classroom_name) VALUES('classroom1');
INSERT INTO public.classroom (classroom_name) VALUES('classroom2');
You have two options for do it.
Get benches tables list from your public.classroom table
CREATE OR REPLACE FUNCTION all_customers_dynamic()
RETURNS int4
AS $$
DECLARE
sh RECORD;
k int4;
total int4;
begin
total = 0;
for sh in execute
'SELECT classroom_name FROM public.classroom'
loop
execute 'select count(distinct id) from ' || sh.classroom_name || '.benches' into k;
total = total + k;
end loop;
return total;
END;
$$ LANGUAGE plpgsql;
Get all benches named tables automatically from information_schema (system tables)
CREATE OR REPLACE FUNCTION all_customers_dynamic_1()
RETURNS int4
AS $$
DECLARE
sh RECORD;
k int4;
total int4;
begin
total = 0;
for sh in execute
'SELECT schemaname || ''.'' || tablename as tb from pg_catalog.pg_tables where tablename = ''benches'''
loop
execute 'select count(distinct id) from ' || sh.tb into k;
total = total + k;
end loop;
return total;
END;
$$ LANGUAGE plpgsql;
Related
how to write trigger in postgresql which maintain old value, new value and table name.
I have 5 tables and each tables is different data structure i want to maintain audit details in single table with old value new value and table name.old value and new value contain multiple columns in json format with column name and value.
example
audit_details
----------------------------------------------------------------
date_time|table_name|old_data|new_data|user|primary_key_of_table
----------------------------------------------------------------
I wrote sample trigger function for you. You can make additional changes yourself.
CREATE TABLE test.log_table_data (
id serial not null,
schema_name varchar(100) NOT NULL,
table_name varchar(100) NOT NULL,
action_date timestamp NOT NULL DEFAULT now(),
action_type varchar(10) NOT NULL,
table_id int4 NOT NULL,
old_data jsonb NULL,
new_data jsonb NULL,
CONSTRAINT log_table_data_pk PRIMARY KEY (id)
);
create or replace function test.register_as_log()
returns trigger
language plpgsql
security definer
as $function$
declare
v_old_data json;
v_new_data json;
shemaname varchar;
tablename varchar;
begin
shemaname = tg_table_schema;
tablename = tg_table_name;
/* json_strip_nulls - removes null values */
if (tg_op = 'UPDATE') then
v_old_data = (select json_strip_nulls(row_to_json(old.*)));
v_new_data = (select json_strip_nulls(row_to_json(new.*)));
insert into test.log_table_data (schema_name, table_name, action_type, table_id, old_data, new_data)
values (shemaname, tablename, 'update', old.id, v_old_data, v_new_data);
return new;
end if;
if (tg_op = 'DELETE') then
v_old_data = (select json_strip_nulls(row_to_json(old.*)));
insert into test.log_table_data (schema_name, table_name, action_type, table_id, old_data, new_data)
values (shemaname, tablename, 'delete', old.id, v_old_data, null);
return old;
end if;
end;
$function$
;
I am pretty new to PL/pgSQL programming. I have a requirement of audit logging updated columns in my table
Table
create table sample_table(name varchar(15),city varchar(15),age int,mail varchar(20) primary key);
Audit table
create table sample_table__audits_dynamicols(mail varchar(20), columnchanged varchar(10), oldvalue varchar(10), changed_on timestamp(6) NOT NULL)
Trigger Function
CREATE FUNCTION public.log_sample_table_allchanges() RETURNS trigger AS $BODY$DECLARE
_colname text;
_tablename varchar(15) := 'sample_table';
_schema varchar(15) := 'public';
_changed_on time := now();
BEGIN
FOR _colname IN SELECT column_name FROM information_schema.Columns WHERE table_schema = _schema AND table_name = _tablename LOOP
IF NEW._colname <> OLD._colname THEN
INSERT INTO sample_table__audits_dynamicols(mail,columnchanged, oldvalue ,changed_on)
VALUES(OLD.mail,_colname,OLD.:_colname,_changed_on);
END IF;
END LOOP;
RETURN NEW;
END$BODY$
LANGUAGE plpgsql VOLATILE NOT LEAKPROOF;
Trigger
create TRIGGER log_sample_table_allchanges
BEFORE UPDATE
ON SAMPLE_TABLE
FOR EACH ROW
EXECUTE PROCEDURE log_sample_table_allchanges();
Requirement: Whenever a column value is changed i want to log it as
(mail, columnname, columnvalue, date)
E.g:
insert into sample_table (name, mail, city, age) values('kanta','mk#foo.com','hyd',23);
insert into sample_table (name, mail, city, age) values('kmk','mk#gmail.com','hyd',23);
So when i update like the following
update sample_table set age=24 where mail='mk#foo.com';
update sample_table set city='bza' where mail='mk#gmail.com'
I want audit table to record like
(mk#foo.com,age,23, timestamp)
(mk#gmail.com, city, hyd, timestamp)
Right now I am facing issue with column comparison in my Trigger function. Please help me rectifying my Trigger function to meet my requirement.
You may use EXECUTE to get the values of columns dynamically and do the comparison.
CREATE OR REPLACE FUNCTION public.log_sample_table_allchanges() RETURNS trigger AS
$BODY$
DECLARE
_colname text;
_tablename varchar(15) := 'sample_table';
_schema varchar(15) := 'public';
_changed_on timestamp := now();
_old_val text;
_new_val text;
BEGIN
FOR _colname IN SELECT column_name FROM information_schema.Columns WHERE table_schema = _schema AND table_name = _tablename
LOOP
EXECUTE 'SELECT $1.' || _colname || ', $2.' || _colname
USING OLD,NEW
INTO _old_val, _new_val; --get the old and new values for the column.
IF _new_val <> _old_val THEN
INSERT INTO sample_table__audits_dynamicols(mail,columnchanged, oldvalue ,changed_on)
VALUES(OLD.mail,_colname,_old_val,_changed_on);
END IF;
END LOOP;
RETURN NEW;
END$BODY$
LANGUAGE plpgsql VOLATILE NOT LEAKPROOF;
I'm not sure why you have defined mail as a PRIMARY KEY in the audits table, it will cause unique constraint violation if the same mail gets updated twice.
I have the following small MVWE for a basic micro-auditing system which works fine but lacks a functionality:
DROP TABLE IF EXISTS audit CASCADE;
CREATE TABLE audit(
Id BIGSERIAL NOT NULL
,TimeValue TIMESTAMP NOT NULL
,RoleName NAME NOT NULL
,Operation NAME NOT NULL
,SchemaName NAME NOT NULL
,TableName NAME NOT NULL
,Identifiers BIGINT[]
---
,PRIMARY KEY(Id)
);
-- Audit Trigger:
DROP FUNCTION IF EXISTS audit_trigger() CASCADE;
CREATE OR REPLACE FUNCTION audit_trigger()
RETURNS TRIGGER AS
$BODY$
BEGIN
INSERT INTO audit(TimeValue, RoleName, Operation, SchemaName, TableName) VALUES
(now()::TIMESTAMP, current_user, TG_OP, TG_TABLE_SCHEMA, TG_RELNAME);
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql SECURITY DEFINER;
-- Channels:
DROP TABLE IF EXISTS channels CASCADE;
CREATE TABLE channels(
Id INTEGER NOT NULL
,UserKey TEXT NOT NULL
,Active BOOLEAN NOT NULL DEFAULT(TRUE)
---
,PRIMARY KEY(Id)
,UNIQUE(UserKey)
);
CREATE TRIGGER channel_audit_trigger BEFORE INSERT OR UPDATE OR DELETE ON channels
FOR EACH STATEMENT EXECUTE PROCEDURE audit_trigger();
-- Perform some operations:
INSERT INTO channels(
SELECT C.Id, 'Channel-' || C.Id
FROM generate_series(1, 300, 10) AS C(Id)
);
DELETE FROM channels WHERE id < 10;
UPDATE channels
SET UserKey = 'wild channel'
WHERE id = 21;
I would like to add into the last column of audit table, identifiers of rows that have been inserted/updated/deleted in channels.
I have used STATEMENT level because I just need to collects identifiers in a array. But I do not find how to access DML statistics. Conversely at the ROW level I must handle OLD and NEW cases and I cannot succeed in aggregate all touched identifier.
How can I proceed in order to fill the last column of audit table with touched identifiers?
Update
Finally I reached my goal, but this solution might not be scalable and may have some unwanted drawbacks (I am open to any constructive feedback and advice).
Basically, how I have solved my problem:
Log at ROW level BEFORE DML is performed into a table audit_rowlevel;
Aggregate the freshly added content of audit_rowlevel into audit_statementlevel at STATEMENT level AFTER DML is performed;
Minimal Working Example is now:
DROP TABLE IF EXISTS audit_rowlevel CASCADE;
CREATE TABLE audit_rowlevel(
Id BIGSERIAL NOT NULL
,Aggregated BOOLEAN NOT NULL DEFAULT(FALSE)
,TimeValue TIMESTAMP NOT NULL
-- https://www.postgresql.org/docs/current/static/functions-info.html
,RoleName NAME NOT NULL
,ClientIP INET NOT NULL
,ClientPid INTEGER NOT NULL
-- https://www.postgresql.org/docs/current/static/plpgsql-trigger.html
,Operation TEXT NOT NULL
,SchemaName NAME NOT NULL
,TableName NAME NOT NULL
,RowId BIGINT NOT NULL
-- https://www.postgresql.org/docs/current/static/functions-json.html
,OldValue JSONB
,NewValue JSONB
---
,PRIMARY KEY(Id)
);
-- Row Level Trigger:
DROP FUNCTION IF EXISTS audit_rowlevel_trigger() CASCADE;
CREATE OR REPLACE FUNCTION audit_rowlevel_trigger()
RETURNS TRIGGER AS
$BODY$
DECLARE
history BOOLEAN := (TG_NARGS > 0) AND (TG_ARGV[0]::BOOLEAN);
rowid BIGINT;
oldvalue JSONB;
newvalue JSONB;
BEGIN
-- Handle NEW:
IF TG_OP = ANY('{INSERT,UPDATE}') THEN
IF history THEN
newvalue := to_jsonb(NEW);
END IF;
rowid := NEW.Id::BIGINT;
END IF;
-- Handle OLD:
IF TG_OP = ANY('{UPDATE,DELETE}') THEN
IF history THEN
oldvalue := to_jsonb(OLD);
END IF;
rowid := OLD.Id::BIGINT;
END IF;
-- INSERT:
INSERT INTO audit_rowlevel(TimeValue, RoleName, ClientIP, ClientPID, Operation, SchemaName, TableName, RowId, NewValue, OldValue) VALUES
(now()::TIMESTAMP, current_user, inet_client_addr(), pg_backend_pid(), TG_OP, TG_TABLE_SCHEMA, TG_RELNAME, RowId, NewValue, OldValue);
-- RETURN:
IF TG_OP = ANY('{INSERT,UPDATE}') THEN
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
RETURN OLD;
ELSE
RETURN NULL;
END IF;
END;
$BODY$
LANGUAGE plpgsql SECURITY DEFINER;
-- Statement Level Trigger:
DROP TABLE IF EXISTS audit_statementlevel CASCADE;
CREATE TABLE audit_statementlevel(
Id BIGSERIAL NOT NULL
,TimeValue TIMESTAMP NOT NULL
,RoleName NAME NOT NULL
,ClientIP INET NOT NULL
,ClientPid INTEGER NOT NULL
,Operation TEXT NOT NULL
,SchemaName NAME NOT NULL
,TableName NAME NOT NULL
,RowCount BIGINT NOT NULL
,RowIds BIGINT[] NOT NULL
,AuditIds BIGINT[] NOT NULL
---
,PRIMARY KEY(Id)
);
-- Row Level Trigger:
DROP FUNCTION IF EXISTS audit_statementlevel_trigger() CASCADE;
CREATE OR REPLACE FUNCTION audit_statementlevel_trigger()
RETURNS TRIGGER AS
$BODY$
DECLARE
rowcount BIGINT;
BEGIN
WITH
A AS (
SELECT
TimeValue, RoleName, ClientIP, ClientPid, Operation, SchemaName, TableName
,COUNT(*)
,array_agg(RowId)
,array_agg(Id)
FROM
audit_rowlevel
WHERE
NOT Aggregated
GROUP BY
TimeValue, RoleName, ClientIP, ClientPid, Operation, SchemaName, TableName
ORDER BY
TimeValue
),
B AS (
INSERT INTO audit_statementlevel(TimeValue, RoleName, ClientIP, ClientPid, Operation, SchemaName, TableName, RowCount, RowIds, AuditIds)
(SELECT * FROM A)
RETURNING AuditIds
),
C AS (
SELECT array_agg(DISTINCT T.id) AS Ids FROM B, unnest(B.AuditIds) AS T(id)
)
UPDATE
audit_rowlevel
SET
Aggregated = TRUE
FROM
C
WHERE
Id = ANY(C.Ids);
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql SECURITY DEFINER;
-- Channels:
DROP TABLE IF EXISTS channels CASCADE;
CREATE TABLE channels(
Id INTEGER NOT NULL
,UserKey TEXT NOT NULL
,Active BOOLEAN NOT NULL DEFAULT(TRUE)
---
,PRIMARY KEY(Id)
,UNIQUE(UserKey)
);
CREATE TRIGGER channel_audit_rowlevel_trigger BEFORE INSERT OR UPDATE OR DELETE ON channels
FOR EACH ROW EXECUTE PROCEDURE audit_rowlevel_trigger(TRUE);
CREATE TRIGGER channel_audit_statementlevel_trigger AFTER INSERT OR UPDATE OR DELETE ON channels
FOR EACH STATEMENT EXECUTE PROCEDURE audit_statementlevel_trigger();
-- Perform some operations:
INSERT INTO channels(
SELECT C.Id, 'Channel-' || C.Id
FROM generate_series(1, 300, 10) AS C(Id)
);
DELETE FROM channels WHERE id < 10;
UPDATE channels
SET UserKey = 'wild channel'
WHERE id = 21;
I am interested knowing if this solution looks right to professional developers.
Am I going into the good direction or is this solution evil?
I want clients of my application to call DELETE statements indirectly or using a function. This is what I tried:
CREATE OR REPLACE FUNCTION layer_250_delete(layer_id integer)
RETURNS bool AS
$BODY$
BEGIN
EXECUTE 'DELETE FROM layer_250_ WHERE id = $1' USING layer_id;
RETURN TRUE;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION layer_250_delete(integer)
OWNER TO postgres;
I also tried a raw query:
...
DELETE FROM layer_250_ WHERE id = 1';
RETURN TRUE;
...
However, when I call this function (variant 1 or 2) like this:
select layer_250_delete(1);
it does not delete a row from layer_250_ table. I scanned dozens of threads here at stackoverflow, but could not find anything similar to my task.
STRUCTURE
CREATE TABLE public.layer_250_
(
id integer NOT NULL DEFAULT nextval('layer_250__id_seq'::regclass),
feature_type character varying(100) NOT NULL,
feature_hash character varying(500) NOT NULL,
feature_uid character varying(100) NOT NULL,
geom geometry,
radius integer,
group_id integer,
object_id integer NOT NULL DEFAULT 0,
row_id integer NOT NULL DEFAULT 0,
action_time timestamp without time zone NOT NULL DEFAULT now(),
action_type character varying(255),
action_user_id integer,
action_user_ip character varying(255),
CONSTRAINT layer_250__pkey PRIMARY KEY (id),
CONSTRAINT layer_250__feature_uid_key UNIQUE (feature_uid),
CONSTRAINT enforce_dims_geom_layer_250_ CHECK (st_ndims(geom) = 2),
CONSTRAINT enforce_srid_geom_layer_250_ CHECK (st_srid(geom) = 3857)
)
WITH (
OIDS=FALSE
);
ALTER TABLE public.layer_250_
OWNER TO postgres;
Yrs, it does:
t=# create table layer_250_ (id int);
CREATE TABLE
t=# insert into layer_250_ select 1;
INSERT 0 1
t=# CREATE OR REPLACE FUNCTION layer_250_delete(layer_id integer)
t-# RETURNS bool AS
t-# $BODY$
t$# BEGIN
t$# EXECUTE 'DELETE FROM layer_250_ WHERE id = $1' USING layer_id;
t$# RETURN TRUE;
t$# END;
t$# $BODY$
t-# LANGUAGE plpgsql VOLATILE
t-# COST 100;
CREATE FUNCTION
t=# ALTER FUNCTION layer_250_delete(integer)
t-# OWNER TO postgres;
ALTER FUNCTION
t=# begin;
BEGIN
t=# select layer_250_delete(1);
layer_250_delete
------------------
t
(1 row)
t=# select * from layer_250_;
id
----
(0 rows)
t=# end;
COMMIT
I have these tables:
CREATE EXTENSION citext;
CREATE EXTENSION "uuid-ossp";
CREATE TABLE cities
(
city_id serial PRIMARY KEY,
city_name citext NOT NULL UNIQUE
);
INSERT INTO cities(city_name) VALUES
('New York'), ('Paris'), ('Madrid');
CREATE TABLE etags
(
etag_name varchar(128) PRIMARY KEY,
etag_value uuid
);
INSERT INTO etags(etag_name, etag_value)
VALUES ('cities', uuid_generate_v4());
I want to update the cities etag when the cities table changes. If no rows are affected by the insert, update or delete statement, I'd like to avoid to change the cities etag, so I wrote the following statement level trigger:
CREATE OR REPLACE FUNCTION update_etag()
RETURNS trigger AS
$BODY$
DECLARE
record_count integer;
vetag_name varchar(128);
BEGIN
GET DIAGNOSTICS record_count = ROW_COUNT;
vetag_name := TG_ARGV[0];
RAISE NOTICE 'affected %:%', vetag_name, record_count;
IF record_count = 0 THEN
RETURN NULL;
END IF;
UPDATE etags SET etag_value = uuid_generate_v4()
WHERE etag_name = vetag_name;
RETURN null;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
CREATE TRIGGER update_cities_etag_trigger
AFTER INSERT OR UPDATE OR DELETE
ON cities
FOR EACH STATEMENT
EXECUTE PROCEDURE update_etag('cities');
However GET DIAGNOSTICS record_count = ROW_COUNT; doesn't work for me, as it always returns 0.
If I execute the following:
DELETE FROM cities;
The following is output:
NOTICE: affected cities:0 Query returned successfully: 3 rows
affected, 47 msec execution time.
Is there a way to figure out how many rows are affected by the statement that triggers the trigger in a PostgreSQL statement-level trigger?
Version 10
CREATE TRIGGER
...
[ REFERENCING { { OLD | NEW } TABLE [ AS ] transition_relation_name } [ ... ] ]
...
https://www.postgresql.org/docs/current/static/release-10.html
Add AFTER trigger transition tables to record changed rows (Kevin
Grittner, Thomas Munro)
Transition tables are accessible from triggers written in server-side
languages.
Example
Solves it:
CREATE OR REPLACE FUNCTION update_etag()
RETURNS trigger AS
$BODY$
DECLARE
record_count integer;
vetag_name varchar(128);
begin
IF (TG_OP = 'DELETE') or (TG_OP = 'UPDATE') THEN
select count(*) from oldtbl into record_count ;
ELSE
select count(*) from newtbl into record_count ;
END IF;
vetag_name := TG_ARGV[0];
RAISE NOTICE 'affected %:%:%', vetag_name,TG_OP, record_count;
IF record_count = 0 THEN
RETURN NULL;
END IF;
UPDATE etags SET etag_value = uuid_generate_v4()
WHERE etag_name = vetag_name;
RETURN null;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
CREATE TRIGGER update_ins_cities_etag_trigger
AFTER INSERT
ON cities
REFERENCING NEW TABLE AS newtbl
FOR EACH STATEMENT
EXECUTE PROCEDURE update_etag('cities');
CREATE TRIGGER update_upd_cities_etag_trigger
AFTER UPDATE
ON cities
REFERENCING OLD TABLE AS oldtbl
FOR EACH STATEMENT
EXECUTE PROCEDURE update_etag('cities');
CREATE TRIGGER update_del_cities_etag_trigger
AFTER DELETE
ON cities
REFERENCING OLD TABLE AS oldtbl
FOR EACH STATEMENT
EXECUTE PROCEDURE update_etag('cities');
so=# INSERT INTO cities(city_name) VALUES
so-# ('New York'), ('Paris'), ('Madrid');
NOTICE: affected cities:INSERT:3
INSERT 0 3
so=# select * from etags;
etag_name | etag_value
-----------+--------------------------------------
cities | dc7d1525-eea7-4822-b736-5141a20764f8
(1 row)
so=# insert into cities(city_name) values ('Budapest');
NOTICE: affected cities:INSERT:1
INSERT 0 1
so=# select * from etags;
etag_name | etag_value
-----------+--------------------------------------
cities | df835f44-dada-4a94-bb62-5890f2316103
(1 row)
so=# delete from cities where city_id > 42;
NOTICE: affected cities:DELETE:0
DELETE 0
so=# select * from etags;
etag_name | etag_value
-----------+--------------------------------------
cities | df835f44-dada-4a94-bb62-5890f2316103
(1 row)