OriginChainDB
Fts

Summarize matching documents

← FTS examples

The language tabs use earlier client interfaces. Check SDK versions and connection setup before running them.

Search text and calculate summaries across its matches, such as counts by category. A facet is a stored value used for a summary.

Before you run it: An over-limit match set is refused rather than summarized partially. Facet values must be written first.

A bucket is a group of matching documents, such as all blue products. The top-level field selects the indexed text collection; color and price inside aggs are saved facet values. The small write below is enough to inspect the response shape, but the illustrated totals require the corresponding full set of documents.

Summarize the complete set of text matchesA refused oversized match set should not be displayed as a partial total.
  1. Store text and facetsWrite searchable words and the separate summary fields.
  2. Find the bounded match setUse every match permitted by the aggregation limit.
  3. Group and add valuesCount colors or sum prices across those matched documents.
  4. Check the summaryConfirm the groups describe the whole accepted match set.

Follow the example

  1. Write text and facet values with the setup below.
  2. Choose search and aggregation fields separately.
  3. Send the request.
  4. Read the buckets or calculated values.

Write text and facet values

An aggregation reads per-document records, not the indexed text. Postings written by the text-indexing sample carry no attribute values, so give each document a facets map first. Re-posting the same doc_id replaces its record.

# Attach the values the aggregation will fold over. "text" is the stored
# text highlighting reads; "facets" maps a facet field to its values.
curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/fts/shop.products/description/doc" \
  -H "Authorization: Bearer $OC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "doc_id": "p001",
    "text":   "Over-ear headphones with active noise cancellation",
    "facets": { "color": ["red"], "price": ["249"] }
  }'

Run the request

Bucket every matching document by color and sum price inside each bucket. Omit search entirely and the tree runs over every document indexed under the field.

curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/fts/shop.products/_aggs" \
  -H "Authorization: Bearer $OC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "field": "description",
    "search": { "query": { "bool": {
      "must":     [{ "match": { "field": "description", "query": "headphones" } }],
      "must_not": [{ "term":  { "field": "description", "value": "refurbished" } }]
    } } },
    "aggs": {
      "by_color": {
        "terms": { "field": "color" },
        "aggs":  { "revenue": { "sum": { "field": "price" } } }
      }
    }
  }'

Check the result

Summaries use the complete match set within the limit, not just displayed top hits.

{
  "doc_count": 2,
  "aggs": {
    "by_color": {
      "kind": "buckets",
      "buckets": [
        {
          "key": "red",
          "doc_count": 2,
          "aggs": {
            "revenue": { "kind": "value", "value": 349.0 }
          }
        }
      ],
      "sum_other_doc_count": 0
    }
  }
}

doc_count is the size of the set the tree was evaluated over. Every result is tagged with kind - buckets, value, stats, cardinality and so on - so a client can dispatch on the shape without knowing which aggregation produced it.

Understand the behavior

  • Two different "field" keys. The top-level field names the FTS field whose per-document records hold the values. The field inside each aggregation names the facet being bucketed. They are rarely the same string.
  • Buckets cover the whole match set. Ranked search stops at a top-k; an aggregation cannot, or it would answer a different question with a number that looks right. The executor is asked for every match, so a terms count counts all of them.
  • Too many matches is a refusal, not a sample. A match set above the hit ceiling (OC_FTS_MAX_HITS, default 10,000) returns 400 naming the limit rather than quietly aggregating a prefix.
  • Trees nest. Each bucket carries its own aggs map, up to MAX_AGG_DEPTH = 8 levels. terms buckets default to count-descending.
  • Facet values are stored as strings and coerced to numbers by the numeric aggregations, which is why "249" sums correctly.

Important limits and checks

  • Aggregating a field that has no per-document records. Indexing text writes postings only. Without a /doc write carrying facets, there is nothing to fold and the bucket lists come back empty.
  • Sending an empty aggs object. Refused with 400 - an empty tree would return an object that reads like a result.
  • A pipeline aggregation at the top level. cumulative_sum and its siblings act on a parent bucket list; at the root there is none, so the request is refused rather than answered with zeroes.
  • Expecting top_k to bound the cost. It is ignored here by design. If the query matches too much, narrow the query - the endpoint will not hand back a prefix.
  • Putting doc_values_field in the search block. Refused: the aggregated field is already the value source, and the two could only disagree.

Continue

Continue to rank text matches.

© 2026 Silicoyn Technologies Pvt Ltd

On this page