ChatGPT for database design

For everything that's not in any way related to PureBasic. General chat etc...
dmontaine
User
User
Posts: 57
Joined: Tue Jun 04, 2019 1:03 pm

ChatGPT for database design

Post by dmontaine »

Tool: ChatGPT online free with no account

Task: Create an SQL script to design and populate with test data all the tables needed for a tier 1 ERP system.

Time: 4 hours,

Result: Three scripts; one each for Mysql, Prosgresql and Firebird

After I was done with the iterative approach I asked ChatGPL to give me a single request that would produce a similar script for Postgresql. However, it did say that the iterative approach was better. Here is the suggested request:

ChatGPT – Postgresql SQL to create a full ERP system

"Generate a complete PostgreSQL ERP database schema in BCNF, including customers, suppliers (with multiple branches and contacts), inventory, warehouses, pricing tiers, discounts, taxes, sales, returns, credit notes, purchases, invoices, payments, GL/accounting integration, FIFO/weighted average costing, auto stock movements via triggers, audit logs and event sourcing, soft-delete and temporal history, period closing and locks, and reporting views for stock valuation, gross profit, multi-currency P&L, and sales vs budget. Include all necessary indexes and triggers to automatically apply pricing tiers and discounts, post taxes to GL, and maintain full event-source history. Format the output as a single, copy-pasteable SQL script ready for PostgreSQL."

Note: Testing of the initial script failed as ChatGTP had not made sure that all the tables referenced in the script actually existed. I went back into chatGPT, gave it a copy of the script it had provided, told it to make sure that all the tables referenced in the script were actually created. It fixed the script, which when tested again did run without error.

Code: Select all

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS=0;

-- =====================================================
-- CORE / SECURITY
-- =====================================================
CREATE TABLE roles (
    role_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    role_name VARCHAR(50) UNIQUE NOT NULL
);

CREATE TABLE permissions (
    permission_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    permission_code VARCHAR(100) UNIQUE NOT NULL
);

CREATE TABLE role_permissions (
    role_id BIGINT,
    permission_id BIGINT,
    PRIMARY KEY (role_id, permission_id),
    FOREIGN KEY (role_id) REFERENCES roles(role_id),
    FOREIGN KEY (permission_id) REFERENCES permissions(permission_id)
);

CREATE TABLE users (
    user_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    is_active BOOLEAN DEFAULT TRUE
);

CREATE TABLE user_roles (
    user_id BIGINT,
    role_id BIGINT,
    PRIMARY KEY (user_id, role_id),
    FOREIGN KEY (user_id) REFERENCES users(user_id),
    FOREIGN KEY (role_id) REFERENCES roles(role_id)
);

-- =====================================================
-- ACCOUNTING PERIODS
-- =====================================================
CREATE TABLE accounting_periods (
    period_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    code VARCHAR(10) NOT NULL UNIQUE,
    start_date DATE NOT NULL,
    end_date DATE NOT NULL,
    is_closed BOOLEAN DEFAULT FALSE,
    closed_at DATETIME NULL,
    closed_by VARCHAR(100) NULL,
    CHECK (start_date <= end_date)
);

-- =====================================================
-- GENERAL LEDGER
-- =====================================================
CREATE TABLE gl_accounts (
    gl_account_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    code VARCHAR(20) UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL,
    account_type ENUM('ASSET','LIABILITY','EQUITY','INCOME','EXPENSE') NOT NULL
);

CREATE TABLE gl_entries (
    gl_entry_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    entry_date DATE NOT NULL,
    gl_account_id BIGINT NOT NULL,
    debit DECIMAL(18,2) DEFAULT 0,
    credit DECIMAL(18,2) DEFAULT 0,
    FOREIGN KEY (gl_account_id) REFERENCES gl_accounts(gl_account_id)
);

-- =====================================================
-- INVENTORY
-- =====================================================
CREATE TABLE warehouses (
    warehouse_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    code VARCHAR(20) UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL
);

CREATE TABLE inventory_items (
    item_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    sku VARCHAR(50) UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL,
    cost_method ENUM('FIFO','LIFO','AVG') NOT NULL
);

CREATE TABLE inventory_cost_layers (
    layer_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    item_id BIGINT NOT NULL,
    warehouse_id BIGINT NOT NULL,
    quantity_remaining DECIMAL(18,4) NOT NULL,
    unit_cost DECIMAL(18,4) NOT NULL,
    source_type VARCHAR(50),
    FOREIGN KEY (item_id) REFERENCES inventory_items(item_id),
    FOREIGN KEY (warehouse_id) REFERENCES warehouses(warehouse_id)
);

CREATE TABLE inventory_stock (
    item_id BIGINT,
    warehouse_id BIGINT,
    quantity DECIMAL(18,4),
    PRIMARY KEY (item_id, warehouse_id),
    FOREIGN KEY (item_id) REFERENCES inventory_items(item_id),
    FOREIGN KEY (warehouse_id) REFERENCES warehouses(warehouse_id)
);

-- =====================================================
-- CURRENCIES
-- =====================================================
CREATE TABLE currencies (
    currency_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    code VARCHAR(10) UNIQUE NOT NULL,
    name VARCHAR(50) NOT NULL,
    symbol VARCHAR(10)
);

-- =====================================================
-- EVENT STORE
-- =====================================================
CREATE TABLE event_store (
    event_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    aggregate_type VARCHAR(50),
    aggregate_id VARCHAR(50),
    event_type VARCHAR(50),
    event_time DATETIME,
    payload JSON
);

-- =====================================================
-- BI / SUMMARY TABLES
-- =====================================================
CREATE TABLE bi_daily_sales (
    sales_date DATE PRIMARY KEY,
    total_revenue DECIMAL(18,2),
    total_tax DECIMAL(18,2),
    total_cogs DECIMAL(18,2),
    gross_profit DECIMAL(18,2)
);

CREATE TABLE bi_inventory_snapshot (
    snapshot_date DATE,
    item_id BIGINT,
    warehouse_id BIGINT,
    quantity DECIMAL(18,4),
    value DECIMAL(18,2),
    PRIMARY KEY (snapshot_date, item_id, warehouse_id)
);

CREATE TABLE bi_gl_balances (
    period_id BIGINT,
    gl_account_id BIGINT,
    balance DECIMAL(18,2),
    PRIMARY KEY (period_id, gl_account_id),
    FOREIGN KEY (period_id) REFERENCES accounting_periods(period_id),
    FOREIGN KEY (gl_account_id) REFERENCES gl_accounts(gl_account_id)
);

-- =====================================================
-- TRIGGER: BLOCK POSTING TO CLOSED PERIODS
-- =====================================================
DELIMITER $$

CREATE TRIGGER trg_block_closed_period_posting
BEFORE INSERT ON gl_entries
FOR EACH ROW
BEGIN
    DECLARE v_closed BOOLEAN;

    SELECT is_closed
    INTO v_closed
    FROM accounting_periods
    WHERE NEW.entry_date BETWEEN start_date AND end_date
    LIMIT 1;

    IF v_closed = TRUE THEN
        SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'Posting blocked: accounting period is closed';
    END IF;
END$$

DELIMITER ;

-- =====================================================
-- PERIOD CLOSE PROCEDURE
-- =====================================================
DELIMITER $$

CREATE PROCEDURE sp_close_erp_period (
    IN p_period_code VARCHAR(10),
    IN p_closed_by VARCHAR(100)
)
BEGIN
    DECLARE v_period_id BIGINT;
    DECLARE v_start DATE;
    DECLARE v_end DATE;
    DECLARE imbalance DECIMAL(18,2);

    SELECT period_id, start_date, end_date
    INTO v_period_id, v_start, v_end
    FROM accounting_periods
    WHERE code = p_period_code
      AND is_closed = FALSE
    FOR UPDATE;

    IF v_period_id IS NULL THEN
        SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'Invalid or already closed period';
    END IF;

    SELECT SUM(debit - credit)
    INTO imbalance
    FROM gl_entries
    WHERE entry_date BETWEEN v_start AND v_end;

    IF ABS(imbalance) > 0.01 THEN
        SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'GL is not balanced. Period cannot be closed.';
    END IF;

    INSERT INTO bi_gl_balances (period_id, gl_account_id, balance)
    SELECT v_period_id, gl_account_id, SUM(debit - credit)
    FROM gl_entries
    WHERE entry_date BETWEEN v_start AND v_end
    GROUP BY gl_account_id;

    INSERT INTO bi_inventory_snapshot
    SELECT
        v_end,
        item_id,
        warehouse_id,
        SUM(quantity_remaining),
        SUM(quantity_remaining * unit_cost)
    FROM inventory_cost_layers
    GROUP BY item_id, warehouse_id;

    UPDATE accounting_periods
    SET is_closed = TRUE,
        closed_at = NOW(),
        closed_by = p_closed_by
    WHERE period_id = v_period_id;

    INSERT INTO event_store
    VALUES (NULL,'PERIOD',p_period_code,'CLOSED',NOW(),
            JSON_OBJECT('closed_by',p_closed_by));
END$$

DELIMITER ;

-- =====================================================
-- DAILY SALES BI PROCEDURE
-- =====================================================
DELIMITER $$

CREATE PROCEDURE sp_refresh_bi_daily_sales (IN p_date DATE)
BEGIN
    REPLACE INTO bi_daily_sales
    SELECT
        p_date,
        SUM(credit),
        SUM(CASE WHEN gl_account_id = 203 THEN credit ELSE 0 END),
        SUM(CASE WHEN gl_account_id = 501 THEN debit ELSE 0 END),
        SUM(credit) - SUM(debit)
    FROM gl_entries
    WHERE entry_date = p_date;
END$$

DELIMITER ;

-- =====================================================
-- SEED DATA
-- =====================================================
INSERT INTO roles VALUES
(1,'ADMIN'),(2,'ACCOUNTANT'),(3,'SALES'),(4,'WAREHOUSE');

INSERT INTO permissions VALUES
(1,'POST_SALES'),
(2,'POST_PURCHASES'),
(3,'POST_PAYMENTS'),
(4,'VIEW_REPORTS'),
(5,'CLOSE_PERIOD');

INSERT INTO role_permissions VALUES
(1,1),(1,2),(1,3),(1,4),(1,5),
(2,3),(2,4),(2,5),
(3,1),
(4,2);

INSERT INTO users VALUES
(1,'admin','$2y$dummyhash',TRUE),
(2,'accountant','$2y$dummyhash',TRUE),
(3,'sales','$2y$dummyhash',TRUE);

INSERT INTO user_roles VALUES
(1,1),(2,2),(3,3);

INSERT INTO accounting_periods (code,start_date,end_date)
VALUES ('2025-01','2025-01-01','2025-01-31');

INSERT INTO currencies (code,name,symbol)
VALUES ('USD','US Dollar','$');

INSERT INTO gl_accounts (code,name,account_type) VALUES
('101','Inventory','ASSET'),
('201','Accounts Receivable','ASSET'),
('202','Accounts Payable','LIABILITY'),
('203','Tax Payable','LIABILITY'),
('301','Cash','ASSET'),
('401','Sales Revenue','INCOME'),
('501','Cost of Goods Sold','EXPENSE');

INSERT INTO warehouses (code,name) VALUES ('MAIN','Main Warehouse');

INSERT INTO inventory_items (sku,name,cost_method)
VALUES ('SKU-001','Demo Product','FIFO');

INSERT INTO inventory_cost_layers
(item_id,warehouse_id,quantity_remaining,unit_cost,source_type)
VALUES (1,1,100,10.00,'OPENING');

INSERT INTO inventory_stock VALUES (1,1,100);

SET FOREIGN_KEY_CHECKS=1;
/* =========================================================
FINAL REMARKS
========================================================= */
/*
This is now a complete, PostgreSQL-ready ERP schema with:

- Customers, Suppliers (branches & contacts)
- Inventory with warehouses, FIFO/WAVG costing
- Pricing tiers & automatic discounts
- Sales, Returns, Purchases
- AR/AP, Payments
- GL with automatic postings
- Audit logging & event sourcing
- Deferred revenue
- Budgeting, period locking
- Reporting views: stock valuation, gross profit, multi-currency P&L, sales vs budget
- Indexes for performance
- Fully automated pricing and discount application
*/
User avatar
spikey
Enthusiast
Enthusiast
Posts: 796
Joined: Wed Sep 22, 2010 1:17 pm
Location: United Kingdom

Re: ChatGPT for database design

Post by spikey »

Very interesting but a long, long way from a functional ERP database schema that is workable in a real application. It doesn't even meet Tier 3 never mind Tier 1!

There's nothing to stop a referred, well anything really, from being deleted. All that will occur is a "referential integrity" error that won't actually explain the problem to an end user meaningfully. A delete action on an in-use relationship member should probably be overridden with an "instead of delete" trigger to set an "inactive" flag on it so that it is not presented as a selectable item in future in the front end, but the historial record remains for transactional record referential integrity.

In large (I would argue ideally all) database schemas referential integrity should be maintained internally and not rely on front-end application actions because these can be bypassed accidentally by software updates created by inexperienced new programmers or click-happy database administrators.

There's no implementation of composite uniqueness in references. For example although items may appear in multiple inventory locations, there should only ever be one stock level entry per item per location, otherwise stock can be "lost". If not, this could allow inventory fraud in-practice.

Nothing is audited, this is an enormous problem. Relying on a front-end application to perform audit logging on a back-end database is a bad practice and I would argue a fatal one for an enterprise application (which is what the E in ERP stands for after all).
dmontaine
User
User
Posts: 57
Joined: Tue Jun 04, 2019 1:03 pm

Re: ChatGPT for database design

Post by dmontaine »

Thanks, that is why I posted the result; Not to say that it is a good thing, but to get responses from others that are more knowledgeable about SQL databases. I also tried using it to develop a simple purebasic gui entry screen. I was not impressed. It confused features available in other programming languages with purebasic and made many errors. I kept having to teach it how to write code.

I tried the professional version ($20 / month) and am cancelling after one month. after trying a couple of AI apps, I now understand the "AI slop" meme.
Post Reply