Czy w Firebase można uzyskać liczbę elementów podrzędnych węzła bez ładowania wszystkich danych węzła?

132

Możesz uzyskać liczbę dzieci za pośrednictwem

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

Ale uważam, że to pobiera całe poddrzewo tego węzła z serwera. W przypadku ogromnych list wydaje się, że pamięć RAM i opóźnienia są duże. Czy istnieje sposób uzyskania liczby (i / lub listy imion dzieci) bez pobierania całości?

Josh
źródło
wielkie dzięki, spróbuję i zadziałało dla mnie
Ahmed Mahmoud
Twój kod nie może obsłużyć dużego zestawu danych. Wystąpił błąd spowodowany przez przestrzeń sterty Java. Nadal czekam na jakąś funkcję.
Panupong Kongarn

Odpowiedzi:

100

Podany przez Ciebie fragment kodu faktycznie ładuje cały zestaw danych, a następnie liczy go po stronie klienta, co może być bardzo powolne w przypadku dużych ilości danych.

Firebase nie ma obecnie możliwości liczenia dzieci bez ładowania danych, ale planujemy je dodać.

Na razie jednym rozwiązaniem byłoby utrzymywanie licznika liczby dzieci i aktualizowanie go za każdym razem, gdy dodajesz nowe dziecko. Możesz użyć transakcji do liczenia przedmiotów, tak jak w tym kodzie śledzącym 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;
});

Więcej informacji można znaleźć pod adresem https://www.firebase.com/docs/transactions.html

AKTUALIZACJA: Firebase niedawno wydało Cloud Functions. Dzięki Cloud Functions nie musisz tworzyć własnego serwera. Możesz po prostu napisać funkcje JavaScript i przesłać je do Firebase. Firebase będzie odpowiedzialny za wyzwalanie funkcji po wystąpieniu zdarzenia.

Jeśli chcesz na przykład liczyć głosy za, powinieneś stworzyć strukturę podobną do tej:

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

A następnie napisz funkcję javascript, aby zwiększyć upvotes_countczas nowego zapisu do upvoteswęzła.

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

Możesz przeczytać Dokumentację, aby dowiedzieć się, jak rozpocząć korzystanie z funkcji chmury .

Inny przykład liczenia postów jest tutaj: https://github.com/firebase/functions-samples/blob/master/child-count/functions/index.js

Aktualizacja styczeń 2018

Dokumentacja Firebase uległa zmianie, więc zamiast tego eventmamy teraz changei context.

Podany przykład zgłasza błąd, narzekając, że event.datajest niezdefiniowany. Ten wzór wydaje się działać lepiej:

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
źródło
74
Czy kiedykolwiek dodałeś do tego wsparcie?
Jim Cooper,
20
Czy licznik po stronie klienta w transakcji jest jednak bezpieczny? Wygląda na to, że można go łatwo zhakować, aby sztucznie zwiększyć liczbę. Może to być złe dla systemów głosowania.
Soviut,
16
++ byłoby naprawdę fajnie policzyć bez ponoszenia kosztów transferu
Jared Forsyth
27
Czy to kiedykolwiek zostało dodane?
Eliezer,
25
Jakieś wieści na temat tego planu działania? Dzięki
Pandaiolo
38

To trochę późno w grze, ponieważ kilku innych już ładnie odpowiedziało, ale opowiem, jak mogę to zaimplementować.

Zależy to od faktu, że Firebase REST API oferujeshallow=true parametr.

Załóżmy, że masz postobiekt, a każdy z nich może mieć kilka comments:

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

Oczywiście nie chcesz pobierać wszystkich komentarzy, tylko liczbę komentarzy.

Zakładając, że masz klucz do wpisu, możesz wysłać GETprośbę do https://yourapp.firebaseio.com/posts/[the post key]/comments?shallow=true .

To zwróci obiekt składający się z par klucz-wartość, gdzie każdy klucz jest kluczem komentarza, a jego wartość to true :

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

Rozmiar tej odpowiedzi jest znacznie mniejszy niż żądanie równoważnych danych, a teraz możesz obliczyć liczbę kluczy w odpowiedzi, aby znaleźć swoją wartość (np. CommentCount = Object.keys(result).length ).

Może to nie rozwiązać całkowicie problemu, ponieważ nadal obliczasz liczbę zwracanych kluczy i niekoniecznie możesz subskrybować wartość, gdy się zmienia, ale znacznie zmniejsza rozmiar zwracanych danych bez konieczności wprowadzania jakichkolwiek zmian w schemat.

Alex Klibisz
źródło
Może sprawić, że będzie to akceptowana odpowiedź, ponieważ shallow = true jest nowym dodatkiem od poprzednich odpowiedzi. Sam nie miałem czasu, aby się temu przyjrzeć, więc poczekam kilka dni, aby zobaczyć, co myślą ludzie ...
josh
1
Shallow jest prawdopodobnie najlepszą opcją na razie, ale nie jest zwracana przy kompresji i może stać się dość powolna i doświadczać w przypadku dużych zestawów danych
Mbrevda,
Jeśli klucze komentarza nie mają wartości logicznych, ale zamiast tego mają dzieci, czy nadal zwraca pary klucz-wartość kluczy?
alltej
1
Możesz zachować ostrożność, korzystając z REST API: startupsventurecapital.com/ ...
Remi Sture
3
Wystarczy zaznaczyć, że .jsonna końcu adresu URL należy dołączyć , na przykład:https://yourapp.firebaseio.com/posts/comments.json?shallow=true
Osama Xäwãñz
22

Zapisuj licznik na bieżąco - i wymuszaj walidację. Zhakowałem to razem - za liczenie unikalnych głosów i zliczeń, które wciąż rosną !. Ale tym razem przetestowałem swoją sugestię! (niezależnie od błędów wycinania / wklejania!).

„Sztuczka” polega na tym, aby użyć priorytetu węzła jako liczby głosów ...

Dane to:

głosuj / $ issueBeingVotedOn / user / $ uniqueIdOfVoter = thisVotesCount, priority = thisVotesCount głos / $ 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()" 

użytkownik może głosować tylko raz && liczba musi być o jeden wyższa niż bieżąca liczba, a wartość danych musi być taka sama jak priorytet.

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

count (tak naprawdę ostatni wyborca) - głos musi istnieć, a jego liczba musi być równa nowej liczbie, && newcount (priorytet) może wzrosnąć tylko o jeden.

  }
}

Skrypt testowy dodający 10 głosów od różnych użytkowników (w tym przykładzie fałszywy identyfikator, jeśli użytkownik auth.uid powinien być w środowisku produkcyjnym). Odlicz do (i--) 10, aby zobaczyć, że walidacja nie powiodła się.

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

„Ryzyko” polega na tym, że głos został oddany, ale licznik nie został zaktualizowany (hakerstwo lub błąd skryptu). Dlatego głosy mają unikalny `` priorytet '' - skrypt powinien naprawdę zaczynać się od upewnienia się, że nie ma głosowania z priorytetem wyższym niż bieżąca liczba, jeśli tak, należy zakończyć tę transakcję przed wykonaniem własnego - spraw, aby klienci wyczyścili do ciebie :)

Licznik musi zostać zainicjowany z priorytetem przed rozpoczęciem - forge nie pozwala na to, więc potrzebny jest skrypt zastępczy (zanim walidacja będzie aktywna!).

pperrin
źródło
To jest niesamowite!!! Co jednak dzieje się w przypadku konfliktów? To znaczy dwie osoby głosują w tym samym czasie? Idealnie byłoby, gdybyś chciał to automatycznie rozwiązać, zamiast po prostu odrzucać jeden z ich głosów ... może głosować w transakcji?
josh
Cześć Josh, logicznie rzecz biorąc, prawdziwy głos może się nie powieść tylko wtedy, gdy został oddany poprzedni głos, ale suma nie została zaktualizowana (jeszcze). Mój drugi do ostatniego paragraf obejmuje to - i tak dokonałbym całkowitej aktualizacji dla poprzednich głosujących (za każdym razem) - gdyby nie było to potrzebne, więc co z tego? a następnie ta aktualizacja głosów. O ile głosowanie działa dobrze. Jeśli twoja „całkowita” aktualizacja się nie powiedzie, następny głosujący to naprawi, więc znowu - i co z tego?
pperrin
Naprawdę kusi mnie, aby po prostu powiedzieć, że węzeł `` count '' powinien być węzłem `` ostatniego poprzedniego głosowania '' - więc każdy wyborca ​​/ klient aktualizuje / naprawia / naprawia ten węzeł / wartość, a następnie dodaje własny głos - (pozwalając następnemu wyborcy zaktualizować łącznie, aby uwzględnić „ten” głos). - Jeśli łapiesz mój dryf ...
pperrin
4

Napisz funkcję chmury do i zaktualizuj liczbę węzłów.

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

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

root - | | -users (ten węzeł zawiera listę wszystkich użytkowników) |
| -count | -userscount: (ten węzeł dodawany dynamicznie przez funkcję chmury z liczbą użytkowników)

indvin
źródło