Skip to content

Formula scoring

Formula queries reshape a candidate score with Qdrant score-builder expressions. Use them after a retrieval stage when business signals such as freshness, popularity, distance, or a payload tier should influence ranking.

Bare score (or $score) represents the upstream score. Prefer bare score in shell scripts, CLI invocations, and code: POSIX shells (bash, zsh) treat $score inside double quotes as an environment variable and replace it with "", triggering syntax errors unless backslash-escaped (\$score). Bare score is shell-safe and canonical. DEFAULTS makes missing payload values predictable instead of leaving their behavior implicit.

QQLBlend relevance and citation countTry in playground
WITH
candidates AS (QUERY 'distributed systems' USING dense LIMIT 100)
QUERY FORMULA score * 0.8 + LOG(citation_count + 1.0) * 0.2 DEFAULTS (score = 0.0, citation_count = 0)
FROM papers
PREFETCH (candidates)
LIMIT 10;

Decay functions take a payload field followed by named (TARGET =, SCALE =, MIDPOINT =, DECAY =) or positional arguments. scale and midpoint/decay are numeric constants. TARGET can be a numeric constant or an ISO 8601 datetime string (e.g. TARGET = "2026-01-01T00:00:00Z"). When TARGET is a datetime string, the payload field automatically infers datetime_key.

FunctionChoose it when
EXP_DECAYThe effect should drop quickly near the target and then taper
GAUSS_DECAYValues near the target should receive the smoothest peak
LIN_DECAYA simple, linear reduction is easiest to reason about
QQLFreshness and popularity signalsTry in playground
QUERY FORMULA score + EXP_DECAY(age_days, TARGET = 0.0, SCALE = 30.0, MIDPOINT = 0.5) + GAUSS_DECAY(popularity, TARGET = 100.0, SCALE = 25.0) DEFAULTS (score = 0.0, age_days = 365, popularity = 0)
FROM articles
LIMIT 10;

Function names are case-insensitive. Bare score (or $score) holds the upstream candidate score when no DEFAULTS entry overrides it.

FunctionSignatureNotes
ABS, EXPNAME(expr)Standard absolute value and $e^x$
SQRT, LOG, LN, ACOSHNAME(expr) [DEFAULT = n]Optional [DEFAULT = n] guards bounded domains against NaN crashes
POWPOW(base, exponent)Exponentiation
MAX / MINMAX(expr, expr, …)Largest/smallest of n ≥ 1 operands
GEO_DISTANCEGEO_DISTANCE(lat, lon, field) or GEO_DISTANCE({lat: ..., lon: ...}, field)Distance in meters
MATCH / MATCH_ANYMATCH(field, value) or MATCH_ANY(field, list)Payload condition in formulas
EXP_DECAY, GAUSS_DECAY, LIN_DECAYNAME(field, TARGET = n, SCALE = n, ...)Named or positional decay parameters
datetimeDATETIME('2026-01-01')Literal timestamp (canonical uppercase; lowercase still parses)
datetime_keyDATETIME_KEY('published_at')Payload timestamp field

MAX / MIN fold any number of operands (at least one), and ACOSH maps scores into a strictly positive range. Bounded functions accept an optional [DEFAULT = n] suffix that prevents Qdrant HTTP 400 NaN crashes on boundary values:

QQLClamp a boosted score and shape with ACOSHTry in playground
QUERY FORMULA MAX(MIN(score * 2.0, 10.0), 0.0) + ACOSH(popularity + 1.0) [DEFAULT = 0.0]
DEFAULTS (score = 0.0, popularity = 0)
FROM papers
LIMIT 10;

Formula precedence, highest to lowest, is unary -, multiplication/division, then addition/subtraction; operators are left-associative. A division may carry an explicit by-zero default with [DEFAULT = number]:

QQLDivision with a by-zero defaultTry in playground
WITH
candidates AS (QUERY 'distributed systems' USING dense LIMIT 10)
QUERY FORMULA score / (views + 1.0) [DEFAULT = 0.0] DEFAULTS (score = 0.0, views = 0)
FROM docs
PREFETCH (candidates)
LIMIT 10;

Use CASE WHEN for a discrete condition. Keep the fallback explicit so a missing or non-matching value remains easy to audit.

QQLBoost a premium tierTry in playground
QUERY FORMULA CASE WHEN tier MATCH ANY ('premium') THEN score * 1.5 ELSE score END DEFAULTS (score = 0.0)
FROM docs
LIMIT 10;

Keep formula inputs indexed and measurable before adding them to a production ranking path.