FTS, noktalı e-postalarda beklendiği gibi çalışmıyor


9

Daha büyük bir sistemin parçası olarak bir arama geliştiriyoruz.

Microsoft SQL Server 2014 - 12.0.2000.8 (X64) Standard Edition (64-bit)Bu kurulumla sahibiz :

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 gibi yapılandırılmış virgülle ayrılmış basamak dizesidir "77777777777, 88888888888"
  2. Emailgibi virgül ile e-postalar dize yapılandırılmıştır "email1@gmail.com, email2@gmail.com"(gibi hiç virgül veya olmadan "email1@gmail.com")
  3. Contacts1, Contacts2, Contacts3, Contacts4kullanıcıların iletişim bilgilerini serbest biçimde belirleyebilecekleri metin alanlarıdır. Gibi "John Smith +1 202 555 0156"veya "Bob, +1-999-888-0156, bob@company.com". Bu alanlar, daha fazla aramak istediğimiz e-postaları ve telefonları içerebilir.

Burada tam metin şeyler yaratıyoruz

-- 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

İşte bir veri örneği

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)

Aslında bu türden yaklaşık 100 bin kayıt var.

Kullanıcıların e-postanın "@ gmail.com" gibi bir bölümünü belirleyebileceğini umuyoruz ve bu, Gmail e-posta adreslerine sahip tüm satırları Email, Contacts1, Contacts2, Contacts3, Contacts4alanlardan herhangi birinde döndürmelidir .

Aynı telefon numaraları için. Kullanıcılar "70283" gibi bir model arayabilir ve bir sorgu bu rakamları içeren telefonları döndürmelidir. Contacts1, Contacts2, Contacts3, Contacts4Aramadan önce büyük olasılıkla rakamlar ve boşluk karakterleri hariç tümünü kaldırmamız gereken serbest form alanları için bile .

Biz LIKEyaklaşık 1500 kayıt varken arama için kullanıyorduk ve iyi çalıştı ama şimdi çok fazla kayıt var ve LIKEarama sonuç almak için sonsuz oluyor.

Oradan veri almaya çalışıyoruz:

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
Neden tüm sütunlarınız nvarchar(MAX)burada? Daha önce hiç duymadım ya da adı 1 Milyar ~ karakter uzunluğunda olan biriyle tanışmadım. Ve bu cevaba göre , bir e-posta adresi 254 karakterden daha uzun olamaz; orada 1 Milyar ~ boşa harcanan karakter var.
Larnu

2
Tam metin aramanın kelime kesicileriyle savaşıyor gibisiniz. @gmail.comArama terimi olarak kullanan herhangi bir şey bulamıyorsunuz çünkü @karakter bir kelime kırıcı. Sahip olduğunuz SQL Server sürümü olarak diğer deyişle, endekste kelimeler için user@gmail.comya (A) olacaktır user, gmailve comveya (B) user, user@gmail.com, gmailve com. REF: Tam metin
aramasında

1
"ama bu alanlarda e-postalar ve telefonlar dışında bir şey aramak istemiyorum" o zaman daha önce söylediğim gibi, uygun bir sütun saklanmalıdır. Bu veriler için normalleştirilmesi gereken sütunlarınız var. Kelime kesiciler örnek / veritabanı düzeyinde ayarlanır. bu yüzden kaldırılması önemli bir değişiklik olur ..
Larnu

1
Tüm telefon, e-posta vb. Kayıtlar için tabloları 1-M olarak normalleştirmek istersiniz. İkinci seçenek, Dış Uygulamayla birlikte sütunları bölmektir (string_split (email, ',' kullanın). bir kullanıcının sahip olabileceği e-postaların sayısı hakkında teorik bir sınır belirleyin Daha sonra böyle bir arama yazın: 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+'%')Alanların her biri için yaklaşık beş ayrı dizin oluşturun ve birincil anahtarı
ekleyin

2
@TheDudeWithHat Gitmemek, yapmamak gerektiği anlamına gelmez. OP'nin bu konuyu ele almasının nedeni normalleşmenin olmamasıdır.
Larnu

Yanıtlar:


2

Aslında istekler

SEÇ [...] İÇERİR ([...], '"6662211 *"') - hiçbir şey almaz

karşı 'Call only at weekends +7-999-666-22-11' ve

SELECT [...] CONTAINS (Ad, '"zimuth *"') - hiçbir şey almaz

karşısında 'PJSC Azimuth'

do beklendiği gibi çalışması .
Bkz. Önek Terimi . Çünkü 6662211*bir değil önek ait +7-999-666-22-11yanısıra zimuth*bir değil önek aitAzimuth

Gelince

SELECT [...] CONTAINS ([...], '"sms@gmail.com*"') - bu satır almıyor

Bu muhtemelen kelime kırıcılardan kaynaklanmaktadır, çünkü her zaman öğrenme yorumlarda belirtilmiştir. Kelime kesicilere bakın

Göreviniz için Tam Metin Arama'nın geçerli olduğunu düşünmüyorum.

Neden FTS için LIKE operatörünün kullanıldığı aynı görevlerde kullanılsın? LIKE sorguları için daha iyi bir dizin türü olsaydı ... o zaman tamamen farklı teknoloji ve sözdizimi değil, daha iyi dizin türü olurdu .
Ve hiçbir şekilde "6662211*""666 bazı keyfi karakter 22 bazı keyfi karakter 11" ile eşleşmenize yardımcı olmaz .
Tam Metin araması regex-es ile ilgili değildir (ve "6662211*"iş için doğru bir ifade bile değildir - eş anlamlılar, kelime formları vb.)

Ancak alt dizeleri etkili bir şekilde aramak mümkün müdür?

Evet öyle. Kendi arama motorunuzu yazmak gibi umutları bir yana bırakarak, içinde ne yapabiliriz SQL?

Her şeyden önce - verilerinizi temizlemek zorunludur! Kullanıcılara girdikleri dizeleri tam olarak döndürmek istiyorsanız

kullanıcılar iletişim bilgilerini serbest biçimde belirleyebilir

... onları olduğu gibi kurtarabilir ve bırakabilirsiniz.
Sonra gerek ayıklamak ve bazı kanonik formda verileri kaydetmek (e-posta ve telefon numaraları için çok zor değildir) serbest form metinden verileri. E-posta için, gerçekten yapmanız gereken tek şey - hepsini küçük harf veya büyük harf yapmak (önemli değil) ve belki @şarkıya bölün . Ancak telefon numaralarında sadece rakam bırakmanız gerekir
(... Ve sonra bunları rakam olarak saklayabilirsiniz . Bu size biraz yer ve zaman kazandırabilir. Ama arama farklı olacak ... Şimdilik daha basit bir dalış yapalım ve dizeleri kullanarak evrensel çözüm.)

MatthewBaker'ın belirttiği gibi bir sonek tablosu oluşturabilirsiniz. Sonra böyle arama yapabilirsiniz

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

Joker karakteri %yalnızca sonuna yerleştirmelisiniz . Ya da Sonekler tablosundan hiçbir fayda sağlamaz.

Örneğin bir telefon numarası alalım

+ 7-999-666-22-11

İçindeki atık karakterlerinden kurtulduktan sonra 11 hanesi olacak. Bu, bir telefon numarası için 11 son eke ihtiyacımız olacağı anlamına gelir

           1
          11
         211
        2211
       62211
      662211
     6662211
    96662211
   996662211
  9996662211
 79996662211

Yani bu çözümün alan karmaşıklığı doğrusal ... o kadar da kötü değil, diyebilirim ki ... Ama bekle , kayıt sayısındaki karmaşıklık. Ama sembollerde ... N(N+1)/2tüm ekleri saklamak için sembollere ihtiyacımız var - bu ikinci dereceden karmaşıklık ... iyi değil ... ama şimdi 100 000kayıtlarınız varsa ve yakın gelecekte milyonlarca planınız yoksa - bununla gidebilirsiniz çözüm.

Alan karmaşıklığını azaltabilir miyiz?

Sadece fikri anlatacağım, onu uygulamak biraz çaba gerektirecektir. Ve muhtemelen sınırlarını aşmamız gerekecekSQL

Diyelim ki içinde 2 satır NewCompaniesve içinde 2 serbest form metni dizesi var:

    aaaaa
    11111

Sonek tablosu ne kadar büyük olmalı? Açıkçası, sadece 2 kayda ihtiyacımız var.

Bir örnek daha verelim. Ayrıca 2 satır, 2 serbest metin dizesi aramak için. Ama şimdi:

    aa11aa
    cc11cc

Şimdi kaç eke ihtiyacımız olduğunu görelim:

         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

Hayır o kadar da kötü değil ama o kadar da iyi değil.

Başka ne yapabiliriz?

Diyelim ki kullanıcı "c11"arama alanına giriyor . Daha sonra başarılı olmak için LIKE 'c11%'' c11 cc' sonekine ihtiyaç vardır . Ama eğer aramak yerine "c11"önce ararsak "c%", o zaman "c1%"vb. İlk arama yalnızca bir satır olarak verilir NewCompanies. Ve sonraki aramalara gerek kalmaz. Ve biz yapabiliriz

       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

ve sadece 4 sonekle karşılaşıyoruz

      11aa
    aa11aa
      11cc
    cc11cc

Bu durumda uzay karmaşıklığının ne olacağını söyleyemem, ancak kabul edilebilir gibi geliyor.


1

Bu gibi durumlarda tam metin araması idealden daha azdır. Seninle aynı gemideydim. Aramalar çok yavaştır ve tam metin aramaları, bir terim yerine bir terimle başlayan kelimeleri arar.

Birkaç çözüm denedik, saf bir SQL seçeneği, tam metin araması, özellikle de tersine çevrilmiş bir dizin araması kendi sürümünüzü oluşturmaktır. Bunu denedik ve başarılı oldu, ancak çok yer kapladı. Kısmi arama terimleri için ikincil bir tutma tablosu oluşturduk ve bunun için tam metin dizine ekleme kullandık. Ancak bu, aynı şeyin birden çok kopyasını tekrar tekrar sakladığımız anlamına gelir. Örneğin, "longword" kelimesini Longword, ongword, ngword, gword .... vb. Olarak sakladık. Bu nedenle, içerilen tüm ifadeler her zaman dizine eklenen terimin başında olacaktır. Korkunç bir çözüm, kusurlarla dolu, ama işe yaradı.

Sonra aramalar için ayrı bir sunucu barındırma baktı. Google'ı araştırmak Lucene ve elastisearch, raf paketleri hakkında size iyi bilgi verecektir.

Sonunda, yan SQL boyunca çalışan kendi şirket içi arama motorumuzu geliştirdik. Bu, fonetik aramaları (çift metafon) uygulamamıza ve daha sonra alaka düzeyi oluşturmak için yan soundex boyunca levenshtein hesaplamaları kullanmamıza izin verdi. Çok fazla çözüm için aşırı ödeme yapın, ancak kullanım durumumuzdaki çabaya değer. Artık Nvidia GPU'larını cuda aramaları için kullanma seçeneğimiz de var, ancak bu tamamen yeni bir baş ağrısı ve uykusuz geceler grubunu temsil ediyordu. Tüm bunların alaka düzeyi, aramalarınızın ne sıklıkta yapıldığını ve ne kadar reaktif olmaları gerektiğine bağlı olacaktır.


1

Tam Metin Dizinleri'nin bazı sınırlamaları vardır. Dizin bulduğu sözcüklerin tamamında "parça" olarak kullanılan joker karakterleri kullanabilirsiniz, ancak o zaman bile sözcüğün son kısmı ile sınırlandırılırsınız. Bu yüzden kullanabilirsiniz CONTAINS(Name, '"Azimut*"')ama kullanamazsınızCONTAINS(Name, '"zimuth*"')

Microsoft belgelerinden :

Önek terimi bir cümle olduğunda, cümleyi oluşturan her jeton ayrı bir önek terimi olarak kabul edilir. Önek terimleriyle başlayan kelimeler içeren tüm satırlar döndürülür. Örneğin, "hafif ekmek *" önek terimi, "hafif ekmekli", "hafif ekmekli" veya "hafif ekmek" metnine sahip satırları bulur, ancak "hafif kızarmış ekmek" i döndürmez.

E-postadaki başlıkta belirtildiği gibi noktalar ana sorun değildir. Örneğin, bu çalışır:

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

Bu durumda, dizin "gmail" ve "gmail.com" yanı sıra tüm e-posta dizesini geçerli olarak tanımlar. Sadece "sms" olsa da geçerli değil.

Son örnek benzer. Telefon numarasının bölümleri dizine eklenir (örneğin, 666-22-11 ve 999-666-22-11), ancak kısa çizgilerin kaldırılması dizinin bileceği bir dize değildir. Aksi takdirde, bu işe yarar:

SELECT * FROM NewCompanies 
WHERE CONTAINS((Phone, Contacts1, Contacts2, Contacts3, Contacts4), '"666-22-11*"')
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.