SQL Feature Matrix
This document provides a comprehensive matrix of SQL features, comparing support across DecentDB, SQLite, PostgreSQL, and DuckDB.
See also: Comparison: DecentDB vs SQLite vs DuckDB for a narrative discussion of design differences, trade-offs, and architecture.
DDL (Data Definition Language)
| Feature | DecentDB | SQLite | PostgreSQL | DuckDB |
| CREATE TABLE | ✅ | ✅ | ✅ | ✅ |
| DROP TABLE | ✅ | ✅ | ✅ | ✅ |
| CREATE INDEX | ✅ | ✅ | ✅ | ✅ |
| DROP INDEX | ✅ | ✅ | ✅ | ✅ |
| ALTER TABLE ADD COLUMN | ✅ | ✅ | ✅ | ✅ |
| ALTER TABLE DROP COLUMN | ✅ | ✅ | ✅ | ✅ |
| ALTER TABLE RENAME COLUMN | ✅ | ✅ (via ALTER TABLE RENAME) | ✅ | ✅ |
| ALTER TABLE ALTER COLUMN TYPE | ✅ | ✅ | ✅ | ✅ |
| CREATE VIEW | ✅ | ✅ | ✅ | ✅ |
| DROP VIEW | ✅ | ✅ | ✅ | ✅ |
| CREATE TRIGGER | ✅ | ✅ | ✅ | ❌ |
| DROP TRIGGER | ✅ | ✅ | ✅ | ❌ |
| CREATE TEMP TABLE | ✅ | ✅ | ✅ | ✅ |
| CREATE TEMP VIEW | ✅ | ✅ | ✅ | ✅ |
| Generated columns (STORED) | ✅ | ✅ | ✅ | ✅ |
| Table-level FOREIGN KEY | ✅ | ✅ | ✅ | ⚠️ (parsed, not enforced) |
Examples
-- CREATE TABLE with constraints
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE);
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
amount DECIMAL(10,2) NOT NULL
);
-- Table-level FOREIGN KEY
CREATE TABLE order_items (
order_id INTEGER,
product_id INTEGER,
qty INTEGER NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (product_id) REFERENCES products(id)
);
-- CREATE INDEX
CREATE INDEX idx_users_name ON users (name);
CREATE INDEX idx_orders_user ON orders (user_id) WHERE user_id IS NOT NULL;
-- ALTER TABLE
ALTER TABLE users ADD COLUMN email TEXT;
ALTER TABLE users DROP COLUMN email;
ALTER TABLE users RENAME COLUMN name TO full_name;
ALTER TABLE users ALTER COLUMN name TYPE VARCHAR(255);
-- CREATE VIEW
CREATE VIEW user_orders AS
SELECT u.name, o.id FROM users u LEFT JOIN orders o ON u.id = o.user_id;
CREATE OR REPLACE VIEW active_users AS
SELECT * FROM users WHERE active = TRUE;
-- TEMP objects (session-scoped, not persisted)
CREATE TEMP TABLE scratch (id INTEGER PRIMARY KEY, val TEXT);
CREATE TEMP VIEW recent_orders AS SELECT * FROM orders WHERE id > 100;
-- Generated columns
CREATE TABLE products (
id INTEGER PRIMARY KEY,
price DECIMAL(10,2),
tax_rate DECIMAL(4,2),
total DECIMAL(10,2) GENERATED ALWAYS AS (price * (1 + tax_rate)) STORED
);
-- CREATE TRIGGER (AFTER row trigger)
CREATE TRIGGER log_insert AFTER INSERT ON users
FOR EACH ROW BEGIN
SELECT decentdb_exec_sql('INSERT INTO audit_log (msg) VALUES (''user added'')');
END;
DML (Data Manipulation Language)
| Feature | DecentDB | SQLite | PostgreSQL | DuckDB |
| SELECT | ✅ | ✅ | ✅ | ✅ |
| INSERT | ✅ | ✅ | ✅ | ✅ |
| UPDATE | ✅ | ✅ | ✅ | ✅ |
| DELETE | ✅ | ✅ | ✅ | ✅ |
| INSERT ... RETURNING | ✅ | ❌ | ✅ | ✅ |
| INSERT ... ON CONFLICT | ✅ (DO NOTHING/DO UPDATE) | ✅ (ON CONFLICT) | ✅ (ON CONFLICT) | ✅ (ON CONFLICT) |
| Bulk INSERT | ✅ | ✅ | ✅ | ✅ |
Examples
-- INSERT with RETURNING
INSERT INTO users (name) VALUES ('Alice') RETURNING id;
-- INSERT ON CONFLICT (upsert)
INSERT INTO users (id, name) VALUES (1, 'Alice')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name;
-- ON CONFLICT DO NOTHING
INSERT INTO users (id, name) VALUES (1, 'Alice')
ON CONFLICT (id) DO NOTHING;
-- Bulk INSERT (multiple rows)
INSERT INTO users (name) VALUES ('Alice'), ('Bob'), ('Charlie');
-- UPDATE with WHERE
UPDATE orders SET amount = amount * 1.1 WHERE user_id = 1;
-- DELETE with WHERE
DELETE FROM orders WHERE amount < 1.00;
JOINs
| Feature | DecentDB | SQLite | PostgreSQL | DuckDB |
| INNER JOIN | ✅ | ✅ | ✅ | ✅ |
| LEFT JOIN | ✅ | ✅ | ✅ | ✅ |
| RIGHT JOIN | ✅ (rewritten as LEFT JOIN) | ✅ | ✅ | ✅ |
| FULL OUTER JOIN | ✅ | ✅ | ✅ | ✅ |
| CROSS JOIN | ✅ | ✅ | ✅ | ✅ |
| NATURAL JOIN | ✅ | ✅ | ✅ | ✅ |
Examples
-- INNER JOIN
SELECT u.name, o.amount
FROM users u INNER JOIN orders o ON u.id = o.user_id;
-- LEFT JOIN (all users, even those without orders)
SELECT u.name, o.amount
FROM users u LEFT JOIN orders o ON u.id = o.user_id;
-- RIGHT JOIN (all orders, even those without matching users)
SELECT u.name, o.amount
FROM users u RIGHT JOIN orders o ON u.id = o.user_id;
-- FULL OUTER JOIN (all rows from both sides)
SELECT u.name, o.amount
FROM users u FULL OUTER JOIN orders o ON u.id = o.user_id;
-- CROSS JOIN (cartesian product)
SELECT u.name, p.name AS product
FROM users u CROSS JOIN products p;
-- NATURAL JOIN (join on columns with matching names)
SELECT * FROM orders NATURAL JOIN order_details;
Queries and Clauses
| Feature | DecentDB | SQLite | PostgreSQL | DuckDB |
| WHERE | ✅ | ✅ | ✅ | ✅ |
| ORDER BY | ✅ | ✅ | ✅ | ✅ |
| LIMIT/OFFSET | ✅ | ✅ | ✅ | ✅ |
| GROUP BY | ✅ | ✅ | ✅ | ✅ |
| HAVING | ✅ | ✅ | ✅ | ✅ |
| DISTINCT | ✅ | ✅ | ✅ | ✅ |
| DISTINCT ON | ✅ | ❌ | ✅ | ✅ |
| LIMIT ALL | ✅ | ✅ | ✅ | ✅ |
| OFFSET with FETCH | ✅ | ✅ | ✅ | ✅ |
Examples
-- Basic filtering and sorting
SELECT id, name FROM users WHERE id > 10 ORDER BY name LIMIT 5;
-- GROUP BY with HAVING
SELECT user_id, COUNT(*) AS order_count
FROM orders GROUP BY user_id HAVING COUNT(*) > 5;
-- DISTINCT ON (first order per user, by date)
SELECT DISTINCT ON (user_id) user_id, id, created_at
FROM orders ORDER BY user_id, created_at DESC;
-- OFFSET with FETCH (SQL-standard pagination)
SELECT * FROM users ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
-- LIMIT / OFFSET (traditional form)
SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 20;
Aggregate Functions
| Feature | DecentDB | SQLite | PostgreSQL | DuckDB |
| COUNT(*) | ✅ | ✅ | ✅ | ✅ |
| COUNT(expr) | ✅ | ✅ | ✅ | ✅ |
| SUM | ✅ | ✅ | ✅ | ✅ |
| AVG | ✅ | ✅ | ✅ | ✅ |
| MIN | ✅ | ✅ | ✅ | ✅ |
| MAX | ✅ | ✅ | ✅ | ✅ |
| GROUP_CONCAT | ✅ | ✅ | ✅ | ❌ (use STRING_AGG) |
| STRING_AGG | ✅ | ❌ | ✅ | ✅ |
| TOTAL | ✅ | ✅ | ❌ | ❌ |
| COUNT(DISTINCT) | ✅ | ✅ | ✅ | ✅ |
| SUM(DISTINCT) | ✅ | ✅ | ✅ | ✅ |
| AVG(DISTINCT) | ✅ | ✅ | ✅ | ✅ |
Examples
-- Basic aggregates
SELECT COUNT(*), SUM(amount), AVG(amount), MIN(amount), MAX(amount) FROM orders;
-- DISTINCT aggregates (unique values only)
SELECT COUNT(DISTINCT user_id) AS unique_customers,
SUM(DISTINCT amount) AS distinct_totals,
AVG(DISTINCT amount) AS avg_distinct
FROM orders;
-- GROUP_CONCAT (concatenate values into a string)
SELECT department, GROUP_CONCAT(name, ', ') FROM employees GROUP BY department;
-- STRING_AGG (Postgres-style equivalent)
SELECT department, STRING_AGG(name, ', ') FROM employees GROUP BY department;
-- TOTAL (returns 0.0 for empty sets, unlike SUM which returns NULL)
SELECT TOTAL(amount) FROM orders WHERE 1 = 0; -- returns 0.0
SELECT SUM(amount) FROM orders WHERE 1 = 0; -- returns NULL
Window Functions
| Feature | DecentDB | SQLite | PostgreSQL | DuckDB |
| ROW_NUMBER() | ✅ | ✅ | ✅ | ✅ |
| RANK() | ✅ | ✅ | ✅ | ✅ |
| DENSE_RANK() | ✅ | ✅ | ✅ | ✅ |
| LAG() | ✅ | ✅ | ✅ | ✅ |
| LEAD() | ✅ | ✅ | ✅ | ✅ |
| FIRST_VALUE() | ✅ | ✅ | ✅ | ✅ |
| LAST_VALUE() | ✅ | ✅ | ✅ | ✅ |
| NTH_VALUE() | ✅ | ✅ | ✅ | ✅ |
Examples
-- ROW_NUMBER, RANK, DENSE_RANK
SELECT name, department, salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rnk,
DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dense_rnk
FROM employees;
-- LAG / LEAD (access previous/next row values)
SELECT name, salary,
LAG(salary, 1) OVER (ORDER BY salary) AS prev_salary,
LEAD(salary, 1) OVER (ORDER BY salary) AS next_salary
FROM employees;
-- FIRST_VALUE / LAST_VALUE / NTH_VALUE
SELECT name, department, salary,
FIRST_VALUE(name) OVER (PARTITION BY department ORDER BY salary DESC) AS top_earner,
LAST_VALUE(name) OVER (PARTITION BY department ORDER BY salary DESC) AS low_earner,
NTH_VALUE(name, 2) OVER (PARTITION BY department ORDER BY salary DESC) AS second_earner
FROM employees;
Scalar Functions
Math Functions
| Function | DecentDB | SQLite | PostgreSQL | DuckDB |
| ABS() | ✅ | ✅ | ✅ | ✅ |
| CEIL()/CEILING() | ✅ | ✅ | ✅ | ✅ |
| FLOOR() | ✅ | ✅ | ✅ | ✅ |
| ROUND() | ✅ | ✅ | ✅ | ✅ |
| SQRT() | ✅ | ✅ | ✅ | ✅ |
| POWER()/POW() | ✅ | ✅ | ✅ | ✅ |
| MOD() | ✅ | ✅ | ✅ | ✅ |
| SIGN() | ✅ | ✅ | ✅ | ✅ |
| LN() | ✅ | ❌ | ✅ | ✅ |
| LOG() | ✅ | ❌ | ✅ | ✅ |
| EXP() | ✅ | ✅ | ✅ | ✅ |
| RANDOM() | ✅ (returns FLOAT64) | ✅ (returns INT64) | ✅ (returns FLOAT64) | ✅ (returns DOUBLE) |
String Functions
| Function | DecentDB | SQLite | PostgreSQL | DuckDB |
| LENGTH() | ✅ | ✅ | ✅ | ✅ |
| LOWER() | ✅ | ✅ | ✅ | ✅ |
| UPPER() | ✅ | ✅ | ✅ | ✅ |
| TRIM() | ✅ | ✅ | ✅ | ✅ |
| LTRIM() | ✅ | ✅ | ✅ | ✅ |
| RTRIM() | ✅ | ✅ | ✅ | ✅ |
| SUBSTR()/SUBSTRING() | ✅ | ✅ | ✅ | ✅ |
| REPLACE() | ✅ | ✅ | ✅ | ✅ |
| INSTR() | ✅ | ✅ | ✅ | ✅ (via strpos) |
| LEFT() | ✅ | ❌ | ✅ | ✅ |
| RIGHT() | ✅ | ❌ | ✅ | ✅ |
| LPAD() | ✅ | ❌ | ✅ | ✅ |
| RPAD() | ✅ | ❌ | ✅ | ✅ |
| REPEAT() | ✅ | ✅ | ✅ | ✅ |
| REVERSE() | ✅ | ✅ | ✅ | ✅ |
| CHR() | ✅ | ❌ (uses CHAR) | ✅ | ✅ |
| HEX() | ✅ | ✅ | ✅ | ✅ |
Date/Time Functions
| Function | DecentDB | SQLite | PostgreSQL | DuckDB |
| NOW() | ✅ | ❌ | ✅ | ✅ |
| CURRENT_TIMESTAMP | ✅ | ✅ | ✅ | ✅ |
| CURRENT_DATE | ✅ | ✅ | ✅ | ✅ |
| CURRENT_TIME | ✅ | ✅ | ✅ | ✅ |
| date() | ✅ | ✅ | ✅ (different) | ❌ (use CAST) |
| datetime() | ✅ | ✅ | ✅ (different) | ❌ (use CAST) |
| strftime() | ✅ | ✅ | ❌ | ✅ |
| EXTRACT() | ✅ | ❌ | ✅ | ✅ |
JSON Functions
| Function | DecentDB | SQLite | PostgreSQL | DuckDB |
| JSON_EXTRACT() | ✅ | ✅ | ✅ (->) | ✅ |
| JSON_ARRAY_LENGTH() | ✅ | ✅ | ✅ | ✅ |
| json_type() | ✅ | ✅ | ✅ | ✅ |
| json_valid() | ✅ | ✅ | ✅ | ✅ |
| json_object() | ✅ | ✅ | ✅ | ✅ |
| json_array() | ✅ | ✅ | ✅ | ✅ |
| -> | ✅ | ✅ | ✅ | ✅ |
| ->> | ✅ | ✅ | ✅ | ✅ |
| json_each() | ✅ | ✅ | ❌ | ❌ (use unnest) |
| json_tree() | ✅ | ✅ | ❌ | ❌ |
Math Examples
SELECT ABS(-42), CEIL(3.2), FLOOR(3.8), ROUND(3.14159, 2);
SELECT SQRT(144), POWER(2, 10), MOD(17, 5), SIGN(-99);
SELECT LN(2.71828), LOG(1000), EXP(1);
SELECT RANDOM(); -- returns a random FLOAT64 in [0.0, 1.0)
String Examples
SELECT LENGTH('hello'), LOWER('HELLO'), UPPER('hello');
SELECT TRIM(' hello '), LTRIM(' hello'), RTRIM('hello ');
SELECT SUBSTR('hello world', 1, 5), REPLACE('hello', 'l', 'r');
SELECT INSTR('hello world', 'world'); -- returns 7
SELECT LEFT('hello', 3), RIGHT('hello', 3); -- 'hel', 'llo'
SELECT LPAD('42', 5, '0'), RPAD('hi', 5, '!'); -- '00042', 'hi!!!'
SELECT REPEAT('ab', 3), REVERSE('hello'); -- 'ababab', 'olleh'
SELECT CHR(65), HEX('ABC'); -- 'A', '414243'
Date/Time Examples
-- Current date/time values
SELECT NOW(), CURRENT_TIMESTAMP, CURRENT_DATE, CURRENT_TIME;
-- date() and datetime() (SQLite-compatible)
SELECT date('now'), date('2024-03-15', '+1 month');
SELECT datetime('now'), datetime('2024-03-15 10:30:00', '+2 hours');
-- strftime() formatting
SELECT strftime('%Y-%m-%d', 'now');
SELECT strftime('%H:%M:%S', '2024-03-15 14:30:00');
SELECT strftime('%Y', '2024-03-15'); -- '2024'
-- EXTRACT() (Postgres-compatible)
SELECT EXTRACT(YEAR FROM CURRENT_TIMESTAMP);
SELECT EXTRACT(MONTH FROM '2024-03-15');
SELECT EXTRACT(DOW FROM '2024-03-15'); -- day of week
JSON Examples
-- JSON extraction
SELECT json_extract('{"name":"Alice","age":30}', '$.name'); -- 'Alice'
SELECT '{"name":"Alice"}'->>'name'; -- 'Alice' (text)
SELECT '{"name":"Alice"}'->'name'; -- '"Alice"' (JSON)
-- JSON construction
SELECT json_object('name', 'Alice', 'age', 30); -- '{"name":"Alice","age":30}'
SELECT json_array(1, 2, 'three'); -- '[1,2,"three"]'
-- JSON inspection
SELECT json_type('{"a":1}'); -- 'object'
SELECT json_valid('{"a":1}'); -- 1 (true)
SELECT json_valid('not json'); -- 0 (false)
SELECT json_array_length('[1,2,3]'); -- 3
-- Table-valued: json_each (iterate array/object)
SELECT key, value FROM json_each('[10, 20, 30]');
-- Returns: (0, 10), (1, 20), (2, 30)
SELECT key, value FROM json_each('{"a":1,"b":2}');
-- Returns: ('a', 1), ('b', 2)
-- Table-valued: json_tree (recursive traversal)
SELECT key, value, type FROM json_tree('{"a":{"b":1},"c":[2,3]}');
Operators
| Operator | DecentDB | SQLite | PostgreSQL | DuckDB |
| + - * / | ✅ | ✅ | ✅ | ✅ |
| % (modulo) | ✅ | ✅ | ✅ | ✅ |
| || (concat) | ✅ | ✅ | ✅ | ✅ |
| LIKE/ILIKE | ✅ | ✅ | ✅ | ✅ |
| BETWEEN | ✅ | ✅ | ✅ | ✅ |
| IN | ✅ | ✅ | ✅ | ✅ |
| IS NULL | ✅ | ✅ | ✅ | ✅ |
| CASE | ✅ | ✅ | ✅ | ✅ |
| COALESCE | ✅ | ✅ | ✅ | ✅ |
| NULLIF | ✅ | ✅ | ✅ | ✅ |
Examples
-- Arithmetic and modulo
SELECT 10 + 3, 10 - 3, 10 * 3, 10 / 3, 10 % 3;
-- String concatenation
SELECT 'Hello' || ' ' || 'World';
-- Pattern matching
SELECT * FROM users WHERE name LIKE 'A%';
SELECT * FROM users WHERE name ILIKE '%alice%'; -- case-insensitive
-- Range and membership
SELECT * FROM orders WHERE amount BETWEEN 10.00 AND 100.00;
SELECT * FROM users WHERE id IN (1, 2, 3);
-- NULL handling
SELECT * FROM users WHERE email IS NULL;
SELECT COALESCE(email, 'no-email@example.com') FROM users;
SELECT NULLIF(score, 0) FROM results; -- returns NULL if score = 0
-- CASE expressions
SELECT name,
CASE WHEN salary > 100000 THEN 'high'
WHEN salary > 50000 THEN 'mid'
ELSE 'low' END AS band
FROM employees;
Transaction Control
| Feature | DecentDB | SQLite | PostgreSQL | DuckDB |
| BEGIN | ✅ | ✅ | ✅ | ✅ |
| BEGIN IMMEDIATE | ✅ (treated as BEGIN) | ✅ | ❌ | ❌ |
| COMMIT | ✅ | ✅ | ✅ | ✅ |
| ROLLBACK | ✅ | ✅ | ✅ | ✅ |
| SAVEPOINT | ✅ | ✅ | ✅ | ❌ |
| RELEASE SAVEPOINT | ✅ | ✅ | ✅ | ❌ |
| ROLLBACK TO SAVEPOINT | ✅ | ✅ | ✅ | ❌ |
Examples
-- Basic transaction
BEGIN;
INSERT INTO users (name) VALUES ('Alice');
INSERT INTO users (name) VALUES ('Bob');
COMMIT;
-- Rollback on error
BEGIN;
INSERT INTO users (name) VALUES ('Charlie');
ROLLBACK; -- nothing committed
-- Savepoints (partial rollback within a transaction)
BEGIN;
INSERT INTO users (name) VALUES ('Alice');
SAVEPOINT sp1;
INSERT INTO users (name) VALUES ('Bob');
ROLLBACK TO SAVEPOINT sp1; -- undoes Bob only
INSERT INTO users (name) VALUES ('Charlie');
RELEASE SAVEPOINT sp1;
COMMIT; -- Alice and Charlie are committed
-- Nested savepoints
BEGIN;
SAVEPOINT outer;
INSERT INTO users (name) VALUES ('X');
SAVEPOINT inner;
INSERT INTO users (name) VALUES ('Y');
ROLLBACK TO SAVEPOINT inner; -- undoes Y
RELEASE SAVEPOINT outer;
COMMIT; -- only X is committed
Data Types
| Type | DecentDB | SQLite | PostgreSQL | DuckDB |
| NULL | ✅ | ✅ | ✅ | ✅ |
| INTEGER/INT | ✅ | ✅ | ✅ | ✅ |
| BIGINT | ✅ | ✅ | ✅ | ✅ |
| FLOAT/REAL | ✅ | ✅ | ✅ | ✅ |
| DOUBLE PRECISION | ✅ | ✅ | ✅ | ✅ |
| TEXT | ✅ | ✅ | ✅ | ✅ (VARCHAR) |
| BLOB | ✅ | ✅ | ✅ | ✅ |
| BOOLEAN | ✅ | ✅ | ✅ | ✅ |
| UUID | ✅ | ❌ | ✅ | ✅ |
| DECIMAL/NUMERIC | ✅ | ✅ | ✅ | ✅ |
| DATE | ✅ (native int64 µs UTC) | ✅ | ✅ | ✅ (native) |
| TIMESTAMP | ✅ (native int64 µs UTC) | ✅ | ✅ | ✅ (native) |
Examples
-- Integer types
CREATE TABLE t1 (a INTEGER, b BIGINT, c INT);
-- Floating point
CREATE TABLE t2 (a FLOAT, b REAL, c DOUBLE PRECISION);
-- Text and binary
CREATE TABLE t3 (name TEXT, data BLOB);
-- Boolean
CREATE TABLE t4 (active BOOLEAN DEFAULT TRUE);
-- Decimal (exact numeric)
CREATE TABLE t5 (price DECIMAL(10,2), tax NUMERIC(5,4));
-- UUID
CREATE TABLE t6 (id UUID PRIMARY KEY DEFAULT GEN_RANDOM_UUID());
-- Date and Timestamp (stored as ISO-format TEXT in DecentDB)
CREATE TABLE events (
id INTEGER PRIMARY KEY,
event_date DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO events (event_date, created_at) VALUES ('2024-03-15', '2024-03-15 14:30:00');
Constraints
| Constraint | DecentDB | SQLite | PostgreSQL | DuckDB |
| PRIMARY KEY | ✅ | ✅ | ✅ | ✅ |
| FOREIGN KEY | ✅ | ✅ | ✅ | ⚠️ (parsed, not enforced) |
| NOT NULL | ✅ | ✅ | ✅ | ✅ |
| UNIQUE | ✅ | ✅ | ✅ | ✅ |
| CHECK | ✅ | ✅ | ✅ | ✅ |
| DEFAULT | ✅ | ✅ | ✅ | ✅ |
Examples
-- PRIMARY KEY (auto-assignment with a single INT64 PRIMARY KEY; INT/INTEGER/INT64 are aliases)
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
-- FOREIGN KEY with actions
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
);
-- NOT NULL and UNIQUE
CREATE TABLE accounts (
email TEXT NOT NULL UNIQUE,
username TEXT NOT NULL
);
-- CHECK constraint
CREATE TABLE products (
price DECIMAL(10,2) CHECK (price >= 0),
qty INTEGER CHECK (qty >= 0)
);
-- DEFAULT values
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
status TEXT DEFAULT 'draft',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Set Operations
| Operation | DecentDB | SQLite | PostgreSQL | DuckDB |
| UNION | ✅ | ✅ | ✅ | ✅ |
| UNION ALL | ✅ | ✅ | ✅ | ✅ |
| INTERSECT | ✅ | ✅ | ✅ | ✅ |
| INTERSECT ALL | ✅ | ✅ | ✅ | ✅ |
| EXCEPT | ✅ | ✅ | ✅ | ✅ |
| EXCEPT ALL | ✅ | ✅ | ✅ | ✅ |
Examples
-- UNION (deduplicated) vs UNION ALL (keeps duplicates)
SELECT name FROM employees UNION SELECT name FROM contractors;
SELECT name FROM employees UNION ALL SELECT name FROM contractors;
-- INTERSECT (rows in both sets)
SELECT user_id FROM orders INTERSECT SELECT user_id FROM returns;
-- INTERSECT ALL (preserves duplicate counts)
SELECT user_id FROM orders INTERSECT ALL SELECT user_id FROM returns;
-- EXCEPT (rows in first set but not second)
SELECT user_id FROM all_users EXCEPT SELECT user_id FROM banned_users;
-- EXCEPT ALL (multiset difference)
SELECT item_id FROM inventory EXCEPT ALL SELECT item_id FROM sold;
CTEs (Common Table Expressions)
| Feature | DecentDB | SQLite | PostgreSQL | DuckDB |
| WITH ... AS | ✅ | ✅ | ✅ | ✅ |
| WITH RECURSIVE | ✅ | ✅ | ✅ | ✅ |
| Multiple CTEs | ✅ | ✅ | ✅ | ✅ |
Examples
-- Basic CTE
WITH active_users AS (
SELECT * FROM users WHERE active = TRUE
)
SELECT name FROM active_users ORDER BY name;
-- Multiple CTEs
WITH
dept_totals AS (
SELECT department, SUM(salary) AS total FROM employees GROUP BY department
),
high_spend AS (
SELECT * FROM dept_totals WHERE total > 500000
)
SELECT * FROM high_spend;
-- WITH RECURSIVE (generate a sequence 1..10)
WITH RECURSIVE cnt(x) AS (
SELECT 1
UNION ALL
SELECT x + 1 FROM cnt WHERE x < 10
)
SELECT x FROM cnt;
-- WITH RECURSIVE (tree traversal — find all descendants of node 1)
WITH RECURSIVE descendants AS (
SELECT id, name, parent_id FROM categories WHERE id = 1
UNION ALL
SELECT c.id, c.name, c.parent_id
FROM categories c INNER JOIN descendants d ON c.parent_id = d.id
)
SELECT * FROM descendants;