DuckDB Extension
RapidFuzz
Compare strings and rank candidate matches in DuckDB SQL using RapidFuzz. Find similar names, match reordered words, or calculate edit distances.
On this page
Find candidate matches when text differs
Exact equality misses names with typing errors, changed word order, or small spelling differences. RapidFuzz adds similarity scores and edit distances to DuckDB so you can compare values and rank possible matches in SQL.
Use it for record linkage, reviewing possible duplicates, or matching input against a known list. A high score is evidence for a candidate, not proof of identity. For large tables, narrow the candidate pairs with a rule such as a shared postcode before scoring them.
Example
Find the closest name for each record
Compare two input names with three candidates. Keep the highest-scoring candidate with a score of at least 80 for each input, using the candidate id to break ties.
Tested with DuckDB 1.5.6. Browser examples use Haybarn-Wasm 1.5.6.
INSTALL rapidfuzz FROM community;
LOAD rapidfuzz;
WITH names(id, name) AS (
VALUES (1, 'Jon Smith'), (2, 'Jane Doe')
), candidates(id, name) AS (
VALUES (10, 'John Smith'), (11, 'Jane Doe'), (12, 'Jon Smyth')
)
SELECT a.name AS input_name, b.name AS matched_name,
round(rapidfuzz_ratio(a.name, b.name), 1) AS score
FROM names a CROSS JOIN candidates b
WHERE rapidfuzz_ratio(a.name, b.name) >= 80
QUALIFY row_number() OVER (
PARTITION BY a.id
ORDER BY rapidfuzz_ratio(a.name, b.name) DESC, b.id
) = 1
ORDER BY a.id;
SET allow_extension_repositories = 'allowed';
CREATE EXTENSION REPOSITORY IF NOT EXISTS query_farm
WITH PREFIX 'https://duckdb-extensions.query.farm';
INSTALL rapidfuzz FROM query_farm;
LOAD rapidfuzz FROM query_farm;
WITH names(id, name) AS (
VALUES (1, 'Jon Smith'), (2, 'Jane Doe')
), candidates(id, name) AS (
VALUES (10, 'John Smith'), (11, 'Jane Doe'), (12, 'Jon Smyth')
)
SELECT a.name AS input_name, b.name AS matched_name,
round(rapidfuzz_ratio(a.name, b.name), 1) AS score
FROM names a CROSS JOIN candidates b
WHERE rapidfuzz_ratio(a.name, b.name) >= 80
QUALIFY row_number() OVER (
PARTITION BY a.id
ORDER BY rapidfuzz_ratio(a.name, b.name) DESC, b.id
) = 1
ORDER BY a.id;
Expected result
| input_name | matched_name | score |
|---|---|---|
| Jon Smith | John Smith | 94.7 |
| Jane Doe | Jane Doe | 100 |
A similarity score identifies a candidate, not a confirmed duplicate. This tiny example compares every pair. For larger tables, reduce the candidate pairs before scoring. Inputs with no score of at least 80 are omitted.
Common tasks
- Choose a similarity function Compare ratio, token, and edit-distance functions.
- Reduce the number of comparisons Use blocking rules before joining large datasets.
- Normalize names before matching Apply consistent casing and punctuation rules.
- Find possible duplicate records Compare records within a table and review the candidate pairs.
Deep Dive
Technical Details
Comparing records
A similarity score helps identify records to review. It does not establish that two records refer to the same person or company. Choose a threshold using examples from your own data, and check both false matches and missed matches.
Architecture
This extension binds the RapidFuzz C++ library β the same engine behind Pythonβs rapidfuzz package and the spiritual successor to fuzzywuzzy β as a set of DuckDB scalar functions. Each function takes (VARCHAR, VARCHAR) and returns a DOUBLE. There is no extra state, no secrets, no ATTACH interface; calls are pure functions composable into any SELECT, WHERE, ON, or ORDER BY clause.
Two function families coexist:
- Ratio scores (
rapidfuzz_ratioand friends) return 0β100, mirroring the Pythonrapidfuzz.fuzzfamily. - Distance algorithms (Jaro, Jaro-Winkler, Hamming, Indel, Levenshtein / OSA, LCS, Prefix, Postfix) ship four variants each β
_distance,_similarity,_normalized_distance,_normalized_similarity. The naming is mechanical:rapidfuzz_<algo>[_normalized]_<distance|similarity>.
Behavior matches the upstream library closely enough that the RapidFuzz documentation is the authoritative reference for algorithm semantics.
Pick the right algorithm
| Situation | Function |
|---|---|
| One score, 0β100, sane default | rapidfuzz_ratio |
| One string is a fragment of the other | rapidfuzz_partial_ratio |
| Same words, different order | rapidfuzz_token_sort_ratio |
| Duplicated or stray words | rapidfuzz_token_set_ratio |
| Human names, typos at the end | rapidfuzz_jaro_winkler_normalized_similarity |
| Single-character typos / adjacent swaps | rapidfuzz_osa_distance |
| Equal-length identifiers (SKUs, hashes) | rapidfuzz_hamming_distance |
For cross-pair comparisons where strings differ in length, prefer the _normalized_* variants β raw distance penalizes longer strings just for being long. See the Wikipedia article on JaroβWinkler distance for the formal definitions of the name-matching variants.
Blocking strategies
A cross join of two tables with 100,000 rows produces 10 billion candidate pairs. Restrict those pairs before scoring. Each restriction trades fewer comparisons for a risk of missing a match:
Length bucket. Two strings that differ in length by more than k characters canβt have a Levenshtein distance β€ k. Filter on length(a) BETWEEN length(b) - k AND length(b) + k.
First-letter (or first-N) prefix. Cheap join key. Misses pairs where the first letter is itself the typo (rare for names, common for OCRβd text).
Length + first-letter combined. Combine both rules when they fit your data. Measure the remaining candidate count before running the full join.
Phonetic prefix (soundex / metaphone). When typos are phonetic (βSmythβ vs βSmithβ). DuckDBβs built-in fuzzy extension supplies the codes; group by code and only score within a group.
Token-based blocking. For multi-word strings, require at least one token in common (e.g. via a normalized array intersection). Excellent for company names where the legal suffix differs but the brand word is shared.
Put the candidate restrictions in the join condition and inspect the query plan when performance matters.
Pre-normalize once, score many
Hoist deterministic transformations out of the join so they run O(n) instead of O(nΒ²):
WITH normalized AS ( SELECT id, regexp_replace(lower(name), '[^a-z0-9 ]', '', 'g') AS name_n, length(name) AS len, left(lower(name), 1) AS block_letter FROM customers)SELECT a.id, b.id, rapidfuzz_token_set_ratio(a.name_n, b.name_n) AS scoreFROM normalized aJOIN normalized b ON a.id < b.id AND a.block_letter = b.block_letter AND a.len BETWEEN b.len - 3 AND b.len + 3WHERE rapidfuzz_token_set_ratio(a.name_n, b.name_n) > 90;When you also need consistent casing, pluralization, or struct-key rewrites, the sibling inflector extension covers the cleanup side of the same workflow β both sit in the matching-and-casing string-munging bucket.
Top-1 per left row
Record-linkage usually wants exactly one best match per left record. Use QUALIFY (DuckDBβs window-filter clause):
SELECT a.id AS left_id, b.id AS right_id, rapidfuzz_jaro_winkler_normalized_similarity(a.name, b.name) AS scoreFROM left_records aJOIN right_records b ON left(a.name, 1) = left(b.name, 1) -- blockingWHERE rapidfuzz_jaro_winkler_normalized_similarity(a.name, b.name) > 0.92QUALIFY ROW_NUMBER() OVER ( PARTITION BY a.id ORDER BY rapidfuzz_jaro_winkler_normalized_similarity(a.name, b.name) DESC, b.id) = 1;When to reach for something else
- Trigram indexes / FTS. For a single query string against millions of rows, a trigram or full-text index returns top-N candidates in sub-linear time. Score the candidates with RapidFuzz, but donβt expect this extension to replace the index.
- Embeddings / vector search. If βsimilarityβ really means semantic similarity (paraphrase, translation), edit distance is the wrong tool. Reach for an embedding model and a vector index instead.
- Production fuzzy joins on >10M rows. Even with aggressive blocking, very large self-joins benefit from purpose-built record-linkage tooling (Splink, Dedupe). RapidFuzz is the right primitive for the inner-loop score; a record-linkage framework manages the candidate generation, blocking, and resolution around it.
Reference
Extension Contents
Quick reference to all available functions and settings organized by category.
| Name | Type | Description |
|---|---|---|
|
Hamming
Position-by-position character match count. Meant for strings of the same length; a shorter input is padded, so each extra character in the longer one counts as a difference. |
||
| rapidfuzz_hamming_distance() | Object type: Scalar function | Number of differing positions between two equal-length strings. |
| rapidfuzz_hamming_normalized_distance() | Object type: Scalar function | Number of differing positions between two equal-length strings. |
| rapidfuzz_hamming_normalized_similarity() | Object type: Scalar function | Number of differing positions between two equal-length strings. |
| rapidfuzz_hamming_similarity() | Object type: Scalar function | Number of matching positions between two strings of equal length. |
|
Indel
Insertion-and-deletion edit distance β no substitutions counted. |
||
| rapidfuzz_indel_distance() | Object type: Scalar function | Insertions+deletions to transform one string into another (no substitutions). |
| rapidfuzz_indel_normalized_distance() | Object type: Scalar function | Insertions+deletions to transform one string into another (no substitutions). |
| rapidfuzz_indel_normalized_similarity() | Object type: Scalar function | Insertions+deletions to transform one string into another (no substitutions). |
| rapidfuzz_indel_similarity() | Object type: Scalar function | Insertions+deletions to transform one string into another (no substitutions). |
|
Jaro
Jaro similarity β match-and-transposition based. Good for short strings and typos. |
||
| rapidfuzz_jaro_distance() | Object type: Scalar function | Jaro similarity β character matches and transpositions. |
| rapidfuzz_jaro_normalized_distance() | Object type: Scalar function | Jaro similarity β character matches and transpositions. |
| rapidfuzz_jaro_normalized_similarity() | Object type: Scalar function | Jaro similarity β character matches and transpositions. |
| rapidfuzz_jaro_similarity() | Object type: Scalar function | Jaro similarity β character matches and transpositions. |
|
Jaro Winkler
Jaro-Winkler β Jaro with extra weight on a shared prefix. Strong default for human names. |
||
| rapidfuzz_jaro_winkler_distance() | Object type: Scalar function | Jaro-Winkler β Jaro with extra weight on common prefixes. |
| rapidfuzz_jaro_winkler_normalized_distance() | Object type: Scalar function | Jaro-Winkler β Jaro with extra weight on common prefixes. |
| rapidfuzz_jaro_winkler_normalized_similarity() | Object type: Scalar function | Jaro-Winkler β Jaro with extra weight on common prefixes. |
| rapidfuzz_jaro_winkler_similarity() | Object type: Scalar function | Jaro-Winkler β Jaro with extra weight on common prefixes. |
|
Lcs Seq
Longest common subsequence β order-preserving but not adjacency-preserving. |
||
| rapidfuzz_lcs_seq_distance() | Object type: Scalar function | Longest common subsequence β preserves character order but not adjacency. |
| rapidfuzz_lcs_seq_normalized_distance() | Object type: Scalar function | Longest common subsequence β preserves character order but not adjacency. |
| rapidfuzz_lcs_seq_normalized_similarity() | Object type: Scalar function | Longest common subsequence β preserves character order but not adjacency. |
| rapidfuzz_lcs_seq_similarity() | Object type: Scalar function | Longest common subsequence β preserves character order but not adjacency. |
|
Osa
Optimal String Alignment (a.k.a. restricted Damerau-Levenshtein) β counts adjacent transpositions as a single edit. |
||
| rapidfuzz_osa_distance() | Object type: Scalar function | Optimal string alignment β Levenshtein with adjacent transpositions counted as one edit. |
| rapidfuzz_osa_normalized_distance() | Object type: Scalar function | Optimal string alignment β Levenshtein with adjacent transpositions counted as one edit. |
| rapidfuzz_osa_normalized_similarity() | Object type: Scalar function | Optimal string alignment β Levenshtein with adjacent transpositions counted as one edit. |
| rapidfuzz_osa_similarity() | Object type: Scalar function | Optimal string alignment β Levenshtein with adjacent transpositions counted as one edit. |
|
Postfix
Edit distance / similarity considering only the matching suffix portion. |
||
| rapidfuzz_postfix_distance() | Object type: Scalar function | Edit distance considering only matching suffixes. |
| rapidfuzz_postfix_normalized_distance() | Object type: Scalar function | Edit distance considering only matching suffixes. |
| rapidfuzz_postfix_normalized_similarity() | Object type: Scalar function | Edit distance considering only matching suffixes. |
| rapidfuzz_postfix_similarity() | Object type: Scalar function | Edit distance considering only matching suffixes. |
|
Prefix
Edit distance / similarity considering only the matching prefix portion of the strings. |
||
| rapidfuzz_prefix_distance() | Object type: Scalar function | Edit distance considering only matching prefixes. |
| rapidfuzz_prefix_normalized_distance() | Object type: Scalar function | Edit distance considering only matching prefixes. |
| rapidfuzz_prefix_normalized_similarity() | Object type: Scalar function | Edit distance considering only matching prefixes. |
| rapidfuzz_prefix_similarity() | Object type: Scalar function | Edit distance considering only matching prefixes. |
|
Ratio Scores
0β100 similarity scores matching the Python |
||
| rapidfuzz_partial_ratio() | Object type: Scalar function | Best similarity score for any substring of the longer input against the shorter β useful when one string is a fragment of the other. |
| rapidfuzz_partial_token_set_ratio() | Object type: Scalar function | Token-set comparison with a partial-ratio fallback β best when one string is a subset/partial match of the other. |
| rapidfuzz_ratio() | Object type: Scalar function | Levenshtein-based similarity (0β100). |
| rapidfuzz_token_set_ratio() | Object type: Scalar function | Compare token sets (deduped, unordered). |
| rapidfuzz_token_sort_ratio() | Object type: Scalar function | Compare after sorting tokens (words). |
No extension contents match that search.
API Reference
Function Reference
Practical Examples
Cookbook
Real-world recipes and patterns for common use cases.
Quick reference
| Need | Pick |
|---|---|
| One score, 0β100, sane default | rapidfuzz_ratio |
| Match a fragment inside a longer string | rapidfuzz_partial_ratio |
| Words in different orders | rapidfuzz_token_sort_ratio |
| Tolerate duplicated words | rapidfuzz_token_set_ratio |
| Names / typos | rapidfuzz_jaro_winkler_normalized_similarity |
| Spelling distance with transpositions | rapidfuzz_osa_distance |
| Strings of identical length | rapidfuzz_hamming_distance |
Record linkage
Find the best match for each row in left against right, scored by Jaro-Winkler:
SELECT a.id AS left_id, b.id AS right_id, rapidfuzz_jaro_winkler_normalized_similarity(a.name, b.name) AS simFROM left_records a, right_records bWHERE rapidfuzz_jaro_winkler_normalized_similarity(a.name, b.name) > 0.92ORDER BY a.id, sim DESC;For the single best match per left row, wrap with QUALIFY:
SELECT *FROM ( SELECT a.id AS left_id, b.id AS right_id, rapidfuzz_jaro_winkler_normalized_similarity(a.name, b.name) AS sim, ROW_NUMBER() OVER (PARTITION BY a.id ORDER BY rapidfuzz_jaro_winkler_normalized_similarity(a.name, b.name) DESC) AS rn FROM left_records a, right_records b)WHERE rn = 1 AND sim > 0.92;Dedupe
Cluster rows by similarity threshold:
WITH pairs AS ( SELECT a.id AS l, b.id AS r, rapidfuzz_token_set_ratio(a.title, b.title) AS sim FROM titles a, titles b WHERE a.id < b.id AND rapidfuzz_token_set_ratio(a.title, b.title) > 90)SELECT * FROM pairs ORDER BY sim DESC;Use the pair list to assign cluster IDs (graph-walk in Python or with a recursive CTE).
Spell check / typo tolerance
For a known-good dictionary, score a candidate against every entry and pick the closest:
SELECT wordFROM dictionaryWHERE rapidfuzz_osa_distance(word, :input) <= 2ORDER BY rapidfuzz_osa_distance(word, :input)LIMIT 5;OSA (Optimal String Alignment) handles single-character typos and adjacent transpositions naturally.
Ratio choice β visual guide
input1: 'New York City'input2: 'new york new york city'
rapidfuzz_ratio β 56 (different overall)rapidfuzz_partial_ratio β 100 (one is a substring)rapidfuzz_token_sort_ratio β 78 (sorted tokens differ in count)rapidfuzz_token_set_ratio β 100 (token sets identical)rapidfuzz_partial_token_set_ratio β 100 (sets identical, partial fallback)The differences matter β pick the one that matches your data semantics. For names with reordered words, token_sort_ratio is usually right. For names with stray duplicates, token_set_ratio. For substring matching, partial_ratio.
Algorithm function naming
Each of the 8 algorithms exposes 4 variants:
rapidfuzz_<algo>_distance(a, b)β raw edit count.rapidfuzz_<algo>_similarity(a, b)β raw match count.rapidfuzz_<algo>_normalized_distance(a, b)β[0, 1], 0 = identical.rapidfuzz_<algo>_normalized_similarity(a, b)β[0, 1], 1 = identical.
For comparisons across pairs of unequal length, the normalized_* variants are usually what you want β they donβt punish longer strings.
Platform Support
Compatibility
Extension availability may vary by platform and DuckDB version. Check below to ensure this extension supports your environment before installation.
Quick Facts
Platforms
DuckDB 2.0.0
- Linux x86_64 aarch64
- Linux (musl) Not available
- macOS Apple Silicon
- Windows x86_64 x86_64 (MinGW)
- WASM Not available
Compiled binary sizes
| Platform | Architecture | Size |
|---|---|---|
| Linux | x86_64 | 4.05 MB |
| Linux | aarch64 | 3.39 MB |
| macOS | Apple Silicon | 2.86 MB |
| Windows | x86_64 | 10.25 MB |
| Windows | x86_64 (MinGW) | 17.89 MB |
Compressed download sizes for the release shown above.
DuckDB & Haybarn
Release calendar- DuckDB v2.0.0 Query.Farm repository Supported
- Haybarn v1.5.6 Built-in community repository Supported
Browser examples use Haybarn-Wasm 1.5.6.
Checked .