No Firebase, existe uma maneira de obter o número de filhos de um nó sem carregar todos os dados do nó?

132

Você pode obter a contagem de crianças via

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

Mas acredito que isso busca a subárvore inteira desse nó no servidor. Para listas enormes, isso parece intensivo em RAM e latência. Existe uma maneira de obter a contagem (e / ou uma lista de nomes filhos) sem buscar a coisa toda?

Josh
fonte
muito obrigado i experimentá-lo e ele trabalhou para mim
Ahmed Mahmoud
seu código não pode lidar com o grande conjunto de dados. Eu recebi um erro causado pelo espaço de heap Java. Eu ainda estou esperando por algum recurso.
Panupong Kongarn

Respostas:

98

O snippet de código que você forneceu realmente carrega todo o conjunto de dados e o conta no lado do cliente, o que pode ser muito lento para grandes quantidades de dados.

No momento, o Firebase não tem como contar crianças sem carregar dados, mas planejamos adicioná-los.

Por enquanto, uma solução seria manter um contador do número de filhos e atualizá-lo toda vez que você adicionar um novo filho. Você pode usar uma transação para contar itens, como neste código que rastreia 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;
});

Para mais informações, consulte https://www.firebase.com/docs/transactions.html

ATUALIZAÇÃO: O Firebase lançou recentemente o Cloud Functions. Com o Cloud Functions, você não precisa criar seu próprio servidor. Você pode simplesmente escrever funções JavaScript e enviá-lo para o Firebase. O Firebase será responsável por acionar funções sempre que ocorrer um evento.

Se você quiser contar votos positivos, por exemplo, crie uma estrutura semelhante a esta:

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

E, em seguida, escreva uma função javascript para aumentar upvotes_countquando houver uma nova gravação no upvotesnó.

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

Você pode ler a documentação para saber como iniciar as funções da nuvem .

Além disso, outro exemplo de contagem de postagens está aqui: https://github.com/firebase/functions-samples/blob/master/child-count/functions/index.js

Atualização janeiro 2018

Os documentos da base de firmas foram alterados. Em vez de eventagora, temos changee context.

O exemplo dado gera um erro de reclamação event.dataindefinido. Esse padrão parece funcionar melhor:

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
fonte
74
Você já adicionou suporte para isso?
Jim Cooper
20
Um contador do lado do cliente em uma transação é seguro? Parece que poderia ser facilmente hackeado para aumentar artificialmente as contagens. Isso pode ser ruim para os sistemas de votação.
Soviut
16
++ seria muito bom para obter a contagem, sem incorrer em custos de transferência
Jared Forsyth
27
Isso já foi adicionado?
Eliezer #
25
Alguma notícia sobre este roteiro de recursos? Obrigado
Pandaiolo
37

Isso é um pouco tarde no jogo, pois vários outros já responderam bem, mas vou compartilhar como implementá-lo.

Isso depende do fato de a API REST do Firebase oferecer um shallow=trueparâmetro.

Suponha que você tenha um postobjeto e cada um possa ter um número de comments:

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

Você obviamente não deseja buscar todos os comentários, apenas o número de comentários.

Supondo que você tenha a chave para uma postagem, você pode enviar uma GETsolicitação para https://yourapp.firebaseio.com/posts/[the post key]/comments?shallow=true.

Isso retornará um objeto de pares de valores-chave, em que cada chave é a chave de um comentário e seu valor é true:

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

O tamanho desta resposta é muito menor do que a solicitação de dados equivalentes e agora você pode calcular o número de chaves na resposta para encontrar seu valor (por exemplo, commentCount = Object.keys(result).length).

Isso pode não resolver completamente o seu problema, pois você ainda está calculando o número de chaves retornadas e não pode necessariamente assinar o valor à medida que ele muda, mas reduz bastante o tamanho dos dados retornados sem exigir nenhuma alteração no seu esquema.

Alex Klibisz
fonte
Pode fazer disso a resposta aceita, já que shallow = true é uma nova adição às respostas anteriores. Não tive tempo para olhar para isso sozinho, então vai esperar alguns dias para ver o que as pessoas pensam ...
josh
1
Rasa é provavelmente a melhor opção para agora, mas não é devolvido com compressão e pode se tornar muito lento e experiência para grandes conjuntos de dados
Mbrevda
Se as chaves de comentário não tiverem valores booleanos, mas tiverem filhos, ainda retornará os pares de chaves de valor-chave?
alltej
1
Convém ter cuidado ao usar a API REST: startupsventurecapital.com/…
Remi Sture
3
Apenas para salientar que você precisa anexar .jsonao final da URL, por exemplo:https://yourapp.firebaseio.com/posts/comments.json?shallow=true
Osama Xäwãñz
22

Salve a contagem à medida que avança - e use a validação para aplicá-la. Eu hackeei isso juntos - por manter uma contagem de votos e contagens únicos que continuam aparecendo !. Mas desta vez eu testei minha sugestão! (apesar dos erros de recortar / colar!).

O 'truque' aqui é usar a prioridade do nó como contagem de votos ...

Os dados são:

vote / $ issueBeingVotedOn / user / $ uniqueIdOfVoter = thisVotesCount, priority = thisVotesCount vote / $ issueBeingVotedOn / count = 'usuário /' + $ 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()" 

o usuário pode votar apenas uma vez que a contagem de&& deve ser uma maior que a contagem atual e o valor dos dados de&& deve ser o mesmo da prioridade.

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

count (último votante realmente) - o voto deve existir e sua contagem é igual a newcount, && newcount (prioridade) pode subir apenas um.

  }
}

Script de teste para adicionar 10 votos de usuários diferentes (neste exemplo, os IDs são falsificados, o usuário deve auth.uid em produção). Contagem regressiva de (i--) 10 para ver a validação falhar.

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

O 'risco' aqui é que a votação é feita, mas a contagem não é atualizada (haking ou falha no script). É por isso que os votos têm uma 'prioridade' única - o script deve realmente começar garantindo que não haja voto com prioridade maior que a contagem atual, se houver, deve concluir essa transação antes de fazer a sua própria - faça com que seus clientes limpem pronto para você :)

A contagem precisa ser inicializada com uma prioridade antes de você iniciar - o forge não permite fazer isso, portanto é necessário um script stub (antes que a validação esteja ativa!).

pperrina
fonte
Isso é incrível!!! O que acontece nos conflitos? Ou seja, duas pessoas votam ao mesmo tempo? Idealmente, você gostaria de resolver isso automaticamente, em vez de apenas descartar um de seus votos ... talvez faça o voto em uma transação?
josh
Olá Josh, logicamente uma votação genuína só pode falhar se uma votação anterior tiver sido lançada, mas o total ainda não estiver atualizado. Meu segundo ao último parágrafo aborda isso - eu faria a atualização total dos votos anteriores de qualquer maneira (sempre) - se não fosse necessário, e daí? e então isso vota atualizações. Enquanto a votação funcionar bem. Se a sua atualização 'total' falhar, o próximo eleitor irá corrigi-la, então novamente - e daí?
Pperrin
Estou realmente tentado a dizer que o nó 'count' deve ser o nó 'last vote anterior' - para que cada eleitor / cliente atualize / corrija / conserte esse nó / valor e adicione seu próprio voto - (deixando o próximo eleitor atualizar o total para incluir o voto 'this'). - Se você me entende ...
pperrin
4

escreva uma função de nuvem e atualize a contagem de nós.

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

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

root-- | | -users (este nó contém a lista de todos os usuários) |
| -count | -userscount: (este nó foi adicionado dinamicamente pela função de nuvem com a contagem de usuários)

indvin
fonte