Gibt es in Firebase eine Möglichkeit, die Anzahl der untergeordneten Elemente eines Knotens abzurufen, ohne alle Knotendaten zu laden?

132

Sie können die Anzahl der Kinder über erhalten

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

Ich glaube jedoch, dass dies den gesamten Unterbaum dieses Knotens vom Server abruft. Bei großen Listen scheint dies RAM und Latenzzeitintensiv zu sein. Gibt es eine Möglichkeit, die Anzahl (und / oder eine Liste von Kindernamen) zu ermitteln, ohne das Ganze abzurufen?

Josh
quelle
Ahmed Mahmoud
Ihr Code kann den großen Datensatz nicht verarbeiten. Ich habe eine Fehlerursache durch Java-Heapspeicher. Ich warte immer noch auf ein Feature.
Panupong Kongarn

Antworten:

98

Das von Ihnen angegebene Code-Snippet lädt tatsächlich den gesamten Datensatz und zählt ihn dann clientseitig, was bei großen Datenmengen sehr langsam sein kann.

Firebase bietet derzeit keine Möglichkeit, Kinder zu zählen, ohne Daten zu laden. Wir planen jedoch, diese hinzuzufügen.

Derzeit besteht eine Lösung darin, einen Zähler für die Anzahl der untergeordneten Elemente zu verwalten und ihn jedes Mal zu aktualisieren, wenn Sie ein neues untergeordnetes Element hinzufügen. Sie können eine Transaktion verwenden, um Elemente zu zählen, wie in diesem Code zur Verfolgung von 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;
});

Weitere Informationen finden Sie unter https://www.firebase.com/docs/transactions.html

UPDATE: Firebase hat kürzlich Cloud-Funktionen veröffentlicht. Mit Cloud-Funktionen müssen Sie keinen eigenen Server erstellen. Sie können einfach JavaScript-Funktionen schreiben und in Firebase hochladen. Firebase ist dafür verantwortlich, Funktionen auszulösen, wenn ein Ereignis eintritt.

Wenn Sie beispielsweise Upvotes zählen möchten, sollten Sie eine ähnliche Struktur erstellen:

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

Schreiben Sie dann eine Javascript-Funktion, um die Anzahl der upvotes_countneuen Schreibvorgänge auf dem upvotesKnoten zu erhöhen .

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

In der Dokumentation erfahren Sie, wie Sie mit Cloud-Funktionen beginnen .

Ein weiteres Beispiel für das Zählen von Posts finden Sie hier: https://github.com/firebase/functions-samples/blob/master/child-count/functions/index.js

Update Januar 2018

Die Firebase-Dokumente haben sich geändert, anstatt dass eventwir jetzt changeund haben context.

Das angegebene Beispiel löst einen Fehler aus, der event.dataundefiniert ist. Dieses Muster scheint besser zu funktionieren:

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
quelle
74
Haben Sie jemals Unterstützung dafür hinzugefügt?
Jim Cooper
20
Ist ein clientseitiger Zähler in einer Transaktion jedoch sicher? Es scheint, dass es leicht gehackt werden könnte, um die Anzahl künstlich zu erhöhen. Dies könnte für Abstimmungssysteme schlecht sein.
Soviut
16
++ Es wäre wirklich schön, die Zählung zu erhalten, ohne Übertragungskosten zu verursachen
Jared Forsyth
27
Wurde dies jemals hinzugefügt?
Elieser
25
Gibt es Neuigkeiten zu dieser Feature-Roadmap? Vielen Dank
Pandaiolo
37

Dies ist etwas spät im Spiel, da einige andere bereits gut geantwortet haben, aber ich werde Ihnen mitteilen, wie ich es implementieren könnte.

Dies hängt davon ab, dass die Firebase-REST-API einen shallow=trueParameter bietet .

Angenommen, Sie haben ein postObjekt und jedes kann eine Reihe von commentsfolgenden Elementen haben :

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

Sie möchten offensichtlich nicht alle Kommentare abrufen, sondern nur die Anzahl der Kommentare.

Angenommen, Sie haben den Schlüssel für einen Beitrag, können Sie eine GETAnfrage an senden https://yourapp.firebaseio.com/posts/[the post key]/comments?shallow=true.

Dies gibt ein Objekt aus Schlüssel-Wert-Paaren zurück, wobei jeder Schlüssel der Schlüssel eines Kommentars ist und sein Wert ist true:

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

Die Größe dieser Antwort ist viel kleiner als das Anfordern der entsprechenden Daten. Jetzt können Sie die Anzahl der Schlüssel in der Antwort berechnen, um Ihren Wert zu ermitteln (z Object.keys(result).length. B. commentCount = ).

Dies löst Ihr Problem möglicherweise nicht vollständig, da Sie immer noch die Anzahl der zurückgegebenen Schlüssel berechnen und den Wert bei Änderungen nicht unbedingt abonnieren können. Die Größe der zurückgegebenen Daten wird jedoch erheblich reduziert, ohne dass Änderungen an Ihren erforderlich sind Schema.

Alex Klibisz
quelle
Könnte dies zur akzeptierten Antwort machen, da flach = wahr eine neue Ergänzung seit den vorherigen Antworten ist. Ich hatte keine Zeit, mich selbst darum zu kümmern, also werde ich ein paar Tage warten, um zu sehen, was die Leute denken ...
Josh
1
Flach ist wahrscheinlich die beste Option für den
Moment
Wenn die Kommentarschlüssel keine booleschen Werte haben, sondern untergeordnete Werte, werden dann immer noch die Schlüssel-Wert-Schlüsselpaare zurückgegeben?
Alltej
1
Vielleicht möchten Sie mit der REST-API vorsichtig sein: startupsventurecapital.com/…
Remi Sture
3
Nur um darauf hinzuweisen, dass Sie .jsonan das Ende der URL anhängen müssen , zum Beispiel:https://yourapp.firebaseio.com/posts/comments.json?shallow=true
Osama Xäwãñz
22

Speichern Sie die Zählung, während Sie fortfahren - und verwenden Sie die Validierung, um sie durchzusetzen. Ich habe das zusammen gehackt - um eine Anzahl einzigartiger Stimmen und Zählungen zu erhalten, die immer wieder auftauchen!. Aber diesmal habe ich meinen Vorschlag getestet! (ungeachtet von Fehlern beim Ausschneiden / Einfügen!).

Der ‚Trick‘ ist hier um den Knoten zu verwenden Priorität als die Stimme ...

Die Daten sind:

Abstimmung / $ issueBeingVotedOn / user / $ uniqueIdOfVoter = thisVotesCount, Priorität = thisVotesCount Abstimmung / $ issueBeingVotedOn / count = 'Benutzer /' + $ idOfLastVoter, Priorität = 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()" 

Benutzer kann nur einmal abstimmen && Anzahl muss eins höher sein als aktuelle Anzahl && Datenwert muss gleich Priorität sein.

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

count (letzter Wähler wirklich) - Abstimmung muss vorhanden sein und die Anzahl entspricht newcount, && newcount (Priorität) kann nur um eins steigen.

  }
}

Testskript zum Hinzufügen von 10 Stimmen von verschiedenen Benutzern (in diesem Beispiel ist die ID gefälscht, sollte der Benutzer auth.uid in der Produktion sein). Countdown bis (i--) 10, um zu sehen, dass die Validierung fehlschlägt.

<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>

Das "Risiko" besteht hier darin, dass eine Stimme abgegeben wird, die Anzahl jedoch nicht aktualisiert wird (Haking oder Skriptfehler). Aus diesem Grund haben die Stimmen eine eindeutige "Priorität" - das Skript sollte zunächst sicherstellen, dass keine Stimme mit einer höheren Priorität als der aktuellen Anzahl vorhanden ist. Wenn diese Transaktion abgeschlossen sein sollte, bevor sie ihre eigene ausführt, lassen Sie Ihre Kunden bereinigen für dich :)

Die Zählung muss vor dem Start mit einer Priorität initialisiert werden - Forge lässt dies nicht zu, daher wird ein Stub-Skript benötigt (bevor die Validierung aktiv ist!).

pperrin
quelle
Das ist fantastisch!!! Was passiert jedoch bei Konflikten? Dh zwei Personen stimmen gleichzeitig ab? Idealerweise möchten Sie das automatisch beheben, anstatt nur eine ihrer Stimmen zu verwerfen ... vielleicht die Abstimmung in einer Transaktion?
Josh
Hallo Josh, logischerweise kann eine echte Abstimmung nur scheitern, wenn eine vorherige Abstimmung abgegeben wurde, aber die Summe (noch) nicht aktualisiert wurde. Mein vorletzter Absatz behandelt Folgendes: Ich würde sowieso (jedes Mal) nur das Gesamtupdate für die Abstimmung der vorherigen Wähler durchführen - wenn es nicht benötigt würde, na und? und dann wird diese Abstimmung aktualisiert. Solange die Abstimmung gut funktioniert. Wenn Ihr 'Total'-Update fehlschlägt, wird es vom nächsten Wähler behoben, also noch einmal - na und?
pperrin
Ich bin wirklich versucht zu sagen, dass der Knoten 'count' ein Knoten 'letzte vorherige Abstimmung' sein sollte - also aktualisiert / repariert / repariert jeder Wähler / Kunde diesen Knoten / Wert und fügt dann seine eigene Stimme hinzu - (damit der nächste Wähler den Knoten aktualisiert insgesamt, um 'diese' Abstimmung einzuschließen). - Wenn Sie meine Drift bekommen ...
pperrin
4

Schreiben Sie eine Cloud-Funktion in und aktualisieren Sie die Knotenanzahl.

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

Siehe: https://firebase.google.com/docs/functions/database-events

root-- | | -users (dieser Knoten enthält alle Benutzerlisten) |
| -count | -userscount: (Dieser Knoten wird dynamisch durch die Cloud-Funktion mit der Benutzeranzahl hinzugefügt.)

Indvin
quelle