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,000 รายการ

เราคาดว่าผู้ใช้สามารถระบุส่วนของอีเมลเช่น "@ 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 พันล้าน ~ ตัวอักษรเสีย
Larnu

2
ดูเหมือนว่าคุณกำลังต่อสู้กับตัวแบ่งคำค้นหาแบบเต็ม คุณไม่น่าจะพบสิ่งที่ใช้@gmail.comเป็นคำค้นหาเนื่องจาก@ตัวละครเป็นตัวทำลายคำ ในคำอื่น ๆ ขึ้นอยู่กับรุ่นของ SQL Server ที่คุณมีคำในดัชนีสำหรับuser@gmail.comจะเป็นอย่างใดอย่างหนึ่ง (A) user, gmailและcomหรือ (B) user, user@gmail.com, และgmail comREF: การเปลี่ยนแปลงพฤติกรรมในการค้นหาข้อความแบบเต็ม
AlwaysLearning

1
"แต่ฉันไม่ต้องการค้นหาอะไรนอกจากอีเมลและโทรศัพท์ในสาขาเหล่านั้น"จากนั้นพวกเขาควรจะเก็บไว้ในคอลัมน์ที่เหมาะสมเช่นที่ฉันพูดก่อนหน้านี้ คุณมีคอลัมน์สำหรับข้อมูลนั้นซึ่งควรทำให้เป็นมาตรฐาน ตัวแบ่งคำถูกตั้งค่าที่ระดับอินสแตนซ์ / ฐานข้อมูล .ดังนั้นจึงจะมีการเปลี่ยนแปลงอย่างมีนัยสำคัญที่จะทำลายลบ
Larnu

1
คุณอาจต้องการปรับมาตรฐานตารางเป็น 1-M สำหรับโทรศัพท์อีเมล ฯลฯ ระเบียนที่สองตัวเลือกที่สองคือการแยกคอลัมน์ (ใช้ 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 ไม่ไปไม่ได้หมายความว่าไม่ควร เหตุผลที่ OP กำลังประสบปัญหาอยู่เนื่องจากไม่มีการฟื้นฟูสภาพ
ลาร์นู

คำตอบ:


2

ขอจริง

เลือก [... ] มีอยู่ ([... ], '"6662211 *"') - ไม่ได้รับอะไรเลย

ต่อต้าน'Call only at weekends +7-999-666-22-11' และ

เลือก [... ] มีอยู่ (ชื่อ '' zimuth * "') - ไม่ได้รับอะไรเลย

ต่อต้าน 'PJSC Azimuth'

ทำทำงานตามที่คาดไว้
ดูคำนำหน้าระยะ เพราะ6662211*ไม่ได้เป็นคำนำหน้าของ+7-999-666-22-11เช่นเดียวกับzimuth*ไม่ได้เป็นคำนำหน้าของAzimuth

ส่วน

เลือก [... ] มีอยู่ ([... ], '"sms@gmail.com*"') - นี่ไม่ได้รับแถว

นี่อาจเป็นเพราะตัวแบ่งคำตามที่การเรียนรู้ชี้ไปที่ความคิดเห็นเสมอ ดูตัวแบ่งคำ

ฉันไม่คิดว่าการค้นหาข้อความแบบเต็มสามารถใช้ได้กับงานของคุณ

เหตุใดจึงใช้ FTS ในงานเดียวกันที่แน่นอนที่ใช้ตัวดำเนินการ LIKE หากมีดัชนีที่ดีกว่าสำหรับคำสั่ง LIKE ... ก็จะมีประเภทดัชนีที่ดีกว่าไม่ใช่เทคโนโลยีและไวยากรณ์ที่แตกต่างกันโดยสิ้นเชิง
และไม่ว่าในทางใดมันจะช่วยให้คุณ"6662211*"ต่อต้าน "666 บางตัวโดยพลการ 22 ตัวบางตัวได้ 11 ตัว"
การค้นหาข้อความแบบเต็มไม่ได้เกี่ยวกับ regex-es (และ"6662211*"ไม่ใช่แม้แต่การแสดงออกที่ถูกต้องสำหรับงาน - ไม่มีอะไรเกี่ยวกับส่วน "char arbitrary") เกี่ยวกับคำพ้องความหมายรูปแบบคำ ฯลฯ

แต่เป็นไปได้ไหมที่จะค้นหาวัสดุพิมพ์อย่างมีประสิทธิภาพ?

ใช่แล้ว. ทิ้งโอกาสเช่นการเขียนเครื่องมือค้นหาของคุณเองเราจะทำ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

ในกรณีเช่นนี้การค้นหาข้อความแบบเต็มจะน้อยกว่าอุดมคติ ฉันอยู่ในเรือลำเดียวกันกับคุณ Like การค้นหาช้าเกินไปและการค้นหาข้อความแบบเต็มค้นหาคำที่เริ่มต้นด้วยคำแทนที่จะมีคำ

เราได้ลองใช้วิธีแก้ปัญหาหลายวิธีตัวเลือก SQL บริสุทธิ์คือการสร้างการค้นหาข้อความแบบเต็มโดยเฉพาะการค้นหาดัชนีแบบกลับด้าน เราลองทำสิ่งนี้และมันก็ประสบความสำเร็จ แต่ใช้พื้นที่มาก เราสร้างตารางโฮลดิ้งรองสำหรับคำค้นหาบางส่วนและใช้การจัดทำดัชนีข้อความแบบเต็ม อย่างไรก็ตามนี่หมายความว่าเราเก็บสิ่งเดียวกันหลาย ๆ ครั้งซ้ำ ๆ กัน ตัวอย่างเช่นเราเก็บ "longword" เป็น Longword, ongword, ngword, gword .... เป็นต้นดังนั้นวลีใด ๆ ที่มีอยู่จะเป็นจุดเริ่มต้นของคำที่จัดทำดัชนีเสมอ วิธีการแก้ปัญหาที่น่ากลัวเต็มไปด้วยข้อบกพร่อง แต่มันได้ผล

จากนั้นเราดูที่การโฮสต์เซิร์ฟเวอร์แยกต่างหากสำหรับการค้นหา Googling Lucene และ elastisearch จะให้ข้อมูลที่ดีเกี่ยวกับแพ็คเกจชั้นวาง

ในที่สุดเราพัฒนาเครื่องมือค้นหาของเราเองซึ่งทำงานควบคู่ไปกับ SQL สิ่งนี้ทำให้เราสามารถใช้การค้นหาแบบออกเสียง (metaphone คู่) จากนั้นใช้การคำนวณ levenshtein ควบคู่ไปกับ soundex ด้านข้างเพื่อสร้างความเกี่ยวข้อง เกินขนาดสำหรับการแก้ปัญหาจำนวนมาก แต่คุ้มค่ากับความพยายามในกรณีการใช้งานของเรา ตอนนี้เรามีตัวเลือกในการใช้ประโยชน์จาก GPU Nvidia สำหรับการค้นหา cuda แต่สิ่งนี้แสดงให้เห็นถึงความปวดหัวและการนอนไม่หลับทั้งคืน ความเกี่ยวข้องของสิ่งเหล่านี้จะขึ้นอยู่กับความถี่ที่คุณเห็นการค้นหาของคุณและปฏิกิริยาที่คุณต้องการให้เป็น


1

ดัชนีข้อความแบบเต็มมีจำนวนข้อ จำกัด คุณสามารถใช้ไวด์การ์ดในคำที่ดัชนีพบว่าเป็น "ส่วน" ทั้งหมด แต่ถึงอย่างนั้นคุณก็จะถูกบังคับให้ใช้ส่วนท้ายของคำนั้น นั่นคือเหตุผลที่คุณสามารถใช้งานCONTAINS(Name, '"Azimut*"')ได้ แต่ไม่ใช่CONTAINS(Name, '"zimuth*"')

จากเอกสารของ Microsoft :

เมื่อคำนำหน้าเป็นวลีแต่ละโทเค็นที่ประกอบกันเป็นวลีจะถือว่าเป็นคำนำหน้าแยกต่างหาก แถวทั้งหมดที่มีคำที่ขึ้นต้นด้วยคำนำหน้าจะถูกส่งคืน ตัวอย่างเช่นคำนำหน้า "light bread *" จะค้นหาแถวที่มีข้อความของ "light breaded," "light breaded เล็กน้อย" หรือ "light bread" แต่จะไม่ส่งคืน "toasted bread toly"

จุดในอีเมลตามที่ระบุโดยชื่อไม่ใช่ประเด็นหลัก ตัวอย่างเช่นนี้ใช้งานได้:

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*"')
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.