วิธีค้นหาส่วนหนึ่งของคำด้วย ElasticSearch


128

ฉันเพิ่งเริ่มใช้ ElasticSearch และดูเหมือนจะไม่สามารถค้นหาส่วนใดส่วนหนึ่งของคำได้

ตัวอย่าง: ฉันมีเอกสารสามชุดจาก couchdb ที่จัดทำดัชนีใน ElasticSearch:

{
  "_id" : "1",
  "name" : "John Doeman",
  "function" : "Janitor"
}
{
  "_id" : "2",
  "name" : "Jane Doewoman",
  "function" : "Teacher"
}
{
  "_id" : "3",
  "name" : "Jimmy Jackal",
  "function" : "Student"
} 

ตอนนี้ฉันต้องการค้นหาเอกสารทั้งหมดที่มี "Doe"

curl http://localhost:9200/my_idx/my_type/_search?q=Doe

ที่ไม่กลับมาฮิตใด ๆ แต่ถ้าฉันค้นหา

curl http://localhost:9200/my_idx/my_type/_search?q=Doeman

ส่งคืนเอกสารหนึ่งฉบับ (John Doeman)

ฉันได้ลองตั้งค่าตัววิเคราะห์ที่แตกต่างกันและตัวกรองต่างๆเป็นคุณสมบัติของดัชนีของฉัน ฉันได้ลองใช้ข้อความค้นหาแบบเต็ม (ตัวอย่างเช่น:

{
  "query": {
    "term": {
      "name": "Doe"
    }
  }
}

) แต่ดูเหมือนจะไม่ได้ผล

ฉันจะทำให้ ElasticSearch พบทั้ง John Doeman และ Jane Doewoman ได้อย่างไรเมื่อฉันค้นหา "Doe"

UPDATE

ฉันพยายามใช้โทเค็นและตัวกรอง nGram เช่นเดียวกับที่อิกอร์เสนอดังนี้:

{
  "index": {
    "index": "my_idx",
    "type": "my_type",
    "bulk_size": "100",
    "bulk_timeout": "10ms",
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "type": "custom",
          "tokenizer": "my_ngram_tokenizer",
          "filter": [
            "my_ngram_filter"
          ]
        }
      },
      "filter": {
        "my_ngram_filter": {
          "type": "nGram",
          "min_gram": 1,
          "max_gram": 1
        }
      },
      "tokenizer": {
        "my_ngram_tokenizer": {
          "type": "nGram",
          "min_gram": 1,
          "max_gram": 1
        }
      }
    }
  }
}

ปัญหาที่ฉันพบตอนนี้คือแต่ละแบบสอบถามส่งคืนเอกสารทั้งหมด คำแนะนำใด ๆ เอกสาร ElasticSearch เกี่ยวกับการใช้ nGram ไม่ดี ...


9
ไม่น่าแปลกใจที่คุณตั้งค่า min / max สูงสุดเป็น 1 ดังนั้น 1 ตัวอักษร :)
Martin B.

คำตอบ:


85

ฉันใช้ nGram ด้วย ฉันใช้โทเค็นไนเซอร์มาตรฐานและ nGram เป็นตัวกรอง นี่คือการตั้งค่าของฉัน:

{
  "index": {
    "index": "my_idx",
    "type": "my_type",
    "analysis": {
      "index_analyzer": {
        "my_index_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "mynGram"
          ]
        }
      },
      "search_analyzer": {
        "my_search_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "standard",
            "lowercase",
            "mynGram"
          ]
        }
      },
      "filter": {
        "mynGram": {
          "type": "nGram",
          "min_gram": 2,
          "max_gram": 50
        }
      }
    }
  }
}

ให้คุณค้นหาส่วนของคำได้ถึง 50 ตัวอักษร ปรับ max_gram ตามที่คุณต้องการ ในคำภาษาเยอรมันอาจมีขนาดใหญ่มากดังนั้นฉันจึงตั้งค่าให้สูง



นั่นคือสิ่งที่คุณได้รับจากการตั้งค่าของดัชนีหรือนั่นคือสิ่งที่คุณโพสต์ไปยัง elasticsearch เพื่อกำหนดค่า?
Tomas Jansson

เป็น POST สำหรับกำหนดค่า Elasticsearch
roka

ฉันไม่ได้ บริษัท กับรุ่นปัจจุบันของ ElasticSearch แต่ควรพูดถึงมันในเอกสาร: elastic.co/guide/en/elasticsearch/reference/current/index.html
Roka

1
@JimC ฉันไม่ได้ใช้ ElasticSearch เป็นเวลาอย่างน้อย 7 ปีดังนั้นฉันจึงไม่รู้การเปลี่ยนแปลงในปัจจุบันของโครงการ
roka

63

การค้นหาด้วยสัญลักษณ์นำหน้าและต่อท้ายจะช้ามากในดัชนีขนาดใหญ่ หากคุณต้องการค้นหาด้วยคำนำหน้าคำให้ลบสัญลักษณ์นำหน้าออก หากคุณต้องการค้นหาสตริงย่อยที่อยู่ตรงกลางของคำคุณควรใช้ ngram tokenizer ดีกว่า


14
อิกอร์พูดถูก อย่างน้อยก็ลบ * นำหน้า สำหรับตัวอย่าง NGram ElasticSearch ดูส่วนสำคัญนี้: gist.github.com/988923
karmi

3
@karmi: ขอบคุณสำหรับตัวอย่างที่สมบูรณ์ของคุณ! บางทีคุณอาจต้องการเพิ่มความคิดเห็นของคุณเป็นคำตอบจริงสิ่งที่ทำให้ฉันได้ผลและสิ่งที่ฉันต้องการเพิ่มคะแนน
Fabian Steeg

54

ฉันคิดว่าไม่จำเป็นต้องเปลี่ยนการทำแผนที่ใด ๆ ลองนำไปใช้ query_stringก็สมบูรณ์แบบ สถานการณ์ทั้งหมดจะทำงานร่วมกับตัววิเคราะห์มาตรฐานเริ่มต้น:

เรามีข้อมูล:

{"_id" : "1","name" : "John Doeman","function" : "Janitor"}
{"_id" : "2","name" : "Jane Doewoman","function" : "Teacher"}

สถานการณ์ที่ 1:

{"query": {
    "query_string" : {"default_field" : "name", "query" : "*Doe*"}
} }

การตอบสนอง:

{"_id" : "1","name" : "John Doeman","function" : "Janitor"}
{"_id" : "2","name" : "Jane Doewoman","function" : "Teacher"}

สถานการณ์ที่ 2:

{"query": {
    "query_string" : {"default_field" : "name", "query" : "*Jan*"}
} }

การตอบสนอง:

{"_id" : "1","name" : "John Doeman","function" : "Janitor"}

สถานการณ์ที่ 3:

{"query": {
    "query_string" : {"default_field" : "name", "query" : "*oh* *oe*"}
} }

การตอบสนอง:

{"_id" : "1","name" : "John Doeman","function" : "Janitor"}
{"_id" : "2","name" : "Jane Doewoman","function" : "Teacher"}

แก้ไข - การใช้งานเดียวกันกับการค้นหาแบบยืดหยุ่นของข้อมูลสปริง https://stackoverflow.com/a/43579948/2357869

อีกหนึ่งคำอธิบายว่า query_string ดีกว่าที่อื่นอย่างไร https://stackoverflow.com/a/43321606/2357869


3
ฉันคิดว่านี่เป็นวิธีที่ง่ายที่สุด
Esgi Dendyanri

ครับ. ฉันได้ดำเนินการในโครงการของฉัน
Opster Elasticsearch Pro-Vijay

จะรวมหลายช่องเพื่อค้นหาได้อย่างไร?
Shubham A.

ลองสิ่งนี้: - {"query": {"query_string": {"fields": ["content", "name"], "query": "this AND that"}}}
Opster Elasticsearch Pro-Vijay


14

โดยไม่ต้องเปลี่ยนการแมปดัชนีของคุณคุณสามารถทำการค้นหาคำนำหน้าแบบง่ายๆที่จะทำการค้นหาบางส่วนอย่างที่คุณคาดหวัง

กล่าวคือ

{
  "query": { 
    "prefix" : { "name" : "Doe" }
  }
}

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-prefix-query.html


คุณสามารถค้นหาหลายช่องโดยใช้คำนำหน้าได้หรือไม่?
batmaci

ขอบคุณสิ่งที่ฉันกำลังมองหา! มีความคิดเห็นเกี่ยวกับผลกระทบด้านประสิทธิภาพหรือไม่?
Vingtoft

6

ลองใช้วิธีแก้ปัญหาโดยอธิบายไว้ที่นี่: การค้นหาสตริงย่อยที่แน่นอนใน ElasticSearch

{
    "mappings": {
        "my_type": {
            "index_analyzer":"index_ngram",
            "search_analyzer":"search_ngram"
        }
    },
    "settings": {
        "analysis": {
            "filter": {
                "ngram_filter": {
                    "type": "ngram",
                    "min_gram": 3,
                    "max_gram": 8
                }
            },
            "analyzer": {
                "index_ngram": {
                    "type": "custom",
                    "tokenizer": "keyword",
                    "filter": [ "ngram_filter", "lowercase" ]
                },
                "search_ngram": {
                    "type": "custom",
                    "tokenizer": "keyword",
                    "filter": "lowercase"
                }
            }
        }
    }
}

เพื่อแก้ปัญหาการใช้งานดิสก์และปัญหาข้อความค้นหาที่ยาวเกินไปจะใช้ngramsสั้น 8 อักขระ(กำหนดค่าด้วย: "max_gram": 8 ) หากต้องการค้นหาคำที่มีอักขระมากกว่า 8 ตัวให้เปลี่ยนการค้นหาของคุณเป็นคำค้นหาบูลีน AND โดยมองหาสตริงย่อย 8 อักขระที่แตกต่างกันทั้งหมดในสตริงนั้น ตัวอย่างเช่นหากผู้ใช้ค้นหาหลาขนาดใหญ่ (สตริง 10 อักขระ) การค้นหาจะเป็น:

"arge ya AND arge yar AND rge yard .


2
ลิงก์ที่ตายแล้วโปรดแก้ไข
DarkMukke

ฉันหาอะไรแบบนี้มาสักพักแล้ว ขอบคุณ! คุณรู้หรือไม่ว่าเครื่องชั่งน้ำหนักกับหน่วยความจำmin_gramและmax_gramดูเหมือนว่ามันจะเป็นเส้นตรงขึ้นอยู่กับขนาดของค่าสนามและช่วงของและmin maxขมวดคิ้วเมื่อใช้อะไรแบบนี้?
Glen Thompson

นอกจากนี้ยังมีเหตุผลใดที่ngramตัวกรองนี้อยู่เหนือ tokenizer? คุณไม่เพียง แต่มีมันเป็นindex_ngram: { type: "custom", tokenizer: "ngram_tokenizer", filter: [ "lowercase" ] }
Glen Thompson

2

หากคุณต้องการใช้ฟังก์ชันการเติมข้อความอัตโนมัติเสร็จสิ้น Suggesterเป็นโซลูชันที่เรียบร้อยที่สุด โพสต์บล็อกถัดไปมีคำอธิบายที่ชัดเจนมากว่ามันทำงานอย่างไร

กล่าวได้สองคำคือโครงสร้างข้อมูลในหน่วยความจำเรียกว่า FST ซึ่งมีคำแนะนำที่ถูกต้องและได้รับการปรับให้เหมาะสมสำหรับการดึงข้อมูลและการใช้หน่วยความจำอย่างรวดเร็ว โดยพื้นฐานแล้วมันเป็นเพียงกราฟ ยกตัวอย่างเช่น FST และมีคำhotel, marriot, mercure, munchenและmunichจะมีลักษณะเช่นนี้

ป้อนคำอธิบายภาพที่นี่


2

คุณสามารถใช้ regexp

{ "_id" : "1", "name" : "John Doeman" , "function" : "Janitor"}
{ "_id" : "2", "name" : "Jane Doewoman","function" : "Teacher"  }
{ "_id" : "3", "name" : "Jimmy Jackal" ,"function" : "Student"  } 

หากคุณใช้แบบสอบถามนี้:

{
  "query": {
    "regexp": {
      "name": "J.*"
    }
  }
}

คุณจะได้รับข้อมูลทั้งหมดที่ชื่อของพวกเขาขึ้นต้นด้วย "J" พิจารณาว่าคุณต้องการรับเพียงสองระเบียนแรกที่ชื่อของพวกเขาลงท้ายด้วย "man" เพื่อให้คุณสามารถใช้ข้อความค้นหานี้:

{
  "query": { 
    "regexp": {
      "name": ".*man"
    }
  }
}

และหากคุณต้องการรับบันทึกทั้งหมดที่มีอยู่ในชื่อ "m" คุณสามารถใช้ข้อความค้นหานี้:

{
  "query": { 
    "regexp": {
      "name": ".*m.*"
    }
  }
}

สิ่งนี้ใช้ได้กับฉันและฉันหวังว่าคำตอบของฉันจะเหมาะกับการแก้ปัญหาของคุณ


1

การใช้ wilcards (*) ป้องกันการคำนวณคะแนน


1
คุณช่วยเพิ่มรายละเอียดคำตอบของคุณได้ไหม ระบุรหัสตัวอย่างหรือเอกสารอ้างอิงเกี่ยวกับสิ่งที่ทำ
Cray

0

ฉันใช้สิ่งนี้และได้ผล

"query": {
        "query_string" : {
            "query" : "*test*",
            "fields" : ["field1","field2"],
            "analyze_wildcard" : true,
            "allow_leading_wildcard": true
        }
    }

-6

ไม่เป็นไร.

ฉันต้องดูเอกสารของ Lucene ดูเหมือนว่าฉันสามารถใช้สัญลักษณ์แทนได้! :-)

curl http://localhost:9200/my_idx/my_type/_search?q=*Doe*

เคล็ดลับ!


11
ดูคำตอบของ @imotov การใช้สัญลักษณ์แทนจะไม่สามารถปรับขนาดได้ดีเลย
Mike Munroe

5
@Idx - ดูว่าคำตอบของคุณถูกลงคะแนนอย่างไร การโหวตลงแสดงถึงคุณภาพและความเกี่ยวข้องของคำตอบ คุณขอเวลาสักครู่เพื่อยอมรับคำตอบที่ถูกต้องได้ไหม อย่างน้อยผู้ใช้ใหม่จะขอบคุณคุณ
asyncwait

3
โหวตดาวน์มากพอ OP ทำให้ชัดเจนว่าตอนนี้คำตอบที่ดีที่สุดคืออะไร +1 สำหรับการแบ่งปันสิ่งที่ดูเหมือนจะเป็นคำตอบที่ดีที่สุดก่อนที่จะมีคนโพสต์สิ่งที่ดีกว่า
แดเนียล
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.