Мы разрабатываем поиск как часть более крупной системы.
У нас есть Microsoft SQL Server 2014 - 12.0.2000.8 (X64) Standard Edition (64-bit)
с этой настройкой:
CREATE TABLE NewCompanies(
[Id] [uniqueidentifier] NOT NULL,
[Name] [nvarchar](400) NOT NULL,
[Phone] [nvarchar](max) NULL,
[Email] [nvarchar](max) NULL,
[Contacts1] [nvarchar](max) NULL,
[Contacts2] [nvarchar](max) NULL,
[Contacts3] [nvarchar](max) NULL,
[Contacts4] [nvarchar](max) NULL,
[Address] [nvarchar](max) NULL,
CONSTRAINT PK_Id PRIMARY KEY (Id)
);
Phone
является структурированной строкой, разделенной запятой"77777777777, 88888888888"
Email
структурированная строка писем с запятыми"email1@gmail.com, email2@gmail.com"
(или без запятых"email1@gmail.com"
)Contacts1, Contacts2, Contacts3, Contacts4
текстовые поля, в которых пользователи могут указывать контактные данные в свободной форме. Нравится"John Smith +1 202 555 0156"
или"Bob, +1-999-888-0156, bob@company.com"
. Эти поля могут содержать электронные письма и телефоны, которые мы хотим искать дальше.
Здесь мы создаем полнотекстовые материалы
-- FULL TEXT SEARCH
CREATE FULLTEXT CATALOG NewCompanySearch AS DEFAULT;
CREATE FULLTEXT INDEX ON NewCompanies(Name, Phone, Email, Contacts1, Contacts2, Contacts3, Contacts4, Address)
KEY INDEX PK_Id
Вот образец данных
INSERT INTO NewCompanies(Id, Name, Phone, Email, Contacts1, Contacts2, Contacts3, Contacts4)
VALUES ('7BA05F18-1337-4AFB-80D9-00001A777E4F', 'PJSC Azimuth', '79001002030, 78005005044', 'regular@hotmail.com, s.m.s@gmail.com', 'John Smith', 'Call only at weekends +7-999-666-22-11', NULL, NULL)
На самом деле у нас есть около 100 тысяч таких записей.
Мы ожидаем, что пользователи могут указать часть электронной почты, например "@ gmail.com", и это должно вернуть все строки с адресами электронной почты Gmail в любом из Email, Contacts1, Contacts2, Contacts3, Contacts4
полей.
То же самое для телефонных номеров. Пользователи могут искать шаблон типа «70283», и запрос должен вернуть телефоны с этими цифрами в них. Это даже для Contacts1, Contacts2, Contacts3, Contacts4
полей свободной формы, где мы, вероятно, должны сначала удалить все, кроме цифр и пробелов перед поиском.
Раньше мы использовали LIKE
для поиска, когда у нас было около 1500 записей, и это работало нормально, но теперь у нас много записей, и LIKE
поиск занимает бесконечно много времени, чтобы получить результаты.
Вот как мы пытаемся получить данные оттуда:
SELECT * FROM NewCompanies WHERE CONTAINS((Email, Contacts1, Contacts2, Contacts3, Contacts4), '"s.m.s@gmail.com*"') -- this doesn't get the row
SELECT * FROM NewCompanies WHERE CONTAINS((Phone, Contacts1, Contacts2, Contacts3, Contacts4), '"6662211*"') -- doesn't get anything
SELECT * FROM NewCompanies WHERE CONTAINS(Name, '"zimuth*"') -- doesn't get anything
nvarchar(MAX)
здесь? Я никогда не слышал и не встречал никого, чье имя составляет 1 миллиард символов. И, согласно этому ответу , адрес электронной почты не может быть длиннее 254 символов; таким образом, у вас также есть 1 миллиард потраченных впустую персонажей.@gmail.com
в качестве поискового запроса, потому что@
символ является средством разбиения по словам . Другими словами, в зависимости от версии SQL Server у вас есть, слова в индексе дляuser@gmail.com
будет либо (А)user
,gmail
иcom
или (B)user
,user@gmail.com
,gmail
иcom
. REF: изменения поведения в полнотекстовом поиске.
.SELECT * FROM NewCompanies WHERE Id IN (SELECT ID from .... where MyOuterApply.EmailCol1 LIKE '%'+@SearchString+'%') OR Id IN (SELECT ID from .... where MyOuterApply.EmailCol2 LIKE '%'+@SearchString+'%')
Создайте около пяти отдельных индексов в каждом из полей иОтветы:
Собственно запросы
против
'Call only at weekends +7-999-666-22-11'
ипротив
'PJSC Azimuth'
делать работу, как ожидалось .
Смотрите Префикс Термин . Потому что
6662211*
это не префикс из+7-999-666-22-11
, а такжеzimuth*
не является префиксом изAzimuth
Что касается
Это, вероятно, связано с нарушителями слов, как всегда указано в комментариях. Смотрите брейкеры
Я не думаю, что полнотекстовый поиск применим для вашей задачи.
Зачем использовать FTS в тех же задачах, для которых используется оператор LIKE? Если бы был лучший тип индекса для запросов LIKE ... тогда был бы лучший тип индекса , а не совершенно другая технология и синтаксис.
И ни в коем случае это не поможет вам сопоставить
"6662211*"
«666 с произвольным символом 22 с произвольным символом 11».Полнотекстовый поиск не относится к регулярным выражениям (и
"6662211*"
даже не является правильным выражением для задания - здесь нет ничего о «некотором произвольном символе»), а о синонимах, словоформах и т. Д.Но возможно ли вообще эффективно искать подстроки?
Да, это так. Оставляя в стороне такие перспективы, как написание собственной поисковой системы, что мы можем сделать внутри
SQL
?Прежде всего - это необходимость очистки ваших данных! Если вы хотите вернуть пользователям точные введенные строки
... вы можете сохранить их как есть ... и оставить их вместе.
Затем вам нужно извлечь данные из текста свободной формы (это не так сложно для электронной почты и телефонных номеров) и сохранить данные в некоторой канонической форме. Что касается электронной почты, единственное, что вам действительно нужно сделать - сделать их все строчными или прописными (не имеет значения), а затем разделить их на
@
пение. Но в телефонных номерах нужно оставлять только цифры(... И тогда вы даже можете хранить их как числа . Это может сэкономить вам пространство и время. Но поиск будет другим ... А теперь давайте углубимся в более простую и универсальное решение с использованием строк.)
Как упомянул MatthewBaker, вы можете создать таблицу суффиксов. Тогда вы можете искать так
Вы должны поместить подстановочный знак
%
только в конце . Или не будет никакой выгоды от таблицы суффиксов.Давайте возьмем для примера номер телефона
После того, как мы избавимся от ненужных символов, в нем будет 11 цифр. Это означает, что нам нужно 11 суффиксов для одного номера телефона
Таким образом, сложность пространства для этого решения является линейной ... не так уж и плохо, я бы сказал ... Но подождите, это сложность в количестве записей. Но в символах ... нам нужны
N(N+1)/2
символы для хранения всех суффиксов - это квадратичная сложность ... не хорошо ... но если у вас есть100 000
записи и у вас нет планов на миллионы в ближайшем будущем - вы можете пойти с этим решение.Можем ли мы уменьшить сложность пространства?
Я только опишу идею, для ее реализации потребуются определенные усилия. И, вероятно, нам нужно пересечь границы
SQL
Допустим, у вас есть 2 строки
NewCompanies
и 2 строки текста произвольной формы:Насколько большим должен быть стол Suffixes? Очевидно, нам нужно только 2 записи.
Давайте возьмем другой пример. Также 2 строки, 2 строки свободного текста для поиска. Но теперь это:
Давайте посмотрим, сколько суффиксов нам нужно сейчас:
Не так плохо, но и не так хорошо.
Что еще мы можем сделать?
Допустим, пользователь вводит
"c11"
в поле поиска. ЗатемLIKE 'c11%'
требуется суффикс « c11 cc» для успеха. Но если вместо поиска"c11"
мы сначала ищем"c%"
, то"c1%"
и так далее? Первый поиск даст только одну строку изNewCompanies
. И не было бы необходимости в последующих поисках. И мы можеми у нас останется только 4 суффикса
Я не могу сказать, какой будет космическая сложность в этом случае, но кажется, что это будет приемлемо.
источник
В таких случаях полнотекстовый поиск не идеален. Я был в той же лодке, что и ты. Подобные поиски являются слишком медленными, а полнотекстовые поиски ищут слова, начинающиеся с термина, а не содержащие термин.
Мы опробовали несколько решений, одна из которых на чистом SQL - создать собственную версию полнотекстового поиска, в частности поиск с инвертированным индексом. Мы попробовали это, и это было успешно, но заняло много места. Мы создали вторичную таблицу хранения для частичных поисковых терминов и использовали для этого полнотекстовую индексацию. Однако это означает, что мы неоднократно хранили несколько копий одной и той же вещи. Например, мы сохранили «длинное слово» как Longword, ongword, ngword, gword .... и т. Д. Таким образом, любая содержащаяся фраза всегда будет в начале индексированного термина. Ужасное решение, полное недостатков, но оно сработало.
Затем мы посмотрели на хостинг отдельного сервера для поиска. Поиск в Google Lucene и elastisearch даст вам хорошую информацию об этих готовых пакетах.
В итоге мы разработали собственную поисковую систему, которая работает на стороне SQL. Это позволило нам осуществить фонетический поиск (двойной метафон), а затем использовать вычисления Левенштейна вдоль бокового звукового индекса для установления релевантности. Избыток для многих решений, но стоит усилий в нашем случае использования. У нас даже сейчас есть возможность использовать графические процессоры Nvidia для поисков куда, но это представляло собой новый набор головных болей и бессонных ночей. Актуальность всего этого будет зависеть от того, как часто вы видите, как выполняются ваши поиски, и насколько вы должны реагировать на них.
источник
Полнотекстовые индексы имеют ряд ограничений. Вы можете использовать подстановочные знаки в словах, которые индекс находит как целые «части», но даже в этом случае вы ограничены конечной частью слова. Вот почему вы можете использовать,
CONTAINS(Name, '"Azimut*"')
но неCONTAINS(Name, '"zimuth*"')
Из документации Microsoft :
Точки в письме, как указано в заголовке, не являются основной проблемой. Это, например, работает:
В этом случае индекс идентифицирует всю строку электронной почты как допустимую, а также «gmail» и «gmail.com». Просто "смс" хоть и не действителен.
Последний пример похож. Части телефонного номера индексируются (например, 666-22-11 и 999-666-22-11), но удаление дефисов не является строкой, о которой будет знать индекс. В противном случае это работает:
источник