Firestore查询子集合


125

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

  • 跳舞[收藏]
    • danceName
    • 歌曲[收藏]
      • 歌名

我如何查询“查找SongName =='X'的所有舞蹈”


1
到2020年,是否仍受支持?
sajanyamaha

Answers:


148

更新2019-05-07

今天,我们发布了集合组查询,这些查询使您可以跨子集合进行查询。

因此,例如在Web SDK中:

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

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

您最初的问题是关于在songName =='X'处查找舞蹈,但是仍然不可能直接实现,但是,对于匹配的每首歌曲,您都可以加载其父项。

原始答案

此功能尚不存在。这称为“收藏组查询”,无论您跳舞的是哪种歌曲,都可以查询所有歌曲。这是我们打算支持的事情,但没有具体的时间表。

此时的另一种结构是使歌曲成为顶级收藏,并使歌曲以哪种舞蹈成为歌曲属性的一部分。


147
如果Firestore开发团队尽快实施子集合查询,那就更好了。毕竟,根据Firestore手册,“更强大的查询”是主要卖点之一。现在,Firestore就像一辆没有轮子的保时捷。
Arne Wolframm

21
我们同意!每天只有几个小时:-)。
吉尔·吉尔伯特

20
我不明白,如果火力有限,人们要付什么呢?似乎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用?如果我没记错的话,它只能用于搜索没有特定歌曲名称的舞蹈,前提是我们需要a songName_Title3: falsedances.where("songName_"+songTitle, "==", false); 返回这样的结果,这样就不会让每个舞蹈都为每个可能的歌曲都带有布尔标志名称...
epeleg

很好,但是文档限制为1MB,因此,如果您需要将一长串字符串或其他内容与特定文档相关联,则无法使用此方法。
Supertecnoboff

@Supertecnoboff似乎必须是一个很大且很长的字符串列表。这个“ array_contains”查询的性能如何?还有哪些性能更​​好的选择?
杰·奥德威

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的答案或官方的“ 收藏组查询”文档


上一个答案

正如吉尔·吉尔伯特(Gil Gilbert)所说,似乎 正在进行集合组查询。同时,最好使用根级别集合,并仅使用文档UID在这些集合之间进行链接。

对于那些尚不了解的人,Jeff Delaney为在AngularFirebase上使用Firebase(和Angular)的任何人提供了一些不可思议的指南和资源。

Firestore NoSQL关系数据建模 -在这里他分解了NoSQL和Firestore DB结构化的基础知识

通过示例使用Firestore进行高级数据建模 -这些是更先进的技术,可让您牢记在心。对于那些希望将其Firestore技能提高到一个新水平的人来说是一本好书


7

2019年7月8日的新更新:

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

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. 带!=子句的查询。在这种情况下,您应该将查询分为大于查询和小于查询。例如,尽管不支持查询子句where(“ age”,“!=”,“ 30”),但您可以通过组合两个查询获得相同的结果集,其中一个查询与子句where(“ age”,“ < “,” 30“)和带有子句where(” 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();
            });
        });

然后,您可以从console.firebase.google.com在Cloud Firestore中查看“数据”,“规则”,“索引”,“使用情况”选项卡。最后,您应该在“索引”选项卡中设置索引。在此处输入图片说明

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


这没有回答问题。上面提到的查询只是获取所有带有songName ='X'的歌曲。这不会提供songName ='X'的舞蹈。
sachin rathod

0

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

这有点疯狂,我仍在学习有关可观察物的信息,但我可能过时了。但这是一个很好的练习。

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

  • songId $是可观察到的,将发出ID
  • dance $是一个可观察值,它读取该ID,然后仅获取第一个值。
  • 然后,它查询所有歌曲的collectionGroup以查找它的所有实例。
  • 根据实例,它遍历到父级Dances并获取其ID。
  • 现在我们有了所有Dance ID,我们需要查询它们以获取其数据。但是我希望它表现良好,因此我不想一一查询,而是将它们按10个桶进行批处理(一个in查询需要最大角度)。
  • 我们最终得到N个存储桶,并且需要在Firestore上进行N个查询以获取其值。
  • 在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测试。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.