В Firebase есть ли способ получить количество дочерних узлов узла без загрузки всех данных узла?


132

Вы можете получить подсчет детей через

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

Но я считаю, что это извлекает с сервера все поддерево этого узла. Для огромных списков это кажется большим объемом оперативной памяти и задержек. Есть ли способ получить счетчик (и / или список дочерних имен) без извлечения всего?


спасибо большое, я пробую, и это сработало для меня
Ахмед Махмуд

ваш код не может обрабатывать большой набор данных. У меня возникла ошибка, связанная с пространством кучи Java. Я все еще жду какой-то особенности.
Panupong Kongarn,

Ответы:


98

Приведенный вами фрагмент кода действительно загружает весь набор данных, а затем считает их на стороне клиента, что может быть очень медленным для больших объемов данных.

Firebase в настоящее время не может подсчитывать дочерние элементы без загрузки данных, но мы планируем добавить его.

На данный момент одним из решений было бы поддерживать счетчик количества дочерних элементов и обновлять его каждый раз, когда вы добавляете нового ребенка. Вы можете использовать транзакцию для подсчета элементов, как в этом коде отслеживания 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;
});

Для получения дополнительной информации см. Https://www.firebase.com/docs/transactions.html.

ОБНОВЛЕНИЕ: Firebase недавно выпустила облачные функции. Благодаря облачным функциям вам не нужно создавать собственный сервер. Вы можете просто написать функции JavaScript и загрузить их в Firebase. Firebase будет отвечать за запуск функций при возникновении события.

Например, если вы хотите подсчитать количество голосов, вам следует создать структуру, подобную этой:

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

А затем напишите функцию javascript, чтобы увеличить время upvotes_countзаписи в upvotesузел.

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

Вы можете прочитать документацию, чтобы узнать, как начать работу с облачными функциями .

Также здесь есть еще один пример подсчета сообщений: https://github.com/firebase/functions-samples/blob/master/child-count/functions/index.js

Обновление за январь 2018 г.

Документация по firebase изменилась, поэтому eventтеперь у нас есть changeи context.

В данном примере выдается ошибка с сообщением о event.dataнеопределенном значении. Этот шаблон, кажется, работает лучше:

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

`` `


74
Вы когда-нибудь добавляли поддержку для этого?
Джим Купер

20
Безопасен ли счетчик на стороне клиента в транзакции? Кажется, его можно легко взломать, чтобы искусственно увеличить счетчики. Это может быть плохо для систем голосования.
Soviut

16
++ было бы неплохо получить подсчет без затрат на передачу
Джаред Форсайт,

27
Это когда-нибудь добавлялось?
Элиэзер

25
Есть новости об этой дорожной карте функции? Спасибо
Pandaiolo

38

Это немного поздно, так как несколько других уже хорошо ответили, но я поделюсь, как я могу это реализовать.

Это зависит от того, что Firebase REST API предлагает shallow=trueпараметр.

Предположим, у вас есть postобъект, и у каждого из них может быть несколько comments:

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

Очевидно, вы хотите получать не все комментарии, а только их количество.

Предполагая, что у вас есть ключ для публикации, вы можете отправить GETзапрос по адресу https://yourapp.firebaseio.com/posts/[the post key]/comments?shallow=true.

Это вернет объект пар ключ-значение, где каждый ключ является ключом комментария, а его значение true:

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

Размер этого ответа намного меньше, чем при запросе эквивалентных данных, и теперь вы можете рассчитать количество ключей в ответе, чтобы найти свое значение (например, commentCount = Object.keys(result).length).

Это может не полностью решить вашу проблему, поскольку вы все еще рассчитываете количество возвращаемых ключей, и вы не можете обязательно подписаться на значение по мере его изменения, но это значительно уменьшает размер возвращаемых данных, не требуя каких-либо изменений в вашем схемы.


Возможно, это будет принятый ответ, поскольку shallow = true является новым дополнением со времени предыдущих ответов. У меня не было времени самому разобраться в этом, поэтому подожду несколько дней, чтобы узнать, что думают люди ...
Джош

1
Shallow, вероятно, лучший вариант на данный момент, но он не возвращается со сжатием и может стать довольно медленным и опытным для больших наборов данных
Mbrevda

Если ключи комментария не имеют логических значений, но вместо этого имеют дочерние элементы, возвращаются ли пары ключей ключ-значение?
alltej


3
Просто укажите, что вы должны добавить .jsonв конец URL, например:https://yourapp.firebaseio.com/posts/comments.json?shallow=true
Osama Xäwãñz

22

Сохраняйте счетчик на ходу - и используйте проверку, чтобы обеспечить его соблюдение. Я вместе взломал это - для того, чтобы вести подсчет уникальных голосов и подсчетов, который продолжает расти! Но на этот раз я проверил свое предложение! (несмотря на ошибки вырезания / вставки!).

«Уловка» здесь в том, чтобы использовать приоритет узла в качестве подсчета голосов ...

Данные:

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

пользователь может проголосовать только один раз && count должно быть на единицу больше текущего счета && значение данных должно быть таким же, как приоритет.

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

count (на самом деле последний голосующий) - голос должен существовать и его счетчик равен newcount, && newcount (приоритет) может увеличиваться только на единицу.

  }
}

Тестовый скрипт для добавления 10 голосов от разных пользователей (в этом примере id faked, если пользователь auth.uid находится в рабочей среде). Обратный отсчет до (i--) 10, чтобы увидеть, что проверка не удалась.

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

«Риск» здесь заключается в том, что голосование подано, но счетчик не обновляется (взлом или сбой скрипта). Вот почему у голосов есть уникальный `` приоритет '' - сценарий действительно должен начинаться с обеспечения того, что нет голоса с приоритетом выше, чем текущий счет, если он есть, он должен завершить эту транзакцию, прежде чем выполнять свою собственную - попросите ваших клиентов очистить за тебя :)

Перед запуском счетчик должен быть инициализирован с приоритетом - forge не позволяет вам этого делать, поэтому необходим сценарий-заглушка (до активации проверки!).


Это круто!!! Но что же происходит с конфликтами? То есть два человека голосуют одновременно? В идеале вы хотели бы автоматически разрешить это, вместо того, чтобы просто отбрасывать один из их голосов ... может быть, голосование в транзакции?
Джош

Привет, Джош, логически подлинное голосование может быть неудачным только в том случае, если был подан предыдущий голос, но общее количество не обновлено (пока). Мой предпоследний параграф охватывает это - я бы просто сделал полное обновление для предыдущих голосов избирателей в любом случае (каждый раз) - если это не было необходимо, и что? а затем это голосование за обновления. Пока голосование работает нормально. Если ваше «общее» обновление не удалось, следующий избиратель исправит это, так что еще раз - и что?
pperrin

Я действительно испытываю искушение просто сказать, что узел 'count' должен быть узлом 'последнего предыдущего голосования' - поэтому каждый избиратель / клиент обновляет / исправляет / ремонтирует этот узел / значение, а затем добавляет свой собственный голос - (позволяя следующему избирателю обновить всего для включения «этого» голоса). - Если вы меня
поняли

4

напишите облачную функцию и обновите количество узлов.

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

Обратитесь: https://firebase.google.com/docs/functions/database-events

root-- | | -users (этот узел содержит список всех пользователей) |
| -count | -userscount: (этот узел добавляется динамически облачной функцией с учетом количества пользователей)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.