Description
Define temporary variables that can be referenced in subsequent stages of the pipeline.
Variables created in the let(...) stage are not included in the final results
unless they are explicitly assigned to a field in a later stage (e.g., using
add_fields(...) or select(...)). This lets you simplify complex logic by
breaking it into smaller, reusable components without cluttering the output
documents. The let(...) stage is particularly useful for correlated
sub-pipelines, where a sub-pipeline needs to reference a value from the parent
document's scope.
Syntax
Node.js
const results = await db.pipeline()
.collection("/awards")
// `let(...)` referred to as `define(...)` in the Web SDK.
.define(rand().as("r"))
.addFields(
switchOn(
lessThan(variable("r"), 0.05), constant("rare"),
lessThan(variable("r"), 0.25), constant("uncommon"),
constant("common")).as("random_score"))
.execute();
Behavior
Variables versus Fields
While fields represent data stored within documents, variables are temporary values that exist only during the execution of the pipeline.
| Fields | Variables | |
|---|---|---|
| Purpose | access or store fields into documents | generate or access temporary values during pipeline execution |
| SDK Usage | field("name") |
variable("name") |
| Scope | local to current document | global to pipeline and sub-pipelines |
| Undefined References | evaluates to absent |
generates runtime error |
Scope:
While fields are scoped to the local document, variables are defined in a
separate scope and remain accessible across stages until the first occurrence of
a stage that "merges" multiple documents together (like aggregate(...) or
distinct(...)). Stages that "merge" multiple documents don't allow variable
references to be used afterwards as by merging the previous stage's results
together there is no longer one value for the variable.
Undefined References:
While referencing an undefined field is fine (and just evaluates to absent),
attempting to reference an undefined variable will fail during request
validation. In this sense, field references can be viewed as performing a lookup
in a map at runtime while variable references are more akin to variables in a
statically compiled programming language.
Global Scope and Subqueries
Variables are essential when working with nested pipelines. A sub-pipeline
executes in its own scope and can only access the fields of the documents it is
currently processing. To use a value from the "parent" document inside a
subquery, you must first define it as a variable using the let(...) stage.
Node.js
// Fetch reviewers alongside their negative reviews.
const pipeline = db.pipeline()
.collection("/reviewers")
// `let(...)` referred to as `define(...)` in the Web SDK.
.define(field("__name__").as("reviewer_name"))
.select("__name__", array(db.pipeline().collectionGroup("reviews")
.where(field("author").equals(variable("reviewer_name")))
.where(field("rating").lessThan(2))
.select("review", "rating")).as("negative_reviews"))
.execute();
Overlapping Variables
Defining a variable with a name that was already defined in a previous
let(...) stage will overwrite the previous variable. This can be used to
update temporary state as the pipeline progresses.
Variables referenced in sub-pipeline follow lexical scoping rules as found in many programming languages and refers to the variables under the same name defined by the closest (parent) pipeline.
Comparison with add_fields(...)
The let(...) stage behaves similarly to the add_fields(...)
stage, but instead of adding fields to the document, it assigns values to
variables.