Google Firestore: ค้นหาสตริงย่อยของค่าคุณสมบัติ (ค้นหาข้อความ)


112

ฉันต้องการเพิ่มช่องค้นหาง่ายๆต้องการใช้สิ่งที่ต้องการ

collectionRef.where('name', 'contains', 'searchTerm')

ฉันลองใช้where('name', '==', '%searchTerm%')แต่มันกลับไม่ได้อะไรเลย


1
ตอนนี้ Firebase รองรับสิ่งนี้แล้ว โปรดอัปเดตคำตอบ: stackoverflow.com/a/52715590/2057171
Albert Renshaw

1
ฉันคิดว่าวิธีที่ดีที่สุดคือสร้างสคริปต์ที่จัดทำดัชนีเอกสารแต่ละฉบับด้วยตนเอง จากนั้นค้นหาดัชนีเหล่านั้น ตรวจสอบสิ่งนี้: angularfirebase.com/lessons/…
Kiblawi_Rabee

คำตอบ:


42

ไม่มีผู้ประกอบการดังกล่าวเป็นคนที่ได้รับอนุญาตให้มี==, <, <=, ,>>=

คุณสามารถกรองตามคำนำหน้าเท่านั้นตัวอย่างเช่นสำหรับทุกสิ่งที่เริ่มต้นระหว่างbarและfooคุณสามารถใช้ได้

collectionRef.where('name', '>=', 'bar').where('name', '<=', 'foo')

คุณสามารถใช้บริการภายนอกเช่นAlgoliaหรือ ElasticSearch ได้


6
นั่นไม่ใช่สิ่งที่ฉันกำลังมองหา ฉันมีรายการสินค้ามากมายที่มีชื่อยาว ๆ "ไม้เทนนิส Rebok Mens". ผู้ใช้อาจค้นหาtennisแต่ขึ้นอยู่กับตัวดำเนินการค้นหาที่มีอยู่ไม่มีทางที่จะได้รับผลลัพธ์เหล่านั้น การรวม>=และ<=ไม่ทำงาน แน่นอนว่าฉันสามารถใช้ Algolia ได้ แต่ฉันก็สามารถใช้กับ Firebase เพื่อตอบคำถามส่วนใหญ่และไม่จำเป็นต้องเปลี่ยนไปใช้ Firestore ...
tehfailsafe

5
@tehfailsafe คำถามของคุณคือ 'จะสอบถามว่าฟิลด์มีสตริงได้อย่างไร' และคำตอบคือ 'คุณไม่สามารถทำได้'
Kuba

21
@ อ. ชากรณ์อะไรคือคำตอบของฉันที่หยาบคาย?
Kuba

20
นี่คือสิ่งที่จำเป็นจริงๆ ฉันไม่เข้าใจว่าทำไมทีมของ Firebase ถึงไม่คิดเรื่องนี้
Dani

2
แปลกใจจริงๆที่ Firebase มีความอ่อนแอในการสืบค้น ไม่อยากจะเชื่อเลยว่าจะมีคนจำนวนมากใช้มันหากไม่สามารถรองรับข้อความค้นหาง่ายๆเช่นนี้ได้
Bagusflyer

51

ฉันเห็นด้วยกับคำตอบของ @ Kuba แต่ถึงกระนั้นก็ต้องเพิ่มการเปลี่ยนแปลงเล็กน้อยเพื่อให้ทำงานได้อย่างสมบูรณ์แบบสำหรับการค้นหาด้วยคำนำหน้า นี่คือสิ่งที่เหมาะกับฉัน

สำหรับการค้นหาระเบียนที่ขึ้นต้นด้วยชื่อ queryText

collectionRef.where('name', '>=', queryText).where('name', '<=', queryText+ '\uf8ff').

อักขระที่\uf8ffใช้ในแบบสอบถามเป็นจุดรหัสที่สูงมากในช่วง Unicode (เป็นรหัสพื้นที่การใช้งานส่วนตัว [PUA]) เพราะมันเป็นหลังจากที่ตัวละครปกติมากที่สุดใน Unicode queryTextแบบสอบถามตรงกับค่าทั้งหมดที่เริ่มต้นด้วย


2
คำตอบที่ดี! ใช้งานได้ดีในการค้นหาข้อความนำหน้า หากต้องการค้นหาคำในข้อความอาจลองใช้ "array-contains" ตามที่อธิบายไว้ในโพสต์นี้medium.com/@ken11zer01/…
guillefd

แค่คิด แต่ในทางทฤษฎีคุณสามารถจับคู่ค่าทั้งหมดที่ลงท้ายด้วยqueryTest ได้โดยการสร้างฟิลด์อื่นและกลับข้อมูล ...
Jonathan

ใช่ @ โจนาธานนั่นก็เป็นไปได้เช่นกัน
Ankit Prajapati

43

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

{
  'terms': {
    'reebok': true,
    'mens': true,
    'tennis': true,
    'racket': true
  }
}

ตอนนี้คุณสามารถสอบถามด้วย

collectionRef.where('terms.tennis', '==', true)

ใช้งานได้เนื่องจาก Firestore จะสร้างดัชนีสำหรับทุกฟิลด์โดยอัตโนมัติ น่าเสียดายที่สิ่งนี้ใช้ไม่ได้โดยตรงสำหรับการสืบค้นแบบผสมเนื่องจาก Firestore ไม่ได้สร้างดัชนีผสมโดยอัตโนมัติ

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

คุณยังอาจจะดีกว่าที่มีเรือค้นหาข้อความเต็ม


มันเป็นไปได้ที่จะใช้ฟังก์ชั่นคลาวด์กับcloud.google.com/appengine/docs/standard/java/search ?
Henry

1
หากคุณถามสิ่งนี้เพื่อติดตามคำตอบนี้: การค้นหาข้อความแบบเต็มของ AppEngine แยกจาก Firestore โดยสิ้นเชิงดังนั้นสิ่งนี้จะไม่ช่วยคุณโดยตรง คุณสามารถจำลองข้อมูลของคุณโดยใช้ฟังก์ชันระบบคลาวด์ แต่โดยพื้นฐานแล้วคำแนะนำในการใช้การค้นหาข้อความแบบเต็มนอกเรือคืออะไร หากคุณกำลังถามอย่างอื่นโปรดเริ่มคำถามใหม่
Gil Gilbert

1
ใน Firestore คุณต้องจัดทำดัชนีคำศัพท์ทั้งหมดก่อนใช้where
Husam

4
ดังที่ Husam กล่าวถึงฟิลด์เหล่านี้ทั้งหมดจะต้องได้รับการจัดทำดัชนี ฉันต้องการเปิดใช้งานการค้นหาคำใด ๆ ที่ชื่อผลิตภัณฑ์ของฉันมี ดังนั้นฉันจึงสร้างคุณสมบัติประเภท 'วัตถุ' บนเอกสารของฉันโดยมีคีย์เป็นส่วนหนึ่งของชื่อผลิตภัณฑ์โดยแต่ละรายการจะมีค่า 'true' ที่กำหนดให้โดยหวังว่าการค้นหาโดยที่ ('nameSegments.tennis', '==', true) จะ ทำงานได้ แต่ firestore แนะนำให้สร้างดัชนีสำหรับ nameSegments.tennis เหมือนกันสำหรับทุก ๆ คำ เนื่องจากสามารถมีจำนวนคำได้ไม่ จำกัด คำตอบนี้จึงใช้ได้เฉพาะกับสถานการณ์ที่ จำกัด มากเมื่อมีการกำหนดคำค้นหาทั้งหมดไว้ข้างหน้า
Slawoj

2
@epeleg คำค้นหาจะใช้งานได้หลังจากที่คุณสร้างดัชนีแล้ว แต่ยังไม่สามารถสร้างดัชนีสำหรับแต่ละคำที่เป็นไปได้ในชื่อผลิตภัณฑ์ของคุณดังนั้นสำหรับการค้นหาข้อความในชื่อผลิตภัณฑ์วิธีนี้ใช้ไม่ได้กับกรณีของฉัน
Slawoj

31

แม้ว่า Firebase จะไม่สนับสนุนการค้นหาคำภายในสตริงอย่างชัดเจน

Firebase รองรับสิ่งต่อไปนี้ซึ่งจะแก้ไขปัญหาของคุณและอื่น ๆ อีกมากมาย:

ตั้งแต่เดือนสิงหาคม 2018 พวกเขารองรับการarray-containsสืบค้น ดู: https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html

ตอนนี้คุณสามารถกำหนดคำสำคัญทั้งหมดของคุณลงในอาร์เรย์เป็นฟิลด์จากนั้นค้นหาเอกสารทั้งหมดที่มีอาร์เรย์ที่มี 'X' คุณสามารถใช้ตรรกะ AND เพื่อทำการเปรียบเทียบเพิ่มเติมสำหรับการสืบค้นเพิ่มเติม (เนื่องจากปัจจุบัน firebase ไม่สนับสนุนการสืบค้นแบบผสมสำหรับการสืบค้นที่มีอาร์เรย์หลายรายการดังนั้นการเรียงลำดับจะต้องดำเนินการตามคำสั่ง 'AND' ที่ส่วนท้ายของไคลเอ็นต์)

การใช้อาร์เรย์ในลักษณะนี้จะช่วยให้สามารถปรับให้เหมาะสมสำหรับการเขียนพร้อมกันซึ่งดีมาก! ยังไม่ได้ทดสอบว่ารองรับคำขอเป็นกลุ่ม (เอกสารไม่ได้บอก) แต่ฉันจะเดิมพันเพราะเป็นโซลูชันอย่างเป็นทางการ


การใช้งาน:

collection("collectionPath").
    where("searchTermsArray", "array-contains", "term").get()

13
นี่เป็นทางออกที่ดี อย่างไรก็ตามโปรดแก้ไขหากฉันผิด แต่ฉันคิดว่ามันไม่อนุญาตให้คุณทำตามที่ @tehfailsafe ขอไว้ เช่นหากคุณต้องการรับชื่อทั้งหมดที่มีสตริง "abc" คุณจะไม่ประสบความสำเร็จกับ array-contains เนื่องจากจะส่งคืนเฉพาะเอกสารที่มีชื่อ "abc" เท่านั้น แต่ "abcD" หรือ "0abc" จะไม่ทำงาน
Yulian

1
@Yulian ในโลกแห่งการเขียนโปรแกรมSearch termมักจะเข้าใจกันว่าหมายถึงคำศัพท์ทั้งหมดที่คั่นด้วยช่องว่างเครื่องหมายวรรคตอน ฯลฯ ทั้งสองด้าน หากคุณ google abcdeตอนนี้คุณจะพบผลการค้นหาสำหรับสิ่งที่ต้องการ%20abcde.หรือแต่ไม่,abcde! abcdefghijk..แม้ว่าตัวอักษรทั้งหมดที่พิมพ์จะพบได้ทั่วไปในอินเทอร์เน็ต แต่การค้นหาไม่ได้ใช้ abcde * สำหรับ abcde ที่แยกต่างหาก
Albert Renshaw

1
ฉันเห็นประเด็นของคุณและฉันเห็นด้วยกับมัน แต่คำนี้อาจทำให้เข้าใจผิด'contains'ซึ่งหมายถึงสิ่งที่ฉันอ้างถึงในภาษาโปรแกรมต่างๆ เช่นเดียวกับ'%searchTerm%'จากจุดยืนของ SQL
Yulian

2
@ ยูเลียนใช่ฉันเข้าใจแล้ว Firebase เป็น NoSQL ดังนั้นจึงเป็นเรื่องที่ดีในการทำให้การดำเนินการประเภทนี้รวดเร็วและมีประสิทธิภาพแม้ว่าอาจมีข้อ จำกัด สำหรับปัญหานอกขอบเขตเช่นการค้นหาสตริงการ์ดตัวแทน
Albert Renshaw

2
คุณสามารถสร้างฟิลด์แยกสำหรับแต่ละฟิลด์โดยใช้การแทนคำที่แยกออกเช่น titleArray: ['this', 'is', 'a', 'title'] ทุกครั้งที่คุณอัปเดตเอกสาร จากนั้นการค้นหาจะขึ้นอยู่กับฟิลด์นั้นแทนชื่อเรื่อง คุณเย็นสร้าง triiger onUpdate เพื่อสร้างฟิลด์นี้ งานจำนวนมากสำหรับข้อความที่ใช้การค้นหา แต่ฉันค่อนข้างมีการปรับปรุงประสิทธิภาพของ NoSQL
sfratini

16

ตามเอกสารของ Firestore Cloud Firestore ไม่รองรับการจัดทำดัชนีดั้งเดิมหรือค้นหาช่องข้อความในเอกสาร นอกจากนี้การดาวน์โหลดคอลเลกชันทั้งหมดเพื่อค้นหาฟิลด์ฝั่งไคลเอ็นต์ไม่สามารถใช้งานได้จริง

ขอแนะนำให้ใช้โซลูชันการค้นหาของบุคคลที่สามเช่นAlgoliaและElastic Search


49
ฉันอ่านเอกสารแล้วแม้ว่าจะไม่เหมาะ ข้อเสียเปรียบคือ Algolia และ Firestore มีรูปแบบการกำหนดราคาที่แตกต่างกัน ... ฉันสามารถมีเอกสาร 600,000 รายการใน Firestore ได้อย่างมีความสุข (ตราบใดที่ฉันไม่ได้สอบถามมากเกินไปต่อวัน) เมื่อฉันส่งพวกเขาไปที่ Algolia เพื่อค้นหาตอนนี้ฉันต้องจ่าย Algolia $ 310 ต่อเดือนเพื่อให้สามารถค้นหาชื่อในเอกสาร Firestore ของฉันได้
tehfailsafe

2
ปัญหาคือมันไม่ฟรี
Dani

นี่คือคำตอบที่ถูกต้องสำหรับคำถามที่วางไว้และควรได้รับการยอมรับว่าดีที่สุด
briznad

13

หมายเหตุบางประการที่นี่:

1. ) \uf8ffทำงานในลักษณะเดียวกับ~

2. )คุณสามารถใช้ where clause หรือ start end clauses:

ref.orderBy('title').startAt(term).endAt(term + '~');

เหมือนกับ

ref.where('title', '>=', term).where('title', '<=', term + '~');

3. )ไม่มันจะไม่ทำงานหากคุณย้อนกลับstartAt()และendAt()ในทุกชุดค่าผสมอย่างไรก็ตามคุณสามารถบรรลุผลลัพธ์เดียวกันได้โดยการสร้างช่องค้นหาที่สองที่ย้อนกลับและรวมผลลัพธ์เข้าด้วยกัน

ตัวอย่าง: ขั้นแรกคุณต้องบันทึกเวอร์ชันย้อนกลับของฟิลด์เมื่อสร้างฟิลด์ สิ่งนี้:

// collection
const postRef = db.collection('posts')

async function searchTitle(term) {

  // reverse term
  const termR = term.split("").reverse().join("");

  // define queries
  const titles = postRef.orderBy('title').startAt(term).endAt(term + '~').get();
  const titlesR = postRef.orderBy('titleRev').startAt(termR).endAt(termR + '~').get();

  // get queries
  const [titleSnap, titlesRSnap] = await Promise.all([
    titles,
    titlesR
  ]);
  return (titleSnap.docs).concat(titlesRSnap.docs);
}

ด้วยวิธีนี้คุณสามารถค้นหาตัวอักษรสุดท้ายของฟิลด์สตริงและตัวแรกไม่ใช่ตัวอักษรกลางแบบสุ่มหรือกลุ่มของตัวอักษร ใกล้เคียงกับผลลัพธ์ที่ต้องการมากขึ้น อย่างไรก็ตามสิ่งนี้จะไม่ช่วยเราจริงๆเมื่อเราต้องการอักษรกลางหรือคำแบบสุ่ม นอกจากนี้อย่าลืมบันทึกตัวพิมพ์เล็กทั้งหมดหรือสำเนาตัวพิมพ์เล็กสำหรับการค้นหาดังนั้นกรณีจะไม่มีปัญหา

4. )หากคุณมีเพียงไม่กี่คำKen Tan's Methodจะทำทุกอย่างที่คุณต้องการหรืออย่างน้อยก็หลังจากที่คุณปรับเปลี่ยนเล็กน้อย อย่างไรก็ตามด้วยข้อความเพียงย่อหน้าคุณจะสร้างข้อมูลได้มากกว่า 1MB แบบทวีคูณซึ่งใหญ่กว่าขีด จำกัด ขนาดเอกสารของ firestore (ฉันรู้ฉันทดสอบแล้ว)

5. )หากคุณสามารถรวมอาร์เรย์ประกอบด้วย (หรืออาร์เรย์บางรูปแบบ) เข้ากับ\uf8ffเคล็ดลับคุณอาจมีการค้นหาที่ทำงานได้ซึ่งไม่ถึงขีด จำกัด ฉันลองทุกชุดแม้กระทั่งกับแผนที่และไม่ต้องไปเลย ใครคิดออกโพสต์ไว้ที่นี่

6. )หากคุณต้องหลีกหนีจาก ALGOLIA และ ELASTIC SEARCH และฉันไม่โทษคุณเลยคุณสามารถใช้ mySQL, postSQL หรือ neo4J บน Google Cloud ได้ตลอดเวลา ทั้ง 3 ตั้งค่าได้ง่ายและมีระดับฟรี คุณจะมีฟังก์ชันคลาวด์หนึ่งฟังก์ชันเพื่อบันทึกข้อมูล onCreate () และอีกฟังก์ชัน onCall () เพื่อค้นหาข้อมูล ง่ายๆ ... ish. ทำไมไม่เปลี่ยนไปใช้ mySQL ล่ะ? ข้อมูลเรียลไทม์แน่นอน! เมื่อมีคนเขียนDGraphด้วย websocks สำหรับข้อมูลแบบเรียลไทม์นับฉันเข้า!

Algolia และ ElasticSearch ถูกสร้างขึ้นเพื่อให้เป็น dbs สำหรับการค้นหาเท่านั้นดังนั้นจึงไม่มีอะไรเร็วเท่า ... แต่คุณต้องจ่ายเอง Google ทำไมคุณถึงนำเราออกจาก Google และคุณไม่ติดตาม MongoDB noSQL และอนุญาตให้ค้นหา?

อัปเดต - ฉันสร้างโซลูชัน:

https://fireblog.io/blog/post/firestore-full-text-search


ภาพรวมที่ดีและเป็นประโยชน์มาก
RedFilter

สุดยอด! โหวตให้การตอบสนองที่มีโครงสร้างดีและให้ข้อมูล
King Of The Jungle

11

คำตอบช้า แต่สำหรับใครก็ตามที่ยังหาคำตอบอยู่สมมติว่าเรามีกลุ่มผู้ใช้และในเอกสารแต่ละชุดของคอลเล็กชันเรามีฟิลด์ "ชื่อผู้ใช้" ดังนั้นหากต้องการค้นหาเอกสารที่ชื่อผู้ใช้ขึ้นต้นด้วย "al" เราสามารถทำสิ่งที่ชอบ

 FirebaseFirestore.getInstance().collection("users").whereGreaterThanOrEqualTo("username", "al")

นี่เป็นวิธีแก้ปัญหาที่ง่ายมากขอบคุณ แต่ถ้าคุณต้องการตรวจสอบมากกว่าหนึ่งฟิลด์ ชอบ "ชื่อ" และ "คำอธิบาย" ที่เชื่อมต่อกันด้วย OR?
ทดลองใช้

ฉันไม่คิดว่าคุณจะสามารถค้นหาโดยอิงจากสองช่องได้ แต่น่าเสียดายที่ firebase ไม่ดีเมื่อพูดถึงการสืบค้นคุณสามารถตรวจสอบสิ่งนี้ได้หวังว่ามันจะช่วยให้stackoverflow.com/questions/26700924/…
MoTahir

1
คอนเฟิร์ม @MoTahir. ไม่มี "OR" ใน Firestore
Rap

โซลูชันนั้นไม่ตรงกับชื่อผู้ใช้ที่ขึ้นต้นด้วย "al" ... เช่น "hello" จะถูกจับคู่ ("hello"> "al")
antoine129

การสืบค้นโดย OR เป็นเพียงการรวมผลการค้นหาสองรายการเข้าด้วยกัน การเรียงลำดับผลลัพธ์นั้นเป็นปัญหาที่แตกต่างกัน ...
Jonathan

7

ฉันแน่ใจว่า Firebase จะออกมาพร้อมกับ "string-contains" เร็ว ๆ นี้เพื่อจับดัชนี [i] startAt ในสตริง ... แต่ฉันได้ค้นคว้าเว็บและพบว่าโซลูชันนี้คิดโดยคนอื่นตั้งค่าข้อมูลของคุณ นี้

state = {title:"Knitting"}
...
const c = this.state.title.toLowerCase()

var array = [];
for (let i = 1; i < c.length + 1; i++) {
 array.push(c.substring(0, i));
}

firebase
.firestore()
.collection("clubs")
.doc(documentId)
.update({
 title: this.state.title,
 titleAsArray: array
})

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

แบบสอบถามเช่นนี้

firebase
.firestore()
.collection("clubs")
.where(
 "titleAsArray",
 "array-contains",
 this.state.userQuery.toLowerCase()
)

ไม่แนะนำเลย. เนื่องจากเอกสารมีขีด จำกัด 20k บรรทัดดังนั้นคุณจึงไม่สามารถใช้วิธีนี้ได้จนกว่าคุณจะแน่ใจว่าเอกสารของคุณจะไม่มีวันถึงขีด จำกัด ดังกล่าว
Sandeep

เป็นตัวเลือกที่ดีที่สุดในขณะนี้มีอะไรแนะนำอีกบ้าง?
Nick Carducci

1
@Sandeep ฉันค่อนข้างมั่นใจว่าขนาด จำกัด ไว้ที่ 1MB ของขนาดและความลึก 20 ระดับต่อเอกสาร คุณหมายถึงอะไร 20k เส้น? ขณะนี้เป็นวิธีแก้ปัญหาที่ดีที่สุดหากการใช้ Algolia หรือ ElasticSearch ไม่อยู่ในตาราง
ppicom

6

หากคุณไม่ต้องการใช้บริการของบุคคลที่สามเช่น Algolia Firebase Cloud Functionsเป็นทางเลือกที่ดี คุณสามารถสร้างฟังก์ชันที่สามารถรับพารามิเตอร์อินพุตประมวลผลผ่านฝั่งเซิร์ฟเวอร์ระเบียนแล้วส่งคืนฟังก์ชันที่ตรงกับเกณฑ์ของคุณ


1
แล้ว Android ล่ะ?
Pratik Butani

คุณกำลังเสนอให้ผู้คนทำซ้ำทุก ๆ ระเบียนในคอลเล็กชันหรือไม่?
DarkNeuron

ไม่จริง. ฉันจะใช้ Array.prototype. * - เช่น. ทุกๆ (), .some (), .map (), .filter () ฯลฯ ซึ่งทำได้ใน Node บนเซิร์ฟเวอร์ในฟังก์ชัน Firebase ก่อนที่จะคืนค่าไปยัง ลูกค้า.
แร็พ

4
คุณยังคงต้องอ่านเอกสารทั้งหมดเพื่อค้นหาซึ่งต้องเสียค่าใช้จ่ายและมีราคาแพงสำหรับเวลา
Jonathan

4

คำตอบที่เลือกใช้ได้กับการค้นหาที่แน่นอนเท่านั้นและไม่ใช่พฤติกรรมการค้นหาของผู้ใช้ตามปกติ (การค้นหา "apple" ใน "Joe ate an apple today" จะใช้ไม่ได้)

ฉันคิดว่าคำตอบของ Dan Fein ข้างต้นควรอยู่ในอันดับที่สูงกว่า หากข้อมูลสตริงที่คุณกำลังค้นหาสั้นคุณสามารถบันทึกสตริงย่อยทั้งหมดของสตริงในอาร์เรย์ในเอกสารของคุณจากนั้นค้นหาในอาร์เรย์ด้วยคำค้นหา array_contains ของ Firebase เอกสาร Firebase จำกัด ไว้ที่ 1 MiB (1,048,576 ไบต์) ( โควต้าและขีด จำกัด ของ Firebase ) ซึ่งบันทึกไว้ในเอกสารประมาณ 1 ล้านอักขระ (ฉันคิดว่า 1 อักขระ ~ = 1 ไบต์) การจัดเก็บสตริงย่อยนั้นทำได้ดีตราบใดที่เอกสารของคุณไม่ได้อยู่ใกล้ 1 ล้านเครื่องหมาย

ตัวอย่างการค้นหาชื่อผู้ใช้:

ขั้นตอนที่ 1: เพิ่มส่วนขยาย String ต่อไปนี้ในโครงการของคุณ วิธีนี้ช่วยให้คุณแยกสตริงออกเป็นสตริงย่อยได้อย่างง่ายดาย ( ฉันพบสิ่งนี้ที่นี่ )

extension String {

var length: Int {
    return count
}

subscript (i: Int) -> String {
    return self[i ..< i + 1]
}

func substring(fromIndex: Int) -> String {
    return self[min(fromIndex, length) ..< length]
}

func substring(toIndex: Int) -> String {
    return self[0 ..< max(0, toIndex)]
}

subscript (r: Range<Int>) -> String {
    let range = Range(uncheckedBounds: (lower: max(0, min(length, r.lowerBound)),
                                        upper: min(length, max(0, r.upperBound))))
    let start = index(startIndex, offsetBy: range.lowerBound)
    let end = index(start, offsetBy: range.upperBound - range.lowerBound)
    return String(self[start ..< end])
}

ขั้นตอนที่ 2: เมื่อคุณจัดเก็บชื่อผู้ใช้ให้เก็บผลลัพธ์ของฟังก์ชันนี้เป็นอาร์เรย์ในเอกสารเดียวกัน สิ่งนี้จะสร้างรูปแบบทั้งหมดของข้อความต้นฉบับและเก็บไว้ในอาร์เรย์ ตัวอย่างเช่นการป้อนข้อความ "Apple" จะสร้างอาร์เรย์ต่อไปนี้: ["a", "p", "p", "l", "e", "ap", "pp", "pl", "le "," app "," ppl "," ple "," appl "," pple "," apple "] ซึ่งควรครอบคลุมเกณฑ์การค้นหาทั้งหมดที่ผู้ใช้อาจป้อน คุณสามารถปล่อย maximumStringSize เป็นศูนย์ได้หากคุณต้องการผลลัพธ์ทั้งหมดอย่างไรก็ตามหากมีข้อความยาวฉันขอแนะนำให้กำหนดขีด จำกัด ก่อนที่ขนาดเอกสารจะใหญ่เกินไป - ประมาณ 15 ที่ใช้ได้ดีสำหรับฉัน (คนส่วนใหญ่ไม่ได้ค้นหาวลีที่ยาวอยู่แล้ว ).

func createSubstringArray(forText text: String, maximumStringSize: Int?) -> [String] {

    var substringArray = [String]()
    var characterCounter = 1
    let textLowercased = text.lowercased()

    let characterCount = text.count
    for _ in 0...characterCount {
        for x in 0...characterCount {
            let lastCharacter = x + characterCounter
            if lastCharacter <= characterCount {
                let substring = textLowercased[x..<lastCharacter]
                substringArray.append(substring)
            }
        }
        characterCounter += 1

        if let max = maximumStringSize, characterCounter > max {
            break
        }
    }

    print(substringArray)
    return substringArray
}

ขั้นตอนที่ 3: คุณสามารถใช้ฟังก์ชัน array_contains ของ Firebase ได้!

[yourDatabasePath].whereField([savedSubstringArray], arrayContains: searchText).getDocuments....

3

ฉันคิดว่าทางออกที่ดีที่สุดในการทำสิ่งนี้ภายใน Firestore คือการใส่สตริงย่อยทั้งหมดในอาร์เรย์และเพียงแค่ทำแบบสอบถาม array_contains สิ่งนี้ช่วยให้คุณทำการจับคู่สตริงย่อยได้ ใช้งานมากเกินไปในการจัดเก็บสตริงย่อยทั้งหมด แต่หากข้อความค้นหาของคุณสั้นก็สมเหตุสมผลมาก


2

ฉันเพิ่งมีปัญหานี้และมีวิธีแก้ปัญหาง่ายๆ

String search = "ca";
Firestore.instance.collection("categories").orderBy("name").where("name",isGreaterThanOrEqualTo: search).where("name",isLessThanOrEqualTo: search+"z")

isGreaterThanOrEqualTo ช่วยให้เรากรองจุดเริ่มต้นของการค้นหาของเราออกและโดยการเพิ่ม "z" ต่อท้าย isLessThanOrEqualT เพื่อที่เราจะ จำกัด การค้นหาของเราเพื่อไม่ให้เลื่อนไปยังเอกสารถัดไป


3
ฉันได้ลองวิธีแก้ปัญหานี้แล้ว แต่สำหรับฉันมันใช้ได้เฉพาะเมื่อป้อนสตริงแบบเต็มเท่านั้น ตัวอย่างเช่นหากฉันต้องการรับคำว่า "ฟรี" หากฉันเริ่มพิมพ์ "fr" จะไม่มีผลตอบแทน เมื่อฉันพิมพ์คำว่า "ฟรี" คำนั้นจะให้ภาพรวมของฉัน
คริส

คุณใช้รูปแบบรหัสเดียวกันหรือไม่ และคำว่าสตริงใน firestore หรือไม่? ฉันรู้ว่าคุณไม่สามารถกรองตาม documentId ได้
Jacob Bonk

0

ด้วย Firestore คุณสามารถใช้การค้นหาข้อความแบบเต็มได้ แต่จะยังคงมีค่าใช้จ่ายในการอ่านมากกว่าที่จะมีและคุณจะต้องป้อนและจัดทำดัชนีข้อมูลในลักษณะเฉพาะดังนั้นในวิธีนี้คุณสามารถใช้ฟังก์ชันคลาวด์ firebase เพื่อ tokenise แล้วกัญชาป้อนข้อความของคุณในขณะที่เลือกฟังก์ชันเชิงเส้นh(x)ที่ตอบสนองความต่อไปนี้ - x < y < z then h(x) < h (y) < h(z)ถ้า สำหรับการใช้โทเค็นคุณสามารถเลือกไลบรารี NLP ที่มีน้ำหนักเบาเพื่อรักษาเวลาเริ่มเย็นของฟังก์ชันของคุณให้ต่ำซึ่งสามารถดึงคำที่ไม่จำเป็นออกจากประโยคของคุณได้ จากนั้นคุณสามารถเรียกใช้แบบสอบถามที่มีตัวดำเนินการน้อยกว่าและมากกว่าใน Firestore ในขณะที่จัดเก็บข้อมูลของคุณคุณจะต้องแน่ใจว่าคุณได้แฮชข้อความก่อนที่จะจัดเก็บและจัดเก็บข้อความธรรมดาราวกับว่าคุณเปลี่ยนข้อความธรรมดาค่าแฮชก็จะเปลี่ยนไปด้วย


0

สิ่งนี้ใช้ได้ผลสำหรับฉันอย่างสมบูรณ์แบบ แต่อาจทำให้เกิดปัญหาด้านประสิทธิภาพ

ทำสิ่งนี้เมื่อสอบถาม firestore:

   Future<QuerySnapshot> searchResults = collectionRef
        .where('property', isGreaterThanOrEqualTo: searchQuery.toUpperCase())
        .getDocuments();

ทำสิ่งนี้ใน FutureBuilder ของคุณ:

    return FutureBuilder(
          future: searchResults,
          builder: (context, snapshot) {           
            List<Model> searchResults = [];
            snapshot.data.documents.forEach((doc) {
              Model model = Model.fromDocumet(doc);
              if (searchQuery.isNotEmpty &&
                  !model.property.toLowerCase().contains(searchQuery.toLowerCase())) {
                return;
              }

              searchResults.add(model);
            })
   };

0

ณ วันนี้มีวิธีแก้ปัญหาที่แตกต่างกัน 3 วิธีซึ่งผู้เชี่ยวชาญแนะนำเป็นคำตอบสำหรับคำถาม

ฉันได้ลองทั้งหมดแล้ว ฉันคิดว่าการบันทึกประสบการณ์ของฉันกับแต่ละคนอาจเป็นประโยชน์

วิธี -A: การใช้: (dbField "> =" searchString) & (dbField "<=" searchString + "\ uf8ff")

แนะนำโดย @Kuba & @Ankit Prajapati

.where("dbField1", ">=", searchString)
.where("dbField1", "<=", searchString + "\uf8ff");

A.1 แบบสอบถาม Firestore สามารถดำเนินการกรองช่วง (>, <,> =, <=) ในฟิลด์เดียวเท่านั้น ไม่รองรับการสืบค้นที่มีตัวกรองช่วงในหลายช่อง เมื่อใช้วิธีนี้คุณไม่สามารถมีตัวดำเนินการช่วงในฟิลด์อื่นบนฐานข้อมูลเช่นฟิลด์วันที่

ก. 2. วิธีนี้ใช้ไม่ได้กับการค้นหาในหลายช่องพร้อมกัน ตัวอย่างเช่นคุณไม่สามารถตรวจสอบว่าสตริงการค้นหาอยู่ใน fileds (ชื่อบันทึกย่อและที่อยู่)

Method-B: การใช้ MAP ของสตริงการค้นหาด้วย "true" สำหรับแต่ละรายการในแผนที่และใช้โอเปอเรเตอร์ "==" ในการสืบค้น

แนะนำโดย @Gil Gilbert

document1 = {
  'searchKeywordsMap': {
    'Jam': true,
    'Butter': true,
    'Muhamed': true,
    'Green District': true,
    'Muhamed, Green District': true,
  }
}

.where(`searchKeywordsMap.${searchString}`, "==", true);

B.1 เห็นได้ชัดว่าวิธีนี้ต้องการการประมวลผลเพิ่มเติมทุกครั้งที่บันทึกข้อมูลลงในฐานข้อมูลและที่สำคัญต้องใช้พื้นที่เพิ่มเติมในการจัดเก็บแผนที่ของสตริงการค้นหา

B.2 ถ้าแบบสอบถาม Firestore มีเงื่อนไขเดียวเหมือนข้างต้นไม่จำเป็นต้องสร้างดัชนีล่วงหน้า วิธีนี้จะใช้ได้ดีในกรณีนี้

B.3 อย่างไรก็ตามหากข้อความค้นหามีเงื่อนไขอื่นเช่น (status === "active") ดูเหมือนว่าต้องใช้ดัชนีสำหรับ "สตริงการค้นหา" แต่ละรายการที่ผู้ใช้ป้อน กล่าวอีกนัยหนึ่งคือหากผู้ใช้ค้นหา "Jam" และผู้ใช้รายอื่นค้นหา "Butter" ควรสร้างดัชนีไว้ล่วงหน้าสำหรับสตริง "Jam" และอีกอันสำหรับ "Butter" เป็นต้นเว้นแต่คุณจะสามารถคาดเดาได้ทั้งหมด สตริงการค้นหาของผู้ใช้ไม่ได้ผล - ในกรณีที่ข้อความค้นหามีเงื่อนไขอื่น!

.where(searchKeywordsMap["Jam"], "==", true); // requires an index on searchKeywordsMap["Jam"]
.where("status", "==", "active");

** Method-C: การใช้ ARRAY ของสตริงการค้นหา & ตัวดำเนินการ "array-contains"

แนะนำโดย @Albert Renshaw และสาธิตโดย @Nick Carducci

document1 = {
  'searchKeywordsArray': [
    'Jam',
    'Butter',
    'Muhamed',
    'Green District',
    'Muhamed, Green District',
  ]
}

.where("searchKeywordsArray", "array-contains", searchString); 

C.1 คล้ายกับ Method-B วิธีนี้ต้องการการประมวลผลเพิ่มเติมทุกครั้งที่บันทึกข้อมูลลงในฐานข้อมูลและที่สำคัญต้องใช้พื้นที่เพิ่มเติมในการจัดเก็บอาร์เรย์ของสตริงการค้นหา

C.2 การสืบค้น Firestore สามารถรวมส่วนคำสั่ง "array-contains" หรือ "array-contains-any" ได้มากที่สุดหนึ่งประโยคในแบบสอบถามแบบผสม

ข้อ จำกัด ทั่วไป:

  1. ดูเหมือนว่าโซลูชันเหล่านี้จะไม่รองรับการค้นหาสตริงบางส่วน ตัวอย่างเช่นหากช่อง db มี "1 Peter St, Green District" คุณจะไม่สามารถค้นหาสตริง "เข้มงวด"
  2. แทบจะเป็นไปไม่ได้เลยที่จะครอบคลุมชุดค่าผสมที่เป็นไปได้ทั้งหมดของสตริงการค้นหาที่คาดหวัง ตัวอย่างเช่นหากช่อง db มี "1 Mohamed St, Green District" คุณอาจไม่สามารถค้นหาสตริง "Green Mohamed" ซึ่งเป็นสตริงที่มีคำในลำดับต่างจากลำดับที่ใช้ในฐานข้อมูล ฟิลด์

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

สำหรับรายการเงื่อนไขการสืบค้นของ Firestore โปรดดูเอกสารhttps://firebase.google.com/docs/firestore/query-data/queries https://firebase.google.com/docs/firestore/query-data/queries

ฉันยังไม่ได้ลองใช้https://fireblog.io/blog/post/firestore-full-text-searchซึ่งแนะนำโดย @Jonathan


-10

เราสามารถใช้ back-tick เพื่อพิมพ์ค่าของสตริง สิ่งนี้ควรใช้งานได้:

where('name', '==', `${searchTerm}`)

ขอบคุณ แต่คำถามนี้เกี่ยวกับการรับค่าที่ไม่แน่นอน ตัวอย่างเช่นตัวอย่างที่เป็นปัญหาสามารถค้นหาได้ว่าชื่อนั้นถูกต้องหรือไม่ ถ้าฉันมีเอกสารที่มีชื่อ: "Test" จากนั้นฉันค้นหา "Test" ก็ใช้ได้ แต่ฉันคาดว่าจะสามารถค้นหา "tes" หรือ "est" และยังคงได้รับผล "Test" ลองนึกภาพกรณีการใช้งานกับชื่อหนังสือ ผู้คนมักค้นหาชื่อหนังสือเพียงบางส่วนแทนที่จะเป็นชื่อที่แน่นอนทั้งหมด
tehfailsafe

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