--- title: "jaro_similarity" slug: "dev-database-reference-sql-functions-string-jaro-similarity" updated: 2026-10-30T18:18:56Z published: 2026-10-30T18:18:56Z canonical: "docs.vastdata.com/dev-database-reference-sql-functions-string-jaro-similarity" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.vastdata.com/llms.txt > Use this file to discover all available pages before exploring further. # jaro_similarity *Applies to VAST Cluster 5.5.* *[Functions](/develop/docs/dev-database-reference-sql-functions-index) › [Text basics](/develop/docs/dev-database-reference-sql-functions-string-text-basics) › Similarity.* Returns the Jaro similarity of two strings: a `DOUBLE` that is higher the more alike they are, with an optional cutoff below which the score is `0.0`. It has no aliases. ## Syntax ``` jaro_similarity(a, b) jaro_similarity(a, b, cutoff) ``` | Argument | Type | Description | | --- | --- | --- | | `a`, `b` | `VARCHAR` | The two strings to compare. | | `cutoff` | `DOUBLE` | Optional. A score below `cutoff` is returned as `0.0`. An integer or a numeric string such as `'0.5'` is converted to `DOUBLE`. | **Returns:** `DOUBLE`. **Aliases:** none. ## Set up the examples The examples connect with the ADBC driver, as in [VAST Query Engine (ADBC)](/develop/docs/dev-database-integrations-adbc), and work in a schema called `docs_sql_fn_jaro_similarity`. Set `QE_ENDPOINT`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `VASTDB_BUCKET` ([SQL reference](/develop/docs/dev-database-reference-sql-index#example-connect-and-use-a-transaction)), and run the blocks in order in one Python session. Below each block is the output you should see. This block connects, defines the `run()` and `plan()` helpers, and creates a table of four contacts: the name on record and the name as someone typed it. The last row has no values: ``` import os import re import adbc_driver_manager import adbc_driver_manager.dbapi as dbapi import adbc_driver_vastdb BUCKET = os.environ["VASTDB_BUCKET"] SCHEMA = f'"{BUCKET}/docs_sql_fn_jaro_similarity"' conn = dbapi.connect( driver=adbc_driver_vastdb.get_driver_path(), db_kwargs={ "vast.db.endpoint": os.environ["QE_ENDPOINT"], "vast.db.access_key": os.environ["AWS_ACCESS_KEY_ID"], "vast.db.secret_key": os.environ["AWS_SECRET_ACCESS_KEY"], }, autocommit=True, ) def server_message(e): """The server's message in a driver error, without the request ID or the echoed statement.""" text = re.sub(r"\[req=0x[0-9a-f]+\]: ", "", str(e)) found = (re.search(r'message: "(.*?)", request_id', text, re.S) or re.search(r"description: (.*)\)", text, re.S)) message = found.group(1) if found else text.strip().splitlines()[-1] message = re.sub(r"^Plan query: '.*?' failed with: ", "", message, flags=re.S) message = re.split(r"\\n|\n", message)[0].replace('\\"', '"') return message.replace(BUCKET, "") def run(sql): """Run one statement. Print the rows it returns, or the error.""" try: with conn.cursor() as cur: cur.execute(sql) rows = cur.fetchall() if cur.description else [] except (adbc_driver_manager.Error, OSError) as e: print(f"{type(e).__name__}: {server_message(e)}") else: print(rows) def plan(sql): """Print the plan without its ~N Rows lines: the table's row count, which can still be 0 just after an insert.""" with conn.cursor() as cur: cur.execute("EXPLAIN " + sql) steps = cur.fetchall()[0][1] print("\n".join(line for line in steps.splitlines() if "Rows" not in line)) TABLE = f"{SCHEMA}.contacts" run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}") run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, name VARCHAR, typed VARCHAR, city VARCHAR)") run(f"INSERT INTO {TABLE} VALUES " "(1, 'Ada Lovelace', 'Ada Lovelase', 'London'), " "(2, 'Grace Hopper', 'Grace Hoper', 'New York'), " "(3, 'José Martí', 'Jose Marti', 'Havana'), " "(4, NULL, NULL, NULL)") ``` ``` [] [] [(4,)] ``` ## What it does The Jaro similarity measures how many units two strings share, and whether the shared units appear in the same order. It's built for short strings such as names and codes, where a typo or two swapped letters should still score high: 1. **Matches.** A unit of `a` matches an equal unit of `b` if their positions are no further apart than half the length of the longer string, minus one. Each unit matches at most once. Call the number of matches *m*. 2. **Transpositions.** Take the matched units of each string in order. Count the positions where the two sequences differ, and halve that count: that's *t*. 3. **Score.** If *m* is 0, the score is `0.0`. Otherwise it's the average of three ratios: *m* / length of `a`, *m* / length of `b`, and (*m* − *t*) / *m*. The units are bytes of the UTF-8 encoding, not characters: an accented letter such as `é` is two bytes, so it counts as two units. Two different accented letters can share their first byte: `jaro_similarity('é', 'è')` returns `0.6666666666666666`, one match out of two units on each side, not `0.0`. The comparison is case-sensitive: `jaro_similarity('abc', 'abc')` returns `1.0` and `jaro_similarity('abc', 'ABC')` returns `0.0`. Apply `lower()` to both arguments for a case-insensitive score. | Call | m and t | Result | | --- | --- | --- | | `jaro_similarity('Hello', 'Hallo')` | 4 matches (`H`, `l`, `l`, `o`) of 5, no transpositions | `0.8666666666666667` | | `jaro_similarity('martha', 'marhta')` | 6 matches; `t` and `h` are swapped, so *t* = 1 | `0.9444444444444445` | | `jaro_similarity('kitten', 'sitting')` | 4 matches (`i`, `t`, `t`, `n`), no transpositions | `0.746031746031746` | | `jaro_similarity('café', 'Hallo')` | both strings are 5 bytes; 1 match (`a`) | `0.4666666666666666` | | `jaro_similarity('abc', 'xyz')` | no matches | `0.0` | **The cutoff.** With a third argument, a score below the cutoff comes back as `0.0`, so a query can treat "not similar enough" as zero. `jaro_similarity('abc', 'abd')` is `0.7777777777777777`, so with a cutoff of `0.5` it's returned unchanged and with `0.9` it's `0.0`. A negative cutoff such as `-1.0` leaves the score unchanged. A cutoff above `1.0` makes every result `0.0`: `jaro_similarity('abc', 'abc', 2.0)` returns `0.0` even though the strings are equal. **Empty strings score 0.0.** Two empty strings aren't treated as equal: `jaro_similarity('', '')` returns `0.0`, as does an empty string against any other string. ## NULLs and edge cases | Input | Result | | --- | --- | | `NULL` as `a` or `b` | `NULL` | | `NULL` as `cutoff` | `NULL` | | `''` and `''` | `0.0` | | `'abc'` and `''` | `0.0` | | `'abc'` and `'xyz'` (nothing in common) | `0.0` | | `'abc'`, `'abd'`, cutoff `0.9` | `0.0` | | `'abc'` and `'abd'`, no cutoff | `0.7777777777777777` | | `'abc'`, `'abc'`, cutoff `2.0` | `0.0` | | `'abc'` and `'abc'`, no cutoff | `1.0` | | A number, decimal, date, boolean, `BLOB` or list | Error: `No function matches the given name and argument types` | The same cases, as the cluster returns them, with the scores without a cutoff for comparison. They use the table and helpers from [Set up the examples](/develop/docs/dev-database-reference-sql-functions-string-jaro-similarity#set-up-the-examples): ``` run("SELECT jaro_similarity(NULL, 'Hallo'), jaro_similarity('a', NULL), jaro_similarity('a', 'a', NULL)") run("SELECT jaro_similarity('', ''), jaro_similarity('abc', ''), jaro_similarity('abc', 'xyz')") run("SELECT jaro_similarity('abc', 'abd', 0.9), jaro_similarity('abc', 'abd'), " "jaro_similarity('abc', 'abc', 2.0), jaro_similarity('abc', 'abc')") run("SELECT jaro_similarity(123, 'Hallo')") run("SELECT jaro_similarity('\\xAA'::BLOB, 'Hallo')") ``` ``` [(None, None, None)] [(0.0, 0.0, 0.0)] [(0.0, 0.7777777777777777, 0.0, 1.0)] InternalError: Binder Error: No function matches the given name and argument types 'jaro_similarity(INTEGER_LITERAL, STRING_LITERAL)'. You might need to add explicit type casts. InternalError: Binder Error: No function matches the given name and argument types 'jaro_similarity(BLOB, STRING_LITERAL)'. You might need to add explicit type casts. ``` ## Examples Score each name as typed against the name on record. The row with no values scores `NULL`: ``` run(f"SELECT id, name, typed, round(jaro_similarity(name, typed), 3) FROM {TABLE} ORDER BY id LIMIT 10") ``` ``` [(1, 'Ada Lovelace', 'Ada Lovelase', 0.944), (2, 'Grace Hopper', 'Grace Hoper', 0.972), (3, 'José Martí', 'Jose Marti', 0.822), (4, None, None, None)] ``` Find the closest names to a misspelt search term, best first: ``` run(f"SELECT name, round(jaro_similarity(name, 'Grace Hoper'), 3) AS score FROM {TABLE} " "WHERE name IS NOT NULL ORDER BY score DESC LIMIT 3") ``` ``` [('Grace Hopper', 0.972), ('Ada Lovelace', 0.557), ('José Martí', 0.396)] ``` Keep only the rows where the typed name is close to the name on record: ``` run(f"SELECT id, name, typed FROM {TABLE} WHERE jaro_similarity(name, typed) > 0.9 ORDER BY id LIMIT 10") ``` ``` [(1, 'Ada Lovelace', 'Ada Lovelase'), (2, 'Grace Hopper', 'Grace Hoper')] ``` ## How it runs in VAST The Query Engine runs on the cluster's CNodes, and a query on a table starts with a scan in VAST DataBase (`VAST_SCAN`). VAST DataBase can evaluate a comparison of a column with a constant, but not a function of a column, so `jaro_similarity()` is always computed by the Query Engine: - **In the `SELECT` list,** the scan reads the columns and the Query Engine computes `jaro_similarity()` for each row it returns. - **In `WHERE`,** the scan sends the columns for every row, and a `FILTER` step in the Query Engine computes `jaro_similarity()` and keeps the rows that match. No rows are skipped in storage. The plan of the `WHERE` example shows the `FILTER` step above the scan: ``` plan(f"SELECT id FROM {TABLE} WHERE jaro_similarity(name, typed) > 0.9") ``` ``` ┌───────────────────────────┐ │ PROJECTION │ │ ──────────────────── │ │ #2 │ │ │ └─────────────┬─────────────┘ ┌─────────────┴─────────────┐ │ FILTER │ │ ──────────────────── │ │ (jaro_similarity(name, │ │ typed) > 0.9) │ │ │ └─────────────┬─────────────┘ ┌─────────────┴─────────────┐ │ VAST_SCAN │ │ ──────────────────── │ │ Function: VAST_SCAN │ │ │ │ Projections: │ │ name │ │ typed │ │ id │ │ │ └───────────────────────────┘ ``` ## Make it faster - **Narrow the rows with a pushed-down condition first.** A comparison of a raw column with a constant is evaluated in VAST DataBase, so only the rows that pass it reach the `FILTER` step. In `city = 'London' AND jaro_similarity(name, 'Ada Lovlace') > 0.9`, `city = 'London'` is pushed down and `jaro_similarity()` runs only on the London rows. The plan of the raw comparison on its own has no `FILTER` step: ``` plan(f"SELECT id FROM {TABLE} WHERE city = 'London'") ``` ``` ┌───────────────────────────┐ │ VAST_SCAN │ │ ──────────────────── │ │ Function: VAST_SCAN │ │ Projections: id │ │ │ └───────────────────────────┘ ``` - **Store the score if you filter on it often.** If you compare against the same reference value every time, compute the score when you write the rows and keep it in its own column: `name_score > 0.9` is pushed down, `jaro_similarity(name, 'Grace Hopper') > 0.9` isn't. ## Limitations and errors - **Strings only.** A number, a decimal, a date, a boolean, a `BLOB` or a list fails with `No function matches the given name and argument types`. Cast it first: `jaro_similarity(CAST(n AS VARCHAR), '123')`. - **Bytes, not characters.** Accented and other non-ASCII letters take two or more bytes each, and each byte is a separate unit in the score. See [What it does](/develop/docs/dev-database-reference-sql-functions-string-jaro-similarity#what-it-does). - **Case-sensitive.** `'abc'` and `'ABC'` score `0.0`. Compare `lower(a)` with `lower(b)` if case shouldn't count. - **Two empty strings score `0.0`, not `1.0`.** Test for empty strings separately if your logic treats them as equal. ``` run("SELECT jaro_similarity(DATE '2026-10-16', 'Hallo')") run("SELECT jaro_similarity(['a', 'b'], 'Hallo')") ``` ``` InternalError: Binder Error: No function matches the given name and argument types 'jaro_similarity(DATE, STRING_LITERAL)'. You might need to add explicit type casts. InternalError: Binder Error: No function matches the given name and argument types 'jaro_similarity(VARCHAR[], STRING_LITERAL)'. You might need to add explicit type casts. ``` ## Similar functions | Function | Measures | Higher means | Result for `'kitten'` and `'sitting'` | | --- | --- | --- | --- | | `jaro_similarity` | matches within a window and their order | more alike | `0.746031746031746` | | [jaro_winkler_similarity](/develop/docs/dev-database-reference-sql-functions-string-jaro-winkler-similarity) | Jaro, plus extra weight for a common prefix | more alike | `0.746031746031746` (no common prefix) | | [levenshtein](/develop/docs/dev-database-reference-sql-functions-string-levenshtein) | insertions, deletions and substitutions needed | less alike | `3` | | [damerau_levenshtein](/develop/docs/dev-database-reference-sql-functions-string-damerau-levenshtein) | as `levenshtein`, and a swap of two adjacent units counts as one edit | less alike | `3` | | [jaccard](/develop/docs/dev-database-reference-sql-functions-string-jaccard) | the overlap of the two strings' sets of bytes | more alike | `0.42857142857142855` | | [hamming](/develop/docs/dev-database-reference-sql-functions-string-hamming) | positions that differ; strings of equal length only | less alike | `2` for `'kitten'` and `'sittin'` | ``` run("SELECT levenshtein('kitten', 'sitting'), damerau_levenshtein('kitten', 'sitting'), hamming('kitten', 'sittin'), " "jaccard('kitten', 'sitting'), jaro_similarity('kitten', 'sitting'), jaro_winkler_similarity('kitten', 'sitting')") run("SELECT jaro_similarity('martha', 'marhta'), jaro_winkler_similarity('martha', 'marhta')") ``` ``` [(3, 3, 2, 0.42857142857142855, 0.746031746031746, 0.746031746031746)] [(0.9444444444444445, 0.9611111111111111)] ``` Clean up: ``` run(f"DROP TABLE IF EXISTS {TABLE}") run(f"DROP SCHEMA IF EXISTS {SCHEMA}") conn.close() ``` ``` [] [] ``` ## See also - [Text basics](/develop/docs/dev-database-reference-sql-functions-string-text-basics): all the text functions. - [jaro_winkler_similarity](/develop/docs/dev-database-reference-sql-functions-string-jaro-winkler-similarity): the same score with extra weight for a common prefix. - [Where filters run](/develop/docs/dev-database-reference-sql-select#where-filters-run) and [pushdowns](/develop/docs/dev-database-reference-pushdowns). - [EXPLAIN](/develop/docs/dev-database-reference-sql-explain): how to read a plan.