---
title: Search
description: "relate, the embedded Cypher graph database (relatedb.ai): Vector and full-text index DDL, the SEARCH clause and its rules, index options and defaults, the search procedures and vector functions, and hybrid search."
type: reference
surfaces:
  - cypher
status: stable
verified: 0.1.2
verified_by:
  - cypher
  - cli
canonical_url: https://relatedb.ai/docs/reference/cypher/search/
prerequisites: []
related:
  - guides/cypher/vector-search
  - cookbook/hybrid-recall
  - reference/cypher/differences-from-neo4j
  - guides/cypher/full-text-and-hybrid-search
last_updated: 2026-11-09
---

# Search

Vector and full-text index DDL, the SEARCH clause and its rules, index options and defaults, the search procedures and vector functions, and hybrid search.

> Verified against relate 0.1.2. If your `relate --version` is newer, read https://relatedb.ai/docs/reference/changelog/#since-0.1.2

relate searches two kinds of [index](https://relatedb.ai/docs/reference/glossary/#index). A [vector index](https://relatedb.ai/docs/reference/glossary/#vector-index) answers "which entities are nearest to this vector" with an exact nearest-neighbor search: every search scores each indexed vector and keeps the top `LIMIT`, ties going to the lower element id; a [full-text index](https://relatedb.ai/docs/reference/glossary/#full-text-index) answers "which entities contain these words". The `SEARCH` clause restricts a `MATCH` to an index's top results and names their score. The cells on this page run on relate's WebAssembly (WASM) build, the weighted fusion included; the `COMBINE` example runs on the CLI, because it needs a preview opt-in.

## Synopsis

```text
CREATE VECTOR INDEX <name> [IF NOT EXISTS]
  FOR (<var>:<Label>) ON (<var>.<property>)
  [WITH [<var>.<property>, …]]
  [OPTIONS {indexConfig: {<key>: <value>, …}}]

CREATE VECTOR INDEX <name> [IF NOT EXISTS]
  FOR ()-[<var>:<TYPE>]-() ON (<var>.<property>)
  [WITH [<var>.<property>, …]]
  [OPTIONS {indexConfig: {<key>: <value>, …}}]

CREATE FULLTEXT INDEX <name> [IF NOT EXISTS]
  FOR (<var>:<Label>) ON EACH [<var>.<property>, …]

CREATE FULLTEXT INDEX <name> [IF NOT EXISTS]
  FOR ()-[<var>:<TYPE>]-() ON EACH [<var>.<property>, …]

DROP INDEX <name> [IF EXISTS]

MATCH <pattern that binds var>
SEARCH <var> IN (
  VECTOR INDEX <name> FOR <query vector> [WHERE <filter>] LIMIT <k>
) [SCORE AS <score var>]

MATCH <pattern that binds var>
SEARCH <var> IN (
  FULLTEXT INDEX <name> FOR <query string> [WITH ANALYZER <expr>] [SKIP <n> | OFFSET <n>] LIMIT <k>
) [SCORE AS <score var>]
```

## Index statements

| Statement               | Result                                      | Notes                                                                                                                                            |
| ----------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `CREATE VECTOR INDEX`   | An index over one property holding vectors  | `WITH [...]` declares the properties a vector search may filter on; `SHOW INDEXES` lists them after the vector property                          |
| `CREATE FULLTEXT INDEX` | An index over one or more string properties | `ON EACH [...]` lists the properties                                                                                                             |
| `IF NOT EXISTS`         | No error when an index of that name exists  | Without it: `semantic error: IndexAlreadyExists`                                                                                                 |
| `DROP INDEX <name>`     | Removes the index                           | The CLI prints nothing on success; a missing name fails with ``semantic error: NoSuchIndex: `<name>` ``, and `IF EXISTS` turns that into a no-op |
| `SHOW INDEXES`          | One row per index                           | Columns include `name`, `type` (`VECTOR`, `FULLTEXT`, `RANGE`), `labelsOrTypes`, `properties`, `state` and `options`                             |

A second vector index on the same label and property with the same `WITH [...]` list, the same properties in the same order, fails with `semantic error: EquivalentSchemaRuleAlreadyExists`, whatever its `OPTIONS`. One with a different `WITH [...]` list, or none where the first has one, is created beside it. A second full-text index on the same label and properties fails with the same error.

## Vector index options

The keys go in `OPTIONS {indexConfig: {…}}`. They contain dots, so each key is quoted in backticks or single quotes; an unquoted key is a parse error. The defaults are the values `SHOW INDEXES` reports for an index created without options.

| Key                                      | Default  | Accepted values                                                                                        |
| ---------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `vector.dimensions`                      | not set  | An integer from 1 to 4096; `0` and `4097` fail with `semantic error: NumberOutOfRange`                 |
| `vector.similarity_function`             | `COSINE` | `'cosine'` or `'euclidean'`, in any case; other values fail with `semantic error: InvalidInputValue`   |
| `vector.hnsw.m`                          | `16`     | An integer; `SHOW INDEXES` reports the value set; stored and reported only, the search does not use it |
| `vector.hnsw.ef_construction`            | `100`    | An integer; `SHOW INDEXES` reports the value set; stored and reported only, the search does not use it |
| `vector.default_search_expansion_factor` | `3.0`    | A number; `SHOW INDEXES` reports the value set                                                         |
| `vector.quantization.type`               | `BINARY` | A string; `SHOW INDEXES` reports the value set                                                         |

An unknown key fails with `semantic error: InvalidInputValue`. A property whose vector length differs from `vector.dimensions` is stored without an error and is never returned by a search. A typed vector, `vector(<list>, <dimensions>, FLOAT32)`, fails the write when the list has another length.

## Rules of the search clause

| Rule                      | Vector search                                                                                                                                                     | Full-text search                                                                             | Error when broken                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LIMIT`                   | Required                                                                                                                                                          | Required                                                                                     | `syntax error: parse error: expected LIMIT, got ")" (RightParen)`                                                                                                                                                                                                                                                                                                                                                                                                               |
| `LIMIT` value             | A non-negative integer; `0` returns no rows                                                                                                                       | Same                                                                                         | `syntax error: NumberOutOfRange: SEARCH LIMIT must be an INTEGER NOT NULL`                                                                                                                                                                                                                                                                                                                                                                                                      |
| Score                     | Only through `SCORE AS <var>`; higher is more similar                                                                                                             | Only through `SCORE AS <var>`; a BM25 relevance score, higher is more relevant               | `syntax error: UndefinedVariable: variable 'score' not defined`                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `WHERE` inside the search | Allowed, on the index's `WITH [...]` properties only, before ranking and `LIMIT`                                                                                  | Not allowed                                                                                  | `WrongPropertyForVectorSearchFilter`; `syntax error: InvalidUseOfCommand: WHERE cannot be used together with 'FULLTEXT INDEX'`                                                                                                                                                                                                                                                                                                                                                  |
| Filter predicates         | Equality, `IN` a list, a half-bounded range or a bounded range per property, joined by `AND`; `IN` cannot be combined with another predicate on the same property | —                                                                                            | `syntax error: SyntaxError: InvalidSearchFilterPredicate: invalid predicate for vector search filtering. …` (for `OR`, `<>`, or `IN` with a second predicate on its property)                                                                                                                                                                                                                                                                                                   |
| `SKIP` or `OFFSET`        | Not allowed                                                                                                                                                       | Allowed                                                                                      | `syntax error: InvalidUseOfCommand: SKIP cannot be used together with VECTOR INDEX`                                                                                                                                                                                                                                                                                                                                                                                             |
| `WITH ANALYZER`           | Not allowed                                                                                                                                                       | Accepted: names the analyzer of the query string ([Full-text matching](#full-text-matching)) | `syntax error: InvalidUseOfCommand: WITH ANALYZER cannot be used together with VECTOR INDEX`                                                                                                                                                                                                                                                                                                                                                                                    |
| Query value               | A list or vector of `vector.dimensions` numbers                                                                                                                   | A string                                                                                     | `type error: VectorIndexDimensionalityMismatch`                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| Search variable           | The variable after `SEARCH`, declared by the `MATCH` pattern; the query cannot refer to it                                                                        | Same                                                                                         | `syntax error: SelfReferencingSearch: the search query cannot reference the binding variable '<var>'`                                                                                                                                                                                                                                                                                                                                                                           |
| Index kind                | A vector index                                                                                                                                                    | A full-text index                                                                            | `WrongIndexType: <name>`                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| Index exists              | Required                                                                                                                                                          | Required                                                                                     | `IndexDoesNotExist: <name>`                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `MATCH` shape             | One path pattern: one node, or one relationship hop, with one variable                                                                                            | Same                                                                                         | Several patterns: `syntax error: SyntaxError: SearchClauseTooComplexPattern: a SEARCH clause's MATCH statement must be a single path pattern`. More than one hop: `syntax error: SyntaxError: SearchClauseTooComplexPattern: a SEARCH clause's MATCH statement must be a single node or single-hop relationship pattern`. More than one variable: `syntax error: SyntaxError: SearchClauseMultipleBoundVariables: a SEARCH clause's MATCH statement may bind only one variable` |
| Searches per `MATCH`      | One                                                                                                                                                               | One                                                                                          | `syntax error: parse error: unexpected token "SEARCH"`                                                                                                                                                                                                                                                                                                                                                                                                                          |

`RELATE_PREVIEW=complex-search-patterns` lifts the `MATCH` shape rule. A `WHERE` after the `SEARCH` clause, or a property map in the `MATCH` pattern, filters only the rows the search returned, so the result can hold fewer than `LIMIT` rows.

## Full-text matching

| Behavior                                | Value                                                                                                                                                                                                                                                                                                                                                |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Terms                                   | Lowercase alphanumeric words; matching ignores case. Accented letters are not folded: `cafe` does not match `café`                                                                                                                                                                                                                                   |
| Stemming and stop words                 | None by default: `graphs` does not match `graph`, and `the` is a term                                                                                                                                                                                                                                                                                |
| Several words in the query              | Each word is a term, and a match on any term returns the entity                                                                                                                                                                                                                                                                                      |
| Operators                               | `AND`, `OR` and `NOT` in capitals, and `&&` and `\|\|`, combine terms; parentheses group them. A lowercase `and` is a term. A query of `NOT` clauses alone matches nothing                                                                                                                                                                           |
| One property                            | `<property>:<term>` matches the term in that indexed property only                                                                                                                                                                                                                                                                                   |
| Phrases, wildcards, fuzzy terms, boosts | Not read: the words of a quoted phrase match as separate terms, `tor*` and `tset~` match nothing, and `+`, `-`, `!` and `^<n>` change no result                                                                                                                                                                                                      |
| Score                                   | BM25, summed over each matching property and term: a term that few entities hold counts for more, and a match in a short text counts for more than one in a long text. Calibrated against Neo4j's full-text scores; not normalized, so it ranks results within one search only                                                                       |
| Order of equal scores                   | No documented order; the examples on this page sort on a second key                                                                                                                                                                                                                                                                                  |
| `WITH ANALYZER <name>`                  | The analyzer of the query string. `english` drops English stop words and stems the query's words, so `graphs` searches for `graph`; every other analyzer name splits the query like the default. `null` keeps the default. An unknown name fails with `semantic error: Invalid input '<name>' for analyzer name. Expected to be arabic, armenian, …` |
| The index's `fulltext.analyzer` option  | Accepted and listed by `SHOW INDEXES`; the indexed text is always split by the default analyzer, so the stored words keep their written form                                                                                                                                                                                                         |

## Procedures

Each procedure is deprecated: `EXPLAIN` reports notification `01N01`, ``The procedure `<name>` is deprecated.``, with the remedy ``Use the `SEARCH` clause instead.``

| Procedure                                                    | Yields                  | Notes                                                   |
| ------------------------------------------------------------ | ----------------------- | ------------------------------------------------------- |
| `db.index.vector.queryNodes(<index>, <k>, <vector>)`         | `node`, `score`         | The top `k` nodes; no filter                            |
| `db.index.vector.queryRelationships(<index>, <k>, <vector>)` | `relationship`, `score` | The top `k` relationships; no filter                    |
| `db.index.fulltext.queryNodes(<index>, <query>)`             | `node`, `score`         | The query syntax and the scores of a full-text `SEARCH` |
| `db.index.fulltext.queryRelationships(<index>, <query>)`     | `relationship`, `score` | The query syntax and the scores of a full-text `SEARCH` |

## Functions

| Function                                | Returns                                                                                | Notes                                                                                                                                                                                                                                                                                                                                                                    |
| --------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `vector(<list>, <dimensions>, <type>)`  | A typed vector, `VECTOR<FLOAT32 NOT NULL>(3) NOT NULL` for three `FLOAT32` coordinates | `<type>` is a bare keyword: `FLOAT32`; `FLOAT` or `FLOAT64`; `INT8` or `INTEGER8`; `INT16` or `INTEGER16`; `INT32` or `INTEGER32`; `INT`, `INTEGER`, `INT64`, `INTEGER64` or `SIGNED INTEGER`; and, behind `RELATE_PREVIEW=float16-coordinate-types`, `FLOAT16` and `BFLOAT16`; a list of another length fails with `type error: vector() dimension does not match list` |
| `vector.similarity.cosine(<a>, <b>)`    | 0 to 1, higher is closer: `1.0` same direction, `0.5` orthogonal, `0.0` opposite       | Lists or vectors of equal length                                                                                                                                                                                                                                                                                                                                         |
| `vector.similarity.euclidean(<a>, <b>)` | Greater than 0, up to 1, higher is closer: `1 / (1 + squared distance)`                | Lists or vectors of equal length                                                                                                                                                                                                                                                                                                                                         |
| `vector_distance(<a>, <b>, <metric>)`   | The raw metric, lower is closer                                                        | Typed vectors only; `<metric>` is `EUCLIDEAN`, `EUCLIDEAN_SQUARED`, `MANHATTAN`, `COSINE`, `DOT` or `HAMMING`; `DOT` is the negated dot product                                                                                                                                                                                                                          |
| `vector_norm(<v>, <metric>)`            | The distance from the origin                                                           | Typed vector; `EUCLIDEAN` or `MANHATTAN`                                                                                                                                                                                                                                                                                                                                 |
| `vector_dimension_count(<v>)`           | The number of coordinates                                                              | Typed vector                                                                                                                                                                                                                                                                                                                                                             |

| Input                                            | Result                                                                |
| ------------------------------------------------ | --------------------------------------------------------------------- |
| `null` for a whole argument                      | `null`                                                                |
| Two lengths                                      | `type error: InvalidArgumentValue: vectors must have same dimensions` |
| A list passed to `vector_distance`               | `type error: InvalidArgumentType: expected a VECTOR`                  |
| An all-zero vector to `vector.similarity.cosine` | `type error: InvalidArgumentValue: not a valid vector`                |

## Hybrid search

Three ways combine a vector search and a full-text search in one statement:

| Form                                                                                                                                              | Result                                                                     | Status                                         |
| ------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------- |
| `MATCH … SEARCH` (vector), `WITH`, then `MATCH … SEARCH` (full-text) on the same variable                                                         | The intersection: the second search keeps only entities the first returned | Stable                                         |
| `CALL () { <vector search> UNION ALL <full-text search> }`, each branch numbering its rows by rank, then `sum(<w> / (<c> + rank + 1))` per entity | One ranking fused by weighted reciprocal rank, written in Cypher           | Stable                                         |
| `SEARCH <var> IN (COMBINE (<source> WEIGHT <w>, <source> WEIGHT <w>, …) USING WRRF [WITH CONSTANT <c>] LIMIT <k>)`                                | One ranking fused by weighted reciprocal rank                              | Preview, behind `RELATE_PREVIEW=hybrid-search` |

`COMBINE` takes two or more sources, each a complete index specification with its own `LIMIT`, and the same entity kind as the variable after `SEARCH`. An entity's fused score is the sum over the sources of `weight / (constant + rank + 1)`, where `rank` counts from 0 within that source and a source that did not return the entity adds nothing. `WEIGHT` defaults to 1 and `WITH CONSTANT` to 60. Equal fused scores order by entity id. Without the opt-in, `COMBINE` fails with `semantic error: SemanticError: preview feature 'hybrid-search' is not enabled: set RELATE_PREVIEW=hybrid-search`.

The `CALL` form computes the same scores without the opt-in: the `weighted-fusion` cell in [Examples](#examples) returns, digit for digit, the scores the `COMBINE` run there prints for the same weights and constant.

## Examples

The shared example graph, with a `focus` vector and a short `bio` on each person:

```cypher run name=seed
CREATE (ada:Person {name: 'Ada', role: 'engineer'}),
       (grace:Person {name: 'Grace', role: 'engineer'}),
       (noor:Person {name: 'Noor', role: 'maintainer'}),
       (relate:Project {name: 'relate', language: 'Rust'}),
       (tck:Project {name: 'opencypher-tck', language: 'Gherkin'}),
       (ada)-[:WORKS_ON {since: 2024}]->(relate),
       (grace)-[:WORKS_ON {since: 2023}]->(relate),
       (grace)-[:WORKS_ON {since: 2022}]->(tck),
       (noor)-[:MAINTAINS]->(relate),
       (ada)-[:KNOWS]->(grace);
MATCH (p:Person)
SET p.focus = CASE p.name
      WHEN 'Ada' THEN vector([0.9, 0.1, 0.2], 3, FLOAT32)
      WHEN 'Grace' THEN vector([0.3, 0.9, 0.1], 3, FLOAT32)
      ELSE vector([0.2, 0.3, 0.9], 3, FLOAT32) END,
    p.bio = CASE p.name
      WHEN 'Ada' THEN 'Writes the storage engine in Rust'
      WHEN 'Grace' THEN 'Keeps the conformance tests green'
      ELSE 'Reviews the docs and the tests' END
```

```table
(no rows) · nodes created: 5, relationships created: 5, properties set: 19, labels added: 5
```

One index of each kind:

```cypher run name=indexes
CREATE VECTOR INDEX person_focus IF NOT EXISTS
  FOR (p:Person) ON (p.focus) WITH [p.role]
  OPTIONS {indexConfig: {`vector.dimensions`: 3, `vector.similarity_function`: 'cosine'}};
CREATE FULLTEXT INDEX person_bio IF NOT EXISTS FOR (p:Person) ON EACH [p.bio]
```

```table
(no rows) · indexes added: 2
```

A vector search, filtered inside the search:

```cypher run name=vector-search
MATCH (p:Person)
SEARCH p IN (VECTOR INDEX person_focus FOR [0.1, 0.9, 0.1] WHERE p.role = 'engineer' LIMIT 2) SCORE AS score
RETURN p.name AS name, round(score, 4) AS score
ORDER BY score DESC
```

```table
| name    | score  |
|---------|--------|
| "Grace" | 0.989  |
| "Ada"   | 0.6184 |
2 rows
```

A full-text search for two words; Noor's bio holds both:

```cypher run name=fulltext-search
MATCH (p:Person)
SEARCH p IN (FULLTEXT INDEX person_bio FOR 'docs tests' LIMIT 3) SCORE AS score
RETURN p.name AS name, score
ORDER BY score DESC, name
```

```table
| name    | score               |
|---------|---------------------|
| "Noor"  | 0.6439728198009622  |
| "Grace" | 0.22443993531397488 |
2 rows
```

The intersection form of hybrid search: the nearest two by vector, kept only if their bio mentions the docs:

```cypher run name=hybrid-intersection
MATCH (p:Person)
SEARCH p IN (VECTOR INDEX person_focus FOR [0.1, 0.9, 0.1] LIMIT 2) SCORE AS vs
WITH p, vs
MATCH (p)
SEARCH p IN (FULLTEXT INDEX person_bio FOR 'docs' LIMIT 3) SCORE AS fs
RETURN p.name AS name, round(vs, 4) AS vector, fs AS fulltext
ORDER BY name
```

```table
| name   | vector | fulltext            |
|--------|--------|---------------------|
| "Noor" | 0.7151 | 0.43535502091904305 |
1 row
```

Weighted reciprocal rank fusion, the `CALL` form. The question, its vector and the number of results per list are parameters:

```json data=question
{"q": "docs tests", "v": [0.1, 0.9, 0.1], "k": 3}
```

Each search on its own returns a ranked list of at most `$k` people, with the name as `id`:

```cypher run name=vector-list params=question
MATCH (p:Person)
SEARCH p IN (VECTOR INDEX person_focus FOR $v LIMIT $k) SCORE AS score
RETURN p.name AS id, score
ORDER BY score DESC, id
```

```table
| id      | score              |
|---------|--------------------|
| "Grace" | 0.9890228509902954 |
| "Noor"  | 0.7151050567626953 |
| "Ada"   | 0.6183618307113647 |
3 rows
```

```cypher run name=fulltext-list params=question
MATCH (p:Person)
SEARCH p IN (FULLTEXT INDEX person_bio FOR $q LIMIT $k) SCORE AS score
RETURN p.name AS id, score
ORDER BY score DESC, id
```

```table
| id      | score               |
|---------|---------------------|
| "Noor"  | 0.6439728198009622  |
| "Grace" | 0.22443993531397488 |
2 rows
```

The fused statement runs both searches, numbers each list's rows from 0, and adds `weight / (60 + rank + 1)` per person, with weight 0.7 for the vector list and 0.3 for the full-text list:

```cypher run name=weighted-fusion params=question
CALL () {
  MATCH (p:Person)
  SEARCH p IN (VECTOR INDEX person_focus FOR $v LIMIT $k) SCORE AS score
  WITH p ORDER BY score DESC, p.name
  WITH collect(p) AS ps
  UNWIND range(0, size(ps) - 1) AS rank
  RETURN ps[rank] AS p, 0.7 AS weight, rank
  UNION ALL
  MATCH (p:Person)
  SEARCH p IN (FULLTEXT INDEX person_bio FOR $q LIMIT $k) SCORE AS score
  WITH p ORDER BY score DESC, p.name
  WITH collect(p) AS ps
  UNWIND range(0, size(ps) - 1) AS rank
  RETURN ps[rank] AS p, 0.3 AS weight, rank
}
WITH p, sum(weight / (60 + rank + 1)) AS rrf
RETURN p.name AS id, rrf
ORDER BY rrf DESC, id
LIMIT $k
```

```table
| id      | rrf                  |
|---------|----------------------|
| "Grace" | 0.01631411951348493  |
| "Noor"  | 0.016208355367530406 |
| "Ada"   | 0.01111111111111111  |
3 rows
```

The view runs the two lists and the fused statement with the chosen `k`, up to the three people the graph holds, and writes out each fused score's arithmetic beside the statement's own:

The 2 ranked lists for "docs tests", as recorded with k = 3:

| Rank | `vector-list` | `fulltext-list` |
| ---- | ------------- | --------------- |
| 1    | `Grace`       | `Noor`          |
| 2    | `Noor`        | `Grace`         |
| 3    | `Ada`         |                 |

Fused by reciprocal rank: each list gives an id its weight / (60 + its rank there), and the shares add up. 3 ids, best first:

| id      | Rank in `vector-list` | Rank in `fulltext-list` | Arithmetic              | Fused score |
| ------- | --------------------- | ----------------------- | ----------------------- | ----------- |
| `Grace` | 1                     | 2                       | 0.7/(60+1) + 0.3/(60+2) | 0.0163      |
| `Noor`  | 2                     | 1                       | 0.7/(60+2) + 0.3/(60+1) | 0.0162      |
| `Ada`   | 3                     |                         | 0.7/(60+3)              | 0.0111      |

Live: a k slider runs the page's search statements on its database in your browser with that k, and the view marks the ids the engine returns.

The full-text list holds two people at most: Ada's bio holds neither `docs` nor `tests`. A person missing from a list gets no share from it, so Ada's score is her vector share alone.

`COMBINE` on the CLI. This setup step writes the same graph, vectors and indexes to a new store, `search.rl`:

```bash verify
relate --database ./search.rl "CREATE (ada:Person {name: 'Ada', role: 'engineer', focus: vector([0.9, 0.1, 0.2], 3, FLOAT32), bio: 'Writes the storage engine in Rust'}),
       (grace:Person {name: 'Grace', role: 'engineer', focus: vector([0.3, 0.9, 0.1], 3, FLOAT32), bio: 'Keeps the conformance tests green'}),
       (noor:Person {name: 'Noor', role: 'maintainer', focus: vector([0.2, 0.3, 0.9], 3, FLOAT32), bio: 'Reviews the docs and the tests'}),
       (relate:Project {name: 'relate', language: 'Rust'}),
       (tck:Project {name: 'opencypher-tck', language: 'Gherkin'}),
       (ada)-[:WORKS_ON {since: 2024}]->(relate),
       (grace)-[:WORKS_ON {since: 2023}]->(relate),
       (grace)-[:WORKS_ON {since: 2022}]->(tck),
       (noor)-[:MAINTAINS]->(relate),
       (ada)-[:KNOWS]->(grace);
CREATE VECTOR INDEX person_focus FOR (p:Person) ON (p.focus) WITH [p.role]
  OPTIONS {indexConfig: {'vector.dimensions': 3, 'vector.similarity_function': 'cosine'}};
CREATE FULLTEXT INDEX person_bio FOR (p:Person) ON EACH [p.bio]"
```

The fused ranking, read-only, with the preview opt-in in the command's environment:

```bash verify
RELATE_PREVIEW=hybrid-search relate --database ./search.rl --read-only --mode table "MATCH (p:Person) SEARCH p IN (COMBINE (VECTOR INDEX person_focus FOR [0.1, 0.9, 0.1] LIMIT 3 WEIGHT 0.7, FULLTEXT INDEX person_bio FOR 'docs tests' LIMIT 3 WEIGHT 0.3) USING WRRF WITH CONSTANT 60 LIMIT 3) SCORE AS score RETURN p.name AS name, score"
```

```text
+---------+----------------------+
| name    | score                |
+---------+----------------------+
| "Grace" | 0.01631411951348493  |
| "Noor"  | 0.016208355367530406 |
| "Ada"   | 0.01111111111111111  |
+---------+----------------------+
```

Grace is first by vector and second by full-text: `0.7 / 61 + 0.3 / 62`. Noor is second by vector and first by full-text: `0.7 / 62 + 0.3 / 61`. Ada appears only in the vector list: `0.7 / 63`. The scores are the `weighted-fusion` cell's, digit for digit.

## See also

- [Search by vector similarity](https://relatedb.ai/docs/guides/cypher/vector-search/): the task, step by step.
- [Hybrid recall](https://relatedb.ai/cookbook/hybrid-recall/): reciprocal rank fusion written in plain Cypher, over 40 notes.
- [Differences from Neo4j](https://relatedb.ai/docs/reference/cypher/differences-from-neo4j/): the procedure forms an agent may reach for, and the clause to write instead.

## Terms used on this page

- **index**: A structure the planner uses to find nodes or relationships by a property without scanning a label: range, text, point, vector or full-text.
- **vector index**: An index over a property holding embeddings that answers nearest-neighbor searches exactly, by cosine or Euclidean similarity.
- **full-text index**: An index over one or more string properties that finds entities containing given words, ranked by a BM25 relevance score.
