คอลเล็กชันย่อยการสืบค้น Firestore


125

ฉันคิดว่าฉันอ่านแล้วว่าคุณสามารถค้นหาคอลเล็กชันย่อยด้วย Firebase Firestore ใหม่ได้ แต่ฉันไม่เห็นตัวอย่างใด ๆ ตัวอย่างเช่นฉันมีการตั้งค่า Firestore ด้วยวิธีต่อไปนี้:

  • เต้นรำ [คอลเลกชัน]
    • danceName
    • เพลง [คอลเลกชัน]
      • ชื่อเพลง

ฉันจะถาม "ค้นหาการเต้นทั้งหมดที่ songName == 'X'" ได้อย่างไร


1
สิ่งนี้ได้รับการสนับสนุนยัง firestore ปี 2020 หรือไม่
sajanyamaha

คำตอบ:


148

อัปเดต 2019-05-07

วันนี้เราปล่อย ค้นหากลุ่มคอลเลกชันและสิ่งเหล่านี้ช่วยให้คุณสามารถค้นหาคอลเล็กชันย่อย

ตัวอย่างเช่นในเว็บ SDK:

db.collectionGroup('Songs')
  .where('songName', '==', 'X')
  .get()

สิ่งนี้จะจับคู่เอกสารในคอลเล็กชันใด ๆ โดยที่ส่วนสุดท้ายของเส้นทางการรวบรวมคือ "เพลง"

คำถามเดิมของคุณคือการค้นหาการเต้นที่ songName == 'X' แต่ยังไม่สามารถทำได้โดยตรงสำหรับแต่ละเพลงที่ตรงกับคุณสามารถโหลดระดับบนสุดได้

คำตอบเดิม

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

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


147
จะดีกว่ามากถ้าทีมนักพัฒนา Firestore ใช้แบบสอบถามชุดย่อยโดยเร็ว ท้ายที่สุดแล้ว 'การสืบค้นที่มีประสิทธิภาพยิ่งขึ้น' เป็นหนึ่งในจุดขายที่สำคัญตามคู่มือ Firestore ตอนนี้ Firestore เหมือนปอร์เช่ที่ไม่มีล้อ
Arne Wolframm

21
เราเห็นด้วย! มีเวลา จำกัด ในหนึ่งวัน :-)
Gil Gilbert

20
ฉันไม่เข้าใจว่าผู้คนจ่ายเงินเพื่ออะไรหาก firebase มี จำกัด ดูเหมือนว่าแม้ Backendless จะมีฟังก์ชันมากกว่า Firebase แล้วทำไม Firebase ถึงได้รับความนิยม? ดูเหมือนคนจะบ้า
nzackoya

15
คุณลักษณะนี้จำเป็นอย่างยิ่งไม่เช่นนั้นผู้คนจะเริ่มค้นหาทางเลือกอื่นแม้ว่าเราจะมีกำหนดเวลาที่จะต้องพบก็ตาม : P
JD-V

13
เราต้องการคุณสมบัตินี้ เมื่อเช่าไทม์ไลน์เพื่อเผยแพร่สิ่งนี้จะช่วยให้เราเตรียมพร้อม
sanjaya panigrahy

22

อัปเดตทันที Firestore รองรับอาร์เรย์ประกอบด้วย

มีเอกสารเหล่านี้

    {danceName: 'Danca name 1', songName: ['Title1','Title2']}
    {danceName: 'Danca name 2', songName: ['Title3']}

ทำแบบนี้

collection("Dances")
    .where("songName", "array-contains", "Title1")
    .get()...

@ Nelson.b.austin เนื่องจาก firestore ยังไม่มีฉันขอแนะนำให้คุณมีโครงสร้างแบบแบนซึ่งหมายความว่า:

Dances = {
    danceName: 'Dance name 1',
    songName_Title1: true,
    songName_Title2: true,
    songName_Title3: false
}

ด้วยวิธีนี้คุณสามารถทำได้:

var songTitle = 'Title1';
var dances = db.collection("Dances");
var query = dances.where("songName_"+songTitle, "==", true);

ฉันหวังว่านี่จะช่วยได้.


2
มีsongName_Title3: falseประโยชน์อะไร ถ้าฉันไม่ผิดก็สามารถนำมาใช้ในการค้นหาการเต้นรำที่ไม่ได้มีเฉพาะชื่อเพลงสมมติว่าเราจำเป็นต้องมีsongName_Title3: falseเพื่อให้dances.where("songName_"+songTitle, "==", false); ผลตอบแทนจากผลดังกล่าวก็จะไม่ทำให้รู้สึกสำหรับการเต้นรำทุกที่จะมีธงบูลีนสำหรับเพลงเป็นไปได้ทุก ชื่อ ...
epeleg

สิ่งนี้ยอดเยี่ยม แต่เอกสารมีขนาด จำกัด เพียง 1MB ดังนั้นหากคุณต้องการเชื่อมโยงรายการสตริงยาว ๆ หรืออะไรก็ตามกับเอกสารเฉพาะคุณจะไม่สามารถใช้แนวทางนี้ได้
Supertecnoboff

@Supertecnoboff ดูเหมือนว่ามันจะต้องเป็นรายการสตริงที่ใหญ่และยาวมาก แบบสอบถาม "array_contains" นี้มีประสิทธิภาพเพียงใดและมีทางเลือกอื่นที่มีประสิทธิภาพมากกว่ากันอย่างไร
Jay Ordway

14

จะเป็นอย่างไรหากคุณเก็บเพลงเป็นวัตถุแทนที่จะเป็นคอลเลคชัน แต่ละคนเต้นเป็นเพลงเป็นฟิลด์: พิมพ์ Object (ไม่ใช่คอลเล็กชัน)

{
  danceName: "My Dance",
  songs: {
    "aNameOfASong": true,
    "aNameOfAnotherSong": true,
  }
}

จากนั้นคุณสามารถสอบถามการเต้นรำทั้งหมดด้วย aNameOfASong:

db.collection('Dances')
  .where('songs.aNameOfASong', '==', true)
  .get()
  .then(function(querySnapshot) {
    querySnapshot.forEach(function(doc) {
      console.log(doc.id, " => ", doc.data());
    });
   })
   .catch(function(error) {
     console.log("Error getting documents: ", error);
    });

3
วิธีนี้จะใช้งานได้ แต่ไม่สามารถปรับขนาดได้ในกรณีที่เพลงมีจำนวนมากหรือสามารถเติบโตแบบไดนามิก สิ่งนี้จะเพิ่มขนาดเอกสารและส่งผลต่อประสิทธิภาพการอ่าน / เขียน ดูข้อมูลเพิ่มเติมได้ในเอกสาร Firebase ที่ลิงก์ด้านล่าง (ดูส่วนสุดท้าย "ข้อ จำกัด " ในหน้า) firebase.google.com/docs/firestore/solutions/arrays
Nouman Hanif

14

อัปเดต 2019

Firestore ได้เปิดตัวแบบสอบถามกลุ่มคอลเลกชัน ดูคำตอบของ Gil ด้านบนหรือเอกสารแบบสอบถามกลุ่มคอลเลกชันอย่างเป็นทางการ


คำตอบก่อนหน้า

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

สำหรับผู้ที่ไม่ได้รู้อยู่แล้วว่าเจฟฟ์ Delaney มีคำแนะนำบางอย่างเหลือเชื่อและทรัพยากรสำหรับทุกคนที่ทำงานกับ Firebase (และเชิงมุม) บนAngularFirebase

Firestore NoSQL Relational Data Modeling - ที่นี่เขาแบ่งพื้นฐานของโครงสร้าง NoSQL และ Firestore DB

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



3

คุณสามารถค้นหาดังนี้: -

this.key$ = new BehaviorSubject(null);

return this.key$.switchMap(key =>
  this.angFirestore
    .collection("dances").doc("danceName").collections("songs", ref =>
      ref
        .where("songName", "==", X)
    )
    .snapshotChanges()
    .map(actions => {
      if (actions.toString()) {
        return actions.map(a => {
          const data = a.payload.doc.data() as Dance;
          const id = a.payload.doc.id;
          return { id, ...data };
        });
      } else {
        return false;
      }
    })
);

3

ข้อ จำกัด การสืบค้น

Cloud Firestore ไม่รองรับการสืบค้นประเภทต่อไปนี้:

  1. แบบสอบถามที่มีตัวกรองช่วงในฟิลด์ต่างๆ

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

  3. ตรรกะหรือแบบสอบถาม ในกรณีนี้คุณควรสร้างแบบสอบถามแยกกันสำหรับเงื่อนไข OR แต่ละรายการและรวมผลการค้นหาในแอปของคุณ

  4. แบบสอบถามด้วย a! = clause ในกรณีนี้คุณควรแบ่งแบบสอบถามออกเป็นแบบสอบถามที่มากกว่าและแบบสอบถามน้อยกว่า ตัวอย่างเช่นแม้ว่าจะไม่รองรับประโยคคำค้นหาที่ ("age", "! =", "30") แต่คุณจะได้รับผลลัพธ์เดียวกันที่กำหนดโดยการรวมสองคำค้นหาเข้าด้วยกันโดยหนึ่งคำสั่งที่ ("age", "< "," 30 ") และอีกหนึ่งประโยคโดยที่ (" age ","> ", 30)


2
var songs = []    
db.collection('Dances')
      .where('songs.aNameOfASong', '==', true)
      .get()
      .then(function(querySnapshot) {
        var songLength = querySnapshot.size
        var i=0;
        querySnapshot.forEach(function(doc) {
           songs.push(doc.data())
           i ++;
           if(songLength===i){
                console.log(songs
           }
          console.log(doc.id, " => ", doc.data());
        });
       })
       .catch(function(error) {
         console.log("Error getting documents: ", error);
        });

1

ควรใช้โครงสร้างข้อมูลแบบแบน
เอกสารระบุข้อดีข้อเสียของโครงสร้างข้อมูลที่แตกต่างกันในหน้านี้

โดยเฉพาะเกี่ยวกับข้อ จำกัด ของโครงสร้างที่มีคอลเลกชันย่อย:

คุณไม่สามารถลบคอลเล็กชันย่อยหรือดำเนินการค้นหาแบบผสมข้ามคอลเล็กชันย่อยได้อย่างง่ายดาย

ขัดแย้งกับข้อดีที่อ้างว่าโครงสร้างข้อมูลแบบแบน:

คอลเลกชันระดับรูทมอบความยืดหยุ่นและความสามารถในการปรับขนาดได้มากที่สุดพร้อมกับการสืบค้นที่มีประสิทธิภาพภายในแต่ละคอลเลกชัน


1

ฉันได้พบวิธีแก้ปัญหาแล้ว โปรดตรวจสอบสิ่งนี้

var museums = Firestore.instance.collectionGroup('Songs').where('songName', isEqualTo: "X");
        museums.getDocuments().then((querySnapshot) {
            setState(() {
              songCounts= querySnapshot.documents.length.toString();
            });
        });

จากนั้นคุณจะเห็นแท็บข้อมูลกฎดัชนีการใช้งานใน cloud firestore ของคุณจาก console.firebase.google.com สุดท้ายคุณควรตั้งค่าดัชนีในแท็บดัชนีใส่คำอธิบายภาพที่นี่

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


สิ่งนี้ไม่ตอบคำถาม การค้นหาที่กล่าวถึงข้างต้นเพียงแค่ดึงเพลงทั้งหมดที่มี songName = 'X' สิ่งนี้จะไม่ให้การเต้นรำที่ songName = 'X'
sachin rathod

0

ฉันกำลังทำงานกับ Observables ที่นี่และAngularFire wrapper แต่นี่คือวิธีที่ฉันจัดการได้

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

คำอธิบายบางส่วน (ไม่ใช่ผู้เชี่ยวชาญ RxJS):

  • songId $ เป็นสิ่งที่สังเกตได้ซึ่งจะปล่อยรหัสออกมา
  • dance $ เป็นค่าที่สังเกตได้ซึ่งอ่าน id นั้นแล้วรับเฉพาะค่าแรกเท่านั้น
  • จากนั้นจะค้นหา collectionGroup ของเพลงทั้งหมดเพื่อค้นหาอินสแตนซ์ทั้งหมดของมัน
  • ขึ้นอยู่กับอินสแตนซ์ที่ข้ามไปยัง Dances หลักและรับรหัสของพวกเขา
  • ตอนนี้เรามีรหัส Dance ทั้งหมดแล้วเราจำเป็นต้องค้นหาเพื่อรับข้อมูล แต่ฉันต้องการให้มันทำงานได้ดีดังนั้นแทนที่จะค้นหาทีละรายการฉันจัดกลุ่มเป็นกลุ่ม 10 (ค่าเชิงมุมสูงสุดจะใช้ในการinสืบค้น
  • เราจบลงด้วย N ที่เก็บข้อมูลและจำเป็นต้องทำ N แบบสอบถามบน firestore เพื่อรับค่าของพวกเขา
  • เมื่อเราทำแบบสอบถามบน firestore เรายังคงต้องแยกวิเคราะห์ข้อมูลจากสิ่งนั้น
  • และในที่สุดเราก็สามารถรวมผลลัพธ์การค้นหาทั้งหมดเพื่อให้ได้อาร์เรย์เดียวกับ Dances ทั้งหมดในนั้น
type Song = {id: string, name: string};
type Dance = {id: string, name: string, songs: Song[]};

const songId$: Observable<Song> = new Observable();
const dance$ = songId$.pipe(
  take(1), // Only take 1 song name
  switchMap( v =>
    // Query across collectionGroup to get all instances.
    this.db.collectionGroup('songs', ref =>
      ref.where('id', '==', v.id)).get()
  ),
  switchMap( v => {
    // map the Song to the parent Dance, return the Dance ids
    const obs: string[] = [];
    v.docs.forEach(docRef => {
      // We invoke parent twice to go from doc->collection->doc
      obs.push(docRef.ref.parent.parent.id);
    });
    // Because we return an array here this one emit becomes N
    return obs;
  }),
  // Firebase IN support up to 10 values so we partition the data to query the Dances
  bufferCount(10),
  mergeMap( v => { // query every partition in parallel
    return this.db.collection('dances', ref => {
      return ref.where( firebase.firestore.FieldPath.documentId(), 'in', v);
    }).get();
  }),
  switchMap( v => {
    // Almost there now just need to extract the data from the QuerySnapshots
    const obs: Dance[] = [];
    v.docs.forEach(docRef => {
      obs.push({
        ...docRef.data(),
        id: docRef.id
      } as Dance);
    });
    return of(obs);
  }),
  // And finally we reduce the docs fetched into a single array.
  reduce((acc, value) => acc.concat(value), []),
);
const parentDances = await dance$.toPromise();

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

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