FTS tidak berfungsi seperti yang diharapkan dengan email dengan titik-titik

9

Kami sedang mengembangkan pencarian sebagai bagian dari sistem yang lebih besar.

Kami miliki Microsoft SQL Server 2014 - 12.0.2000.8 (X64) Standard Edition (64-bit)dengan pengaturan ini:

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 adalah string digit yang dipisahkan koma terstruktur seperti "77777777777, 88888888888"
  2. Emailadalah string email terstruktur dengan koma like "[email protected], [email protected]"(atau tanpa koma sama sekali "[email protected]")
  3. Contacts1, Contacts2, Contacts3, Contacts4adalah bidang teks tempat pengguna dapat menentukan detail kontak dalam bentuk bebas. Suka "John Smith +1 202 555 0156"atau "Bob, +1-999-888-0156, [email protected]". Bidang ini dapat berisi email dan telepon yang ingin kami cari lebih lanjut.

Di sini kami membuat teks lengkap

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

Ini adalah contoh data

INSERT INTO NewCompanies(Id, Name, Phone, Email, Contacts1, Contacts2, Contacts3, Contacts4) 
VALUES ('7BA05F18-1337-4AFB-80D9-00001A777E4F', 'PJSC Azimuth', '79001002030, 78005005044', '[email protected], [email protected]', 'John Smith', 'Call only at weekends +7-999-666-22-11', NULL, NULL)

Sebenarnya kami memiliki sekitar 100 ribu catatan seperti itu.

Kami berharap pengguna dapat menentukan bagian dari email seperti "@ gmail.com" dan ini akan mengembalikan semua baris dengan alamat email Gmail di salah satu Email, Contacts1, Contacts2, Contacts3, Contacts4bidang.

Sama untuk nomor telepon. Pengguna dapat mencari pola seperti "70283" dan kueri harus mengembalikan ponsel dengan angka-angka ini di dalamnya. Itu bahkan untuk Contacts1, Contacts2, Contacts3, Contacts4bidang formulir gratis di mana kita mungkin harus menghapus semua kecuali karakter angka dan spasi terlebih dahulu sebelum mencari.

Kami dulu menggunakan LIKEuntuk pencarian ketika kami memiliki sekitar 1500 catatan dan itu bekerja dengan baik tetapi sekarang kami memiliki banyak catatan dan LIKEpencarian membutuhkan waktu tak terbatas untuk mendapatkan hasil.

Inilah cara kami mencoba mendapatkan data dari sana:

SELECT * FROM NewCompanies WHERE CONTAINS((Email, Contacts1, Contacts2, Contacts3, Contacts4), '"[email protected]*"') -- 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
kseen
sumber
5
Mengapa semua kolom Anda ada di nvarchar(MAX)sini? Saya belum pernah mendengar, atau bertemu orang yang namanya panjangnya 1 Miliar ~ karakter. Dan, menurut jawaban ini , alamat email tidak boleh lebih dari 254 karakter; jadi kamu juga punya 1 Billion ~ karakter terbuang di sana.
Larnu
2
Kedengarannya Anda sedang berkelahi dengan pemecah kata pencarian teks lengkap. Anda tidak mungkin menemukan apa pun yang digunakan @gmail.comsebagai istilah pencarian karena @karakternya adalah pemecah kata. Dengan kata lain, tergantung versi SQL Server yang Anda miliki, kata-kata dalam indeks untuk [email protected]akan baik (A) user, gmaildan comatau (B) user, [email protected], gmaildan com. REF: Perubahan Perilaku menjadi Pencarian Teks Lengkap
AlwaysLearning
1
"tapi saya tidak ingin mencari apa pun selain email dan telepon di bidang itu" maka mereka harus disimpan dalam kolom yang sesuai, seperti yang saya katakan sebelumnya. Anda memiliki kolom untuk data itu, yang harus dinormalisasi. Pemecah kata diatur pada tingkat instance / database. jadi itu akan menjadi perubahan melanggar signifikan untuk dihapus ..
Larnu
1
Anda akan ingin menormalkan tabel menjadi 1-M untuk semua telepon, email, dll. Catatan Pilihan kedua adalah untuk membagi kolom (gunakan string_split (email, ','), dalam kombinasi dengan Outer Apply. Anda harus tentukan batas teoretis pada jumlah email yang bisa dimiliki pengguna, lalu tulis pencarian seperti ini: 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+'%')Buat sekitar lima indeks individu pada masing-masing bidang dan sertakan kunci utama
starbyone
2
@TheDudeWithHat Tidak akan, bukan berarti tidak seharusnya. Alasan OP memiliki masalah mereka adalah karena kurangnya normalisasi.
Larnu

Jawaban:

2

Sebenarnya permintaan

PILIH [...] MENGANDUNG ([...], '"6662211 *"') - tidak mendapatkan apa pun

menentang 'Call only at weekends +7-999-666-22-11' dan

PILIH [...] BERISI (Nama, '"zimuth *"') - tidak mendapatkan apa-apa

melawan 'PJSC Azimuth'

lakukan pekerjaan seperti yang diharapkan .
Lihat Istilah Awalan . Karena 6662211*bukanlah awalan dari +7-999-666-22-11serta zimuth*bukan merupakan awalan dariAzimuth

Untuk

SELECT [...] CONTAINS ([...], '"[email protected]*"') - ini tidak mendapatkan baris

Ini mungkin karena pemecah kata seperti yang selalu dipelajari dalam komentar. Lihat pemecah kata

Saya tidak berpikir bahwa Pencarian Teks Lengkap dapat diterapkan untuk tugas Anda.

Mengapa menggunakan FTS dalam tugas yang sama persis dengan yang digunakan oleh LIKE operator? Jika ada jenis indeks yang lebih baik untuk permintaan LIKE ... maka akan ada jenis indeks yang lebih baik , bukan teknologi dan sintaks yang sama sekali berbeda.
Dan sama sekali tidak akan membantu Anda untuk mencocokkan "6662211*""666 some char arbitrary 22 some arbit arbit char 11".
Pencarian Teks Lengkap bukan tentang regex-es (dan "6662211*"bahkan bukan ekspresi yang benar untuk pekerjaan - tidak ada bagian "arang yang berubah-ubah") ini tentang sinonim, bentuk kata, dll.

Tetapi mungkinkah mencari substring secara efektif?

Ya itu. Mengesampingkan prospek seperti menulis mesin pencari Anda sendiri, apa yang bisa kita lakukan di dalam SQL?

Pertama-tama - sangat penting untuk membersihkan data Anda! Jika Anda ingin kembali ke pengguna, string yang telah mereka masukkan

pengguna dapat menentukan detail kontak dalam bentuk gratis

... Anda dapat menyimpannya apa adanya ... dan membiarkannya.
Maka Anda perlu mengekstraksi data dari teks formulir gratis (tidak begitu sulit untuk email dan nomor telepon) dan menyimpan data dalam beberapa bentuk kanonik. Untuk email, satu-satunya hal yang benar-benar perlu Anda lakukan - buat semuanya menjadi huruf kecil atau besar (tidak masalah), dan mungkin terbagi dua saat @bernyanyi. Tetapi dalam nomor telepon Anda hanya perlu menyisakan digit
(... Dan kemudian Anda bahkan dapat menyimpannya sebagai angka . Itu dapat menghemat ruang dan waktu. Tetapi pencarian akan berbeda ... Untuk sekarang mari selami yang lebih sederhana dan solusi universal menggunakan string.)

Seperti yang disebutkan MatthewBaker, Anda bisa membuat tabel sufiks. Maka Anda dapat mencari seperti itu

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

Anda harus menempatkan wildcard %hanya di akhir . Atau tidak akan ada manfaat dari tabel Suffix.

Misalnya, ambil nomor telepon

+ 7-999-666-22-11

Setelah kita menyingkirkan karakter limbah di dalamnya, itu akan memiliki 11 digit. Itu berarti kita akan membutuhkan 11 sufiks untuk satu nomor telepon

           1
          11
         211
        2211
       62211
      662211
     6662211
    96662211
   996662211
  9996662211
 79996662211

Jadi kompleksitas ruang untuk solusi ini adalah linier ... tidak terlalu buruk, saya akan mengatakan ... Tapi tunggu dulu kompleksitasnya dalam jumlah record. Tetapi dalam simbol ... kita perlu N(N+1)/2simbol untuk menyimpan semua sufiks - yaitu kompleksitas kuadrat ... tidak baik ... tetapi jika Anda sekarang memiliki 100 000catatan dan tidak memiliki rencana untuk jutaan dalam waktu dekat - Anda dapat menggunakan ini larutan.

Bisakah kita mengurangi kompleksitas ruang?

Saya hanya akan menjelaskan ide itu, mengimplementasikannya akan membutuhkan usaha. Dan mungkin kita harus melewati batasSQL

Katakanlah Anda memiliki 2 baris NewCompaniesdan 2 string teks formulir gratis di dalamnya:

    aaaaa
    11111

Seberapa besar seharusnya tabel Suffix? Jelas, kami hanya membutuhkan 2 catatan.

Mari kita ambil contoh lain. Juga 2 baris, 2 string teks gratis untuk dicari. Tapi sekarang:

    aa11aa
    cc11cc

Mari kita lihat berapa banyak sufiks yang kita butuhkan sekarang:

         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

Tidak terlalu buruk, tapi juga tidak begitu baik.

apa lagi yang bisa kita lakukan?

Katakanlah, pengguna masuk "c11"di bidang pencarian. Maka LIKE 'c11%'perlu sufiks ' c11 cc' untuk berhasil. Tetapi jika alih-alih mencari, "c11"pertama-tama kita mencari "c%", lalu untuk "c1%"dan seterusnya? Pencarian pertama akan memberikan hanya satu baris dari NewCompanies. Dan tidak perlu untuk pencarian selanjutnya. Dan kita bisa

       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

dan kita berakhir dengan hanya 4 sufiks

      11aa
    aa11aa
      11cc
    cc11cc

Saya tidak bisa mengatakan apa kompleksitas ruang dalam kasus ini, tetapi rasanya itu bisa diterima.

x00
sumber
1

Dalam kasus seperti ini pencarian teks lengkap kurang dari ideal. Saya berada di kapal yang sama seperti Anda. Seperti pencarian terlalu lambat, dan pencarian teks lengkap mencari kata-kata yang dimulai dengan istilah daripada mengandung istilah.

Kami mencoba beberapa solusi, salah satu opsi SQL murni adalah membangun versi Anda sendiri dari pencarian teks lengkap, khususnya pencarian indeks terbalik. Kami mencoba ini, dan itu berhasil, tetapi memakan banyak ruang. Kami membuat tabel penahan sekunder untuk istilah pencarian parsial, dan menggunakan pengindeksan teks lengkap untuk itu. Namun ini berarti kami berulang kali menyimpan banyak salinan dari hal yang sama. Sebagai contoh, kami menyimpan "kata panjang" sebagai Longword, ongword, ngword, gword .... dll. Jadi setiap frase yang terkandung akan selalu berada di awal istilah yang diindeks. Solusi menghebohkan, penuh kekurangan, tetapi berhasil.

Kami kemudian mencari hosting server terpisah untuk pencarian. Googling Lucene dan elastisearch akan memberi Anda informasi yang baik tentang paket yang ada di rak ini.

Akhirnya, kami mengembangkan sendiri mesin pencari in-house kami, yang berjalan di sepanjang sisi SQL. Ini memungkinkan kami untuk mengimplementasikan pencarian fonetik (metafon ganda) dan kemudian menggunakan perhitungan levenshtein di samping soundex untuk membangun relevansi. Berlebihan untuk banyak solusi, tetapi sepadan dengan usaha dalam kasus penggunaan kami. Kami bahkan sekarang memiliki opsi untuk meningkatkan GPU Nvidia untuk pencarian cuda, tetapi ini merepresentasikan serangkaian sakit kepala baru dan malam tanpa tidur. Relevansi semua ini akan tergantung pada seberapa sering Anda melihat pencarian Anda dilakukan, dan seberapa reaktif Anda membutuhkannya.

Matthew Baker
sumber
1

Indeks Teks Lengkap memiliki sejumlah batasan. Anda dapat menggunakan wildcard pada kata-kata yang indeks temukan adalah seluruh "bagian" tetapi meskipun begitu Anda dibatasi pada bagian akhir kata. Itu sebabnya Anda bisa menggunakan CONTAINS(Name, '"Azimut*"')tetapi tidakCONTAINS(Name, '"zimuth*"')

Dari dokumentasi Microsoft :

Ketika istilah awalan adalah frasa, setiap token yang membentuk frasa tersebut dianggap sebagai istilah awalan yang terpisah. Semua baris yang memiliki kata-kata yang dimulai dengan istilah awalan akan dikembalikan. Misalnya, istilah awalan "roti ringan *" akan menemukan baris dengan teks "roti ringan," "roti ringan," atau "roti ringan," tetapi tidak akan mengembalikan "roti panggang ringan."

Titik-titik dalam email, seperti yang ditunjukkan oleh judul, bukan masalah utama. Ini, misalnya, berfungsi:

SELECT * FROM NewCompanies 
WHERE CONTAINS((Email, Contacts1, Contacts2, Contacts3, Contacts4), '[email protected]') 

Dalam hal ini, indeks mengidentifikasi seluruh string email sebagai valid, serta "gmail" dan "gmail.com." Hanya "sms" meskipun tidak valid.

Contoh terakhir mirip. Bagian-bagian dari nomor telepon diindeks (666-22-11 dan 999-666-22-11 misalnya), tetapi menghapus tanda hubung bukanlah string yang akan diketahui oleh indeks. Kalau tidak, ini bekerja:

SELECT * FROM NewCompanies 
WHERE CONTAINS((Phone, Contacts1, Contacts2, Contacts3, Contacts4), '"666-22-11*"')
smoore4
sumber