Dynamic Creation of Table and Update Function in PostgreSQL for User-Specific Schemas

General information about my question and the structure of the Database (db_racao_dev2).

The project is a feed stock application. I’m working with PostgreSQL >14. The database architecture is designed with multiple users (ROLE group called appuser) which each have their own particular schema (user1 -> sch1, user2 -> sch2, etc.). Only the super user of the DATABASE has access to the ‘public’ scheme (therefore it is not public). It has a single table (usuario) where it saves the passwords of the application’s users. Whenever a user is inserted into the usuario table, the criar_usuario_schema() function is called. And then for each schema (to store the data of the respective users) the tables racao, compra_racao, saida_racao e estoque_racao are created. Example:

sch1.racao = are the feed registered in the system;
sch1.compra_racao = are the feed purchased and entered into stock;
sch1.saida_racao = feed that leave stock;
sch1.estoque_racao = current feed present in stock.

Well, I have a function that recalculates the current stock whenever a feed enters or leaves stock. And obviously the triggers that are activated when the feed enters or leaves.

Everything was built dynamically as shown in the code below. However, I don’t know how to put the function that calculates the current stock [atualizar_estoque()] in the criar_usuario_schema() function so that it is also created dynamically.

To facilitate understanding, I provide the database code without schema structure. And other code with the schema structure but without the implementation of what I’m trying to do. It only automates the creation of tables but not the trigger and function to update each estoque_racao of the particular schemas.

Below is a Codes with Minimum reproductive example.

Code that works but without the schema structure. In other words, the stock_ration table is updated whenever feed enters or leaves.

-- Criando Banco de dados 
CREATE DATABASE db_racao_dev2;

-- SET DATABASE to db_racao_dev2
c db_racao_dev2;

-- TABELA racao
CREATE TABLE racao(
    id_racao SERIAL PRIMARY KEY NOT NULL,
    nome VARCHAR(20) NOT NULL,
    proteina REAL NOT NULL
);

-- TABELA compra_racao
CREATE TABLE compra_racao(
    id_comp_racao SERIAL PRIMARY KEY NOT NULL,
    id_racao SERIAL NOT NULL REFERENCES racao(id_racao) ON DELETE CASCADE,
    valor_uni NUMERIC NOT NULL, 
    quantidade REAL NOT NULL, 
    valor_entrada NUMERIC NOT NULL 
);

-- TABELA saida_racao
CREATE TABLE saida_racao(
    id_saida_racao SERIAL PRIMARY KEY NOT NULL,
    id_racao SERIAL NOT NULL REFERENCES racao(id_racao) ON DELETE CASCADE,
    quantidade REAL NOT NULL,
    valor_saida NUMERIC NOT NULL,
    id_comp_racao SERIAL NOT NULL REFERENCES compra_racao(id_comp_racao) ON DELETE CASCADE
    );
    
-- TABELA estoque_racao
CREATE TABLE estoque_racao(
    id_lote SERIAL PRIMARY KEY,
    id_comp_racao INT NOT NULL REFERENCES compra_racao(id_comp_racao) ON DELETE CASCADE,
    id_racao INT NOT NULL REFERENCES racao(id_racao) ON DELETE CASCADE,
    quantidade NUMERIC DEFAULT 0 CHECK (quantidade >= 0),
    valor_total NUMERIC(10, 2) DEFAULT 0 CHECK (valor_total >= 0)
    );
------------------------------------------------------------------------------------------
-- Funçao para atualizar o estoque de raçao sempre que comprar raçao ou sair raçao do estoque
CREATE OR REPLACE FUNCTION atualizar_estoque()
RETURNS TRIGGER AS $$
BEGIN
    IF TG_TABLE_NAME = 'compra_racao' THEN
        IF EXISTS (SELECT 1 FROM estoque_racao WHERE id_racao = NEW.id_racao) THEN
            UPDATE estoque_racao
            SET quantidade = quantidade + NEW.quantidade,
                valor_total = valor_total + NEW.valor_entrada
            WHERE id_racao = NEW.id_racao;
        ELSE
            INSERT INTO estoque_racao (id_comp_racao, id_racao, quantidade, valor_total)
            VALUES (NEW.id_comp_racao, NEW.id_racao, NEW.quantidade, NEW.valor_entrada);
        END IF;
    ELSIF TG_TABLE_NAME = 'saida_racao' THEN
        UPDATE estoque_racao
        SET quantidade = quantidade - NEW.quantidade,
            valor_total = valor_total - NEW.valor_saida
        WHERE id_racao = NEW.id_racao;
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;
------------------------------------------------------------------------------------------
-- Atribuir o trigger a funçao para a tabela compra racao
CREATE TRIGGER atualizar_estoque_compra_racao
AFTER INSERT ON compra_racao
FOR EACH ROW
EXECUTE FUNCTION atualizar_estoque();

-- Atribuir trigger a funcao atualizar_estoque() ao sair raçao
CREATE TRIGGER atualizar_estoque_saida_racao
AFTER INSERT ON saida_racao
FOR EACH ROW
EXECUTE FUNCTION atualizar_estoque();
------------------------------------------------------------------------------------------
-- Inserindo dados nas tabelas
-- TABELA racao
INSERT INTO racao(nome,proteina)
VALUES
('A',40),
('B',45),
('C',50),
('D',36),
('E',32);

-- TABELA compra_racao
INSERT INTO compra_racao(id_racao,valor_uni,quantidade,valor_entrada)
VALUES
(1,12,100,1200),
(2,13,100,1300),
(3,14,100,1400),
(4,15,100,1500);

-- TABELA saida_racao
INSERT INTO saida_racao(id_racao,quantidade,valor_saida,id_comp_racao)
VALUES
(1,50,600,1),
(2,50,650,2),
(1,10,120,1),
(3,60,840,3);

-- Conferindo quanto tem no estoque
TABLE estoque_racao;

Code that DOESN’T work, but it creates users and schemas dynamically to help implement what I want.

/* Minimal reproductive example.
* Data: 08/05/2024
* Creating app user groups, creating the database, creating schemas and tables for each user
* Author: Carlos Antonio Zarzar */
----------------------------#----------------------------#----------------------------#----------------------------

-----------------------------
-- First let's create a Role Group of app users (appuser)
-----------------------------
-- Role: appuser
-- DROP ROLE IF EXISTS appuser;
CREATE ROLE appuser WITH
  NOLOGIN
  NOSUPERUSER
  INHERIT
  NOCREATEDB
  NOCREATEROLE
  NOREPLICATION
  NOBYPASSRLS;
-----------------------------
-- Now we create the database (db_racao_dev2)
-----------------------------
-- Database: db_racao_dev2
-- DROP DATABASE IF EXISTS db_racao_dev2;
CREATE DATABASE db_racao_dev2; 

GRANT TEMPORARY, CONNECT ON DATABASE db_racao_dev2 TO PUBLIC;

-----------------------------
-- We create a users table 
-----------------------------
-- SET DATABASE to db_racao_dev2
c db_racao_dev2
-- DROP TABLE IF EXISTS public.usuarios;
CREATE TABLE usuarios (
    id_usuario SERIAL PRIMARY KEY,
    nome VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE,
    senha VARCHAR(100) NOT NULL,
    admin BOOLEAN NOT NULL,
    criado_em TIMESTAMPTZ DEFAULT Now(),
    modificado_em TIMESTAMPTZ NULL,
    CONSTRAINT usuarios_email_key UNIQUE (email)
);

------------------------------------------------------------------------------------------------------------
----------------------------
-- Now let's create a function to automate the creation of schemas, users (role) belonging to the appuser group and their tables (belonging to the dynamically created schema).
-- This function is activated (trigger) whenever a new user is registered in the system.
-- That is, when inserting information into the public.usuario table
-----------------------------
CREATE OR REPLACE FUNCTION criar_usuario_schema()
RETURNS TRIGGER AS $$
DECLARE
    novo_usuario TEXT;
    novo_schema TEXT;
BEGIN
    novo_usuario := 'user' || NEW.id_usuario;
    novo_schema := 'sch' || NEW.id_usuario;

    -- Criar um novo usuário
    EXECUTE format('CREATE ROLE %I WITH LOGIN NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS', novo_usuario);

    -- Atribuir permissões ao novo usuário
    EXECUTE format('GRANT appuser TO %I', novo_usuario);

    -- Criar um novo esquema para o novo usuário
    EXECUTE format('CREATE SCHEMA %I AUTHORIZATION %I', novo_schema, novo_usuario);

-------------------------# Criando Tabela racao #------------------------------------        
    -- Criar tabela racao no novo esquema
    EXECUTE format(
    'CREATE TABLE %I.racao(
    id_racao SERIAL PRIMARY KEY NOT NULL,
    nome VARCHAR(20) NOT NULL,
    proteina REAL NOT NULL
    );',
    novo_schema
    );
    -- Alterar a propriedade da tabela tipos_carro para o usuário 'novo_usuario' do esquema 'novo_schema'
    EXECUTE format('ALTER TABLE %I.racao OWNER TO %I', novo_schema, novo_usuario);

-------------------------# Criando Tabela compra_racao #---------------------------------        
    -- Criar tabela compra_racao no novo esquema
    EXECUTE format(
    'CREATE TABLE %I.compra_racao(
    id_comp_racao SERIAL PRIMARY KEY NOT NULL,
    id_racao SERIAL NOT NULL REFERENCES %I.racao(id_racao) ON DELETE CASCADE,
    valor_uni NUMERIC NOT NULL, -- Real R$/kg da ração
    quantidade REAL NOT NULL, -- quantidade comprada (kg)
    valor_entrada NUMERIC NOT NULL -- Valor total da compra para essa ração (R$)
    );',
    novo_schema,novo_schema
    );
    -- Alterar a propriedade da tabela tipos_carro para o usuário 'novo_usuario' do esquema 'novo_schema'
    EXECUTE format('ALTER TABLE %I.compra_racao OWNER TO %I', novo_schema, novo_usuario);

-------------------------# Criando Tabela saida_racao #---------------------------------        
    -- Criar tabela saida_racao no novo esquema
    EXECUTE format(
    'CREATE TABLE %I.saida_racao(
    id_saida_racao SERIAL PRIMARY KEY NOT NULL,
    id_racao SERIAL NOT NULL REFERENCES %I.racao(id_racao) ON DELETE CASCADE,
    quantidade REAL NOT NULL,
    valor_saida NUMERIC NOT NULL,
    id_comp_racao SERIAL NOT NULL REFERENCES %I.compra_racao(id_comp_racao) ON DELETE CASCADE
    );',
    novo_schema,novo_schema,novo_schema
    );
    -- Alterar a propriedade da tabela tipos_carro para o usuário 'novo_usuario' do esquema 'novo_schema'
    EXECUTE format('ALTER TABLE %I.saida_racao OWNER TO %I', novo_schema, novo_usuario);

-------------------------# Criando Tabela estoque_racao #---------------------------------        
    -- Criar tabela estoque_racao no novo esquema
    EXECUTE format(
    'CREATE TABLE %I.estoque_racao(
    id_lote SERIAL PRIMARY KEY,
    id_comp_racao INT NOT NULL REFERENCES %I.compra_racao(id_comp_racao) ON DELETE CASCADE,
    id_racao INT NOT NULL REFERENCES %I.racao(id_racao) ON DELETE CASCADE,
    quantidade NUMERIC DEFAULT 0 CHECK (quantidade >= 0),
    valor_total NUMERIC(10, 2) DEFAULT 0 CHECK (valor_total >= 0)
    );',
    novo_schema,novo_schema,novo_schema
    );
    -- Alterar a propriedade da tabela tipos_carro para o usuário 'novo_usuario' do esquema 'novo_schema'
    EXECUTE format('ALTER TABLE %I.estoque_racao OWNER TO %I', novo_schema, novo_usuario);
--------------------------------------------------------------------------------------       
-- I believe that the dynamically created stock update function for the newly created user schema should go in here.

--------------------------------------------------------------------------------------
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-------------------------------------------
CREATE TRIGGER criar_usuario_schema_trigger
AFTER INSERT ON public.usuarios
FOR EACH ROW
EXECUTE FUNCTION criar_usuario_schema();
----------------------------#----------------------------#----------------------------#----------------------------
------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------
-- Vamos fazer o teste para saber se está tudo funcionando bem
----------------------------------------------------------------
-- Inserindo alguns usuários cadastrados no app (O adm zarzar quem insere os usuários)
INSERT INTO usuarios (nome, email, senha, admin) VALUES ('carlos', '[email protected]', '1234', 'TRUE');
INSERT INTO usuarios (nome, email, senha, admin) VALUES ('rafaela', '[email protected]', '1234', 'TRUE');
INSERT INTO usuarios (nome, email, senha, admin) VALUES ('tateu', '[email protected]', '1234', 'FALSE');
------------------------------------------------------------------------------------------

Note: I placed a comment in the code above where I believe the code should go. “I believe that the dynamically created stock update function for the newly created user schema should go in here.”

I hope to make my question clear to the reader. Please note that I am not an expert in the PostgreSQL programming language. And in advance, I thank you for your attention.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật