FTS не работает должным образом с электронными письмами с точками - PullRequest
9 голосов
/ 18 февраля 2020

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

У нас есть 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

Ответы [ 3 ]

2 голосов
/ 27 февраля 2020

На самом деле запрашивает

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

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

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

против 'PJSC Azimuth'

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

Что касается

SELECT [...] СОДЕРЖИТ ([...], '"sm s@gmail.com*"') - строка не получается

Это, вероятно, из-за разбиения по словам, как всегда учится в комментариях. См. разбиение по словам

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

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

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

Да, это так. Оставляя в стороне такие перспективы, как написание вашей собственной поисковой системы, что мы можем сделать в 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 символов для хранения всех суффиксов - это квадратичная c сложность ... не хорошо ... но если у вас сейчас 100 000 записей и у вас нет планов на миллионы в ближайшее будущее - вы можете go с этим решением.

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

Я только опишу идею, реализация которой потребует некоторых усилий. И, вероятно, нам нужно пересечь границы 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 голос
/ 26 февраля 2020

Полнотекстовые индексы имеют ряд ограничений. Вы можете использовать подстановочные знаки в словах, которые индекс находит как целые «части», но даже в этом случае вы ограничены конечной частью слова. Вот почему вы можете использовать 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*"')
1 голос
/ 25 февраля 2020

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

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

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

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

...