Sous-collections de requêtes Firestore

125

Je pensais avoir lu que vous pouvez interroger des sous-collections avec le nouveau Firebase Firestore, mais je ne vois aucun exemple. Par exemple, j'ai ma configuration Firestore de la manière suivante:

  • Danses [collection]
    • danceName
    • Chansons [collection]
      • titre de chanson

Comment pourrais-je interroger "Trouver toutes les danses où songName == 'X'"

Nelson.b.austin
la source
1
est-ce pris en charge encore Firestore, année 2020?
sajanyamaha

Réponses:

148

Mise à jour 07/05/2019

Aujourd'hui, nous avons publié des requêtes de groupe de collection , et celles-ci vous permettent d'interroger entre les sous-collections.

Ainsi, par exemple dans le SDK Web:

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

Cela correspondrait aux documents de toute collection où la dernière partie du chemin de la collection est «Chansons».

Votre question initiale portait sur la recherche de danses où songName == 'X', et cela n'est toujours pas possible directement, cependant, pour chaque morceau correspondant, vous pouvez charger son parent.

Réponse originale

C'est une fonctionnalité qui n'existe pas encore. Cela s'appelle une "requête de groupe de collection" et vous permettrait d'interroger toutes les chansons, quelle que soit la danse qui les contient. C'est quelque chose que nous avons l'intention de soutenir, mais nous n'avons pas de calendrier concret sur le moment où cela arrivera.

La structure alternative à ce stade est de faire des chansons une collection de haut niveau et de faire danser quelle chanson la chanson fait partie d'une propriété de la chanson.

Gil Gilbert
la source
147
Ce serait BEAUCOUP mieux si l'équipe de développement Firestore implémentait les requêtes de sous-collection dès que possible. Après tout, les «requêtes plus puissantes» sont l'un des principaux arguments de vente selon le manuel Firestore. En ce moment, Firestore est comme une Porsche sans roues.
Arne Wolframm
21
Nous sommes d'accord! Il y a juste un nombre limité d'heures dans la journée :-).
Gil Gilbert
20
Je ne comprends pas, pour ce que les gens paient, si la base de feu est limitée? On dirait que même Backendless a plus de fonctionnalités que Firebase. Et pourquoi Firebase est-il si populaire? On dirait que les gens sont
devenus
15
Cette fonctionnalité est très nécessaire sinon les gens commenceront à trouver des alternatives, même si nous avons des délais à respecter. : P
JD-V
13
Nous avons besoin de cette fonctionnalité. Au bail, le calendrier de publication nous aidera à nous préparer.
sanjaya panigrahy
22

METTRE À JOUR maintenant Firestore prend en charge array-contains

Avoir ces documents

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

fais-le de cette façon

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

@ Nelson.b.austin Puisque Firestore n'a pas encore cela, je vous suggère d'avoir une structure plate, ce qui signifie:

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

En l'ayant de cette manière, vous pouvez le faire:

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

J'espère que ça aide.

norgematos
la source
2
à quoi songName_Title3: falseça sert ? si je ne me trompe pas, cela ne peut être utilisé que pour rechercher des danses qui n'ont pas de nom de chanson spécifique en supposant que nous avons besoin d'un songName_Title3: falsepour dances.where("songName_"+songTitle, "==", false); renvoyer de tels résultats, il ne serait pas logique que chaque danse ait des drapeaux booléens pour chaque chanson possible name ...
epeleg
C'est génial mais les documents sont limités à 1 Mo, donc si vous avez besoin d'associer une longue liste de chaînes ou autre à un document spécifique, vous ne pouvez pas utiliser cette approche.
Supertecnoboff
@Supertecnoboff Cela semble devoir être une liste extrêmement longue et très longue de chaînes. Quelle est la performance de cette requête "array_contains" et quelles sont les alternatives les plus performantes?
Jay Ordway
14

Et si vous stockiez des chansons comme un objet plutôt que comme une collection? Chaque danse comme, avec des chansons comme champ: tapez Object (pas une collection)

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

alors vous pouvez interroger toutes les danses avec 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);
    });
dmartins
la source
3
Cette solution fonctionnerait mais elle n'est pas évolutive dans le cas où le nombre de chansons est important ou peut augmenter dynamiquement. Cela augmenterait la taille du document et affecterait les performances de lecture / écriture. Vous trouverez plus d'informations à ce sujet dans la documentation Firebase liée ci-dessous (voir la dernière section «Limitations» sur la page) firebase.google.com/docs/firestore/solutions/arrays
Nouman Hanif
14

MISE À JOUR 2019

Firestore a publié des requêtes de groupe de collection. Voir la réponse de Gil ci-dessus ou la documentation officielle des requêtes de groupe de collection


Réponse précédente

Comme l'a déclaré Gil Gilbert, il semble que des requêtes de groupe de collection soient actuellement en cours. En attendant, il est probablement préférable d'utiliser des collections de niveau racine et de simplement relier ces collections à l'aide des UID du document.

Pour ceux qui ne le savent pas déjà, Jeff Delaney a des guides et des ressources incroyables pour tous ceux qui travaillent avec Firebase (et Angular) sur AngularFirebase .

Modélisation de données relationnelles Firestore NoSQL - Ici, il décompose les bases de la structuration de bases de données NoSQL et Firestore

Modélisation avancée des données avec Firestore par exemple - Ce sont des techniques plus avancées à garder à l'esprit. Une excellente lecture pour ceux qui souhaitent faire passer leurs compétences Firestore au niveau supérieur

Matthew Mullin
la source
7

NOUVELLE MISE À JOUR 8 juillet 2019:

db.collectionGroup('Songs')
  .where('songName', isEqualTo:'X')
  .get()
Nhật Trần
la source
3

Vous pouvez toujours rechercher comme ceci: -

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;
      }
    })
);
Ankur Biswas
la source
3

Limitations des requêtes

Cloud Firestore ne prend pas en charge les types de requêtes suivants:

  1. Requêtes avec des filtres de plage sur différents champs.

  2. Requêtes uniques sur plusieurs collections ou sous-collections. Chaque requête s'exécute sur une seule collection de documents. Pour plus d'informations sur la manière dont votre structure de données affecte vos requêtes, voir Choisir une structure de données .

  3. Requêtes OU logiques. Dans ce cas, vous devez créer une requête distincte pour chaque condition OR et fusionner les résultats de la requête dans votre application.

  4. Requêtes avec une clause! =. Dans ce cas, vous devez diviser la requête en une requête supérieure à et une requête inférieure à. Par exemple, bien que la clause de requête où ("age", "! =", "30") ne soit pas prise en charge, vous pouvez obtenir le même jeu de résultats en combinant deux requêtes, une avec la clause where ("age", "< "," 30 ") et un avec la clause where (" age ","> ", 30).

ggDeGreat
la source
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);
        });
Alok Prusty
la source
1

Il pourrait être préférable d'utiliser une structure de données plate.
La documentation spécifie les avantages et les inconvénients des différentes structures de données sur cette page .

Plus précisément sur les limites des structures avec sous-collections:

Vous ne pouvez pas facilement supprimer des sous-collections ou effectuer des requêtes composées dans des sous-collections.

Contrastant avec les prétendus avantages d'une structure de données plate:

Les collections au niveau racine offrent le plus de flexibilité et d'évolutivité, ainsi que des requêtes puissantes au sein de chaque collection.

MattCochrane
la source
1

J'ai trouvé une solution. S'il te plaît, vérifie cela.

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

Et puis vous pouvez voir les onglets Données, Règles, Index, Utilisation dans votre firestore cloud à partir de console.firebase.google.com. Enfin, vous devez définir les index dans l'onglet index.entrez la description de l'image ici

Remplissez ici l'ID de collection et une valeur de champ. Sélectionnez ensuite l'option de groupe de collecte. Profitez-en. Merci

Heureux fils
la source
Cela ne répond pas à la question. La requête mentionnée ci-dessus ne récupère que toutes les chansons avec songName = 'X'. Cela ne fournira pas les danses où songName = 'X'.
sachin rathod le
0

Je travaille avec Observables ici et le wrapper AngularFire, mais voici comment j'ai réussi à le faire.

C'est un peu fou, j'apprends toujours les observables et j'en ai peut-être trop fait. Mais c'était un bel exercice.

Quelques explications (pas un expert RxJS):

  • songId $ est une observable qui émettra des identifiants
  • dance $ est une observable qui lit cet id et n'obtient ensuite que la première valeur.
  • il interroge ensuite le collectionGroup de toutes les chansons pour en trouver toutes les instances.
  • En fonction des instances, il traverse les danses parentes et récupère leurs identifiants.
  • Maintenant que nous avons tous les identifiants de danse, nous devons les interroger pour obtenir leurs données. Mais je voulais que cela fonctionne bien, donc au lieu de les interroger un par un, je les répartis dans des seaux de 10 (l'angulaire maximum prendra pour une inrequête.
  • Nous nous retrouvons avec N buckets et devons faire N requêtes sur Firestore pour obtenir leurs valeurs.
  • une fois que nous avons effectué les requêtes sur Firestore, nous devons encore analyser les données à partir de cela.
  • et enfin, nous pouvons fusionner tous les résultats de la requête pour obtenir un seul tableau contenant toutes les danses.
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();

J'ai copié-collé mon code et changé les noms de variables pour les vôtres, je ne sais pas s'il y a des erreurs, mais cela a bien fonctionné pour moi. Faites-moi savoir si vous trouvez des erreurs ou si vous pouvez suggérer une meilleure façon de le tester avec peut-être une fausse cheminée.

Eduardo
la source