Skip to main content
DQL now supports projections and aggregates in addition to SELECT * operations. You can select specific fields, perform calculations, and use aggregate functions like COUNT, SUM, AVG, MIN, and MAX.

SELECT Statements

The following syntax outlines the basic structure and optional clauses you can use within your SELECT statements:
DQL
SELECT Syntax Diagram Where projection can be:
  • * - Returns all fields from the documents
  • field1, field2, ... - Returns specific fields
  • expression AS alias - Returns calculated values with custom names
  • Aggregate functions like COUNT(*), SUM(field), etc.
For instance, retrieve all documents in the cars collection WHERE the color property is set to the value β€˜blue’ :
DQL

Clauses for Filtering

The following table provides an overview of the different clauses you can use to define specific conditions and calculations within your DQL SELECT statements to provide more granular control over your queries:

FROM

Required in each SELECT statement you write in DQL, the FROM element identifies the collection for document retrieval.
DQL
FROM Clause Syntax Diagram CollectionTerm Syntax Diagram For example, a SELECT statement querying documents from the cars collection:
DQL

USE IDS

The optional USE IDS clause allows you to efficiently retrieve specific documents by their IDs without performing a collection scan. This is more performant than using WHERE _id IN (...) filters. Syntax:
DQL
USE IDS Syntax Diagram Examples:
DQL
Performance Tip: Where possible, simple equality filters on _id (like WHERE _id = '123') are automatically optimized internally similar to USE IDS. However, for explicit control and guaranteed performance, USE IDS is recommended. See also the #auto_use_ids directive in Directives.

Collection Aliasing

You can assign an alias to a collection in the FROM clause to create shorter, more readable queries. The AS keyword is optional:
DQL
Collection aliases are particularly useful in:
  • Complex queries: Shortening long collection names for readability
  • Qualified field references: Explicitly referencing fields from a specific collection
  • Query directives: Specifying collection-level directives (see Directives)
DQL
Collection aliases follow the same identifier rules as projection aliases and can also use backticks for special characters or reserved words.

JOIN

Joins are only supported for local SDK queries. Joins are not supported in subscription queries or Big Peer/Server queries. See restrictions for more details.
The FROM clause may be followed by one or more JOIN terms to correlate documents from additional collections. Ditto supports INNER JOIN, LEFT OUTER JOIN, and RIGHT OUTER JOIN. Each join requires an access path that isn’t a full collection scan on the inner collection (by default); the ADVISE statement can recommend appropriate indexes.
DQL
For full syntax, join types, index requirements, query plan details, and advanced usage, see Joins below.

WHERE

The WHERE clause filters data based on either an expression or a set of conditions that narrow the result set returned to you:
DQL
WHERE Clause Syntax Diagram For example, here is a basic SELECT statement querying documents from the cars collection based on a given address:
DQL
To demonstrate a more complex query, here is a SELECT statement that queries using multiple expressions and logical operators to further refine the criteria for document retrieval:
DQL

ORDER BY

With the ORDER BY clause, if you’d like, you can integrate calculations or expressions in your SELECT statement. Then sort the resulting documents to return in either ascending (ASC) or descending (DESC) alphabetical order:
DQL
ORDER BY Clause Syntax Diagram For example, here is a simple SELECT statement that uses the ORDER BY clause to query and sort documents from the cars collection in descending (DESC) alphabetical order based on the field value set for the color property:
DQL
In this syntax:
  • your_collection_name is the name of the collection from which you want to retrieve the data.
  • expression_1, expression_2, ... are the expressions evaluated to sort the result. Expressions are resolved in order.
  • [ASC|DESC] is an optional parameter that specifies the sort order. If omitted, the default sort order is ascending (ASC). To sort in descending order, you can specify DESC.
Example In this example, the result set from the query will be sorted in descending order based on the values in the field:
DQL
For instance, here "blue" cars return first and other cars sort by the natural order in the collection:
DQL

Sort Order by Object Type

In DQL, the hierarchy for comparing and sorting objects varies based on the following criteria:
  • If ascending (ASC) order operations:
    • boolean
    • number
    • binary
    • string
    • array
    • object
    • null
    • missing
  • If descending (DESC) order operations, sorting order is reversed:
    • missing
    • null
    • object
    • array
    • string
    • binary
    • number
    • boolean
  • If evaluating values, true results are prioritized and ordered first followed by false results.

Expressing Sort Order

Unless explicitly defined as DESC in your query, Ditto defaults to sorting in ascending (ASC). So, if you want to sort in ascending order, you do not have to express that in your query.

LIMIT

The LIMIT clause is used to restrict the number of documents returned by a query, allowing you to specify a maximum limit on the number of documents to be included in the result set:
DQL
In this syntax:
  • your_collection_name is the name of the collection from which you want to retrieve the data.
  • limit_value is the maximum number of documents you want to include in the result set.
For example, only return the first 10 documents from the your_collection_name collection:
DQL

OFFSET

The OFFSET clause is used to specify the number of records to skip before starting to return documents from the query result:
DQL
In this syntax:
  • your_collection_name is the name of the collection from which you want to retrieve the data.
  • number_of_items_to_skip is the number of items before returning the result set.
Using OFFSET with LIMIT is a common way to utilize OFFSET; for example:
DQL

Projections

Projections allow you to specify exactly which fields or calculated values to return from your query, rather than returning entire documents. This reduces data transfer and processing overhead. Projection Syntax Diagram
The same projection syntax is used by the RETURNING clause of INSERT, UPDATE and DELETE/EVICT/TOMBSTONE statements, with a small number of restrictions.

Basic Field Selection

Select specific fields from documents:
DQL

Excluding Fields

To exclude specific fields from a projection, use the MISSING keyword. This can be combined with * to select all fields except those explicitly excluded:
DQL

Aliasing

Use aliases to rename fields in your results:
DQL
Default aliases are assigned if not specified:
  • Simple field references use the field name as the alias
  • Expressions get aliases like ($1), ($2), etc.
Aliases bind to the element immediately prior in the statement. SELECT COUNT(*) AS car_count FROM cars names the output field, but SELECT COUNT(*) FROM cars AS car_count aliases the cars collection β€” COUNT(*) falls back to its default alias.

Alias Constraints

Aliases must follow these rules:
  • Uniqueness: Each alias in a SELECT list must be unique. Duplicate aliases will raise an error.
  • Identifier Rules: Aliases must be valid identifiers following field naming conventions.
  • Special Characters: Use backticks (grave accents) to quote aliases containing special characters or reserved words.
DQL

Alias Scope

Projection aliases defined in the SELECT list can be referenced by the ORDER BY clause to sort by an aliased expression:
DQL
GROUP BY and HAVING do not accept projection aliases. They must reference the underlying source expression or aggregate function directly: use GROUP BY color (the source field) rather than GROUP BY c (a projection alias), and HAVING COUNT(*) > 5 (the aggregate) rather than HAVING car_count > 5 (the alias).

Calculated Fields

Create new fields using expressions:
DQL

DISTINCT Results

Remove duplicate rows from your results:
DQL
DISTINCT Performance Considerations:DISTINCT has performance implications you should understand:Memory Buffering:
  • All projections must be buffered in memory to enforce uniqueness
  • This increases memory requirements proportional to the number of unique results
  • Memory usage grows with the result set size
Redundant Usage:
  • When the _id field is included (e.g., SELECT DISTINCT *), DISTINCT is redundant
  • Each document already has a unique _id, making DISTINCT unnecessary
  • This adds overhead without benefit - avoid in these cases
Best Practices:
  • Only use DISTINCT when you truly need unique combinations of projected fields
  • Avoid DISTINCT when projecting complete documents (SELECT DISTINCT *)
  • Consider if your application logic can handle duplicates instead
  • For large result sets, evaluate if DISTINCT is worth the memory cost
Projections in a RETURNING clause may not specify DISTINCT; there, the word is taken to be a field name rather than a keyword.

Aggregate Functions

Aggregate functions perform calculations across multiple documents and return a single result. DQL supports the following aggregate functions:
For a quick reference table of all aggregate functions and their syntax, see Operator Expressions - Aggregate Functions.

COUNT

Count documents or non-null values:
DQL

SUM

Calculate the sum of numeric values:
DQL

AVG

Calculate the average of numeric values:
DQL

MIN and MAX

Find minimum and maximum values:
DQL

MID

Calculate the midpoint between minimum and maximum values:
DQL
The MID function returns the midpoint value between MIN(expr) and MAX(expr). Non-numeric values are silently ignored.

MEDIAN

Calculate the median (middle) value:
DQL
The MEDIAN function returns the middle value when all values are sorted. Non-numeric values are silently ignored.

Combining Aggregates

Use multiple aggregate functions in a single query:
DQL

GROUP BY

Group results and calculate aggregates for each group:
DQL
GROUP BY Clause Syntax Diagram When using aggregates with GROUP BY, non-aggregate projections must be part of the GROUP BY clause:
DQL

HAVING

Filter grouped results based on aggregate values:
DQL
HAVING Clause Syntax Diagram
Aggregate functions form a β€œdam” in the execution pipeline - all documents must be processed before results can be returned. This differs from non-aggregate queries which can stream results.

Aggregate Function Behavior

Understanding how aggregate functions work internally helps optimize query performance: Memory Requirements:
  • Aggregates accumulate results per group, so memory usage depends on the number of groups
  • A small number of groups requires little memory regardless of document count
  • A large number of groups increases memory requirements proportionally
  • DISTINCT aggregates must buffer all distinct values in memory, which can significantly increase memory usage with many unique values
MISSING Values:
  • If no documents qualify for an aggregate in a group, the result is MISSING (absent from results)
  • To always include a result, use conditional functions: SUM(IFMISSING(field, 0))
  • MIN and MAX silently ignore MISSING values
  • COUNT does not count NULL, MISSING, or FALSE values unless explicitly handled
Type Handling:
  • SUM, AVG, MID, and MEDIAN silently ignore non-numeric values
  • MIN and MAX compare values using Ditto’s standard type ordering rules
  • COUNT(*) is equivalent to COUNT(true) with no additional overhead
Performance Tips:
  • Use COUNT(*) instead of COUNT(field) when counting all documents
  • Avoid DISTINCT with aggregates unless necessary due to memory overhead
  • Minimize the number of groups in GROUP BY for better memory efficiency
  • Use HAVING to filter groups rather than filtering after aggregation

Advanced Aggregate Examples

Handling MISSING Values:
DQL
Conditional Aggregation with CASE:
DQL
Complex Business Logic:
DQL
Filtering Within Aggregates:
DQL
Handling NULL and MISSING Gracefully:
DQL
Multi-level Aggregations:
DQL
Working with Arrays and Complex Fields:
DQL

Joins

Joins are available in SDK version 5.1 and later and are supported only on the Ditto SDK (small peer). They are not supported in Big Peer (cloud/server) environments. See Restrictions for details.
DQL joins let you correlate documents across two or more collections within a single SELECT statement. Rather than issuing separate queries and stitching results together in application code, you express the relationship directly in DQL and let the query engine handle it efficiently. A join appends fields from an additional collection to each row produced by the preceding collection (or join result), based on a condition you supply in the ON clause. The engine iterates over the outer (driving) collection (or join result) and, for each outer row, probes the inner (joined) collection using an appropriate access path in a strategy known as a nested-loop join (nlJoin in EXPLAIN output). Why use joins?
  • Avoid data duplication. Store related data in separate collections and compose views on demand rather than embedding copies everywhere.
  • Cross-collection queries. Filter, sort, and project fields from multiple collections in one round trip to the store.
  • Richer aggregations. Group and aggregate across collections β€” for example, sum order amounts per customer, or count products per order.
Practical example. Suppose you maintain a customers collection and an orders collection. Without joins you would fetch all orders, then issue a second query per customer to look up the name. With a join you express this directly:
DQL

Syntax

Each term (collection or prior join) in the FROM clause may be followed by a JOIN. JoinTerm Syntax Diagram

Join types

ON condition

The ON expression may reference any collection alias that has already been introduced in the FROM clause β€” that is, the driving collection and any collections joined before the current term. It may be any valid DQL expression, including compound conditions with AND or OR.

Collection aliases

Aliases are strongly recommended when joining so that field references are unambiguous. Aliases must be unique across all collections in the statement.
DQL

USE IDS, USE INDEX, and USE DIRECTIVES

Each collection term in a join may include USE IDS, USE INDEX, or USE DIRECTIVES sub-clauses to control how that collection is accessed. These apply per-collection and are particularly useful for the inner leg of a join.
DQL

Index requirements

By default, every inner collection in a join must be accessed via an index scan (or combine index scans) or an ID scan. Attempting to join without an appropriate access path will produce an error:
You can override this on a per-collection basis by adding USE INDEX "" to the join collection term, which explicitly permits a collection scan. See Overriding the index requirement for details.

Creating a join index

The join key on the inner (right-hand) collection must be indexed. Create the index before executing a join query:
DQL
The INCLUDE MISSING option ensures documents that lack the cust_id field are covered by the index and will not be silently omitted from results.

Getting index recommendations with ADVISE

If you are unsure which indexes a join requires, run ADVISE on the statement. The engine analyses the query and suggests the optimal indexes, including composite covering indexes where beneficial:
DQL
Example output:
Once all required indexes exist, ADVISE confirms this:

Examples

The examples below use these two collections:
  • customers β€” _id, cust_id (integer), name, tier
  • orders β€” _id, order_id (integer), cust_id (integer), amount, status
Assume the following index exists for all examples:
DQL

INNER JOIN

Returns only rows where the ON condition is satisfied in both collections. Customers with no orders and orders with no matching customer are excluded.
DQL

LEFT OUTER JOIN

All customers are returned. Where no matching order exists, the order-side fields (o.amount) are MISSING in the result document.
DQL
You can identify unmatched outer rows by testing for MISSING:
DQL

RIGHT OUTER JOIN

Supported only as the first join in the statement, and is internally rewritten to a LEFT OUTER JOIN with the tables swapped. All orders are returned; customers with no matching order have MISSING name fields.
DQL

Post-join WHERE filter

WHERE predicates are evaluated after the join. Predicates that reference only one collection are pushed down into the join condition where possible.
DQL

Multi-expression ON clause

The ON clause may contain compound expressions. Conditions that can be evaluated against the outer row alone are filtered early; the remainder form the join condition.
DQL

Wildcard projection across joined collections

Use * to include all collections, each under its alias:
DQL
Or expand a single collection’s fields:
DQL

Multi-collection chains

You may chain any number of join terms (See Directives). Each subsequent join may reference all previously introduced aliases in its ON clause.
DQL
DQL

Joining on document ID

When the ON condition equates a field from the outer collection to _id of the inner collection, the planner uses an efficient idScan rather than a regular index scan β€” no additional index is required on the inner collection.
DQL

Directives

All query directives are available when using joins. A number are particularly relevant.

Limiting the number of join terms

The #max_joins global directive limits how many join terms a statement may contain. The default is 10. Set it to 0 to prohibit joins entirely in a given query.
DQL

Controlling access paths per collection

Use the USE INDEX or USE DIRECTIVES sub-clause on any collection term to influence the access path for that specific leg of the join.
DQL
The USE INDEX and USE DIRECTIVES sub-clauses on join collections are collection-level directives and apply to that collection instance only. They follow the same precedence rules as described in Query Directives.

Overriding the index requirement

You can explicitly permit a collection scan on an inner join leg by supplying an empty string to USE INDEX. This overrides the default requirement for indexed access and should only be used when the inner collection is small or when you have a specific reason to avoid an index access path.
DQL
The override is per-collection. If you have three join terms and supply USE INDEX "" on only one, the other two must still have index access paths.

Prefer-order with joins

The #prefer_order directive applies to the driving collection. It is rarely necessary for joins β€” the join order is fixed by the statement β€” but can influence index selection on the outer leg when ORDER BY is present.

Understanding query plans with EXPLAIN

Prefix any join SELECT with EXPLAIN to inspect the plan without executing the query. The plan reveals which access path the planner chose for each leg of the join β€” index scan, covering index scan, intersect scan, or ID scan β€” and confirms whether LEFT OUTER semantics are in effect. See EXPLAIN β€” Join plans for a full description of the nlJoin operator, dynamic span bounds, covering and intersect scans, and how LEFT OUTER, RIGHT OUTER, and multi-collection joins appear in the plan.

Best practices

  1. Index every join key on the inner collection. Use ADVISE to confirm which indexes are needed.
  2. Use composite covering indexes that include both the join key and any projected or filtered fields from the inner collection. This eliminates the fetch step and reduces document reads.
  3. Order join terms thoughtfully. Place the most selective (smallest) collection first as the outer (driving) leg to reduce the number of inner-leg probes.
  4. Qualify all field references with aliases. Unqualified field names in multi-collection queries can be ambiguous.
  5. Use EXPLAIN during development to confirm the planner has chosen index scans rather than collection scans for inner legs.
  6. Use ADVISE before deploying new join queries to ensure optimal indexes are in place.

Restrictions

  • Not supported in Big Peer environments. Joins are a Ditto SDK (small peer) feature only.
  • Each inner collection must have an indexed access path β€” either an index scan, a covering index scan, an intersect/union scan, or an ID scan. A collection scan on an inner leg is rejected unless explicitly permitted via USE INDEX "".
  • RIGHT OUTER JOIN is only supported as the first join term. Subsequent join terms must use INNER or LEFT OUTER. The right join is rewritten internally; the rewrite is transparent to the result.
  • The join count limit defaults to 10 join terms per statement. Adjust with /*+ {"#max_joins": N} */.
  • Using joins in registerSubscription will result in an unsupported query error. Joins are supported in execute and registerObserver.