Text Query Building Starter Pack

Compose text queries without turning your code into spaghetti

personClever Llamas
CleverLlamasMinimum Llamaverse Version: 2.4.0
databaseMinimum MarkLogic Version: 11

Each example keeps data, query, and result together so you can compare option behaviour without losing the thread.

This pack covers the cts text-building blocks you reach for when stakeholders say, "Can we search for this, but not that, and maybe also this other thing?"

When a query supports behaviour-changing options, the example below calls out the ones that matter and keeps the result tab focused on the output that explains the match.

The examples in this article assume the llamaverse (v2.4.0+) is deployed. The llamaverse sample data is freely available from github.com/cleverllamas/llamaverse - see the llamaverse article for full setup instructions. The query samples below use the Aaron llama document from that dataset so the input data, query, and result stay easy to compare as you review each example.

API Context for This Pack

Context ItemWhat this pack uses
Data setllamaverse v2.4.0+ sample data
Primary document scopewild llama JSON documents (/cleverllamas/llamaverse/raw/wild-llamas/llamas/{uuid}.json)
Primary constraintstext terms with boolean composition (word, or, not)
Fields usedNone in these examples
Index contextWord query resolution through cts text indexes, including stemming where requested

If text matches look off, verify the exact query terms and options (for example stemming) before changing broader query logic.

Functions in This Pack

cts:word-query

The bread-and-butter text matcher. Simple, sharp, and surprisingly dangerous in the wrong hands.

Source docs: https://docs.marklogic.com/11.0/cts:word-query

Option / ArgumentWhat it controlsUsed here
$textWord or phrase terms to matchSingle and multi-word sample terms
$optionsMatch behaviour (stemming, case, punctuation, synonym, wildcard)See section below for the key options
$weightRelative relevance contribution for this queryDefault weight in sample
{
  "envelope": {
    "headers": { "type": "llamas" },
    "instance": {
      "llamas": {
        "id": "0c8bdb0d-ac62-49b7-ac74-94dbba46efa5",
        "name": "Aaron",
        "heightCm": 180,
        "weightKg": 138,
        "eyeColor": "Amber",
        "hairColor": "Blonde",
        "breed": "Huacaya",
        "placeOfBirth": "Cusco, Peru",
        "interests": ["playing chess", "birdwatching", "writing poetry"],
        "medicalCondition": null,
        "relatedTo": {
          "id": "a6c69bb3-fa75-4327-bed6-1f0623ae2c6c",
          "relationship": "father"
        },
        "description": "Aaron is an amber-eyed llama with blonde hair, standing 180 cm tall. Originally from Cusco, Peru, Aaron enjoys playing chess, birdwatching, and writing poetry.",
        "secretPower": {
          "name": "Snake Charmer",
          "id": "f6236908-9c0e-416e-a591-a1bd3986dc05"
        }
      }
    }
  }
}
xquery version "1.0-ml";

let $query := cts:and-query((
  cts:collection-query("wild-llamas"),
  cts:word-query("llama")
))
return cts:uris((), (), $query)[1 to 20]
'use strict';

const query = cts.andQuery([
  cts.collectionQuery('llamaverse'),
  cts.wordQuery('llama')
]);

const results = cts.uris(null, ['limit=10'], query).toArray();

({
  sample: 'cts/text-query-building-starter-pack/assets/cts-word-query.sjs',
  kind: Array.isArray(results) ? (results.every((item) => typeof item === 'object' && 'subject' in item && 'predicate' in item && 'object' in item) ? 'triples' : 'rows') : ((results !== null && typeof results === 'object') ? 'object' : 'scalar'),
  count: Array.isArray(results) ? results.length : 0,
  data: results
});
/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
/cleverllamas/llamaverse/raw/wild-llamas/llamas/002c7133-e985-4bd6-9ea8-f057fa0c6ee7.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.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
/cleverllamas/llamaverse/raw/wild-llamas/llamas/0087ad24-a720-4d82-b513-79ae5feebcc6.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/009cf2b9-d4db-4491-9cc2-34e2f43a5ee1.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/00a0fe3a-da61-493c-990e-af7719db0d18.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/00cf82a3-4937-40c6-80fe-6ae0f1100364.json
... (8 more URIs)
#URI
1/cleverllamas/llamaverse/raw/wild-llamas/llamas/00048384-cb13-4557-805e-a6b4e57f7eab.json
2/cleverllamas/llamaverse/raw/wild-llamas/llamas/0005e261-4302-4ae8-9574-732a54040423.json
3/cleverllamas/llamaverse/raw/wild-llamas/llamas/000eca10-d166-469f-b17a-3c3b35ee0883.json
4/cleverllamas/llamaverse/raw/wild-llamas/llamas/002c7133-e985-4bd6-9ea8-f057fa0c6ee7.json
5/cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.json
6/cleverllamas/llamaverse/raw/wild-llamas/llamas/006c5ffa-6c5e-4169-8a08-eec49f48148c.json
7/cleverllamas/llamaverse/raw/wild-llamas/llamas/007229e2-ee25-47c1-b18a-2e0c0ec0dbcd.json
8/cleverllamas/llamaverse/raw/wild-llamas/llamas/00863414-306e-49cb-acd0-6579bea5421e.json
9/cleverllamas/llamaverse/raw/wild-llamas/llamas/0087ad24-a720-4d82-b513-79ae5feebcc6.json
10/cleverllamas/llamaverse/raw/wild-llamas/llamas/009cf2b9-d4db-4491-9cc2-34e2f43a5ee1.json

What to notice: this shows the baseline text match shape before layering additional boolean logic.

cts:word-query options to consider

cts:word-query() is not really a single shape of query. Its behaviour changes a lot once you start using options, and those options are often the difference between a useful search and a noisy one.

Option / ArgumentWhat it controlsUsed here
$optionsBehaviour flags that tune matching and scoringDetailed option families listed below
$weightRelative relevance impact of this word-query clauseLeft at default in this pack's code samples
$options ValueWhat it controlsDefault behaviour
"case-sensitive" / "case-insensitive"Whether uppercase and lowercase are treated as different wordsDerived from the text you pass in
"diacritic-sensitive" / "diacritic-insensitive"Whether accents and similar marks matterDerived from the text you pass in
"punctuation-sensitive" / "punctuation-insensitive"Whether punctuation changes the matchDerived from the text you pass in
"whitespace-sensitive" / "whitespace-insensitive"Whether spacing affects the match"whitespace-insensitive"
"stemmed" / "unstemmed"Whether morphological variants like plurals or verb forms are normalisedDepends on database configuration
"wildcarded" / "unwildcarded"Whether * and ? are treated as wildcard operatorsDepends on database configuration and the text
"synonym"Whether equivalent terms are scored togetherOff by default
$weightHow strongly this query contributes to overall relevance1.0

For the llamaverse examples in this pack, the main practical choices are usually case-insensitive, stemmed, and synonym, because they make query intent clearer without forcing the reader to care about the exact surface form of the sample text.

cts:word-query documented option interactions

  1. "wildcarded" and "unwildcarded" control whether wildcard operators are interpreted or treated literally.
  2. "stemmed" and "unstemmed" control whether morphological variants are included.
  3. "case-sensitive"/"case-insensitive" and "diacritic-sensitive"/"diacritic-insensitive" control lexical sensitivity.
  4. "synonym" enables synonym-aware matching when synonym data is available.
  5. $weight controls the relative scoring contribution of the word-query clause.

cts:word-query case and diacritic sensitivity

Case and diacritic behaviour is where search often feels "random" to end users unless you are explicit about what the query should treat as equivalent.

Option / ArgumentWhat it controlsUsed here
"case-insensitive" / "case-sensitive"Whether letter case changes matchesBoth settings compared against the same collection scope
"diacritic-insensitive" / "diacritic-sensitive"Whether accent marks are normalised during matchingBoth settings compared in a controlled mini-check
{
  "envelope": {
    "headers": { "type": "llamas" },
    "instance": {
      "llamas": {
        "id": "0c8bdb0d-ac62-49b7-ac74-94dbba46efa5",
        "name": "Aaron",
        "heightCm": 180,
        "weightKg": 138,
        "eyeColor": "Amber",
        "hairColor": "Blonde",
        "breed": "Huacaya",
        "placeOfBirth": "Cusco, Peru",
        "interests": ["playing chess", "birdwatching", "writing poetry"],
        "medicalCondition": null,
        "relatedTo": {
          "id": "a6c69bb3-fa75-4327-bed6-1f0623ae2c6c",
          "relationship": "father"
        },
        "description": "Aaron is an amber-eyed llama with blonde hair, standing 180 cm tall. Originally from Cusco, Peru, Aaron enjoys playing chess, birdwatching, and writing poetry.",
        "secretPower": {
          "name": "Snake Charmer",
          "id": "f6236908-9c0e-416e-a591-a1bd3986dc05"
        }
      }
    }
  }
}
xquery version "1.0-ml";

let $caseSample := text { "Aaron" }
let $caseInsensitive :=
  cts:contains($caseSample, cts:word-query("aaron", ("case-insensitive")))
let $caseSensitive :=
  cts:contains($caseSample, cts:word-query("aaron", ("case-sensitive")))
let $diacriticSample := text { "Peru Peru" }
let $diacriticInsensitive :=
  cts:contains($diacriticSample, cts:word-query("Perú", ("diacritic-insensitive")))
let $diacriticSensitive :=
  cts:contains($diacriticSample, cts:word-query("Perú", ("diacritic-sensitive")))

return
<result>
  <caseInsensitiveMatches>{$caseInsensitive}</caseInsensitiveMatches>
  <caseSensitiveMatches>{$caseSensitive}</caseSensitiveMatches>
  <diacriticInsensitiveMatches>{$diacriticInsensitive}</diacriticInsensitiveMatches>
  <diacriticSensitiveMatches>{$diacriticSensitive}</diacriticSensitiveMatches>
</result>
'use strict';

const text = 'Áaron likes LLAMAS';
const caseSensitive = cts.wordQuery('llamas', ['case-sensitive']);
const caseInsensitive = cts.wordQuery('llamas', ['case-insensitive']);
const diacriticSensitive = cts.wordQuery('aaron', ['diacritic-sensitive']);
const diacriticInsensitive = cts.wordQuery('aaron', ['diacritic-insensitive']);

const results = {
  caseSensitive: cts.contains(text, caseSensitive),
  caseInsensitive: cts.contains(text, caseInsensitive),
  diacriticSensitive: cts.contains(text, diacriticSensitive),
  diacriticInsensitive: cts.contains(text, diacriticInsensitive)
};

({
  sample: 'cts/text-query-building-starter-pack/assets/cts-word-query-case-and-diacritic.sjs',
  kind: Array.isArray(results) ? (results.every((item) => typeof item === 'object' && 'subject' in item && 'predicate' in item && 'object' in item) ? 'triples' : 'rows') : ((results !== null && typeof results === 'object') ? 'object' : 'scalar'),
  count: Array.isArray(results) ? results.length : 0,
  data: results
});
X-Path: /result
<result><caseInsensitiveMatches>true</caseInsensitiveMatches><caseSensitiveMatches>false</caseSensitiveMatches><diacriticInsensitiveMatches>true</diacriticInsensitiveMatches><diacriticSensitiveMatches>false</diacriticSensitiveMatches></result>
FieldValue
XPath/result
Payload<result><caseInsensitiveMatches>true</caseInsensitiveMatches><caseSensitiveMatches>false</caseSensitiveMatches><diacriticInsensitiveMatches>true</diacriticInsensitiveMatches><diacriticSensitiveMatches>false</diacriticSen...

What to notice: these options only make sense when you understand the word-search configuration behind the target database. See Word Search Configuration Settings That Affect These Samples.

cts:word-query wildcard behaviour

Wildcard queries are useful, but they are heavily dependent on database-level wildcard settings. This is exactly where short patterns (2 and 3 character prefixes) and trailing wildcards can behave differently between environments.

Option / ArgumentWhat it controlsUsed here
"wildcarded"Treats * and ? as wildcard operatorsCompared using ll* and lla* patterns
"unwildcarded"Treats * and ? as literal charactersUsed as control comparison
{
  "envelope": {
    "headers": { "type": "llamas" },
    "instance": {
      "llamas": {
        "id": "0c8bdb0d-ac62-49b7-ac74-94dbba46efa5",
        "name": "Aaron",
        "heightCm": 180,
        "weightKg": 138,
        "eyeColor": "Amber",
        "hairColor": "Blonde",
        "breed": "Huacaya",
        "placeOfBirth": "Cusco, Peru",
        "interests": ["playing chess", "birdwatching", "writing poetry"],
        "medicalCondition": null,
        "relatedTo": {
          "id": "a6c69bb3-fa75-4327-bed6-1f0623ae2c6c",
          "relationship": "father"
        },
        "description": "Aaron is an amber-eyed llama with blonde hair, standing 180 cm tall. Originally from Cusco, Peru, Aaron enjoys playing chess, birdwatching, and writing poetry.",
        "secretPower": {
          "name": "Snake Charmer",
          "id": "f6236908-9c0e-416e-a591-a1bd3986dc05"
        }
      }
    }
  }
}
xquery version "1.0-ml";

let $twoCharPattern := "ll*"
let $threeCharPattern := "lla*"
let $twoCharWildcarded := count(
  cts:uris(
    (),
    (),
    cts:and-query((
      cts:collection-query("wild-llamas"),
      cts:word-query($twoCharPattern, ("wildcarded", "case-insensitive"))
    ))
  )
)
let $twoCharLiteral := count(
  cts:uris(
    (),
    (),
    cts:and-query((
      cts:collection-query("wild-llamas"),
      cts:word-query($twoCharPattern, ("unwildcarded", "case-insensitive"))
    ))
  )
)
let $threeCharWildcarded := count(
  cts:uris(
    (),
    (),
    cts:and-query((
      cts:collection-query("wild-llamas"),
      cts:word-query($threeCharPattern, ("wildcarded", "case-insensitive"))
    ))
  )
)

return
<result>
  <collection>wild-llamas</collection>
  <twoCharPattern>{$twoCharPattern}</twoCharPattern>
  <twoCharWildcardedCount>{$twoCharWildcarded}</twoCharWildcardedCount>
  <twoCharLiteralCount>{$twoCharLiteral}</twoCharLiteralCount>
  <threeCharPattern>{$threeCharPattern}</threeCharPattern>
  <threeCharWildcardedCount>{$threeCharWildcarded}</threeCharWildcardedCount>
</result>
'use strict';

const query = cts.andQuery([
  cts.collectionQuery('llamaverse'),
  cts.wordQuery('llam*', ['wildcarded'])
]);

const results = cts.uris(null, ['limit=10'], query).toArray();

({
  sample: 'cts/text-query-building-starter-pack/assets/cts-word-query-wildcarded-options.sjs',
  kind: Array.isArray(results) ? (results.every((item) => typeof item === 'object' && 'subject' in item && 'predicate' in item && 'object' in item) ? 'triples' : 'rows') : ((results !== null && typeof results === 'object') ? 'object' : 'scalar'),
  count: Array.isArray(results) ? results.length : 0,
  data: results
});
X-Path: /result
<result><collection>wild-llamas</collection><twoCharPattern>ll*</twoCharPattern><twoCharWildcardedCount>4561</twoCharWildcardedCount><twoCharLiteralCount>0</twoCharLiteralCount><threeCharPattern>lla*</threeCharPattern><threeCharWildcardedCount>3001</threeCharWildcardedCount></result>
FieldValue
XPath/result
Payload<result><collection>wild-llamas</collection><twoCharPattern>ll*</twoCharPattern><twoCharWildcardedCount>4561</twoCharWildcardedCount><twoCharLiteralCount>0</twoCharLiteralCount><threeCharPattern>lla*</threeCharPattern><t...

What to notice: if trailing wildcard searches or short-character wildcard indexes are disabled, wildcarded counts can drop sharply or look inconsistent between databases. See Word Search Configuration Settings That Affect These Samples.

cts:word-query stemmed vs unstemmed

Stemming is the difference between asking for the exact surface form and asking MarkLogic to treat related word forms as the same search idea. That is useful when you want broad recall, but it is also the fastest way to make a search feel noisier than you intended.

This example searches for sing — a term with obvious morphological variants (sang, sung, singing). The contrast between stemmed and unstemmed counts is immediate.

Option / ArgumentWhat it controlsUsed here
"stemmed"Broadens matches to morphological variantsUsed in the broader variant query
"unstemmed"Restricts matches to exact word formsUsed in the strict variant query
{
  "envelope": {
    "headers": { "type": "llamas" },
    "instance": {
      "llamas": {
        "id": "0c8bdb0d-ac62-49b7-ac74-94dbba46efa5",
        "name": "Aaron",
        "heightCm": 180,
        "weightKg": 138,
        "eyeColor": "Amber",
        "hairColor": "Blonde",
        "breed": "Huacaya",
        "placeOfBirth": "Cusco, Peru",
        "interests": ["playing chess", "birdwatching", "writing poetry"],
        "medicalCondition": null,
        "relatedTo": {
          "id": "a6c69bb3-fa75-4327-bed6-1f0623ae2c6c",
          "relationship": "father"
        },
        "description": "Aaron is an amber-eyed llama with blonde hair, standing 180 cm tall. Originally from Cusco, Peru, Aaron enjoys playing chess, birdwatching, and writing poetry.",
        "secretPower": {
          "name": "Snake Charmer",
          "id": "f6236908-9c0e-416e-a591-a1bd3986dc05"
        }
      }
    }
  }
}
xquery version "1.0-ml";

let $stemmed-query := cts:and-query((
  cts:collection-query("wild-llamas"),
  cts:word-query("sing", ("stemmed"))
))
let $unstemmed-query := cts:and-query((
  cts:collection-query("wild-llamas"),
  cts:word-query("sing", ("unstemmed"))
))
return
  <comparison>
    <stemmed>
      <count>{ cts:estimate($stemmed-query) }</count>
      <samples>{
        for $uri in cts:uris((), (), $stemmed-query)[1 to 3]
        return <uri>{ $uri }</uri>
      }</samples>
    </stemmed>
    <unstemmed>
      <count>{ cts:estimate($unstemmed-query) }</count>
      <samples>{
        for $uri in cts:uris((), (), $unstemmed-query)[1 to 3]
        return <uri>{ $uri }</uri>
      }</samples>
    </unstemmed>
  </comparison>
'use strict';

const stemmed = cts.andQuery([
  cts.collectionQuery('llamaverse'),
  cts.wordQuery('sing', ['stemmed'])
]);
const unstemmed = cts.andQuery([
  cts.collectionQuery('llamaverse'),
  cts.wordQuery('sing', ['unstemmed'])
]);

const results = {
  stemmedEstimate: cts.estimate(stemmed),
  unstemmedEstimate: cts.estimate(unstemmed),
  sampleUris: cts.uris(null, ['limit=10'], stemmed).toArray()
};

({
  sample: 'cts/text-query-building-starter-pack/assets/cts-word-query-stemmed-vs-unstemmed.sjs',
  kind: Array.isArray(results) ? (results.every((item) => typeof item === 'object' && 'subject' in item && 'predicate' in item && 'object' in item) ? 'triples' : 'rows') : ((results !== null && typeof results === 'object') ? 'object' : 'scalar'),
  count: Array.isArray(results) ? results.length : 0,
  data: results
});
stemmed count: 992
unstemmed count: 0
stemmed sample URIs:
/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/0043e95e-3209-4b97-8871-f9b7073377f2.json
#URI
1stemmed count: 992
2unstemmed count: 0
3stemmed sample URIs:
4/cleverllamas/llamaverse/raw/wild-llamas/llamas/00048384-cb13-4557-805e-a6b4e57f7eab.json
5/cleverllamas/llamaverse/raw/wild-llamas/llamas/0005e261-4302-4ae8-9574-732a54040423.json
6/cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.json

What to notice: sing as an exact string appears nowhere in the llamaverse — all 992 matches are morphological variants (sang, sung, singing). Without stemming, the result count drops to zero. That is the difference between broad recall and exact-form precision.

cts:word-query synonym matches

Synonym matching is what you reach for when your search vocabulary leans on a controlled vocabulary or thesaurus. This sample compares a plain word query with the same query using the "synonym" option, so you can immediately see whether your current configuration expands the term set.

Option / ArgumentWhat it controlsUsed here
"synonym"Treats equivalent terms as matching conceptsEnabled for synonym-aware match behaviour
{
  "envelope": {
    "headers": { "type": "llamas" },
    "instance": {
      "llamas": {
        "id": "0c8bdb0d-ac62-49b7-ac74-94dbba46efa5",
        "name": "Aaron",
        "heightCm": 180,
        "weightKg": 138,
        "eyeColor": "Amber",
        "hairColor": "Blonde",
        "breed": "Huacaya",
        "placeOfBirth": "Cusco, Peru",
        "interests": ["playing chess", "birdwatching", "writing poetry"],
        "medicalCondition": null,
        "relatedTo": {
          "id": "a6c69bb3-fa75-4327-bed6-1f0623ae2c6c",
          "relationship": "father"
        },
        "description": "Aaron is an amber-eyed llama with blonde hair, standing 180 cm tall. Originally from Cusco, Peru, Aaron enjoys playing chess, birdwatching, and writing poetry.",
        "secretPower": {
          "name": "Snake Charmer",
          "id": "f6236908-9c0e-416e-a591-a1bd3986dc05"
        }
      }
    }
  }
}
xquery version "1.0-ml";

let $plain-query := cts:and-query((
  cts:collection-query("wild-llamas"),
  cts:word-query("hiking")
))
let $synonym-query := cts:and-query((
  cts:collection-query("wild-llamas"),
  cts:word-query("hiking", "synonym")
))
return
  <result>
    <plain-count>{ cts:estimate($plain-query) }</plain-count>
    <synonym-count>{ cts:estimate($synonym-query) }</synonym-count>
    <samples>{
      for $uri in cts:uris((), (), $synonym-query)[1 to 10]
      return <uri>{ $uri }</uri>
    }</samples>
  </result>
'use strict';

const plainQuery = cts.andQuery([
  cts.collectionQuery('llamaverse'),
  cts.wordQuery('hiking')
]);

const synonymQuery = cts.andQuery([
  cts.collectionQuery('llamaverse'),
  cts.wordQuery('hiking', ['synonym'])
]);

const results = {
  plainEstimate: cts.estimate(plainQuery),
  synonymEstimate: cts.estimate(synonymQuery),
  uris: cts.uris(null, ['limit=10'], synonymQuery).toArray()
};

({
  sample: 'cts/text-query-building-starter-pack/assets/cts-word-query-synonym.sjs',
  kind: Array.isArray(results) ? (results.every((item) => typeof item === 'object' && 'subject' in item && 'predicate' in item && 'object' in item) ? 'triples' : 'rows') : ((results !== null && typeof results === 'object') ? 'object' : 'scalar'),
  count: Array.isArray(results) ? results.length : 0,
  data: results
});
X-Path: /result
<result><plain-count>1002</plain-count><synonym-count>1002</synonym-count><samples><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/006c5ffa-6c5e-4169-8a08-eec49f48148c.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/00863414-306e-49cb-acd0-6579bea5421e.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/00a0fe3a-da61-493c-990e-af7719db0d18.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/00cf82a3-4937-40c6-80fe-6ae0f1100364.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/00d80680-7f0c-4613-a58d-db38fcdbc22c.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/014566d5-d9c8-4caa-a5f8-07a929e004f0.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/019653d9-e191-4e09-a03d-d9f6d395ab3e.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/01a9a1bc-e506-495c-9f1d-6984b1b5f68d.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/01be86ce-b01a-42f3-9795-b182a85aa925.json</uri></samples></result>
FieldValue
XPath/result
Payload<result><plain-count>1002</plain-count><synonym-count>1002</synonym-count><samples><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.json</uri><uri>/cleverllamas/llamaverse/raw/wil...

What to notice: in this environment, plain-count and synonym-count are the same, which tells you no extra synonym expansion is currently being applied for this term. With a configured thesaurus, synonym-count is where you should expect the wider match set. See Word Search Configuration Settings That Affect These Samples for the key environment assumptions.

cts:or-query

When business logic says "either works" and your query finally agrees.

Source docs: https://docs.marklogic.com/11.0/cts:or-query

Option / ArgumentWhat it controlsUsed here
$queriesList of alternative sub-queries; any match satisfies the clauseMultiple term alternatives in one composed query
{
  "envelope": {
    "headers": { "type": "llamas" },
    "instance": {
      "llamas": {
        "id": "0c8bdb0d-ac62-49b7-ac74-94dbba46efa5",
        "name": "Aaron",
        "heightCm": 180,
        "weightKg": 138,
        "eyeColor": "Amber",
        "hairColor": "Blonde",
        "breed": "Huacaya",
        "placeOfBirth": "Cusco, Peru",
        "interests": ["playing chess", "birdwatching", "writing poetry"],
        "medicalCondition": null,
        "relatedTo": {
          "id": "a6c69bb3-fa75-4327-bed6-1f0623ae2c6c",
          "relationship": "father"
        },
        "description": "Aaron is an amber-eyed llama with blonde hair, standing 180 cm tall. Originally from Cusco, Peru, Aaron enjoys playing chess, birdwatching, and writing poetry.",
        "secretPower": {
          "name": "Snake Charmer",
          "id": "f6236908-9c0e-416e-a591-a1bd3986dc05"
        }
      }
    }
  }
}
xquery version "1.0-ml";

let $query := cts:and-query((
  cts:collection-query("wild-llamas"),
  cts:or-query((
    cts:word-query("llama"),
    cts:word-query("suri")
  ))
))
return cts:uris((), (), $query)[1 to 20]
'use strict';

const query = cts.andQuery([
  cts.collectionQuery('llamaverse'),
  cts.orQuery([
    cts.wordQuery('llama'),
    cts.wordQuery('alpaca')
  ])
]);

const results = cts.uris(null, ['limit=10'], query).toArray();

({
  sample: 'cts/text-query-building-starter-pack/assets/cts-or-query.sjs',
  kind: Array.isArray(results) ? (results.every((item) => typeof item === 'object' && 'subject' in item && 'predicate' in item && 'object' in item) ? 'triples' : 'rows') : ((results !== null && typeof results === 'object') ? 'object' : 'scalar'),
  count: Array.isArray(results) ? results.length : 0,
  data: results
});
/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
/cleverllamas/llamaverse/raw/wild-llamas/llamas/002c7133-e985-4bd6-9ea8-f057fa0c6ee7.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.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
/cleverllamas/llamaverse/raw/wild-llamas/llamas/0087ad24-a720-4d82-b513-79ae5feebcc6.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/009cf2b9-d4db-4491-9cc2-34e2f43a5ee1.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/00a0fe3a-da61-493c-990e-af7719db0d18.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/00cf82a3-4937-40c6-80fe-6ae0f1100364.json
... (8 more URIs)
#URI
1/cleverllamas/llamaverse/raw/wild-llamas/llamas/00048384-cb13-4557-805e-a6b4e57f7eab.json
2/cleverllamas/llamaverse/raw/wild-llamas/llamas/0005e261-4302-4ae8-9574-732a54040423.json
3/cleverllamas/llamaverse/raw/wild-llamas/llamas/000eca10-d166-469f-b17a-3c3b35ee0883.json
4/cleverllamas/llamaverse/raw/wild-llamas/llamas/002c7133-e985-4bd6-9ea8-f057fa0c6ee7.json
5/cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.json
6/cleverllamas/llamaverse/raw/wild-llamas/llamas/006c5ffa-6c5e-4169-8a08-eec49f48148c.json
7/cleverllamas/llamaverse/raw/wild-llamas/llamas/007229e2-ee25-47c1-b18a-2e0c0ec0dbcd.json
8/cleverllamas/llamaverse/raw/wild-llamas/llamas/00863414-306e-49cb-acd0-6579bea5421e.json
9/cleverllamas/llamaverse/raw/wild-llamas/llamas/0087ad24-a720-4d82-b513-79ae5feebcc6.json
10/cleverllamas/llamaverse/raw/wild-llamas/llamas/009cf2b9-d4db-4491-9cc2-34e2f43a5ee1.json

What to notice: cts:or-query is ideal for alternate term paths without duplicating surrounding query constraints.

cts:not-query

Sometimes the fastest way to find the right llamas is to exclude the wrong ones first.

Source docs: https://docs.marklogic.com/11.0/cts:not-query

Option / ArgumentWhat it controlsUsed here
$querySub-query whose matches should be excludedNegative clause used to remove unwanted matches
{
  "envelope": {
    "headers": { "type": "llamas" },
    "instance": {
      "llamas": {
        "id": "0c8bdb0d-ac62-49b7-ac74-94dbba46efa5",
        "name": "Aaron",
        "heightCm": 180,
        "weightKg": 138,
        "eyeColor": "Amber",
        "hairColor": "Blonde",
        "breed": "Huacaya",
        "placeOfBirth": "Cusco, Peru",
        "interests": ["playing chess", "birdwatching", "writing poetry"],
        "medicalCondition": null,
        "relatedTo": {
          "id": "a6c69bb3-fa75-4327-bed6-1f0623ae2c6c",
          "relationship": "father"
        },
        "description": "Aaron is an amber-eyed llama with blonde hair, standing 180 cm tall. Originally from Cusco, Peru, Aaron enjoys playing chess, birdwatching, and writing poetry.",
        "secretPower": {
          "name": "Snake Charmer",
          "id": "f6236908-9c0e-416e-a591-a1bd3986dc05"
        }
      }
    }
  }
}
xquery version "1.0-ml";

let $query := cts:and-query((
  cts:collection-query("wild-llamas"),
  cts:word-query("hiking"),
  cts:not-query(cts:word-query("dancing"))
))
let $count := cts:estimate($query)
let $samples := cts:uris((), (), $query)[1 to 10]
return
  <result>
    <count>{ $count }</count>
    <samples>{
      for $uri in $samples
      return <uri>{ $uri }</uri>
    }</samples>
  </result>
'use strict';

const query = cts.andQuery([
  cts.collectionQuery('llamaverse'),
  cts.wordQuery('hiking'),
  cts.notQuery(cts.wordQuery('dancing'))
]);

const results = {
  estimate: cts.estimate(query),
  uris: cts.uris(null, ['limit=10'], query).toArray()
};

({
  sample: 'cts/text-query-building-starter-pack/assets/cts-not-query.sjs',
  kind: Array.isArray(results) ? (results.every((item) => typeof item === 'object' && 'subject' in item && 'predicate' in item && 'object' in item) ? 'triples' : 'rows') : ((results !== null && typeof results === 'object') ? 'object' : 'scalar'),
  count: Array.isArray(results) ? results.length : 0,
  data: results
});
X-Path: /result
<result><count>760</count><samples><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/006c5ffa-6c5e-4169-8a08-eec49f48148c.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/00a0fe3a-da61-493c-990e-af7719db0d18.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/00cf82a3-4937-40c6-80fe-6ae0f1100364.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/014566d5-d9c8-4caa-a5f8-07a929e004f0.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/019653d9-e191-4e09-a03d-d9f6d395ab3e.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/01a9a1bc-e506-495c-9f1d-6984b1b5f68d.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/01be86ce-b01a-42f3-9795-b182a85aa925.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/02917e85-3ab6-4330-8faa-e4f3f58e4ff1.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/02eb04d8-70b3-4acc-b66e-a78af69cfb85.json</uri></samples></result>
FieldValue
XPath/result
Payload<result><count>760</count><samples><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/006c5ffa-6c5e-4169-8a08-eec49f48...

What to notice: this exclusion is selective. You are not removing an unrelated edge term; you are intentionally narrowing from "hiking" to "hiking but not dancing", which makes the negative clause meaningful.

For this particular query, a plain URI list is not as helpful as a summary because the result set is large enough that the interesting question is "how many?" first, and "which ones?" second. That is why the sample output above shows a count plus a few representative URIs.

Word Search Configuration Settings That Affect These Samples

These examples are intentionally explicit about query options, but the final behaviour still depends on database configuration. If your results differ from this starter pack, check these settings first.

Setting areaWhy it mattersWhat to verify
Default collation and languageAffects term comparison and linguistic behaviourEnsure the content database collation/language are the expected values for your text corpus
Stemming settingsControls broadening from root forms to variantsConfirm stemming behaviour matches your expectation for the selected language
Trailing wildcard searchesControls whether patterns like llam* are expanded as wildcardsVerify trailing wildcard searches are enabled when using wildcard-heavy patterns
2-character and 3-character wildcard supportControls whether very short wildcard prefixes are indexed efficientlyCheck short wildcard settings for 2/3 character terms if you use ll* or similar prefixes
Word positions / tokenisationAffects phrase, punctuation, and whitespace-sensitive matchingConfirm tokenisation and word position behaviour in your environment
Thesaurus/synonym dataControls synonym expansion qualityEnsure synonym data exists and is appropriate before relying on "synonym"

Operational guidance for this and related packs:

  1. Validate wildcard and stemming settings in the target content database before tuning query options.
  2. Re-run a small control query after any collation/language change.
  3. Treat environment-level search configuration as part of the code sample contract, not as a background detail.

Think This is Powerful? Keep Going.

Decision rule: lock your text option defaults (stemming, wildcard, sensitivity) before scaling query logic across endpoints.

These text-building skills unlock even more sophisticated patterns:

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!