HeliosDB SQL API Reference
HeliosDB SQL API Reference
Version: 6.0 (PostgreSQL 17 Compatible) Last Updated: November 2, 2025
Introduction
HeliosDB provides a PostgreSQL-compatible SQL interface with extensions for multi-model data operations. This reference documents all supported SQL statements, functions, operators, and multi-model extensions.
Connection
Connection String
psql "postgresql://username:password@localhost:5432/heliosdb"Connection Parameters
host=localhostport=5432dbname=heliosdbuser=adminpassword=secrettenant=acme-corp # Multi-tenancyapplication_name=myappUsing pg Client Libraries
import psycopg2
conn = psycopg2.connect( host="localhost", port=5432, dbname="heliosdb", user="admin", password="secret")Data Definition Language (DDL)
CREATE TABLE
Basic Table:
CREATE TABLE users ( id SERIAL PRIMARY KEY, username VARCHAR(255) NOT NULL UNIQUE, email VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);With Constraints:
CREATE TABLE orders ( id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL, status VARCHAR(50) CHECK (status IN ('pending', 'completed', 'cancelled')), total DECIMAL(10, 2) CHECK (total >= 0), created_at TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE);Partitioned Table:
CREATE TABLE events ( id BIGSERIAL, event_type VARCHAR(100), payload JSONB, created_at TIMESTAMP NOT NULL) PARTITION BY RANGE (created_at);
CREATE TABLE events_2025_01 PARTITION OF events FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');Vector Table:
CREATE TABLE documents ( id SERIAL PRIMARY KEY, title TEXT, content TEXT, embedding VECTOR(1536) -- Multi-model extension);
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);ALTER TABLE
Add Column:
ALTER TABLE users ADD COLUMN phone VARCHAR(20);ALTER TABLE users ADD COLUMN age INTEGER DEFAULT 0;Drop Column:
ALTER TABLE users DROP COLUMN phone;Modify Column:
ALTER TABLE users ALTER COLUMN email TYPE TEXT;ALTER TABLE users ALTER COLUMN username SET NOT NULL;ALTER TABLE users ALTER COLUMN age DROP DEFAULT;Add Constraint:
ALTER TABLE users ADD CONSTRAINT unique_email UNIQUE (email);ALTER TABLE orders ADD CONSTRAINT check_total CHECK (total >= 0);Rename:
ALTER TABLE users RENAME TO customers;ALTER TABLE customers RENAME COLUMN username TO login;DROP TABLE
DROP TABLE users;DROP TABLE IF EXISTS users;DROP TABLE users CASCADE; -- Also drop dependent objectsCREATE INDEX
B-Tree Index (Default):
CREATE INDEX idx_users_email ON users(email);CREATE UNIQUE INDEX idx_users_username ON users(username);Partial Index:
CREATE INDEX idx_active_users ON users(created_at)WHERE status = 'active';Composite Index:
CREATE INDEX idx_orders_user_status ON orders(user_id, status);Expression Index:
CREATE INDEX idx_users_lower_email ON users(LOWER(email));Full-Text Search Index:
CREATE INDEX idx_documents_content ON documentsUSING GIN (to_tsvector('english', content));Vector Index (HNSW):
CREATE INDEX idx_documents_embedding ON documentsUSING hnsw (embedding vector_cosine_ops)WITH (m = 16, ef_construction = 64);Spatial Index:
CREATE INDEX idx_locations_geom ON locationsUSING GIST (geom);CREATE VIEW
Simple View:
CREATE VIEW active_users ASSELECT id, username, emailFROM usersWHERE status = 'active';Materialized View:
CREATE MATERIALIZED VIEW user_stats ASSELECT DATE_TRUNC('day', created_at) as day, COUNT(*) as user_count, COUNT(DISTINCT email) as unique_emailsFROM usersGROUP BY DATE_TRUNC('day', created_at);
-- Refresh materialized viewREFRESH MATERIALIZED VIEW user_stats;REFRESH MATERIALIZED VIEW CONCURRENTLY user_stats;CREATE SCHEMA
CREATE SCHEMA analytics;CREATE SCHEMA IF NOT EXISTS reporting;
-- Create table in schemaCREATE TABLE analytics.metrics ( id SERIAL PRIMARY KEY, metric_name VARCHAR(255), value DOUBLE PRECISION);CREATE TYPE
Enum Type:
CREATE TYPE user_role AS ENUM ('admin', 'user', 'guest');
CREATE TABLE users ( id SERIAL PRIMARY KEY, role user_role DEFAULT 'user');Composite Type:
CREATE TYPE address AS ( street VARCHAR(255), city VARCHAR(100), country VARCHAR(100), postal_code VARCHAR(20));
CREATE TABLE customers ( id SERIAL PRIMARY KEY, name VARCHAR(255), shipping_address address);Data Manipulation Language (DML)
SELECT
Basic Query:
SELECT * FROM users;SELECT id, username, email FROM users;SELECT username AS login, email FROM users;WHERE Clause:
SELECT * FROM users WHERE age >= 18;SELECT * FROM users WHERE status = 'active' AND age > 21;SELECT * FROM users WHERE email LIKE '%@gmail.com';SELECT * FROM users WHERE created_at BETWEEN '2025-01-01' AND '2025-12-31';SELECT * FROM users WHERE id IN (1, 2, 3, 4, 5);ORDER BY:
SELECT * FROM users ORDER BY created_at DESC;SELECT * FROM users ORDER BY age ASC, username DESC;SELECT * FROM users ORDER BY RANDOM() LIMIT 10; -- Random sampleLIMIT and OFFSET:
SELECT * FROM users LIMIT 10;SELECT * FROM users LIMIT 10 OFFSET 20;SELECT * FROM users ORDER BY id LIMIT 10; -- Top 10GROUP BY and HAVING:
SELECT status, COUNT(*) as countFROM usersGROUP BY status;
SELECT DATE_TRUNC('month', created_at) as month, COUNT(*) as signupsFROM usersGROUP BY DATE_TRUNC('month', created_at)HAVING COUNT(*) > 100ORDER BY month DESC;DISTINCT:
SELECT DISTINCT status FROM users;SELECT DISTINCT ON (email) * FROM users ORDER BY email, created_at DESC;JOINS:
-- INNER JOINSELECT u.username, o.totalFROM users uINNER JOIN orders o ON u.id = o.user_id;
-- LEFT JOINSELECT u.username, COUNT(o.id) as order_countFROM users uLEFT JOIN orders o ON u.id = o.user_idGROUP BY u.id, u.username;
-- RIGHT JOINSELECT u.username, o.id as order_idFROM users uRIGHT JOIN orders o ON u.id = o.user_id;
-- FULL OUTER JOINSELECT u.username, o.idFROM users uFULL OUTER JOIN orders o ON u.id = o.user_id;
-- CROSS JOINSELECT a.name, b.colorFROM products aCROSS JOIN colors b;Subqueries:
-- Scalar subquerySELECT username, (SELECT COUNT(*) FROM orders WHERE user_id = users.id) as order_countFROM users;
-- IN subquerySELECT * FROM usersWHERE id IN (SELECT DISTINCT user_id FROM orders WHERE total > 100);
-- EXISTS subquerySELECT * FROM users uWHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
-- WITH (CTE)WITH active_users AS ( SELECT * FROM users WHERE status = 'active'),user_orders AS ( SELECT user_id, COUNT(*) as order_count FROM orders GROUP BY user_id)SELECT au.username, COALESCE(uo.order_count, 0) as ordersFROM active_users auLEFT JOIN user_orders uo ON au.id = uo.user_id;Window Functions:
-- ROW_NUMBERSELECT username, ROW_NUMBER() OVER (ORDER BY created_at) as row_numFROM users;
-- RANK and DENSE_RANKSELECT username, score, RANK() OVER (ORDER BY score DESC) as rank, DENSE_RANK() OVER (ORDER BY score DESC) as dense_rankFROM leaderboard;
-- PARTITION BYSELECT username, category, sales, RANK() OVER (PARTITION BY category ORDER BY sales DESC) as rank_in_categoryFROM sales_data;
-- LAG and LEADSELECT date, revenue, LAG(revenue) OVER (ORDER BY date) as prev_revenue, LEAD(revenue) OVER (ORDER BY date) as next_revenueFROM daily_revenue;
-- Running totalsSELECT date, amount, SUM(amount) OVER (ORDER BY date) as running_totalFROM transactions;INSERT
Single Row:
INSERT INTO users (username, email) VALUES ('john', 'john@example.com');Multiple Rows:
INSERT INTO users (username, email) VALUES ('alice', 'alice@example.com'), ('bob', 'bob@example.com'), ('charlie', 'charlie@example.com');Returning:
INSERT INTO users (username, email)VALUES ('dave', 'dave@example.com')RETURNING id, created_at;From SELECT:
INSERT INTO archived_usersSELECT * FROM users WHERE created_at < '2020-01-01';ON CONFLICT (Upsert):
INSERT INTO users (id, username, email)VALUES (1, 'john', 'john@example.com')ON CONFLICT (id) DO UPDATE SET username = EXCLUDED.username, email = EXCLUDED.email, updated_at = NOW();
-- Do nothing on conflictINSERT INTO users (username, email)VALUES ('john', 'john@example.com')ON CONFLICT (username) DO NOTHING;UPDATE
Basic Update:
UPDATE users SET status = 'active' WHERE id = 1;UPDATE users SET age = age + 1;Multiple Columns:
UPDATE users SET email = 'newemail@example.com', updated_at = NOW()WHERE id = 1;From Another Table:
UPDATE orders oSET status = 'completed'FROM shipments sWHERE o.id = s.order_id AND s.delivered_at IS NOT NULL;Returning:
UPDATE usersSET last_login = NOW()WHERE id = 1RETURNING id, username, last_login;DELETE
Basic Delete:
DELETE FROM users WHERE id = 1;DELETE FROM users WHERE status = 'inactive';Delete All:
DELETE FROM users; -- Deletes all rows (use with caution!)TRUNCATE TABLE users; -- Faster for deleting all rowsReturning:
DELETE FROM users WHERE status = 'banned'RETURNING id, username;Using Another Table:
DELETE FROM users uUSING banned_emails bWHERE u.email = b.email;Transaction Control Language (TCL)
Transactions
Basic Transaction:
BEGIN;INSERT INTO users (username, email) VALUES ('test', 'test@example.com');UPDATE accounts SET balance = balance - 100 WHERE id = 1;UPDATE accounts SET balance = balance + 100 WHERE id = 2;COMMIT;Rollback:
BEGIN;DELETE FROM users WHERE status = 'inactive';-- Oops, made a mistake!ROLLBACK;Savepoints:
BEGIN;INSERT INTO users (username) VALUES ('user1');SAVEPOINT sp1;
INSERT INTO users (username) VALUES ('user2');SAVEPOINT sp2;
INSERT INTO users (username) VALUES ('user3');-- Rollback to savepointROLLBACK TO sp2;
COMMIT; -- Only user1 and user2 are insertedIsolation Levels:
BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;Multi-Model Extensions
Vector Operations
Vector Similarity Search:
-- Cosine similaritySELECT id, title, embedding <=> '[0.1, 0.2, 0.3, ...]'::VECTOR as distanceFROM documentsORDER BY embedding <=> '[0.1, 0.2, 0.3, ...]'::VECTORLIMIT 10;
-- Euclidean distance (L2)SELECT id, title, embedding <-> '[0.1, 0.2, 0.3, ...]'::VECTOR as distanceFROM documentsORDER BY embedding <-> '[0.1, 0.2, 0.3, ...]'::VECTORLIMIT 10;
-- Inner productSELECT id, title, embedding <#> '[0.1, 0.2, 0.3, ...]'::VECTOR as distanceFROM documentsORDER BY embedding <#> '[0.1, 0.2, 0.3, ...]'::VECTOR DESCLIMIT 10;Vector Functions:
-- Vector dimensionsSELECT vector_dims(embedding) FROM documents LIMIT 1;
-- Vector normSELECT vector_norm(embedding) FROM documents;
-- Normalize vectorUPDATE documents SET embedding = vector_normalize(embedding);
-- Average vectorSELECT AVG(embedding) FROM documents;Graph Operations
Recursive CTE for Graph Traversal:
WITH RECURSIVE connected AS ( -- Base case: start node SELECT id, name, parent_id, 1 as depth FROM categories WHERE id = 1
UNION ALL
-- Recursive case: connected nodes SELECT c.id, c.name, c.parent_id, co.depth + 1 FROM categories c INNER JOIN connected co ON c.parent_id = co.id WHERE co.depth < 10 -- Max depth)SELECT * FROM connected;Cypher-Style Graph Query:
-- Find friends of friendsSELECT DISTINCT u3.usernameFROM users u1JOIN friendships f1 ON u1.id = f1.user_idJOIN users u2 ON f1.friend_id = u2.idJOIN friendships f2 ON u2.id = f2.user_idJOIN users u3 ON f2.friend_id = u3.idWHERE u1.username = 'alice' AND u3.id != u1.id;Shortest Path:
WITH RECURSIVE paths AS ( SELECT id as node, ARRAY[id] as path, 0 as length FROM nodes WHERE id = 1 -- Start node
UNION ALL
SELECT e.target_node, p.path || e.target_node, p.length + e.weight FROM paths p JOIN edges e ON p.node = e.source_node WHERE NOT e.target_node = ANY(p.path) -- Avoid cycles)SELECT path, lengthFROM pathsWHERE node = 10 -- End nodeORDER BY lengthLIMIT 1;Document Operations
JSON Queries:
-- Extract JSON fieldSELECT id, metadata->>'name' as nameFROM documentsWHERE metadata->>'status' = 'active';
-- JSON array containsSELECT * FROM documentsWHERE metadata @> '{"tags": ["important"]}';
-- JSON path querySELECT id, jsonb_path_query(metadata, '$.user.email') as emailFROM documents;
-- Aggregate JSONSELECT jsonb_agg(metadata) FROM documents;MongoDB-Compatible Syntax:
-- Find documentsSELECT * FROM collectionWHERE doc @> '{"status": "active", "age": {"$gte": 18}}';
-- Update documentUPDATE collectionSET doc = jsonb_set(doc, '{status}', '"inactive"')WHERE doc->>'_id' = '123';Time-Series Operations
Time-Bucket Aggregation:
SELECT time_bucket('1 hour', timestamp) as hour, AVG(value) as avg_value, MAX(value) as max_value, MIN(value) as min_valueFROM metricsWHERE timestamp > NOW() - INTERVAL '24 hours'GROUP BY time_bucket('1 hour', timestamp)ORDER BY hour;Gap Filling:
SELECT time_bucket_gapfill('5 minutes', timestamp) as time, interpolate(AVG(temperature)) as temperatureFROM sensor_dataWHERE timestamp BETWEEN '2025-11-01' AND '2025-11-02'GROUP BY time_bucket_gapfill('5 minutes', timestamp);Continuous Aggregates:
CREATE MATERIALIZED VIEW hourly_metricsWITH (timescaledb.continuous) ASSELECT time_bucket('1 hour', timestamp) as hour, sensor_id, AVG(value) as avg_valueFROM metricsGROUP BY hour, sensor_id;Spatial Operations
Point in Polygon:
SELECT * FROM locationsWHERE ST_Contains( ST_GeomFromText('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))'), geom);Distance Query:
SELECT id, name, ST_Distance(geom, ST_MakePoint(-122.4194, 37.7749)) as distanceFROM locationsWHERE ST_DWithin(geom, ST_MakePoint(-122.4194, 37.7749), 10000) -- 10kmORDER BY distanceLIMIT 10;Nearest Neighbor:
SELECT id, nameFROM locationsORDER BY geom <-> ST_MakePoint(-122.4194, 37.7749)LIMIT 5;Built-in Functions
String Functions
SELECT CONCAT('Hello', ' ', 'World'); -- 'Hello World'SELECT SUBSTRING('Hello World' FROM 1 FOR 5); -- 'Hello'SELECT UPPER('hello'); -- 'HELLO'SELECT LOWER('WORLD'); -- 'world'SELECT LENGTH('Hello'); -- 5SELECT TRIM(' hello '); -- 'hello'SELECT REPLACE('Hello World', 'World', 'Universe'); -- 'Hello Universe'SELECT SPLIT_PART('a,b,c', ',', 2); -- 'b'SELECT REGEXP_REPLACE('Hello123', '[0-9]', '', 'g'); -- 'Hello'Numeric Functions
SELECT ABS(-5); -- 5SELECT CEIL(4.3); -- 5SELECT FLOOR(4.8); -- 4SELECT ROUND(4.567, 2); -- 4.57SELECT POWER(2, 3); -- 8SELECT SQRT(16); -- 4SELECT MOD(10, 3); -- 1SELECT RANDOM(); -- Random number between 0 and 1Date/Time Functions
SELECT NOW(); -- Current timestampSELECT CURRENT_DATE; -- Current dateSELECT CURRENT_TIME; -- Current timeSELECT EXTRACT(YEAR FROM NOW()); -- 2025SELECT DATE_TRUNC('hour', NOW()); -- Truncate to hourSELECT AGE('2025-01-01', '2020-01-01'); -- Interval '5 years'SELECT NOW() + INTERVAL '1 day'; -- TomorrowSELECT TO_CHAR(NOW(), 'YYYY-MM-DD HH24:MI:SS'); -- Format timestampAggregate Functions
SELECT COUNT(*) FROM users;SELECT COUNT(DISTINCT email) FROM users;SELECT SUM(total) FROM orders;SELECT AVG(age) FROM users;SELECT MIN(created_at), MAX(created_at) FROM users;SELECT STRING_AGG(username, ', ') FROM users; -- 'alice, bob, charlie'SELECT ARRAY_AGG(id) FROM users; -- [1, 2, 3, ...]SELECT JSON_AGG(username) FROM users; -- ["alice", "bob", ...]JSON Functions
SELECT json_build_object('name', 'John', 'age', 30);SELECT json_agg(username) FROM users;SELECT jsonb_pretty('{"a":1}'::jsonb);SELECT jsonb_set('{"a":1}'::jsonb, '{b}', '2'::jsonb);SELECT jsonb_strip_nulls('{"a":1,"b":null}'::jsonb);Array Functions
SELECT ARRAY[1, 2, 3, 4, 5];SELECT ARRAY_LENGTH(ARRAY[1, 2, 3], 1); -- 3SELECT 2 = ANY(ARRAY[1, 2, 3]); -- trueSELECT ARRAY_AGG(id) FROM users;SELECT UNNEST(ARRAY[1, 2, 3]); -- Expands array to rowsConditional Functions
SELECT COALESCE(NULL, NULL, 'default'); -- 'default'SELECT NULLIF(value, 0); -- Returns NULL if value is 0SELECT GREATEST(1, 5, 3, 9, 2); -- 9SELECT LEAST(1, 5, 3, 9, 2); -- 1SELECT CASE WHEN age < 18 THEN 'minor' WHEN age < 65 THEN 'adult' ELSE 'senior'END as category FROM users;Full-Text Search
Basic Search
-- Create text search vectorSELECT to_tsvector('english', 'The quick brown fox jumps over the lazy dog');
-- SearchSELECT * FROM documentsWHERE to_tsvector('english', content) @@ to_tsquery('english', 'fox & dog');With Ranking
SELECT id, title, ts_rank(to_tsvector('english', content), query) as rankFROM documents, to_tsquery('english', 'database & search') queryWHERE to_tsvector('english', content) @@ queryORDER BY rank DESC;With Index
-- Add generated columnALTER TABLE documentsADD COLUMN content_tsv tsvectorGENERATED ALWAYS AS (to_tsvector('english', content)) STORED;
-- Create GIN indexCREATE INDEX idx_documents_content_tsv ON documents USING GIN(content_tsv);
-- QuerySELECT * FROM documentsWHERE content_tsv @@ to_tsquery('english', 'database & search');Examples
User Registration and Login
-- Register userINSERT INTO users (username, email, password_hash)VALUES ('john', 'john@example.com', crypt('password123', gen_salt('bf')))RETURNING id;
-- Login verificationSELECT id, username FROM usersWHERE username = 'john' AND password_hash = crypt('password123', password_hash);E-Commerce Order Processing
BEGIN;
-- Create orderINSERT INTO orders (user_id, status, total)VALUES (123, 'pending', 0)RETURNING id INTO @order_id;
-- Add order itemsINSERT INTO order_items (order_id, product_id, quantity, price)SELECT @order_id, product_id, quantity, priceFROM cart_itemsWHERE user_id = 123;
-- Update order totalUPDATE ordersSET total = ( SELECT SUM(quantity * price) FROM order_items WHERE order_id = @order_id)WHERE id = @order_id;
-- Clear cartDELETE FROM cart_items WHERE user_id = 123;
COMMIT;Analytics Query
WITH daily_metrics AS ( SELECT DATE_TRUNC('day', created_at) as day, COUNT(*) as signups, COUNT(*) FILTER (WHERE referrer IS NOT NULL) as referred_signups FROM users WHERE created_at >= NOW() - INTERVAL '30 days' GROUP BY DATE_TRUNC('day', created_at)),retention AS ( SELECT DATE_TRUNC('day', u.created_at) as cohort_day, COUNT(DISTINCT CASE WHEN a.created_at >= u.created_at + INTERVAL '7 days' THEN u.id END) as week_1_retained FROM users u LEFT JOIN activities a ON u.id = a.user_id GROUP BY DATE_TRUNC('day', u.created_at))SELECT dm.day, dm.signups, dm.referred_signups, ROUND(100.0 * dm.referred_signups / dm.signups, 2) as referral_rate, COALESCE(r.week_1_retained, 0) as week_1_retainedFROM daily_metrics dmLEFT JOIN retention r ON dm.day = r.cohort_dayORDER BY dm.day DESC;Hybrid Search (Vector + Full-Text)
WITH vector_results AS ( SELECT id, title, embedding <=> $1::vector as vector_distance FROM documents ORDER BY embedding <=> $1::vector LIMIT 100),text_results AS ( SELECT id, title, ts_rank(to_tsvector('english', content), query) as text_rank FROM documents, to_tsquery('english', $2) query WHERE to_tsvector('english', content) @@ query ORDER BY text_rank DESC LIMIT 100)SELECT DISTINCT COALESCE(vr.id, tr.id) as id, COALESCE(vr.title, tr.title) as title, (0.7 * (1 - COALESCE(vr.vector_distance, 1)) + 0.3 * COALESCE(tr.text_rank, 0)) as combined_scoreFROM vector_results vrFULL OUTER JOIN text_results tr ON vr.id = tr.idORDER BY combined_score DESCLIMIT 20;Performance Tips
- Use indexes for frequently queried columns
- EXPLAIN ANALYZE to understand query plans
- Batch inserts instead of many single inserts
- Use CTEs for complex queries
- Partition large tables by date or range
- Materialized views for expensive aggregations
- Connection pooling for better concurrency
- Prepared statements to avoid SQL injection and improve performance