En Firebase, ¿hay alguna forma de obtener el número de hijos de un nodo sin cargar todos los datos del nodo?

132

Puede hacer que el niño cuente a través de

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

Pero creo que esto obtiene todo el subárbol de ese nodo del servidor. Para listas enormes, parece que la RAM y la latencia son intensivas. ¿Hay alguna manera de obtener el recuento (y / o una lista de nombres de niños) sin obtener todo?

josh
fuente
muchas gracias lo probé y funcionó para mí
Ahmed Mahmoud
su código no puede manejar el gran conjunto de datos. Obtuve un error por el espacio de almacenamiento dinámico de Java. Todavía estoy esperando alguna característica.
Panupong Kongarn

Respuestas:

98

El fragmento de código que proporcionó realmente carga todo el conjunto de datos y luego lo cuenta del lado del cliente, lo que puede ser muy lento para grandes cantidades de datos.

Firebase actualmente no tiene una forma de contar niños sin cargar datos, pero planeamos agregarlos.

Por ahora, una solución sería mantener un contador del número de hijos y actualizarlo cada vez que agregue un hijo nuevo. Puede usar una transacción para contar artículos, como en este código de seguimiento de las mejoras:

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 obtener más información, consulte https://www.firebase.com/docs/transactions.html

ACTUALIZACIÓN: Firebase lanzó recientemente Cloud Functions. Con Cloud Functions, no necesita crear su propio servidor. Simplemente puede escribir funciones de JavaScript y subirlo a Firebase. Firebase será responsable de activar las funciones cada vez que ocurra un evento.

Si desea contar votos a favor, por ejemplo, debe crear una estructura similar a esta:

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

Y luego escriba una función de JavaScript para aumentar upvotes_countcuando hay una nueva escritura en el upvotesnodo.

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

Puede leer la Documentación para saber cómo comenzar con las funciones en la nube .

Además, otro ejemplo de conteo de publicaciones está aquí: https://github.com/firebase/functions-samples/blob/master/child-count/functions/index.js

Actualización enero 2018

Los documentos de Firebase han cambiado, así que en lugar de eventahora tenemos changey context.

El ejemplo dado arroja un error quejándose que event.datano está definido. Este patrón parece funcionar mejor:

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
fuente
74
¿Alguna vez agregaste soporte para esto?
Jim Cooper
20
Sin embargo, ¿es seguro un contador del lado del cliente en una transacción? Parece que podría piratearse fácilmente para aumentar artificialmente los recuentos. Esto podría ser malo para los sistemas de votación.
Soviut
16
++ sería realmente agradable obtener el conteo sin incurrir en costos de transferencia
Jared Forsyth
27
¿Se ha agregado esto alguna vez?
Eliezer
25
¿Alguna noticia sobre esta hoja de ruta? Gracias
Pandaiolo
37

Esto es un poco tarde en el juego ya que muchos otros ya han respondido bien, pero compartiré cómo podría implementarlo.

Esto depende del hecho de que Firebase REST API ofrece un shallow=trueparámetro.

Suponga que tiene un postobjeto y cada uno puede tener varios comments:

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

Obviamente, no desea obtener todos los comentarios, solo la cantidad de comentarios.

Suponiendo que tiene la clave para una publicación, puede enviar una GETsolicitud a https://yourapp.firebaseio.com/posts/[the post key]/comments?shallow=true.

Esto devolverá un objeto de pares clave-valor, donde cada clave es la clave de un comentario y su valor es true:

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

El tamaño de esta respuesta es mucho más pequeño que solicitar los datos equivalentes, y ahora puede calcular el número de claves en la respuesta para encontrar su valor (por ejemplo, commentCount = Object.keys(result).length).

Es posible que esto no resuelva completamente su problema, ya que todavía está calculando el número de claves devueltas, y no necesariamente puede suscribirse al valor a medida que cambia, pero reduce en gran medida el tamaño de los datos devueltos sin requerir ningún cambio en su esquema.

Alex Klibisz
fuente
Podría hacer de esta la respuesta aceptada ya que shallow = true es una nueva adición desde las respuestas anteriores. No han tenido tiempo de mirar en él mismo, por lo que va a esperar unos días para ver lo que la gente piensa ...
Josh
1
Shallow es probablemente la mejor opción por ahora, pero no se devuelve con compresión y puede volverse bastante lento y experimentar para grandes conjuntos de datos
Mbrevda
Si las claves de comentario no tienen valores booleanos, sino que tienen elementos secundarios, ¿aún devuelve los pares de claves clave-valor?
alltej
1
Es posible que desee tener cuidado al usar la API REST: startupsventurecapital.com/…
Remi Sture
3
Solo para señalar que debe agregar .jsonal final de la URL, por ejemplo:https://yourapp.firebaseio.com/posts/comments.json?shallow=true
Osama Xäwãñz
22

Guarde el recuento a medida que avanza y use la validación para aplicarlo. Pirateé esto juntos, ¡por mantener un recuento de votos únicos y recuentos que siguen surgiendo! ¡Pero esta vez he probado mi sugerencia! (¡a pesar de los errores de cortar / pegar!).

El 'truco' aquí es usar la prioridad de nodo como el recuento de votos ...

Los datos son:

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()" 

el usuario solo puede votar una vez && count debe ser uno más alto que el conteo actual && el valor de los datos debe ser igual a la prioridad.

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

recuento (último votante realmente): el voto debe existir y su recuento es igual al recuento nuevo, y & recuento nuevo (prioridad) solo puede aumentar en uno.

  }
}

Pruebe la secuencia de comandos para agregar 10 votos de diferentes usuarios (para este ejemplo, la identificación es falsa, debería usar auth.uid en producción). Cuenta atrás para (i--) 10 para ver la validación falla.

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

El 'riesgo' aquí es que se emite un voto, pero el recuento no se actualiza (piratería o falla del script). Esta es la razón por la cual los votos tienen una 'prioridad' única: el guión realmente debería comenzar asegurándose de que no haya un voto con prioridad más alta que el conteo actual, si es que debe completar esa transacción antes de hacer la suya, haga que sus clientes limpien para ti :)

El recuento debe inicializarse con una prioridad antes de comenzar: forge no le permite hacer esto, por lo que se necesita un script de código auxiliar (¡antes de que la validación esté activa!).

pperrin
fuente
¡Esto es asombroso! ¿Qué pasa con los conflictos, sin embargo? Es decir, ¿dos personas votan al mismo tiempo? Idealmente, desearía resolverlo automáticamente, en lugar de simplemente descartar uno de sus votos ... ¿tal vez votar en una transacción?
josh
Hola Josh, lógicamente un voto genuino solo puede fallar si se ha emitido un voto anterior, pero el total no está actualizado (todavía). Mi segundo a último párrafo cubre eso: de todos modos, simplemente haría la actualización total para los votos de los votantes anteriores (siempre), si no fuera necesario, ¿y qué? y luego esto vota actualizaciones. Mientras el voto funcione bien. Si su actualización 'total' falla, el próximo votante lo arreglará, así que de nuevo, ¿y qué?
pperrin
Estoy realmente tentado a decir que el nodo 'contar' debe ser un nodo 'último voto anterior', por lo que cada votante / cliente actualiza / repara / repara ese nodo / valor y luego agrega su propio voto (dejando que el próximo votante actualice el total para incluir "este" voto). - Si me entiendes ...
pperrin
4

escribir una función en la nube y actualizar el recuento de nodos.

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

raíz-- | | -users (este nodo contiene la lista de todos los usuarios) |
| -count | -userscount: (este nodo agregado dinámicamente por la función de la nube con el recuento de usuarios)

indvin
fuente