jessevent@supa_profile

v1.0.0Created a month agoBy jessevent

supa_profile - PostgreSQL Database Table & Column Profiler

supa_profile is a relocatable, pure SQL PostgreSQL Trusted Language Extension (TLE) designed for native table and column-level statistical profiling. It introspects schema metadata, dynamically constructs profiling queries, and compiles a comprehensive statistical report (including counts, nullability, distinctness, min/max, string length distributions, averages, medians, percentiles, and frequent value distributions) returned directly as a single structured JSONB document.

The JSONB output format is fully compatible with the ProfilingResult TypeScript interface used by frontend profiling dashboards.


🚀 Installation

1. Enable TLE Support

supa_profile runs as a Trusted Language Extension and requires the pg_tle extension to be enabled:

CREATE EXTENSION IF NOT EXISTS "pg_tle";

2. Installing from database.dev

Using the dbdev CLI:

dbdev add -o ./migrations -s extensions -v 1.0.0 package -n "jvent@supa_profile"

This generates a migration script to load the TLE. Apply the migration, then enable the extension:

CREATE EXTENSION "jvent@supa_profile" VERSION '1.0.0' SCHEMA supa_profile;

🛠️ API Reference

1. Profile Table

Introspects and profiles a physical table, view, or materialized view.

  • Function: supa_profile.profile_table(target_table regclass, options jsonb DEFAULT '{}') RETURNS jsonb

  • Options Parameter (options): A JSON object supporting the following parameters:

    • scan_field_values (boolean, default: true): Scan column values to compute top value frequency distributions.
    • min_cell_count (int, default: 5): Minimum occurrence count of a value to include it in the value distribution list.
    • max_distinct_values (int, default: 100): Maximum distinct values returned per column in the value distribution list.
    • rows_per_table (int, default: 0 = unlimited): Maximum rows to scan. If greater than zero, it wraps the source in a sampled subquery (LIMIT N).
    • calculate_numeric_stats (boolean, default: true): Compute average, median, 90th percentile, and 99th percentile for numeric fields.
  • Example:

    SELECT supa_profile.profile_table(
        'public.users'::regclass,
        '{"rows_per_table": 10000, "min_cell_count": 10}'::jsonb
    );
    

📊 Output Schema (JSONB)

The function returns a JSONB object containing:

  • tableStats: Table name, row count, column count.
  • fields: Array of column-level statistics.
  • values: Value frequency distribution array.
  • profiledAt: Timestamp in UTC.
  • durationMs: Total duration of the profiling process.
  • queryMode: Current query mode (NORMAL or FAST for sampled scans).

Field Properties:

PropertyTypeDescription
columnNametextColumn identifier
dataTypetextColumn type format
nullablebooleanTrue if nulls are allowed
nonNullCountbigintTotal count of non-null cells
nullCountbigintTotal count of null cells
distinctCountbigintTotal unique value count
minValuetextMinimum value (omitted for JSON/bytes/arrays)
maxValuetextMaximum value (omitted for JSON/bytes/arrays)
avgValuenumericAverage value (numeric columns only)
mediannumericMedian value / P50 (numeric columns only)
p90numeric90th percentile value (numeric columns only)
p99numeric99th percentile value (numeric columns only)
avgLengthnumericAverage string length of text representation
maxLengthbigintMaximum string length of text representation
modetextMost frequent value (omitted if scan_field_values is false)
patterntextInferred regex pattern for type verification

📖 Example Walkthrough

1. Create Sample Table

CREATE TABLE public.customers (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    age INT,
    country VARCHAR(3) DEFAULT 'USA',
    signup_date DATE
);

INSERT INTO public.customers (name, age, country, signup_date) VALUES
('Alice', 25, 'USA', '2025-01-10'),
('Bob', 30, 'USA', '2025-02-15'),
('Charlie', 45, 'CAN', '2024-11-20'),
('Diana', 25, 'USA', '2025-03-01'),
('Ethan', NULL, 'GBR', NULL);

2. Profile the Table

SELECT jsonb_pretty(supa_profile.profile_table('public.customers'::regclass, '{"min_cell_count": 1}'::jsonb));

3. Example JSONB Output

{
  "fields": [
    {
      "mode": "",
      "pattern": "-?\\d+",
      "dataType": "integer",
      "nullable": false,
      "nullCount": 0,
      "avgLength": 1.0,
      "columnName": "id",
      "distinctCount": 5,
      "maxLength": 1,
      "nonNullCount": 5,
      "tableName": "public.customers",
      "avgValue": 3.0,
      "median": 3.0,
      "p90": 5.0,
      "p99": 5.0,
      "minValue": "1",
      "maxValue": "5"
    },
    {
      "mode": "",
      "pattern": ".*",
      "dataType": "text",
      "nullable": false,
      "nullCount": 0,
      "avgLength": 4.6,
      "columnName": "name",
      "distinctCount": 5,
      "maxLength": 7,
      "nonNullCount": 5,
      "tableName": "public.customers",
      "minValue": "Alice",
      "maxValue": "Ethan"
    },
    {
      "mode": "25",
      "pattern": "-?\\d+",
      "dataType": "integer",
      "nullable": true,
      "nullCount": 1,
      "avgLength": 1.6,
      "columnName": "age",
      "distinctCount": 3,
      "maxLength": 2,
      "nonNullCount": 4,
      "tableName": "public.customers",
      "avgValue": 31.25,
      "median": 30.0,
      "p90": 45.0,
      "p99": 45.0,
      "minValue": "25",
      "maxValue": "45"
    },
    {
      "mode": "USA",
      "pattern": ".*",
      "dataType": "character varying(3)",
      "nullable": true,
      "nullCount": 0,
      "avgLength": 3.0,
      "columnName": "country",
      "distinctCount": 3,
      "maxLength": 3,
      "nonNullCount": 5,
      "tableName": "public.customers",
      "minValue": "CAN",
      "maxValue": "USA"
    }
  ],
  "values": [
    {
      "value": "25",
      "percent": 0.4000,
      "frequency": 2,
      "columnName": "age"
    },
    {
      "value": "USA",
      "percent": 0.6000,
      "frequency": 3,
      "columnName": "country"
    }
  ],
  "queryMode": "NORMAL",
  "durationMs": 4.32,
  "profiledAt": "2026-06-08T18:00:00Z",
  "tableStats": {
    "rowCount": 5,
    "selected": false,
    "tableName": "public.customers",
    "columnCount": 5
  }
}

🛡️ License

MIT License. Feel free to use and distribute.

Install

  1. Install the dbdev CLI
  2. Generate migration:
dbdev add -o ./migrations -s extensions -v 1.0.0 package -n "jessevent@supa_profile"

Downloads

  • 0 all time downloads
  • 0 downloads in the last 30 days
  • 0 downloads in the last 90 days
  • 0 downloads in the last 180 days