Интернет, компьютеры, софт и прочий Hi-Tech | |||||
Избранные докиМетки (все метки)hi tech, internet, it, интернет, информационные технологии, ит, по, программное обеспечение, сайт, софт
Подписаться через RSS2Email.ru
Дополнительнооптимизация, сайт, мониторы, движок, Битрикс, хостинг, сайты, мобильные, электроника, видеокарта |
SQLite: расширения FTS3 и FTS4
Содержание
Приложение A: Советы по разработке приложений поискаИзначально FTS был разработан для поддержки логического полнотекстового поиска — запросы должны находить набор документов, которые удовлетворяют заданным критериям. Однако многие (большинство?) приложений поиска требуют ранжирования этих результатов в порядке "релевантности", которая понимается как вероятность того, что пользователь заинтересуется конкретным документом из найденного набора документов. Когда пользователь использует поисковую систему для поиска во всемирной сети, он ожидает, что самые полезные (то-есть "релевантные") документы будут выданы ему на первой странице результатов, и что каждая последующая страница содержит все менее релевантные результаты. Научить машину определять релевантность документов на основе пользовательского запроса — это сложная проблема, которой в настоящее время посвящены многочисленные исследования. Один из самых простых способов — это подсчёт числа нужных пользователю термов, присутствующих в результирующем документе. Документы, которые содержат больше термов, должны считаться более релевантными по сравнению с теми, которые содержат мало термов. В приложении FTS число вхождений термов для каждого результата может быть определено подсчётом целых чисел в строке, возвращаемой функцией offsets. Следующий пример демонстрирует запрос, который может быть использован для получения десяти наиболее релевантных результатов для введенного пользователем запроса: -- Этот пример (и все другие в этом разделе) предполагает следующую -- схему: CREATE VIRTUAL TABLE documents USING fts3(title, content); -- Будем считать, что приложение предоставляет пользовательскую функцию по -- имени "countintegers", которая возвращает число разделенных пробелами целых -- чисел, содержащихся в аргументе. Тогда следующий запрос может быть -- использован для того, чтобы получить заголовки 10 документов, которые -- содержат наибольшее число термов из запроса пользователя. Остается надеяться, -- что эти 10 документов окажутся более или менее релевантными с точки зрения -- пользователя. SELECT title FROM documents WHERE documents MATCH <query> ORDER BY countintegers(offsets(document)) DESC LIMIT 10 OFFSET 0 Запрос выше может работать быстрее при использовании функции FTS
matchinfo для определения числа вхождений термов запроса в каждую
обрабатываемую строку. Функция -- Если приложение предоставляет SQLite пользовательскую функцию по имени -- "rank", которая интерпретирует бинарные данные, возвращаемые matchinfo, и -- переводит их в числовую релевантность, то следующий SQL может быть -- использован для получения заголовков 10 наиболее релевантных -- пользовательскому запросу документов из всего набора документов. SELECT title FROM documents WHERE documents MATCH <query> ORDER BY rank(matchinfo(document)) DESC LIMIT 10 OFFSET 0 SQL-запрос в этом примере нагружает процессор меньше, чем первый пример в
этом разделе. Однако оставляет нерешённой другую неочевидную проблему с
производительностью. Выполняя этот запрос SQLite извлекает значения столбца
"title" и данные Запрос SQL в следующем блоке примеров является одним из решений этой
проблемы. В SQLite, когда используемый в объединении подзапрос использует
выражение SELECT title FROM documents JOIN (
SELECT docid, rank(matchinfo(document)) AS rank
FROM documents
WHERE documents MATCH <query>
ORDER BY rank DESC
LIMIT 10 OFFSET 0
) AS ranktable USING(docid)
ORDER BY ranktable.rank DESC
В следующем блоке SQL-кода приводятся запросы, которые решают две другие проблемы, с которыми можно столкнуться при разработке поисковых приложений с использованием FTS:
Эта версия запроса очень похожа на ту, которая используется в приложении поиска документации на sqlite.org.
-- В этой таблице хранятся статические "веса", назначаемые каждому документу
-- в FTS-таблице "documents". Для каждой записи в таблице документов существует
-- соответствующая запись в данной таблице с тем же самым значением docid.
CREATE TABLE documents_data(docid INTEGER PRIMARY KEY, weight);
-- Этот запрос похож на тот, что в блоке выше, за исключением следующего:
--
-- 1. Он возвращает "сниппет" текста для отображения вместе с заголовком
-- документа. Чтобы использовать для этого функцию snippet, конструкция
-- "WHERE ... MATCH ..." дублируется в подзапросе и в основном запросе.
--
-- 2. Во вложенном запросе объединяются таблицы documents и document_data.
-- Последняя хранит статические веса всех документов, которые используются
-- данной реализацией функции rank.
SELECT title, snippet(documents) FROM documents JOIN (
SELECT docid, rank(matchinfo(document), documents_data.weight) AS rank
FROM documents JOIN documents_data USING(docid)
WHERE documents MATCH <query>
ORDER BY rank DESC
LIMIT 10 OFFSET 0
) AS ranktable USING(docid)
WHERE documents MATCH <query>
ORDER BY ranktable.rank DESC
Все вышеприведённые примеры запросов выдают десять результатов, наиболее
релевантных запросу. Модифицировав значения в выражениях Следующий блок содержит примерную реализацию функции /*
** Пример пользовательской функции SQLite, использующей matchinfo() для
** вычисления релевантности записей в таблице FTS. Функция возвращает оценку
** релевантности (действительное число, равное или большее нуля). Большее
** значение оценки соответствует большей релевантности документа.
**
** Общая релевантность возвращается как сумма релевантностей значений отдельных
** столбцов таблицы FTS. Релевантность значения в столбце является суммой
** релевантностей этого значения для каждой отдельной фразы FTS-запроса, которые
** вычисляются следующим образом:
**
** (<число вхождений>/<общее число вхождений>)*<вес столбца>
**
** где <число вхождений> - это число, сколько раз встречается фраза в
** значении столбца текущей строки; <общее число вхождений> - это число,
** сколько раз встречается фраза в значениях столбца для всех строк в таблице
** FTS; <вес столбца> - это весовой фактор, указываемый для каждого
** столбца при вызове функции (смотрите ниже).
**
** Первый аргумент этой функции должен быть значением, которое возвращает
** FTS-функция matchinfo(). После этого следует задать по одному числовому
** аргументу на каждый столбец таблицы FTS, в которых указать относительные веса
** соответствующих столбцов. Пример:
**
** CREATE VIRTUAL TABLE documents USING fts3(title, content)
**
** Следующий запрос выдаст docid-ы документов, которые соответствуют
** полнотекстовому запросу <query>, отсортировав по убыванию
** релевантности. При вычислении релевантности слова запроса, содержащиеся в
** столбце 'title', добавляют документу вдвое больше веса, чем слова,
** содержащиеся в столбце 'content'.
**
** SELECT docid FROM documents
** WHERE documents MATCH <query>
** ORDER BY rank(matchinfo(documents), 1.0, 0.5) DESC
*/
static void rankfunc(sqlite3_context *pCtx, int nVal, sqlite3_value **apVal){
int *aMatchinfo; /* Значение, возвращаемое matchinfo() */
int nCol; /* Число столбцов в таблице */
int nPhrase; /* Число фраз в запросе */
int iPhrase; /* Текущая фраза */
double score = 0.0; /* Возвращаемое значение */
assert( sizeof(int)==4 );
/* Проверяем корректность числа аргументов, переданных в данную функцию.
** Если оно неверно, переходим к метке wrong_number_args. Задаем aMatchinfo
** как массив беззнаковых целых чисел, возвращаемый FTS-функцией matchinfo.
** Задаем nPhrase как число фраз, переданных пользователем в полнотекстовом
** запросе, а nCol задаем как число столбцов в таблице.
*/
if( nVal<1 ) goto wrong_number_args;
aMatchinfo = (unsigned int *)sqlite3_value_blob(apVal[0]);
nPhrase = aMatchinfo[0];
nCol = aMatchinfo[1];
if( nVal!=(1+nCol) ) goto wrong_number_args;
/* Итерируем по всем фразам пользовательского запроса. */
for(iPhrase=0; iPhrase<nPhrase; iPhrase++){
int iCol; /* Текущий столбец */
/* Новая итерация по каждому столбцу из пользовательского запроса. Для
** каждого столбца оценка релевантности инкрементируется на следующее
** значение:
**
** (<число вхождений>/<общее число вхождений>)*<вес столбца>
**
** aPhraseinfo[] позиционируется на начало нужных данных во фразе iPhrase.
** Таким образом, число вхождений и общее число вхождений могут быть найдены
** для каждого столбца как aPhraseinfo[iCol*3] и aPhraseinfo[iCol*3+1],
** соответственно.
*/
int *aPhraseinfo = &aMatchinfo[2 + iPhrase*nCol*3];
for(iCol=0; iCol<nCol; iCol++){
int nHitCount = aPhraseinfo[3*iCol];
int nGlobalHitCount = aPhraseinfo[3*iCol+1];
double weight = sqlite3_value_double(apVal[iCol+1]);
if( nHitCount>0 ){
score += ((double)nHitCount / (double)nGlobalHitCount) * weight;
}
}
}
sqlite3_result_double(pCtx, score);
return;
/*
** Переходим сюда, если в функцию передано неверное количество аргументов.
*/
wrong_number_args:
sqlite3_result_error(pCtx, "wrong number of arguments to function rank()", -1);
}
Перевод Дмитрия Скоробогатова, 04.06.2011 Предыдущие публикации: Последнее редактирование: 2011-06-04 09:34:16 Метки материала: sqlite, fts3, fts4, базы данных, программирование, разработка по, db, sql, алгоритмы поисковых систем Оставьте, пожалуйста, свой комментарий к публикации |
||||
© 2007-2012, Дмитрий Скоробогатов.
Разрешается воспроизводить, распространять и/или изменять материалы сайта
в соответствии с условиями GNU Free Documentation License,
версии 1.2 или любой более поздней версии, опубликованной FSF,
если только иное не указано в самих материалах.