Документация spellfix1
фактически говорит вам, как это сделать.Из раздела Обзор :
Если вы намереваетесь использовать эту виртуальную таблицу вместе с таблицей FTS4 (для исправления орфографии поисковых терминов), то выможет извлечь словарь, используя таблицу fts4aux :
INSERT INTO demo(word) SELECT term FROM search_aux WHERE col='*';
Оператор SELECT term from search_aux WHERE col='*'
извлекает все индексированные токены .
Соединяя это с вашими примерами, где mytable2
- это ваша виртуальная таблица fts4, вы можете создать таблицу fts4aux
и вставить эти токены в таблицу mytable3
spellfix1 с помощью:
CREATE VIRTUAL TABLE mytable2_terms USING fts4aux(mytable2);
INSERT INTO mytable3(word) SELECT term FROM mytable2_terms WHERE col='*';
Возможно, вы захотитедалее уточните этот запрос, чтобы пропустить любые термины, уже вставленные в spellfix1, в противном случае вы получите двойные записи:
INSERT INTO mytable3(word)
SELECT term FROM mytable2_terms
WHERE col='*' AND
term not in (SELECT word from mytable3_vocab);
Теперь вы можете использовать mytable3
, чтобы отобразить слова с ошибками в исправленные токены, а затем использовать эти исправленные токены.в запросе MATCH
againsts mytable2
.
В зависимости от ваших потребностей это может означать, что вам нужно выполнить собственную обработку токена и построение запроса;нет синтаксического синтаксического анализатора запроса fts4.Таким образом, ваша строка поиска с двумя токенами должна быть разделена, каждый токен проходит через таблицу spellfix1
для сопоставления с существующими токенами, а затем эти токены передаются в запрос fts4.
Игнорирование синтаксиса SQL для обработки этогоИспользование Python для разделения достаточно просто:
def spellcheck_terms(conn, terms):
cursor = conn.cursor()
base_spellfix = """
SELECT :term{0} as term, word FROM spellfix1data
WHERE word MATCH :term{0} and top=1
"""
terms = terms.split()
params = {"term{}".format(i): t for i, t in enumerate(terms, 1)}
query = " UNION ".join([
base_spellfix.format(i + 1) for i in range(len(params))])
cursor.execute(query, params)
correction_map = dict(cursor)
return " ".join([correction_map.get(t, t) for t in terms])
def spellchecked_search(conn, terms):
corrected_terms = spellcheck_terms(conn, terms)
cursor = conn.cursor()
fts_query = 'SELECT * FROM mytable2 WHERE mytable2 MATCH ?'
cursor.execute(fts_query, (corrected_terms,))
return cursor.fetchall()
Затем возвращается [('All the Carmichael numbers',)]
для spellchecked_search(db, "NUMMBER carmickaeel")
.
Сохранение обработки проверки орфографии в Python позволяет затем поддерживать более сложныеЗапросы FTS по мере необходимости;вам, возможно, придется переопределить синтаксический анализатор выражений , чтобы сделать это, но, по крайней мере, Python предоставляет вам инструменты для этого.
Полный пример, упаковывающий вышеупомянутый подход в классе,которые просто извлекают термины в виде буквенно-цифровых символьных последовательностей (чего достаточно, читая спецификации синтаксиса выражений):
import re
import sqlite3
import sys
class FTS4SpellfixSearch(object):
def __init__(self, conn, spellfix1_path):
self.conn = conn
self.conn.enable_load_extension(True)
self.conn.load_extension(spellfix1_path)
def create_schema(self):
self.conn.executescript(
"""
CREATE VIRTUAL TABLE IF NOT EXISTS fts4data
USING fts4(description text);
CREATE VIRTUAL TABLE IF NOT EXISTS fts4data_terms
USING fts4aux(fts4data);
CREATE VIRTUAL TABLE IF NOT EXISTS spellfix1data
USING spellfix1;
"""
)
def index_text(self, *text):
cursor = self.conn.cursor()
with self.conn:
params = ((t,) for t in text)
cursor.executemany("INSERT INTO fts4data VALUES (?)", params)
cursor.execute(
"""
INSERT INTO spellfix1data(word)
SELECT term FROM fts4data_terms
WHERE col='*' AND
term not in (SELECT word from spellfix1data_vocab)
"""
)
# fts3 / 4 search expression tokenizer
# no attempt is made to validate the expression, only
# to identify valid search terms and extract them.
# the fts3/4 tokenizer considers any alphanumeric ASCII character
# and character in the range U+0080 and over to be terms.
if sys.maxunicode == 0xFFFF:
# UCS2 build, keep it simple, match any UTF-16 codepoint 0080 and over
_fts4_expr_terms = re.compile(u"[a-zA-Z0-9\u0080-\uffff]+")
else:
# UCS4
_fts4_expr_terms = re.compile(u"[a-zA-Z0-9\u0080-\U0010FFFF]+")
def _terms_from_query(self, search_query):
"""Extract search terms from a fts3/4 query
Returns a list of terms and a template such that
template.format(*terms) reconstructs the original query.
terms using partial* syntax are ignored, as you can't distinguish
between a misspelled prefix search that happens to match existing
tokens and a valid spelling that happens to have 'near' tokens in
the spellfix1 database that would not otherwise be matched by fts4
"""
template, terms, lastpos = [], [], 0
for match in self._fts4_expr_terms.finditer(search_query):
token, (start, end) = match.group(), match.span()
# skip columnname: and partial* terms by checking next character
ismeta = search_query[end:end + 1] in {":", "*"}
# skip digits if preceded by "NEAR/"
ismeta = ismeta or (
token.isdigit() and template and template[-1] == "NEAR"
and "/" in search_query[lastpos:start])
if token not in {"AND", "OR", "NOT", "NEAR"} and not ismeta:
# full search term, not a keyword, column name or partial*
terms.append(token)
token = "{}"
template += search_query[lastpos:start], token
lastpos = end
template.append(search_query[lastpos:])
return terms, "".join(template)
def spellcheck_terms(self, search_query):
cursor = self.conn.cursor()
base_spellfix = """
SELECT :term{0} as term, word FROM spellfix1data
WHERE word MATCH :term{0} and top=1
"""
terms, template = self._terms_from_query(search_query)
params = {"term{}".format(i): t for i, t in enumerate(terms, 1)}
query = " UNION ".join(
[base_spellfix.format(i + 1) for i in range(len(params))]
)
cursor.execute(query, params)
correction_map = dict(cursor)
return template.format(*(correction_map.get(t, t) for t in terms))
def search(self, search_query):
corrected_query = self.spellcheck_terms(search_query)
cursor = self.conn.cursor()
fts_query = "SELECT * FROM fts4data WHERE fts4data MATCH ?"
cursor.execute(fts_query, (corrected_query,))
return {
"terms": search_query,
"corrected": corrected_query,
"results": cursor.fetchall(),
}
и интерактивную демонстрацию с использованием класса:
>>> db = sqlite3.connect(":memory:")
>>> fts = FTS4SpellfixSearch(db, './spellfix')
>>> fts.create_schema()
>>> fts.index_text("All the Carmichael numbers") # your example
>>> from pprint import pprint
>>> pprint(fts.search('NUMMBER carmickaeel'))
{'corrected': 'numbers carmichael',
'results': [('All the Carmichael numbers',)],
'terms': 'NUMMBER carmickaeel'}
>>> fts.index_text(
... "They are great",
... "Here some other numbers",
... )
>>> pprint(fts.search('here some')) # edgecase, multiple spellfix matches
{'corrected': 'here some',
'results': [('Here some other numbers',)],
'terms': 'here some'}
>>> pprint(fts.search('NUMMBER NOT carmickaeel')) # using fts4 query syntax
{'corrected': 'numbers NOT carmichael',
'results': [('Here some other numbers',)],
'terms': 'NUMMBER NOT carmickaeel'}