FTS не работает должным образом с электронными письмами с точками


9

Мы разрабатываем поиск как часть более крупной системы.

У нас есть 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)
);
  1. Phone является структурированной строкой, разделенной запятой "77777777777, 88888888888"
  2. Emailструктурированная строка писем с запятыми "email1@gmail.com, email2@gmail.com"(или без запятых "email1@gmail.com")
  3. 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

5
Почему все ваши колонки nvarchar(MAX)здесь? Я никогда не слышал и не встречал никого, чье имя составляет 1 миллиард символов. И, согласно этому ответу , адрес электронной почты не может быть длиннее 254 символов; таким образом, у вас также есть 1 миллиард потраченных впустую персонажей.
Ларну

2
Похоже, вы боретесь с помощью средств разбиения по словам полнотекстового поиска. Вам вряд ли удастся найти что-либо, использующее @gmail.comв качестве поискового запроса, потому что @символ является средством разбиения по словам . Другими словами, в зависимости от версии SQL Server у вас есть, слова в индексе для user@gmail.comбудет либо (А) user, gmailи comили (B) user, user@gmail.com, gmailи com. REF: изменения поведения в полнотекстовом поиске
AlwaysLearning

1
«но я не хочу искать ничего, кроме электронной почты и телефонов в этих полях», тогда они должны храниться в соответствующей колонке, как я уже говорил. У вас есть столбцы для этих данных, которые должны быть нормализованы. Средства разбиения по словам устанавливаются на уровне экземпляра / базы данных. так что это было бы значительным переломным изменением для удаления ..
Ларну

1
Вы можете либо нормализовать таблицы до 1-M для всех записей по телефону, электронной почте и т. Д. Второй вариант - разделить столбцы (используйте string_split (email, ',') в сочетании с Outer Apply. Укажите теоретическое ограничение на количество электронных писем, которое может иметь пользователь. Затем напишите запрос, подобный следующему: 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+'%')Создайте около пяти отдельных индексов в каждом из полей и
включите

2
@TheDudeWithHat Не собираюсь, не значит, что не должно. Причина, по которой ОП сталкивается с проблемой, заключается в отсутствии нормализации.
Ларну

Ответы:


2

Собственно запросы

SELECT [...] СОДЕРЖИТ ([...], '"6662211 *"') - ничего не получает

против 'Call only at weekends +7-999-666-22-11' и

SELECT [...] CONTAINS (Name, '"zimuth *"') - ничего не получает

против 'PJSC Azimuth'

делать работу, как ожидалось .
Смотрите Префикс Термин . Потому что 6662211*это не префикс из +7-999-666-22-11, а также zimuth*не является префиксом изAzimuth

Что касается

ВЫБРАТЬ [...] СОДЕРЖИТ ([...], '"sms@gmail.com*") - это не получить строку

Это, вероятно, связано с нарушителями слов, как всегда указано в комментариях. Смотрите брейкеры

Я не думаю, что полнотекстовый поиск применим для вашей задачи.

Зачем использовать FTS в тех же задачах, для которых используется оператор LIKE? Если бы был лучший тип индекса для запросов LIKE ... тогда был бы лучший тип индекса , а не совершенно другая технология и синтаксис.
И ни в коем случае это не поможет вам сопоставить "6662211*"«666 с произвольным символом 22 с произвольным символом 11».
Полнотекстовый поиск не относится к регулярным выражениям (и "6662211*"даже не является правильным выражением для задания - здесь нет ничего о «некотором произвольном символе»), а о синонимах, словоформах и т. Д.

Но возможно ли вообще эффективно искать подстроки?

Да, это так. Оставляя в стороне такие перспективы, как написание собственной поисковой системы, что мы можем сделать внутри SQL?

Прежде всего - это необходимость очистки ваших данных! Если вы хотите вернуть пользователям точные введенные строки

пользователи могут указывать контактные данные в свободной форме

... вы можете сохранить их как есть ... и оставить их вместе.
Затем вам нужно извлечь данные из текста свободной формы (это не так сложно для электронной почты и телефонных номеров) и сохранить данные в некоторой канонической форме. Что касается электронной почты, единственное, что вам действительно нужно сделать - сделать их все строчными или прописными (не имеет значения), а затем разделить их на @пение. Но в телефонных номерах нужно оставлять только цифры
(... И тогда вы даже можете хранить их как числа . Это может сэкономить вам пространство и время. Но поиск будет другим ... А теперь давайте углубимся в более простую и универсальное решение с использованием строк.)

Как упомянул MatthewBaker, вы можете создать таблицу суффиксов. Тогда вы можете искать так

SELECT DISTINCT * FROM NewCompanies JOIN Sufficies ON NewCompanies.Id = Sufficies.Id WHERE Sufficies.sufficies LIKE 'some text%'

Вы должны поместить подстановочный знак %только в конце . Или не будет никакой выгоды от таблицы суффиксов.

Давайте возьмем для примера номер телефона

+ 7-999-666-22-11

После того, как мы избавимся от ненужных символов, в нем будет 11 цифр. Это означает, что нам нужно 11 суффиксов для одного номера телефона

           1
          11
         211
        2211
       62211
      662211
     6662211
    96662211
   996662211
  9996662211
 79996662211

Таким образом, сложность пространства для этого решения является линейной ... не так уж и плохо, я бы сказал ... Но подождите, это сложность в количестве записей. Но в символах ... нам нужны N(N+1)/2символы для хранения всех суффиксов - это квадратичная сложность ... не хорошо ... но если у вас есть 100 000записи и у вас нет планов на миллионы в ближайшем будущем - вы можете пойти с этим решение.

Можем ли мы уменьшить сложность пространства?

Я только опишу идею, для ее реализации потребуются определенные усилия. И, вероятно, нам нужно пересечь границыSQL

Допустим, у вас есть 2 строки NewCompaniesи 2 строки текста произвольной формы:

    aaaaa
    11111

Насколько большим должен быть стол Suffixes? Очевидно, нам нужно только 2 записи.

Давайте возьмем другой пример. Также 2 строки, 2 строки свободного текста для поиска. Но теперь это:

    aa11aa
    cc11cc

Давайте посмотрим, сколько суффиксов нам нужно сейчас:

         a // no need, LIKE `a%`  will match against 'aa' and 'a11aa' and 'aa11aa'
        aa // no need, LIKE `aa%` will match against 'aa11aa'
       1aa
      11aa
     a11aa
    aa11aa
         c // no need, LIKE `c%`  will match against 'cc' and 'c11cc' and 'cc11cc'
        cc // no need, LIKE `cc%` will match against 'cc11cc'
       1cc
      11cc
     c11cc
    cc11cc

Не так плохо, но и не так хорошо.

Что еще мы можем сделать?

Допустим, пользователь вводит "c11"в поле поиска. Затем LIKE 'c11%'требуется суффикс « c11 cc» для успеха. Но если вместо поиска "c11"мы сначала ищем "c%", то "c1%"и так далее? Первый поиск даст только одну строку из NewCompanies. И не было бы необходимости в последующих поисках. И мы можем

       1aa // drop this as well, because LIKE '1%' matches '11aa'
      11aa
     a11aa // drop this as well, because LIKE 'a%' matches 'aa11aa'
    aa11aa
       1cc // same here
      11cc
     c11cc // same here
    cc11cc

и у нас останется только 4 суффикса

      11aa
    aa11aa
      11cc
    cc11cc

Я не могу сказать, какой будет космическая сложность в этом случае, но кажется, что это будет приемлемо.


1

В таких случаях полнотекстовый поиск не идеален. Я был в той же лодке, что и ты. Подобные поиски являются слишком медленными, а полнотекстовые поиски ищут слова, начинающиеся с термина, а не содержащие термин.

Мы опробовали несколько решений, одна из которых на чистом SQL - создать собственную версию полнотекстового поиска, в частности поиск с инвертированным индексом. Мы попробовали это, и это было успешно, но заняло много места. Мы создали вторичную таблицу хранения для частичных поисковых терминов и использовали для этого полнотекстовую индексацию. Однако это означает, что мы неоднократно хранили несколько копий одной и той же вещи. Например, мы сохранили «длинное слово» как Longword, ongword, ngword, gword .... и т. Д. Таким образом, любая содержащаяся фраза всегда будет в начале индексированного термина. Ужасное решение, полное недостатков, но оно сработало.

Затем мы посмотрели на хостинг отдельного сервера для поиска. Поиск в Google Lucene и elastisearch даст вам хорошую информацию об этих готовых пакетах.

В итоге мы разработали собственную поисковую систему, которая работает на стороне SQL. Это позволило нам осуществить фонетический поиск (двойной метафон), а затем использовать вычисления Левенштейна вдоль бокового звукового индекса для установления релевантности. Избыток для многих решений, но стоит усилий в нашем случае использования. У нас даже сейчас есть возможность использовать графические процессоры Nvidia для поисков куда, но это представляло собой новый набор головных болей и бессонных ночей. Актуальность всего этого будет зависеть от того, как часто вы видите, как выполняются ваши поиски, и насколько вы должны реагировать на них.


1

Полнотекстовые индексы имеют ряд ограничений. Вы можете использовать подстановочные знаки в словах, которые индекс находит как целые «части», но даже в этом случае вы ограничены конечной частью слова. Вот почему вы можете использовать, CONTAINS(Name, '"Azimut*"')но неCONTAINS(Name, '"zimuth*"')

Из документации Microsoft :

Когда префиксный термин представляет собой фразу, каждый токен, составляющий фразу, считается отдельным префиксным термином. Все строки, в которых есть слова, начинающиеся с префиксных терминов, будут возвращены. Например, префиксный термин «легкий хлеб *» будет искать строки с текстом «легкий панировка», «легкий панировка» или «легкий хлеб», но он не будет возвращать «слегка поджаренный хлеб».

Точки в письме, как указано в заголовке, не являются основной проблемой. Это, например, работает:

SELECT * FROM NewCompanies 
WHERE CONTAINS((Email, Contacts1, Contacts2, Contacts3, Contacts4), 's.m.s@gmail.com') 

В этом случае индекс идентифицирует всю строку электронной почты как допустимую, а также «gmail» и «gmail.com». Просто "смс" хоть и не действителен.

Последний пример похож. Части телефонного номера индексируются (например, 666-22-11 и 999-666-22-11), но удаление дефисов не является строкой, о которой будет знать индекс. В противном случае это работает:

SELECT * FROM NewCompanies 
WHERE CONTAINS((Phone, Contacts1, Contacts2, Contacts3, Contacts4), '"666-22-11*"')
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.