【问题标题】:Firestore query subcollectionsFirestore 查询子集合
【发布时间】:2019-07-25 05:54:27
【问题描述】:

我以为我读到您可以使用新的 Firebase Firestore 查询子集合,但我没有看到任何示例。例如,我通过以下方式设置 Firestore:

  • 舞蹈[收藏]
    • 舞名
    • 歌曲[收藏]
      • 歌名

如何查询“查找所有歌曲名称 == 'X' 的舞蹈”

【问题讨论】:

    标签: firebase google-cloud-firestore


    【解决方案1】:

    2019-05-07 更新

    今天我们发布了collection group queries,这些允许您跨子集合进行查询。

    因此,例如在 web SDK 中:

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

    这将匹配集合路径最后部分为“歌曲”的任何集合中的文档。

    您最初的问题是关于找到歌曲名称 == 'X' 的舞蹈,这仍然不可能直接进行,但是,对于匹配的每首歌曲,您都可以加载其父级。

    原答案

    这是一个尚不存在的功能。它被称为“集合组查询”,允许您查询所有歌曲,而不管它们包含哪些舞蹈。这是我们打算支持的,但没有具体的时间表。

    此时的替代结构是将歌曲制作为顶级集合,并使歌曲的哪个舞蹈成为歌曲属性的一部分。

    【讨论】:

    • 如果 Firestore 开发团队尽快实施子集合查询会更好。毕竟,根据 Firestore 手册,“更强大的查询”是主要卖点之一。现在,Firestore 就像一辆没有轮子的保时捷。
    • 我们同意!一天只有有限的时间:-)。
    • 我不明白,如果火力基地有限,人们要付什么钱?似乎连 Backendless 的功能都比 Firebase 更多。为什么 Firebase 如此受欢迎?似乎人们疯了
    • 这个功能是非常需要的,否则人们会开始寻找替代品,即使我们有最后期限。 :P
    • 我们需要这个功能。至少发布的时间表将有助于我们做好准备。
    【解决方案2】:

    更新 现在 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);
    

    我希望这会有所帮助。

    【讨论】:

    • songName_Title3: false 有什么用?如果我没记错的话,它只能用于搜索没有特定歌曲名称的舞蹈,假设我们需要 songName_Title3: false 以使 dances.where("songName_"+songTitle, "==", false); 返回这样的结果,它不会让每支舞蹈都有意义每个可能的歌曲名称的布尔标志...
    • 这很好,但文档限制为 1MB,因此如果您需要将一长串字符串或其他内容与特定文档相关联,则不能使用此方法。
    • @Supertecnoboff 这似乎必须是一个非常大而且很长的字符串列表。这个“array_contains”查询的性能如何?还有哪些性能更​​高的替代方案?
    【解决方案3】:

    如果您将歌曲存储为对象而不是集合会怎样?每个舞曲,以歌曲为字段:类型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);
        });
    

    【讨论】:

    • 此解决方案可行,但如果歌曲数量很大或可以动态增长,则它不可扩展。这会增加文档大小并影响读/写性能。可以在下面链接的 Firebase 文档中找到更多相关信息(请参阅页面上的最后一节“限制”)firebase.google.com/docs/firestore/solutions/arrays
    【解决方案4】:

    2019 年更新

    Firestore 已发布集合组查询。见上面吉尔的回答或者官方Collection Group Query Documentation


    上一个答案

    正如 Gil Gilbert 所说,似乎集合组查询目前正在进行中。同时,最好使用根级集合,并使用文档 UID 链接这些集合。

    对于那些还不知道的人,Jeff Delaney 在AngularFirebase 上为任何使用 Firebase(和 Angular)的人提供了一些令人难以置信的指南和资源。

    Firestore NoSQL Relational Data Modeling - 在这里他分解了 NoSQL 和 Firestore 数据库结构的基础知识

    Advanced Data Modeling With Firestore by Example - 这些是更先进的技术,请牢记在心。对于那些希望将 Firestore 技能提升到新水平的人来说,这是一本很好的读物

    【讨论】:

      【解决方案5】:

      2019 年 7 月 8 日新更新:

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

      【讨论】:

        【解决方案6】:

        我找到了解决办法。 请检查这个。

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

        然后您可以从 console.firebase.google.com 在您的云 Firestore 中查看数据、规则、索引、使用情况选项卡。 最后,您应该在索引选项卡中设置索引。

        在此处填写集合 ID 和一些字段值。 然后选择集合组选项。 好好享受。谢谢

        【讨论】:

        • 这不回答问题。上面提到的查询只是获取所有 songName = 'X' 的歌曲。这不会提供 songName = 'X' 的舞蹈。
        【解决方案7】:

        你总是可以这样搜索:-

            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;
                  }
                })
            );
        

        【讨论】:

        • 什么是 BehaviorSubject?
        • @Cassini 这是一个 RxJS 的东西
        【解决方案8】:

        查询限制

        Cloud Firestore 不支持以下类型的查询:

        1. 在不同字段上使用范围过滤器进行查询。

        2. 跨多个集合或子集合的单个查询。每个查询针对单个文档集合运行。更多 有关数据结构如何影响查询的信息,请参阅 Choose a Data Structure.

        3. 逻辑或查询。在这种情况下,您应该为每个 OR 条件创建一个单独的查询,并在您的应用中合并查询结果。

        4. 带有 != 子句的查询。在这种情况下,您应该将查询拆分为大于查询和小于查询。例如,虽然 不支持查询子句 where("age", "!=", "30"),可以 通过组合两个查询获得相同的结果集,一个带有子句 where("age", "", 30) 的子句。

        【讨论】:

          【解决方案9】:
          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);
                  });
          

          【讨论】:

            【解决方案10】:

            我在这里使用 Observables 和 AngularFire 包装器,但这是我设法做到这一点的方法。

            这有点疯狂,我仍在学习 observables,我可能做得过火了。但这是一个很好的练习。

            一些解释(不是 RxJS 专家):

            • songId$ 是一个会发出 id 的 observable
            • dance$ 是一个 observable,它读取该 id,然后只获取第一个值。
            • 然后它会查询所有歌曲的 collectionGroup 以找到它的所有实例。
            • 根据它遍历到父 Dances 的实例并获取它们的 ID。
            • 现在我们已经拥有了所有的 Dance id,我们需要查询它们以获取它们的数据。但我希望它表现良好,因此我不是一个一个地查询,而是将它们分批成 10 个桶(in 查询需要的最大角度。
            • 我们最终有 N 个存储桶,需要在 Firestore 上执行 N 次查询才能获取它们的值。
            • 在 Firestore 上执行查询后,我们仍然需要实际解析其中的数据。
            • 最后我们可以合并所有查询结果,得到一个包含所有舞蹈的数组。
            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 进行测试,请告诉我。

            【讨论】:

              【解决方案11】:

              最好使用平面数据结构。
              文档详细说明了不同数据结构 on this page 的优缺点。

              特别是关于带有子集合的结构的限制:

              您无法轻松删除子集合,或跨子集合执行复合查询。

              与平面数据结构的所谓优势相比:

              根级集合提供最大的灵活性和可扩展性,以及每个集合内的强大查询。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-08-12
                • 2020-07-26
                • 1970-01-01
                相关资源
                最近更新 更多