Skip to Content
We are live but in Staging 🎉

Functions & operators

Where the functions come from

Everything DuckDB documents under duckdb.org/docs/sql/functions  is available: strftime, regexp_matches, list_aggregate, quantile_cont, string_agg, row_number() OVER (…), generate_series, and so on. Where a Postgres name differs from DuckDB’s, DuckDB usually accepts the Postgres alias too (substring, position, to_char via strftime, coalesce, nullif, greatest/least).

The shims below are the ones the engine adds or guarantees on top of that library because drivers and ORMs generate them.

Postgres formStatusNotes
gen_random_uuid(), uuid()SupportedRandom UUID v4 as uuid. uuid()::text for the string.
now(), current_timestamp, current_dateSupportedUTC.
date_trunc('day', ts), EXTRACT(field FROM ts), date_partSupportedEvaluated in UTC, so results are identical from any client.
ts AT TIME ZONE 'zone', timezone('zone', ts)SupportedAT TIME ZONE lowers to timezone().
SELECT a, b FROM t GROUP BY a where a is the keySupportedPostgres allows a functionally dependent projection; the engine completes the GROUP BY with the bare columns so the result is identical.
col = ANY($1) with an array parameterSupportedThe filter-by-list idiom; no string-built IN (…) needed.
$1 … $n placeholdersSupportedTyped from the OID the driver declares, or inferred from the query when undeclared.
::type casts, CAST(x AS type)SupportedPostgres type names accepted (::bigint, ::numeric(10,2), ::jsonb, ::uuid, ::vector).
nextval(), currval(), lastval()Refused42P01There are no sequences (Scalar Function … does not exist); use RETURNING id.
clock_timestamp()Supported since 0.1.44Lowered to now() before execution; works inside expressions.
pg_advisory_lock(), pg_sleep(), txid_current()Refused42P01Scalar Function … does not exist. Use now() for the time.
version(), current_database(), current_schema(), current_setting(), set_config(), pg_is_in_recovery()SupportedAnswered locally when called as the only expression in the SELECT; see Catalog.

Arithmetic

ExpressionStatusResult
NUMERIC / NUMERIC, NUMERIC / integerEnforced since 0.1.43Exact. The quotient carries at least 16 fractional digits; CAST(… AS NUMERIC(p,s)) or assignment to a typed column rounds half away from zero, as Postgres does.
x / 0Enforced22012 since 0.1.43division by zero. Never a silent NULL.
integer / integerSupported since 0.1.44Integer division, truncating toward zero (7 / 2 = 3, -7 / 2 = -3), as in Postgres. / 0 is 22012. Cast a side to NUMERIC or DOUBLE for a fractional result.
integer / 0PartialReturns NULL. Postgres raises 22012. Only the decimal path raises.
DOUBLE / DOUBLESupportedIEEE double, as in Postgres.
+ - * on NUMERICEnforcedExact, with the Postgres result scale. Overflow past 38 digits is 22003.
ROUND(x, n), TRUNC, CEIL, FLOOR, ABS, SIGN, MOD, POWER, SQRTSupportedROUND on NUMERIC rounds half away from zero.
SUM, AVG, MIN, MAX on NUMERICSupportedExact; AVG is an exact division.
SELECT CAST(2.01 / 2 AS NUMERIC(10,2)); -- 1.01 (Postgres: 1.01) SELECT ROUND(100.00 * 0.0325 / 12, 2); -- 0.27 monthly interest, exact SELECT 7 / 2; -- 3.5 (Postgres: 3) ← integer division differs SELECT 1 / 0; -- ERROR: division by zero (22012) SELECT CAST(7 AS NUMERIC) / 2; -- 3.5 exact NUMERIC SELECT amount / 0 FROM ledger; -- ERROR: division by zero (SQLSTATE 22012)

Strings

DuckDB’s string library, including ||, concat, length, lower/upper, trim, split_part, regexp_replace, regexp_matches, regexp_extract, like/ilike, similar to, starts_with, left/right, repeat, format. LIKE is case-sensitive, ILIKE is not, as in Postgres. String comparison is byte-wise; there are no collations.

JSON

A JSON/JSONB column accepts the Postgres operators and DuckDB’s json_* functions:

SELECT payload->'sku' FROM events; -- JSON SELECT payload->>'sku' FROM events; -- text SELECT (payload->>'amount')::NUMERIC(12,2) FROM events; -- typed SELECT json_extract(payload, '$.items[0].sku') FROM events; SELECT json_extract_string(payload, '$.sku') FROM events; SELECT json_keys(payload) FROM events; SELECT json_array_length(payload->'tags') FROM events; SELECT unnest(json_extract(payload, '$.tags[*]')) AS tag FROM events; SELECT payload::json->'tags'->>0 FROM events; -- Postgres-style cast round-trip

Paths use JSONPath ($.a.b[0], $.tags[*]). json_extract returns JSON, json_extract_string returns text. Nested STRUCT and ARRAY columns answer the same operators, because they are carried as JSON on the read path. @>, ? and the other Postgres containment operators are not available; express containment with json_extract comparisons.

For fields you filter or aggregate on constantly, promote them to typed columns and keep JSON for the long tail.

Vector operators

The pgvector distance operators work on VECTOR(n) columns and literals:

SELECT id FROM docs ORDER BY emb <-> '[0.12, -0.04, …]' LIMIT 10; -- L2 SELECT id FROM docs ORDER BY emb <=> '[0.12, -0.04, …]'::vector LIMIT 5; -- cosine SELECT a <#> b FROM pairs; -- negative inner product
OperatorDistance
<->Euclidean (L2)
<#>Negative inner product
<=>Cosine distance

Exactly these three. A '[…]'::vector cast is accepted and dropped. Scores are distances: lower is closer. An ORDER BY <distance> LIMIT k uses an HNSW or IVF index when one exists (Schema changes → Indexes) and an exact scan otherwise. Session knobs: SET hnsw.ef_search = N, SET ivfflat.probes = N, SET dodil.vector_exact_scan = on (force exact), SET dodil.vector_consistency = strong | eventual. The full vector story, including index choice and recall, is on Vector.

Graph functions

Tables bound into a graph with CREATE GRAPH are traversable from SQL through the graph_* functions (k-hop, neighbours, shortest path, PageRank, connected components, BFS) and from Cypher over Bolt. They are documented on Graph → SQL functions.

Reserved words

DuckDB’s reserved words apply. The ones that collide with common column names: at, end, order, table, to, user. A bare at TIMESTAMP in CREATE TABLE, or WHERE at > …, is 42601. Double-quote the identifier everywhere ("at"), or choose a name such as occurred_at.