Skip to content

Instantly share code, notes, and snippets.

@hernandohhoyos
Created February 1, 2026 07:06
Show Gist options
  • Select an option

  • Save hernandohhoyos/4d8c9ed6a6e7ae019f271102197cba3a to your computer and use it in GitHub Desktop.

Select an option

Save hernandohhoyos/4d8c9ed6a6e7ae019f271102197cba3a to your computer and use it in GitHub Desktop.
SQL: Schemeless Data Storage in PostgreSQL
/*
* Approach: Schemeless Data Storage in PostgreSQL
*
* This implementation utilizes the JSONB data type to achieve a schemeless (document-oriented)
* architecture within a relational database.
*
* Advantages:
* - Schema Flexibility: Allows storing diverse data structures without migrations.
* - Development Speed: Faster iteration during early-stage development.
* - Dynamic Attributes: Easily handle entities with unpredictable or sparse fields.
*
* Disadvantages:
* - Validation Overhead: Requires explicit CHECK constraints or triggers to enforce data types.
* - Query Complexity: Accessing nested fields requires specific JSON operators.
* - Performance: Slightly higher CPU/storage cost compared to strictly typed columns.
* - Tooling: Some ORMs and BI tools have limited support for deep JSON structures.
*/
CREATE TABLE IF NOT EXISTS domain_data (
id UUID PRIMARY KEY DEFAULT uuidv7(),
is_active BOOLEAN DEFAULT TRUE, -- For logical deletion.
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
entity VARCHAR(25) NOT NULL,
medadata JSONB NOT NULL
);
-- Option 1: Add checks directly.
ALTER TABLE domain_data ADD CONSTRAINT check_metadata_structure CHECK (
(entity = 'user' AND medadata ? 'email' AND medadata ? 'username') OR
(entity = 'product' AND medadata ? 'sku' AND medadata ? 'price') OR
(entity NOT IN ('user', 'product')) -- Allow others without validation
);
ALTER TABLE domain_data ADD CONSTRAINT check_metadata_types CHECK (
CASE
WHEN entity = 'product' THEN
jsonb_typeof(medadata -> 'price') = 'number' AND
jsonb_typeof(medadata -> 'sku') = 'string'
ELSE TRUE
END
);
-- Option 2: Use a function.
CREATE OR REPLACE FUNCTION validate_entity_metadata(ent VARCHAR, data JSONB)
RETURNS BOOLEAN AS $$
BEGIN
IF ent = 'user' THEN
RETURN (data ? 'email') AND (data ->> 'email' ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$');
ELSIF ent = 'product' THEN
RETURN (data ? 'sku') AND (data -> 'price' != 'null');
END IF;
RETURN TRUE; -- Unknown entities pass by default
END;
$$ LANGUAGE plpgsql;
ALTER TABLE domain_data ADD CONSTRAINT constraint_entity_validation
CHECK (validate_entity_metadata(entity, medadata));
-- This index will only take up space for users, ignoring products.
CREATE UNIQUE INDEX idx_unique_user_email
ON domain_data ((medadata->>'email'))
WHERE (entity = 'user');
-- Option 3: partitions.
-- pg_partman extension. It automatically creates the "next month" partition so you never run out of space to insert.
CREATE TABLE IF NOT EXISTS domain_data (
id UUID PRIMARY KEY DEFAULT uuidv7(),
is_active BOOLEAN DEFAULT TRUE, -- For logical deletion.
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
entity VARCHAR(25) NOT NULL,
medadata JSONB NOT NULL,
-- The partition key must be part of the Primary Key in Postgres
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);
-- Partition for January 2026
CREATE TABLE domain_data_2026_01 PARTITION OF domain_data
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
-- Partition for February 2026
CREATE TABLE domain_data_2026_02 PARTITION OF domain_data
FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
-- 1. GIN index for general searches within any JSONB field
CREATE INDEX idx_metadata_gin ON domain_data USING GIN (metadata);
-- 2. B-Tree index for the entity_type column (useful for fast filters)
CREATE INDEX idx_entity_type ON domain_data (entity_type);
-- 3. Expression Index (Example: if searching frequently by product 'sku')
CREATE INDEX idx_metadata_sku ON domain_data ((metadata->>'sku'))
WHERE (entity_type = 'product');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment