OriginChain docs
examples · sql · newer capabilities

Newer SQL recipes

← SQL examples
verified on the live engine

The recipes below are the SQL features that shipped most recently to /sql. Every request and every result row on this page was captured from a running engine — not aspirational. For the full supported surface see SQL over HTTP; for running totals with ROWS / RANGE frames see Window functions.

CTEs · WITH … AS

Name a subquery, then aggregate over it

A non-recursive WITH block names an intermediate result you can filter, join and aggregate — all resolved server-side in one request. The CTE body reads a real table.

request · SQL
WITH paid AS (
  SELECT customer_id, amount_cents
    FROM shop.orders
   WHERE status = 'paid'
)
SELECT customer_id,
       count(*)          AS n,
       sum(amount_cents) AS total
  FROM paid
 GROUP BY customer_id
result
{ "kind": "select",
  "columns": ["customer_id", "n", "total"],
  "rows": [
    { "customer_id": 1, "n": 2, "total": 18800 },
    { "customer_id": 2, "n": 1, "total": 22000 }
  ] }

Recursive CTEs (WITH RECURSIVE) are the one form not reachable over /sql yet — you get a clear 400, never a silent re-interpretation.

Parameters · $1

Bind values with a params array

Keep SQL and values apart: write positional $1, $2 placeholders and pass a params array alongside the statement. No string interpolation, no injection surface.

request · JSON body
{
  "sql": "SELECT id, amount_cents, status FROM shop.orders WHERE customer_id = $1 AND amount_cents > $2 ORDER BY amount_cents DESC",
  "params": [1, 1000]
}
result
{ "kind": "select",
  "columns": ["id", "amount_cents", "status"],
  "rows": [
    { "id": 1, "amount_cents": 14900, "status": "paid" },
    { "id": 4, "amount_cents": 3900,  "status": "paid" }
  ] }

Bind parameters are also how you write timestamps today — pass the value in params rather than as an inline TIMESTAMP '…' literal:
{ "sql": "INSERT INTO shop.orders (customer_id, amount_cents, status, created_at) VALUES ($1,$2,$3,$4)", "params": [1, 14900, "paid", "2026-01-04 10:00:00"] }{"inserted": 1}.

Time-bucketing · date_trunc

Roll rows up into time buckets

The dashboard shape: bucket a timestamp column by day / month and aggregate per bucket, in a single GROUP BY.

request · SQL
SELECT date_trunc('month', created_at) AS m,
       count(*)          AS n,
       sum(amount_cents) AS rev
  FROM shop.orders
 GROUP BY date_trunc('month', created_at)
result
{ "kind": "select",
  "columns": ["m", "n", "rev"],
  "rows": [
    { "m": 1767225600000, "n": 2, "rev": 18800 },
    { "m": 1769904000000, "n": 2, "rev": 29200 }
  ] }

Buckets come back as epoch-milliseconds (1767225600000 = 2026-01-01). Sort the buckets in your app for now — ORDER BY over a date_trunc group key isn't wired through /sql yet.

CASE

Branch inline with CASE

Derive a column from a per-row condition without a second query — CASE WHEN … THEN … ELSE … END evaluates in the SELECT list.

request · SQL
SELECT id, amount_cents,
       CASE WHEN amount_cents >= 10000 THEN 'big'
            ELSE 'small' END AS tier
  FROM shop.orders
 ORDER BY id
result
{ "kind": "select",
  "columns": ["id", "amount_cents", "tier"],
  "rows": [
    { "id": 1, "amount_cents": 14900, "tier": "big"   },
    { "id": 3, "amount_cents": 7200,  "tier": "small" },
    { "id": 4, "amount_cents": 3900,  "tier": "small" },
    { "id": 6, "amount_cents": 22000, "tier": "big"   }
  ] }
Catalog & session

Ask the engine about itself

Standard Postgres introspection works — read information_schema / pg_catalog, and inspect session state with SHOW and current_schema(). This is what lets ORMs and SQL clients introspect a schema.

request · SQL
SELECT column_name
  FROM information_schema.columns
 WHERE table_name = 'orders'
result
{ "kind": "select",
  "columns": ["column_name"],
  "rows": [
    { "column_name": "id" }, { "column_name": "customer_id" },
    { "column_name": "amount_cents" }, { "column_name": "status" },
    { "column_name": "created_at" }
  ] }

SHOW search_pathpublic · SELECT current_schema()public. SET search_path and GUCs persist for the session.