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: Зміни в поведінці до повнотекстового пошуку
завжди слухайте

1
"але я не хочу шукати нічого, крім електронних листів та телефонів у цих полях", тоді вони повинні зберігатися у відповідному стовпці, як я вже говорив раніше. У вас є стовпці для цих даних, які слід нормалізувати. Вимикачі слів встановлюються на рівні екземпляра / бази даних. тож було б суттєво змінити помилку ..
Ларну

1
Ви хочете нормалізувати таблиці до 1-М для всіх записів телефону, електронної пошти тощо. Другим варіантом є розділення стовпців (використання string_split (електронна пошта, ',') у поєднанні з Зовнішнім застосуванням. Вам потрібно буде вкажіть теоретичний ліміт кількості електронних листів, які може мати користувач. Потім напишіть такий пошук:. 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+'%')Створіть приблизно п’ять індивідуальних індексів у кожному з полів і включіть первинний ключ.
starbyone

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

Відповіді:


2

Насправді прохання

SELECT [...] CONTAINS ([...], '"6662211 *"') - нічого не отримує

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

SELECT [...] ЗМІСТЬ (Ім'я, "" zimuth * "') - нічого не отримує

проти 'PJSC Azimuth'

виконувати роботу, як очікувалося .
Див. Термін "Префікс" . Тому що 6662211*це не префікс з +7-999-666-22-11, а також zimuth*не є префіксом зAzimuth

Як для

SELECT [...] CONTAINS ([...], '"sms@gmail.com*"') - це не отримує рядок

Це, мабуть, пов’язано з вимикачами слів, як завжди навчання вказували в коментарях. Дивіться слова-переривники

Я не думаю, що повнотекстовий пошук застосовний до вашої задачі.

Навіщо використовувати для FTS в тих самих завданнях, для яких використовується оператор LIKE? Якби був кращий тип індексу для запитів LIKE ... тоді був би кращий тип індексу , а не зовсім інша технологія та синтаксис.
І жодним чином це не допоможе вам "6662211*"протистояти "666 деяким довільним знакам 22, деякому довільному чару 11".
Повний текст пошуку - це не про regex-es (і "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

Наскільки великою повинна бути таблиця суфіксів? Очевидно, нам потрібно лише 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" як Longword, ongword, ngword, gword .... тощо. Отже, будь-яка міститься фраза завжди буде на початку індексованого терміна. Жахливе рішення, повне вад, але воно спрацювало.

Потім ми розглянули розміщення окремого сервера для пошуку. Googling Lucene та elastisearch нададуть вам гарну інформацію про ці пакети на полицях.

Врешті-решт ми розробили власну пошукову систему, яка працює поряд із SQL. Це дозволило нам здійснити фонетичний пошук (подвійний метафон), а потім використовувати обчислення Левенштейна вздовж бічного звукового сигналу, щоб встановити актуальність. Перевищення великої кількості рішень, але варто докласти зусиль у нашому випадку використання. У нас навіть зараз є можливість використовувати графічні процесори Nvidia для пошуку cuda, але це являє собою цілий новий набір головних болів і безсонні ночі. Актуальність всього цього буде залежати від того, наскільки часто ви бачите свої пошукові запити та наскільки реактивними вони потрібні.


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". Просто "sms", хоча, не вірно.

Останній приклад подібний. Частини номера телефону індексуються (наприклад, 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.