All About Lexicons
URI, Collection, Value, Tuple and Optic Patterns in MarkLogic
MarkLogic lexicons are often split into separate mental buckets — "the URI index", "the collection index", "range index values" — and treated as unrelated features. They are not. URI lookup, collection enumeration, scalar value retrieval, co-occurrence analysis, and tuple reads all share the same underlying index infrastructure and the same reference-based query model.
The practical consequence is straightforward. Teams that understand the lexicon family build concise, fast, composable queries. Teams that treat each lexicon type as a separate one-off mechanism end up with fragile workarounds, expensive document scans for questions indexes already answer, and query code that becomes painful to maintain as models evolve.
This article covers the full lexicon family: what each type is for, when to use it, and the design and operational patterns that hold up at scale.
The examples in this article assume the llamaverse (v2.3.1+) is deployed. The llamaverse sample data is freely available from github.com/cleverllamas/llamaverse — see the llamaverse article for full setup instructions.
The Lexicon Family
MarkLogic exposes these lexicon surfaces:
| Lexicon type | What it indexes | Primary function |
|---|---|---|
| URI lexicon | Every document URI | Fast identity, pattern matching, pagination |
| Collection lexicon | Every collection name | List, count, co-occurrence by collection |
| Scalar value lexicons | Element/attribute/path values | Distinct values, faceting, range predicates |
| Field lexicons | Values surfaced through named fields | One logical lexicon across multiple sources, including metadata |
| Tuple / co-occurrence | Paired or multi-column value combinations | Analytics, grouping, dimension joins |
All lexicon surfaces are queried through references (cts:uri-reference(), cts:collection-reference(), cts:element-reference(), cts:path-reference(), cts:field-reference("..."), and so on). Once you have a reference, the same family of functions applies: cts:values(), cts:range-query(reference, ...), cts:tuples(), op.fromLexicons(), and index-based ordering in search flows via order by cts:index-order(reference, ...).
The Shared Mental Model
Scope note: this article is about lexicons and lexicon references. TDE template trade-offs and template-led query design are out of scope for this article.
Use this pattern consistently across all lexicon types:
- Define the reference —
cts:collection-reference(),cts:element-reference(xs:QName("breed")),cts:path-reference("/heightCm"), etc. - Decide what you need — distinct values (
cts:values), filtered document sets (cts:range-query), co-occurring combinations (cts:tuples), or fragment-attached rows (op.fromLexicons). - Add scope — a
cts:queryargument narrows retrieval to a meaningful subset.
| Need | Typical function |
|---|---|
| Distinct values from an index | cts:values(reference) |
| Filter documents by an indexed value | cts:range-query(reference, operator, value) |
| Co-occurring value combinations | cts:tuples(references) |
| Lexicon-driven rows in Optic with fragment linkage | op.fromLexicons(indexDef) |
| Document URIs by pattern or constraint | cts:uris() / cts:uri-match() |
| All collection names | cts:collections() / cts:values(cts:collection-reference()) |
Index Prerequisites
Lexicon queries depend on the correct index flags being enabled on the database.
{
"collection-lexicon": true,
"uri-lexicon": true
}
If a query shape is correct but behaves slowly, returns unexpectedly empty results, or throws an index-not-present error, verify index settings before rewriting query logic. The Admin UI path for each index type is:
| Index type | Admin UI path |
|---|---|
| URI lexicon | Databases → db → Settings → URI Lexicon |
| Collection lexicon | Databases → db → Settings → Collection Lexicon |
| Fields | Databases → db → Fields |
| Element range index | Databases → db → Element Range Indexes |
| Path range index | Databases → db → Path Range Indexes |
URI Lexicon
The URI lexicon is easy to describe and easy to underuse. At face value it is an index of document URIs. In practice it becomes a fast control surface for operations, incremental processing, existence checks, and selective maintenance — without loading whole documents.
URI-centric workflows let you ask identity questions cheaply:
- Which documents match this URI pattern?
- Which URI is next after the one I last processed?
- Does this exact URI exist?
- Which URIs in this partition also match a content constraint?
cts:uris() Basics
cts:uris(start, options, query, quality-weight, forest-ids) is the workhorse. The argument order matters and is a frequent source of subtle bugs when values are accidentally shifted.
xquery version "1.0-ml";
(: Basic ordered URI retrieval scoped to wild-llamas. :)
cts:uris(
(),
("document", "item-order", "ascending"),
cts:collection-query("wild-llamas")
)[1 to 3]
/cleverllamas/llamaverse/raw/wild-llamas/llamas/00048384-cb13-4557-805e-a6b4e57f7eab.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/0005e261-4302-4ae8-9574-732a54040423.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/000eca10-d166-469f-b17a-3c3b35ee0883.json
Two practical notes:
- Keep parameter order explicit in shared utility modules to avoid accidental argument shifting.
- Treat URI retrieval as a first pass for identity and partitioning, then load content only for the subset you actually need.
cts:uri-match() for Wildcard Patterns
Use cts:uri-match() when your primary filter is URI shape rather than a CTS query constraint.
xquery version "1.0-ml";
(: Wildcard URI match from lexicon. :)
cts:uri-match(
"/cleverllamas/llamaverse/raw/wild-llamas/llamas/*.json",
("document", "ascending")
)[1 to 3]
/cleverllamas/llamaverse/raw/wild-llamas/llamas/00048384-cb13-4557-805e-a6b4e57f7eab.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/0005e261-4302-4ae8-9574-732a54040423.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/000eca10-d166-469f-b17a-3c3b35ee0883.json
If you also need content constraints, combine URI-first retrieval with a CTS query in cts:uris() rather than running separate scans.
Fast Existence Checks
For URI-space workflows, lexicon existence checks are clean and cheap.
xquery version "1.0-ml";
let $uri := "/cleverllamas/llamaverse/raw/wild-llamas/llamas/00048384-cb13-4557-805e-a6b4e57f7eab.json"
(: "document" ensures this checks for a document fragment specifically. :)
(: A URI can also appear as properties or locks, depending on options. :)
return fn:exists(cts:uri-match($uri, ("document")))
true
The ("document") option is deliberate: this check asks whether a document fragment exists at that URI, not whether the URI appears in the lexicon under any fragment type.
This distinction matters. Per cts:uri-match / cts:uris option semantics, URI lexicon retrieval can target any, document, properties, or locks. In other words, a URI can be present for properties/metadata or lock state even when no document fragment is present, so use the fragment option that matches the lifecycle decision you are making.
Directories are relevant here as well. Directory creation uses xdmp:directory-create. A created directory has a URI and is represented as a document fragment, so the "document" option includes directory URIs alongside regular document URIs.
URI Deep Dive
Want the full URI party? URI Directory Queries is a dedicated article focused entirely on URI patterns, directory semantics, and practical query strategies.
This pattern is useful for idempotent import and replay pipelines where URI existence determines whether to insert, skip, or repair.
Lexical Pagination
Lexicon pagination is most robust when you resume from the last URI value rather than relying on large skip offsets.
xquery version "1.0-ml";
(: Lexical pagination using start URI and truncate option. :)
(: Resume from the last URI returned rather than a skip offset. :)
let $page1 := cts:uris(
(),
("document", "item-order", "ascending", "truncate=3"),
cts:collection-query("wild-llamas")
)
let $last := $page1[fn:last()]
let $page2 := cts:uris(
$last,
("document", "item-order", "ascending", "truncate=4"),
cts:collection-query("wild-llamas")
)[fn:position() gt 1]
return ($page1, "---", $page2)
This pattern gives deterministic traversal and restartability for long-running batch jobs.
Use it for:
- Nightly maintenance over millions of documents.
- Resumable reindex and backfill workflows.
- Retry-safe integration exports.
Combining URI and Content Constraints
A common misconception is that URI lexicon workflows are URI-only. cts:uris() accepts a CTS query constraint, so you can keep URI ordering while limiting to semantically relevant documents.
Pattern:
- Provide a URI start value for resumability.
- Apply a CTS query for business filtering.
- Process returned URIs in deterministic windows.
URI Option Reference
| Option | Typical use |
|---|---|
item-order | Deterministic lexicographic ordering |
ascending / descending | Direction control for scans |
document | Restrict to document fragments |
truncate=N | Controlled batch windows |
concurrent | Parallel lexicon evaluation hints (test before applying) |
Forest-Aware Traversal
For very large datasets, the optional forest-ids parameter in cts:uris() scopes traversal to specific forests. This is useful for staged maintenance, targeted recovery, and controlled parallel execution. Only use forest-scoped traversal if your runbook documents the partitioning assumptions clearly.
URI Lexicon Operational Patterns
| Pattern | Why it works well with the URI lexicon |
|---|---|
| Incremental ETL | Resume from last processed URI |
| Large maintenance sweeps | Process in deterministic URI windows |
| URI naming audits | Match patterns without loading full content |
| Retry queues | Persist and reprocess URI sets explicitly |
Collection Lexicon
The collection lexicon provides instant enumeration of all collection names and supports lexicon-joined scope queries across those names.
List All Collections
xquery version "1.0-ml";
(: List every collection in the database. :)
(: With the collection lexicon enabled, this reads directly from the :)
(: index and returns instantly, regardless of database size. :)
cts:collections()
"llamaverse"
"content"
"raw"
"raw*:wild-llamas"
"wild-llamas"
With the collection lexicon enabled this reads directly from the index and returns instantly, regardless of database size.
Count Documents per Collection
xquery version "1.0-ml";
(: Count documents in every collection, ordered highest-first. :)
(: cts:estimate() reads the collection lexicon — no document scan. :)
for $c in cts:collections()
let $count := cts:estimate(cts:collection-query($c))
order by $count descending
return fn:concat($c, ": ", fn:string($count))
"raw: 4648"
"raw*:wild-llamas: 4560"
"wild-llamas: 4560"
"llamaverse: 136"
"content: 48"
cts:estimate() reads the collection lexicon without scanning document content, making this a fast audit tool even on large databases.
Documents NOT in a Collection
A clean pattern using the collection reference to express exclusion:
xquery version "1.0-ml";
(: Documents NOT in a specific collection. :)
(: Uses cts:collection-reference() to express exclusion cleanly. :)
cts:search(
fn:collection(),
cts:not-query(
cts:range-query(
cts:collection-reference(),
"=",
"wild-llamas"
)
)
)[1 to 10] ! xdmp:node-uri(.)
Using cts:range-query(cts:collection-reference(), "=", ...) inside a cts:not-query() is the absolute textbook way to do this. Any other approach is usually going to cost more time, memory, and CPU. Avoid workarounds that load all documents and then filter.
Co-occurring Collections
Finding which collections always appear together with a given collection is a fast lexicon join:
xquery version "1.0-ml";
(: Find all collections that co-occur with documents in "wild-llamas". :)
(: The third argument to cts:collections() scopes the walk to only :)
(: documents that also match the supplied query — a lexicon join. :)
cts:collections(
(), (: start value: begin from the top :)
(), (: options: none :)
cts:collection-query("wild-llamas") (: scope: only wild-llamas docs :)
)
"raw"
"raw*:wild-llamas"
"wild-llamas"
Co-occurrence analysis is a great way to validate collection assignment strategy in mature content models, and to detect accidental membership drift after ingestion changes.
Observed Dataset Issue
When reviewing this sample output, it exposed a dataset issue: a collection named raw*:wild-llamas. That points to a configuration problem in the wild-llama collection settings - silly me. 🤪 I have intentionally left it in this article because it is a useful real-world example of how co-occurrence analysis can surface data-quality problems quickly.
Scalar Value Lexicons
Scalar value lexicons are backed by range indexes on elements, attributes, or paths. Once an index is present, cts:values() returns distinct values from it without any document scan.
Distinct Values with cts:values()
cts:values(reference) returns one item per unique value — not one item per matching document.
xquery version "1.0-ml";
(: Distinct breed values from a range-reference-backed lexicon. :)
(: Scoped to wild-llamas for relevance. :)
cts:values(
cts:element-reference(xs:QName("breed")),
(),
(),
cts:collection-query("wild-llamas")
)
"Huacaya"
"Suri"
This is ideal for faceting, filter menus, and metadata introspection. The result set is small and comes entirely from the index.
Range Queries over References
cts:range-query(reference, operator, value) applies structured predicates using the same reference model as cts:values().
xquery version "1.0-ml";
(: URI-first range query variant. :)
(: Returns URIs in lexicographic URI order. :)
let $breed-ref := cts:element-reference(xs:QName("breed"))
let $query := cts:and-query((
cts:collection-query("wild-llamas"),
cts:range-query($breed-ref, "=", "Suri")
))
for $uri in cts:uris("", ("item-order", "ascending", "document"), $query)[1 to 5]
return $uri
/cleverllamas/llamaverse/raw/wild-llamas/llamas/0005e261-4302-4ae8-9574-732a54040423.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/002c7133-e985-4bd6-9ea8-f057fa0c6ee7.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/006c5ffa-6c5e-4169-8a08-eec49f48148c.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/007229e2-ee25-47c1-b18a-2e0c0ec0dbcd.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/00863414-306e-49cb-acd0-6579bea5421e.json
xquery version "1.0-ml";
(: Document-node range query variant. :)
(: Ordered by a second indexed dimension (heightCm), not URI. :)
let $breed-ref := cts:element-reference(xs:QName("breed"))
let $height-ref := cts:path-reference("/heightCm")
let $breed-query := cts:range-query($breed-ref, "=", "Suri")
for $doc in cts:search(
fn:collection("wild-llamas"),
$breed-query,
("unfiltered", cts:index-order($height-ref, "ascending"))
)[1 to 5]
let $height := fn:string(($doc//heightCm, $doc//height-cm)[1])
return fn:concat(xdmp:node-uri($doc), " | heightCm=", $height)
/cleverllamas/llamaverse/raw/wild-llamas/llamas/1707fa8c-1d33-45bd-8e45-d4007a6f93fa.json | heightCm=100
/cleverllamas/llamaverse/raw/wild-llamas/llamas/0ceea126-5068-4524-9bc6-df1e39bb8b8d.json | heightCm=100
/cleverllamas/llamaverse/raw/wild-llamas/llamas/1fce7c61-9d43-466e-a608-d01f35c3265e.json | heightCm=100
/cleverllamas/llamaverse/raw/wild-llamas/llamas/d2067696-2fab-4bc9-8b13-e5e618aea78d.json | heightCm=100
/cleverllamas/llamaverse/raw/wild-llamas/llamas/07eabcd8-537a-41ff-a5b1-b26a30d87dcc.json | heightCm=100
Both patterns use the same range predicate, but they serve slightly different purposes:
cts:uris(...)is ideal when you only need URI values and want lexical URI ordering.cts:search(...)returns document nodes, which is useful when you need to order by another indexed value from the matched documents (for exampleheightCmviacts:index-order(...)).
For structured dimensions (breed, height, weight, date), cts:range-query with an explicit reference is usually cleaner and more predictable than word or value query families.
Field Lexicons
Fields are the missing layer in many lexicon designs.
If you need one logical lexicon over multiple physical sources, fields are the right tool. A field can unify values that come from different element/path locations while giving you one stable reference in query code (cts:field-reference("field-name")).
This matters most in two scenarios:
- Data models that evolved over time and now store the same concept in different structures.
- Values stored in fragment metadata, where fields provide the practical route to queryable lexicon access across that metadata.
Why Fields Improve Lexicon Design
Without fields, query code often accumulates multiple references and custom OR logic to represent a single business dimension. With fields:
- Index config maps many sources to one named field.
- Query code uses one reference (
cts:field-reference). - Application semantics stay stable even if source structure changes.
That decoupling is exactly what keeps lexicon-heavy systems maintainable.
Unified Values from a Field Reference
The example below assumes a field (for example, llama-species-unified) has been configured to collect species values from multiple source locations. A field is a general modelling and query abstraction, not a lexicon-only feature. Adding a field range index is one common use when you want value lexicon and range-query behaviour over that unified field surface.
{
"field-name": "llama-species-unified",
"included-paths": [
"/species",
"/traits/species",
"/envelope/instance/species",
"/metadata/species"
],
"notes": "Illustrative field definition excerpt used by the examples in this section."
}
{
"field-name": "llama-species-unified",
"scalar-type": "string",
"range-value-positions": false,
"collation": "http://marklogic.com/collation/",
"invalid-values": "ignore"
}
xquery version "1.0-ml";
for $species in
cts:values(
cts:field-reference("llama-species-unified"),
(),
(),
cts:collection-query("llamaverse")
)
return $species
Alpaca
Llama
Vicuna
Filter Documents with a Field-Based Range Query
Once the field is in place, filtering is the same reference model used elsewhere in this article:
xquery version "1.0-ml";
let $species-ref := cts:field-reference("llama-species-unified")
let $query :=
cts:and-query((
cts:collection-query("llamaverse"),
cts:range-query($species-ref, "=", "Suri")
))
for $uri in cts:uris("", ("item-order", "ascending", "document"), $query)
return $uri
/cleverllamas/llamaverse/content/field-lexicon/field-source-breed.json
Prove It Live: Field + Metadata Coverage
What We Are Proving
Before deploy, we run one query that checks two things in one pass:
- Distinct values resolve through the field reference.
- A metadata-carried value can still be matched through the same field reference.
Think of it as a quick confidence check: one query, two checks to set our mind at ease.
The Verification Query
xquery version "1.0-ml";
let $species-ref := cts:field-reference("llama-species-unified")
let $target-value := "Guanaco"
let $scope := cts:collection-query("llamaverse")
let $metadata-check-query :=
cts:and-query((
$scope,
cts:range-query($species-ref, "=", $target-value)
))
return (
"Distinct values from the unified field:",
cts:values($species-ref, (), (), $scope),
"",
fn:concat("URIs matching ", $target-value, " via the unified field:"),
for $uri in cts:uris("", ("item-order", "ascending", "document"), $metadata-check-query)
return $uri
)
Distinct values from the unified field:
Acanthaster planci
Achatina fulica
Adineta vaga
Agalychnis callidryas
Ailurus fulgens
...
URIs matching Guanaco via the unified field:
/cleverllamas/llamaverse/content/field-lexicon/field-source-species.json
How to Read the Results
You should see both distinct values and URI matches for the target value from one field reference. If distinct values appear but metadata-backed URI matches do not, the issue is usually field include paths or reindex state rather than query logic.
Field + Metadata Pattern
When teams need lexicon behaviour over values carried in fragment metadata, field-based design is typically the cleanest approach:
- Define a field for the business dimension.
- Include all relevant content paths plus metadata-bearing sources in that field definition.
- Query with
cts:field-referenceso content and metadata-backed values are addressed uniformly.
Operationally, this gives one canonical query surface for a dimension, regardless of where values are found in the document, properties or metadata.
Co-occurrences and Tuples
When you need paired or multi-dimensional value combinations, move from single-value lexicons to tuple retrieval.
xquery version "1.0-ml";
(: Multiple range-backed co-occurrence patterns. :)
(: Returns distinct breed values, paired with corresponding heights. :)
(: Uses cts:values() with multiple references to explore correlation. :)
for $breed in cts:values(
cts:element-reference(xs:QName("breed")),
(),
(),
cts:collection-query("wild-llamas")
)[1 to 20]
let $heights := cts:values(
cts:element-reference(xs:QName("heightCm")),
(),
(),
cts:and-query((
cts:element-value-query(xs:QName("breed"), $breed),
cts:collection-query("wild-llamas")
))
)
return element breed-height-pair {
element breed { $breed },
element count-with-this-breed { count($heights) },
element avg-height {
if (count($heights) > 0)
then avg($heights[. castable as xs:double] ! xs:double(.))
else ()
}
}
Huacaya | 100
Huacaya | 101
Huacaya | 102
Huacaya | 103
Huacaya | 104
Huacaya | 105
Huacaya | 106
Huacaya | 107
Huacaya | 108
Huacaya | 109
cts:tuples() returns combinations of referenced values that co-occur in the same fragment scope. This is the right primitive for analytics-style grouping and dimension exploration where you need more than one dimension at a time.
cts:values vs op.fromLexicons
These two functions often confuse teams because they look similar but serve different purposes.
`cts:values(reference)`
- returns distinct values only
- one output item per unique value
- no direct fragment identity in the output
`op.fromLexicons(indexDef, ..., "fragmentId")`
- returns rows sourced from lexicons
- rows remain attached to fragment identity
- can join/filter/project in Optic while keeping fragment linkage
The key distinction is fragment attachment:
cts:values()collapses to distinct values. Fragment identity is gone.op.fromLexicons()keeps rows attached to the fragment they came from.
Optic example with explicit fragment attachment:
'use strict';
const op = require('/MarkLogic/optic');
// fromLexicons returns lexicon values as rows that remain fragment-attached.
// Include fragmentId to make that attachment explicit.
op.fromLexicons(
{
breed: cts.elementReference(xs.QName('breed')),
heightCm: cts.pathReference('/heightCm', ['type=int'])
},
null,
'fragmentId'
)
.where(op.eq(op.col('breed'), 'Suri'))
.select(['fragmentId', 'breed', 'heightCm'])
.limit(10)
.result();
{"fragmentId":"http://marklogic.com/fragment/0000037B96E1D862","breed":"Suri","heightCm":102}
{"fragmentId":"http://marklogic.com/fragment/000003CB96E1D862","breed":"Suri","heightCm":137}
{"fragmentId":"http://marklogic.com/fragment/000003DB96E1D862","breed":"Suri","heightCm":151}
{"fragmentId":"http://marklogic.com/fragment/000003EB96E1D862","breed":"Suri","heightCm":144}
{"fragmentId":"http://marklogic.com/fragment/0000044B96E1D862","breed":"Suri","heightCm":199}
Optic example returning distinct values (similar to cts:values()):
'use strict';
const op = require('/MarkLogic/optic');
// Collapse lexicon rows down to unique breed values, similar to cts:values().
op.fromLexicons({
breed: cts.elementReference(xs.QName('breed'))
})
.groupBy('breed', [])
.orderBy('breed')
.result();
{"breed":"Huacaya"}
{"breed":"Hybrid"}
{"breed":"Suri"}
Optic example returning only the breed value sequence (same end shape as cts:values()):
'use strict';
const op = require('/MarkLogic/optic');
// Convert distinct Optic rows into a plain value sequence shape.
const rows = op.fromLexicons({
breed: cts.elementReference(xs.QName('breed'))
})
.groupBy('breed', [])
.orderBy('breed')
.result();
rows.toArray().map(row => row.breed);
["Huacaya", "Hybrid", "Suri"]
Use op.fromLexicons() when you need to join lexicon-driven rows with other Optic views while preserving provenance. Use cts:values() when you only need the unique values themselves.
Design Decision Matrix
| Query intent | Best lexicon approach | Notes |
|---|---|---|
| Enumerate all document URIs in order | cts:uris() with item-order | Use URI pagination for large datasets |
| Check URI existence | cts:uri-match(uri) | Instant, index-only |
| List all collection names | cts:collections() | One function call; instant |
| Count docs per collection | cts:estimate(cts:collection-query(c)) | No document scan |
| Docs not in a collection | cts:not-query(cts:range-query(cts:collection-reference(),...)) | Reference-based exclusion |
| Distinct facet values | cts:values(reference) | One value per unique item |
| Structured predicate filter | cts:range-query(reference, op, value) | Stable semantics for typed dimensions |
| One dimension spread across multiple paths or metadata | cts:field-reference("name") with cts:values / cts:range-query | Keeps one stable reference as structures evolve |
| Multi-dimensional analytics | cts:tuples(references) | Fragment-scoped co-occurrences |
| Lexicon rows in Optic with fragment linkage | op.fromLexicons(indexDef) | When provenance matters in a pipeline |
Throughput and Safety Guidelines
- Batch URI traversal by windows (
truncate=N) rather than unbounded scans. - Persist the last successful URI as checkpoint state for restartable jobs.
- Keep worker actions idempotent for replay safety.
- Separate discovery (lexicon reads) from expensive mutation where possible.
- Use
item-orderfor deterministic ordering in all long-running jobs. - Centralise reference definitions in shared utility modules — do not re-declare them per query.
- Validate tuple and co-occurrence assumptions against real fragment boundaries before deployment.
Common Mistakes
| Mistake | Consequence | Better approach |
|---|---|---|
| Loading every document to discover URIs | Avoidable I/O overhead | Use cts:uris() or cts:uri-match() first |
| Using large skip-based pagination | Unstable and slower scans at scale | Resume from last URI key |
Mixing option order in cts:uris() | Hard-to-debug result shape issues | Keep canonical parameter order |
| Treating collections as a separate mechanism | Inconsistent query style and poor reuse | Apply the reference mental model uniformly |
| Avoiding fields when one dimension exists in multiple structures | Duplicated query logic and brittle OR predicates | Use one named field and query it via cts:field-reference |
Expecting cts:values() to return document linkage | Loses fragment context | Switch to op.fromLexicons() when provenance matters |
| Using free-text queries for structured dimensions | Unstable semantics | Use cts:range-query with explicit references |
| Trying to model co-occurrence with repeated single-value calls | Expensive and lossy | Use cts:tuples() for paired retrieval |
| Combining URI and content filters as separate passes | Extra processing complexity | Use cts:uris() with a CTS query constraint |
If you keep one consistent reference-first model across URI, collection, value, field, and tuple queries, lexicon-heavy systems stay fast, predictable, and far easier to maintain.
Need Some Help?
Looking for more information on this subject or any other topic related to MarkLogic? Contact Us (info@cleverllamas.com) to find out how we can assist you with consulting or training!
- The Lexicon Family
- The Shared Mental Model
- Index Prerequisites
- URI Lexicon
- cts:uris() Basics
- cts:uri-match() for Wildcard Patterns
- Fast Existence Checks
- Lexical Pagination
- Combining URI and Content Constraints
- URI Option Reference
- Forest-Aware Traversal
- URI Lexicon Operational Patterns
- Collection Lexicon
- List All Collections
- Count Documents per Collection
- Documents NOT in a Collection
- Co-occurring Collections
- Scalar Value Lexicons
- Distinct Values with cts:values()
- Range Queries over References
- Field Lexicons
- Why Fields Improve Lexicon Design
- Unified Values from a Field Reference
- Filter Documents with a Field-Based Range Query
- Co-occurrences and Tuples
- cts:values vs op.fromLexicons
- Design Decision Matrix
- Throughput and Safety Guidelines
- Common Mistakes