Dans Firebase, existe-t-il un moyen d'obtenir le nombre d'enfants d'un nœud sans charger toutes les données du nœud?

132

Vous pouvez obtenir le nombre d'enfants via

firebase_node.once('value', function(snapshot) { alert('Count: ' + snapshot.numChildren()); });

Mais je crois que cela récupère le sous-arbre entier de ce nœud du serveur. Pour les listes énormes, cela semble intensif en RAM et en latence. Existe-t-il un moyen d'obtenir le décompte (et / ou une liste de noms d'enfants) sans récupérer le tout?

Josh
la source
merci beaucoup je l'essaye et cela a fonctionné pour moi
Ahmed Mahmoud
votre code ne peut pas gérer le grand ensemble de données. J'ai eu une cause d'erreur par l'espace de tas Java. J'attends toujours une fonctionnalité.
Panupong Kongarn

Réponses:

98

L'extrait de code que vous avez donné charge en effet l'ensemble des données, puis le compte côté client, ce qui peut être très lent pour de grandes quantités de données.

Firebase n'a actuellement pas de moyen de compter les enfants sans charger de données, mais nous prévoyons de l'ajouter.

Pour l'instant, une solution serait de maintenir un compteur du nombre d'enfants et de le mettre à jour chaque fois que vous ajoutez un nouvel enfant. Vous pouvez utiliser une transaction pour compter les articles, comme dans ce code de suivi des upvodes:

var upvotesRef = new Firebase('https://docs-examples.firebaseio.com/android/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes');
upvotesRef.transaction(function (current_value) {
  return (current_value || 0) + 1;
});

Pour plus d'informations, voir https://www.firebase.com/docs/transactions.html

MISE À JOUR: Firebase a récemment publié Cloud Functions. Avec Cloud Functions, vous n'avez pas besoin de créer votre propre serveur. Vous pouvez simplement écrire des fonctions JavaScript et les télécharger sur Firebase. Firebase sera responsable du déclenchement des fonctions chaque fois qu'un événement se produit.

Si vous souhaitez compter les votes positifs par exemple, vous devez créer une structure similaire à celle-ci:

{
  "posts" : {
    "-JRHTHaIs-jNPLXOQivY" : {
      "upvotes_count":5,
      "upvotes" : {
      "userX" : true,
      "userY" : true,
      "userZ" : true,
      ...
    }
    }
  }
}

Et puis écrivez une fonction javascript pour augmenter le upvotes_countquand il y a une nouvelle écriture sur le upvotesnœud.

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

exports.countlikes = functions.database.ref('/posts/$postid/upvotes').onWrite(event => {
  return event.data.ref.parent.child('upvotes_count').set(event.data.numChildren());
});

Vous pouvez lire la documentation pour savoir comment démarrer avec Cloud Functions .

En outre, un autre exemple de comptage des messages est ici: https://github.com/firebase/functions-samples/blob/master/child-count/functions/index.js

Mise à jour janvier 2018

La documentation de Firebase a changé donc au lieu de eventnous avons maintenant changeet context.

L'exemple donné lève une erreur se plaignant qui event.datan'est pas définie. Ce modèle semble mieux fonctionner:

exports.countPrescriptions = functions.database.ref(`/prescriptions`).onWrite((change, context) => {
    const data = change.after.val();
    const count = Object.keys(data).length;
    return change.after.ref.child('_count').set(count);
});

''

Andrew Lee
la source
74
Avez-vous déjà ajouté un support pour cela?
Jim Cooper
20
Un compteur côté client dans une transaction est-il sécurisé? Il semble qu'il pourrait être facilement piraté pour augmenter artificiellement les comptes. Cela pourrait être mauvais pour les systèmes de vote.
Soviut
16
++ ce serait vraiment bien d'obtenir le décompte sans encourir de frais de transfert
Jared Forsyth
27
Cela a-t-il déjà été ajouté?
Eliezer
25
Des nouvelles sur cette feuille de route? Merci
Pandaiolo
38

C'est un peu tard dans le jeu car plusieurs autres ont déjà répondu gentiment, mais je vais partager comment je pourrais l'implémenter.

Cela dépend du fait que l' API Firebase REST propose un shallow=trueparamètre.

Supposons que vous ayez un postobjet et que chacun puisse avoir un certain nombre de comments:

{
 "posts": {
  "$postKey": {
   "comments": {
     ...  
   }
  }
 }
}

Vous ne voulez évidemment pas récupérer tous les commentaires, juste le nombre de commentaires.

En supposant que vous ayez la clé d'un message, vous pouvez envoyer une GETdemande à https://yourapp.firebaseio.com/posts/[the post key]/comments?shallow=true.

Cela renverra un objet de paires clé-valeur, où chaque clé est la clé d'un commentaire et sa valeur est true:

{
 "comment1key": true,
 "comment2key": true,
 ...,
 "comment9999key": true
}

La taille de cette réponse est beaucoup plus petite que la demande des données équivalentes, et vous pouvez maintenant calculer le nombre de clés dans la réponse pour trouver votre valeur (par exemple commentCount = Object.keys(result).length).

Cela ne résoudra peut-être pas complètement votre problème, car vous calculez toujours le nombre de clés renvoyées et vous ne pouvez pas nécessairement vous abonner à la valeur à mesure qu'elle change, mais cela réduit considérablement la taille des données renvoyées sans nécessiter de modification de votre schéma.

Alex Klibisz
la source
Cela pourrait en faire la réponse acceptée car shallow = true est un nouvel ajout depuis les réponses précédentes. Je n'ai pas eu le temps de l'examiner moi-même, alors j'attendrai quelques jours pour voir ce que les gens en pensent ...
josh
1
Shallow est probablement la meilleure option pour le moment, mais elle n'est pas renvoyée avec la compression et peut devenir assez lente et éprouvante pour les grands ensembles de données
Mbrevda
Si les clés de commentaire n'ont pas de valeurs booléennes mais ont plutôt des enfants, renvoient-elles toujours les paires clé-valeur de clés?
alltej
1
Vous voudrez peut-être faire attention en utilisant l'API REST: startupsventurecapital.com/...
Remi Sture
3
Juste pour souligner que vous devez ajouter .jsonà la fin de l'URL, par exemple:https://yourapp.firebaseio.com/posts/comments.json?shallow=true
Osama Xäwãñz
22

Enregistrez le décompte au fur et à mesure et utilisez la validation pour l'appliquer. J'ai piraté cela ensemble - pour garder un décompte des votes uniques et des décomptes qui ne cessent de monter !. Mais cette fois, j'ai testé ma suggestion! (nonobstant les erreurs de copier / coller!).

Le `` truc '' ici est d'utiliser la priorité du nœud comme compte de vote ...

Les données sont:

vote / $ issueBeingVotedOn / user / $ uniqueIdOfVoter = thisVotesCount, priority = thisVotesCount vote / $ issueBeingVotedOn / count = 'user /' + $ idOfLastVoter, priority = CountofLastVote

,"vote": {
  ".read" : true
  ,".write" : true
  ,"$issue" : {
    "user" : {
      "$user" : {
        ".validate" : "!data.exists() && 
             newData.val()==data.parent().parent().child('count').getPriority()+1 &&
             newData.val()==newData.GetPriority()" 

l'utilisateur ne peut voter qu'une seule fois && le compte doit être supérieur de un au nombre actuel et la valeur des données doit être identique à la priorité.

      }
    }
    ,"count" : {
      ".validate" : "data.parent().child(newData.val()).val()==newData.getPriority() &&
             newData.getPriority()==data.getPriority()+1 "
    }

count (dernier votant vraiment) - le vote doit exister et son compte est égal à newcount, && newcount (priorité) ne peut augmenter que de un.

  }
}

Script de test pour ajouter 10 votes par différents utilisateurs (pour cet exemple, id est falsifié, devrait l'utilisateur auth.uid en production). Compte à rebours de (i--) 10 pour voir l'échec de la validation.

<script src='https://cdn.firebase.com/v0/firebase.js'></script>
<script>
  window.fb = new Firebase('https:...vote/iss1/');
  window.fb.child('count').once('value', function (dss) {
    votes = dss.getPriority();
    for (var i=1;i<10;i++) vote(dss,i+votes);
  } );

function vote(dss,count)
{
  var user='user/zz' + count; // replace with auth.id or whatever
  window.fb.child(user).setWithPriority(count,count);
  window.fb.child('count').setWithPriority(user,count);
}
</script>

Le «risque» ici est qu'un vote est émis, mais le décompte n'est pas mis à jour (piratage ou échec du script). C'est pourquoi les votes ont une `` priorité '' unique - le script doit vraiment commencer par s'assurer qu'il n'y a pas de vote avec une priorité supérieure au décompte actuel, s'il y en a, il doit terminer cette transaction avant de faire la sienne - faites nettoyer vos clients pour vous :)

Le décompte doit être initialisé avec une priorité avant de commencer - forge ne vous permet pas de faire cela, donc un script stub est nécessaire (avant que la validation ne soit active!).

pperrin
la source
C'est génial!!! Mais que se passe-t-il lors des conflits? Ie, deux personnes votent en même temps? Idéalement, vous voudriez résoudre automatiquement cela, au lieu de simplement rejeter l'un de leurs votes ... peut-être voter dans une transaction?
josh
Salut Josh, logiquement un véritable vote ne peut échouer que si un vote précédent a été émis, mais le total n'est pas (encore) mis à jour. Mon deuxième au dernier para couvre cela - je ferais juste la mise à jour totale pour les votants précédents de toute façon (à chaque fois) - si ce n'était pas nécessaire, et alors? et puis ce vote se met à jour. Tant que le vote fonctionne bien. Si votre mise à jour «totale» échoue, le prochain électeur la réparera, donc encore une fois - et alors?
pperrin
Je suis vraiment tenté de dire que le nœud 'count' devrait être un nœud 'dernier vote précédent' - donc chaque électeur / client met à jour / corrige / répare ce nœud / valeur et ajoute ensuite son propre vote - (laissant le prochain électeur mettre à jour le total pour inclure «ce» vote). - Si vous obtenez ma dérive ...
pperrin
4

écrire une fonction cloud et mettre à jour le nombre de nœuds.

// below function to get the given node count.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

exports.userscount = functions.database.ref('/users/')
    .onWrite(event => {

      console.log('users number : ', event.data.numChildren());


      return event.data.ref.parent.child('count/users').set(event.data.numChildren());
    }); 

Référer : https://firebase.google.com/docs/functions/database-events

racine-- | | -users (ce nœud contient la liste de tous les utilisateurs) |
| -count | -userscount: (ce nœud ajouté dynamiquement par la fonction cloud avec le nombre d'utilisateurs)

indvin
la source