Google Firestore - как получить документ по нескольким идентификаторам за одну поездку туда и обратно?

Мне интересно, можно ли получить несколько документов по списку идентификаторов за одну поездку туда и обратно (сетевой вызов) в Firestore.


person Joon    schedule 13.10.2017    source источник
comment
Вы, кажется, предполагаете, что обратные обращения вызывают проблемы с производительностью в вашем приложении. Я бы так не предполагал. Firebase хорошо себя зарекомендовала в таких случаях, поскольку выполняет конвейерную обработку запросов. Хотя я не проверял, как Firestore ведет себя в этом сценарии, мне бы хотелось увидеть доказательства проблемы с производительностью, прежде чем предполагать, что она существует.   -  person Frank van Puffelen    schedule 13.10.2017
comment
Допустим, мне нужны документы a, b, c, чтобы что-то сделать. Я запрашиваю все три параллельно в отдельных запросах. a занимает 100 мс, b занимает 150 мс, а c занимает 3000 мс. В результате мне нужно подождать 3000 мс, чтобы выполнить задачу. Их будет max. Это будет более рискованно, когда количество документов, которые нужно получить, велико. В зависимости от статуса сети, я думаю, это может стать проблемой.   -  person Joon    schedule 13.10.2017
comment
Но разве отправка их всех как одного SELECT * FROM docs WHERE id IN (a,b,c) не займет одинаковое количество времени? Я не вижу разницы, так как соединение устанавливается один раз, а остальное передается по конвейеру. Время (после первоначального установления соединения) - это время загрузки всех документов + 1 поездка туда и обратно, одинаковая для обоих подходов. Если он ведет себя по-другому для вас, не могли бы вы поделиться образцом (как в моем связанном вопросе)?   -  person Frank van Puffelen    schedule 13.10.2017
comment
Я думаю, что потерял тебя. Когда вы говорите, что он конвейерный, вы имеете в виду, что Firestore автоматически группирует и отправляет запросы на свой сервер за один проход к базе данных?   -  person Joon    schedule 14.10.2017
comment
К вашему сведению, то, что я подразумеваю под циклом приема-передачи, - это один сетевой вызов базы данных от клиента. Я спрашиваю, группируются ли несколько запросов автоматически в Firestore как один цикл туда и обратно, или несколько запросов выполняются как несколько запросов в оба конца параллельно.   -  person Joon    schedule 14.10.2017
comment
Вы читали мой ответ, на который я указал в своем первом комментарии? Он объясняет концепцию конвейерной обработки лучше, чем я могу сделать в этих комментариях.   -  person Frank van Puffelen    schedule 14.10.2017
comment
Да, я прочитал ваш ответ, но до сих пор неясно, будет ли несколько сетевых вызовов или только один. Похоже, что будут n сетевые вызовы параллельно для n элементов, а не только один сетевой вызов, который выполняет n запросы одновременно.   -  person Joon    schedule 14.10.2017
comment
На самом деле они не параллельны, а конвейерны. Но результат практически такой же. Опять же: я еще не проверял, что делает Firestore, но я бы сделал несколько измерений, прежде чем предположить, что это займет N*(read_time+transfer_time+latency). Если они конвейерные, это ближе к N*(transfer_time)+read_time+latency.   -  person Frank van Puffelen    schedule 14.10.2017
comment
Я считаю, что на это ответил @FrankvanPuffelen (работает в Firebase) в другом вопросе: stackoverflow.com/questions/35931526/   -  person Nick Franceschina    schedule 30.01.2018
comment
Для всех, кто пытается понять, как выполнять несколько запросов в цикле, поскольку мобильный SDK (Flutter для iOS / Android) не предлагает функцию getAll (); ознакомьтесь с этим сообщением: stackoverflow.com/a/42176121/9248277 (объясняет, как обрабатывать список фьючерсов параллельно)   -  person Lucas Aschenbach    schedule 21.04.2020


Ответы (13)


если вы находитесь в Node:

https://github.com/googleapis/nodejs-firestore/blob/master/dev/src/index.ts#L978

/**
* Retrieves multiple documents from Firestore.
*
* @param {...DocumentReference} documents - The document references
* to receive.
* @returns {Promise<Array.<DocumentSnapshot>>} A Promise that
* contains an array with the resulting document snapshots.
*
* @example
* let documentRef1 = firestore.doc('col/doc1');
* let documentRef2 = firestore.doc('col/doc2');
*
* firestore.getAll(documentRef1, documentRef2).then(docs => {
*   console.log(`First document: ${JSON.stringify(docs[0])}`);
*   console.log(`Second document: ${JSON.stringify(docs[1])}`);
* });
*/

Это специально для серверного SDK

ОБНОВЛЕНИЕ. Cloud Firestore [SDK на стороне клиента] теперь поддерживает запросы IN!

https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html

myCollection.where(firestore.FieldPath.documentId(), 'in', ["123","456","789"])

person Nick Franceschina    schedule 24.01.2018
comment
Для тех, кто хочет вызвать этот метод с динамически сгенерированным массивом ссылок на документы, вы можете сделать это следующим образом: firestore.getAll (... arrayOfReferences) .then () - person Horea; 06.03.2018
comment
@NickFranceschina, знаете ли вы, как это можно использовать в ванильных библиотеках angularfire или firebase? - person Kisinga; 23.01.2019
comment
Мне очень жаль, @KamanaKisinga ... Я не делал никаких работ по firebase почти год и сейчас ничем не могу помочь (послушайте, я действительно опубликовал этот ответ год назад сегодня!) - person Nick Franceschina; 24.01.2019
comment
@NickFranceschina ???????? Достаточно честно, хотя, спасибо - person Kisinga; 24.01.2019
comment
как насчет firestore.getAll.call(null, arrayOfReferences), если оператор распространения еще не поддерживается? - person Julian Paolo Dayag; 12.02.2019
comment
SDK на стороне клиента теперь также предлагают эту функцию. см. ответ Jeodonara для примера: stackoverflow.com/a/58780369 - person Frank van Puffelen; 02.12.2019
comment
предупреждение: в настоящее время фильтр in ограничен 10 элементами. Так что вы, вероятно, обнаружите, что это бесполезно, когда вы собираетесь начать производство. - person Martin Cremer; 12.01.2020
comment
на самом деле вам нужно использовать firebase.firestore.FieldPath.documentId(), а не 'id' - person Maddocks; 26.01.2020
comment
Получение этой странной ошибки Соответствующее значение для FieldPath.documentId () должно быть строкой или DocumentReference. - person funct7; 24.03.2020
comment
Спасибо, это спасло мне жизнь. - person Swetabja Hazra; 16.06.2020
comment
Android: List<String> lst;, FirebaseFirestore.getInstance().collection("collection_name").whereIn(FieldPath.documentId(), lst).get().addOnCompleteListener(...); - person Alaa M.; 04.07.2020
comment
Я никогда не думал о передаче firestore.FieldPath.documentId () для where. Это отличное использование In query! - person Ayyappa; 09.10.2020

Они только что объявили об этой функции, https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html.

Теперь вы можете использовать такие запросы, но помните, что размер ввода не может быть больше 10.

userCollection.where('uid', 'in', ["1231","222","2131"])

person jeadonara    schedule 09.11.2019
comment
Это запрос whereIn, а не where. И я не знаю, как разработать запрос для нескольких документов из списка идентификаторов документов, принадлежащих определенной коллекции. Пожалуйста помоги. - person Compile error end; 17.11.2019
comment
@Compileerrorend не могли бы вы попробовать это? db.collection('users').where(firebase.firestore.FieldPath.documentId(), 'in',["123","345","111"]).get() - person jeadonara; 03.12.2019
comment
спасибо, особенно за firebase.firestore.FieldPath.documentId() - person Ivan Chernykh; 22.12.2019
comment
@jeadonara в случае, если входной массив больше 10, что мне использовать? - person Ramesh Vishnoi; 24.11.2020
comment
@RameshVishnoi, вы можете использовать Promise.all () (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/). - person jeadonara; 29.11.2020

На практике вы бы использовали firestore.getAll как это

async getUsers({userIds}) {
    const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
    const users = await this.firestore.getAll(...refs)
    console.log(users.map(doc => doc.data()))
}

или с синтаксисом обещания

getUsers({userIds}) {
    const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
    this.firestore.getAll(...refs).then(users => console.log(users.map(doc => doc.data())))
}
person Sebastian    schedule 27.11.2018
comment
это действительно должен быть выбранный ответ, потому что он позволяет использовать более 10 идентификаторов - person sshah98; 22.04.2020
comment
Это сработало! Спасибо. Где документация по этому поводу? Я искал getAll и нигде не нашел. - person TravRob; 07.12.2020
comment
@TravRob это может быть доступно в некоторых разновидностях Firebase, таких как Node, но определенно не в JavaScript API. - person zxbEPREF; 21.02.2021

Вы можете использовать такую ​​функцию:

function getById (path, ids) {
  return firestore.getAll(
    [].concat(ids).map(id => firestore.doc(`${path}/${id}`))
  )
}

Его можно вызвать с одним идентификатором:

getById('collection', 'some_id')

или массив идентификаторов:

getById('collection', ['some_id', 'some_other_id'])
person JP de la Torre    schedule 17.05.2018

Нет, прямо сейчас нет возможности пакетировать несколько запросов на чтение с помощью Cloud Firestore SDK и, следовательно, нет способа гарантировать, что вы можете прочитать все данные сразу.

Однако, как сказал Франк ван Пуффелен в комментариях выше, это не означает, что получение 3 документов будет в 3 раза медленнее, чем получение одного документа. Перед тем, как прийти к заключению, лучше всего провести собственные измерения.

person Sam Stern    schedule 16.10.2017
comment
Дело в том, что я хочу знать теоретические пределы производительности Firestore перед переходом на Firestore. Я не хочу мигрировать, а затем понимаю, что этого недостаточно для моего варианта использования. - person Joon; 17.10.2017
comment
Для меня никакая поддержка пакетной обработки не годится. Поскольку большинство вызовов базы данных моего приложения связаны с получением нескольких (часто сотни) документов с несколькими идентификаторами. Чтобы быть эффективными, эти звонки должны быть объединены для меня. - person Joon; 17.10.2017
comment
@Joon, похоже, вам придется переоценить свои структуры данных, чтобы они были более производительными в базе данных NoSQL, такой как Cloud Firestore. Лучший совет, который я могу дать, - это оглянуться на вопросы. Подумайте о запросе, который вы хотите выполнить, и структурируйте свои данные так, чтобы вы могли выразить его просто. Все запросы в Cloud Firestore выполняются быстро. - person Sam Stern; 17.10.2017
comment
Привет, здесь тоже есть соображения по поводу уюта. Допустим, я сохранил список всех идентификаторов моих друзей, и их число равно 500. Я могу получить список за 1 чтение, но для отображения их имени и photoURL это будет стоить мне 500 чтений. - person Tapas Mukherjee; 26.10.2017
comment
Если вы пытаетесь прочитать 500 документов, потребуется 500 прочтений. Если вы объедините информацию, которая вам нужна из всех 500 документов, в один дополнительный документ, потребуется только одно чтение. Это называется своего рода дублированием данных, что является нормальным явлением для большинства баз данных NoSQL, включая Cloud Firestore. - person Frank van Puffelen; 27.10.2017
comment
@FrankvanPuffelen, серьезно, нет другого пути, кроме того, что вы описали как все 500 документов в одном дополнительном документе. Мне трудно принять это ограничение в базе данных NoSQL. не могли бы вы пролить свет на некоторые связанные сообщения или обсуждения общепринятой практики в этом случае? Большое спасибо - person Sitian Liu; 28.10.2017
comment
@FrankvanPuffelen Например, в mongoDb вы можете использовать ObjectId следующим образом: stackoverflow.com/a/32264630/648851. - person Sitian Liu; 28.10.2017
comment
Другое соображение здесь, я думаю (кто-то из команды Firestore должен подтвердить), заключается в том, что если у вас есть данные, которые удовлетворяют вашему запросу, кэшированные локально, что выполняется автоматически с помощью Firestore, это НЕ стоит 500 чтений. Вы платите только за чтение, которое требует получения документов / обновлений с сервера. - person wildcat12; 19.04.2018
comment
@ wildcat12 прямо сейчас это будет стоить вам 500 чтений, потому что мы не указываем способ сказать, давайте мне только кешированные данные, SDK всегда будет отслеживать кешированные данные с проверкой последних. Но мы над этим работаем. - person Sam Stern; 19.04.2018
comment
@SamStern А поддержка пакетных запросов по-прежнему отсутствует? - person Dr. C. Hilarius; 03.12.2018
comment
Как сказал @FrankvanPuffelen, дублирование данных довольно распространено в базе данных NoSQL. Здесь вы должны спросить себя, как часто эти данные необходимо читать и насколько они должны быть актуальными. Если вы храните информацию о 500 пользователях, скажем, их имя + фото + идентификатор, вы можете получить их за одно чтение. Но если они вам нужны в актуальном состоянии, вам, вероятно, придется использовать облачную функцию для обновления этих ссылок каждый раз, когда пользователь обновляет свое имя / фотографию, поэтому запускает облачную функцию + выполняет некоторые операции записи. Нет правильной / лучшей реализации, это просто зависит от вашего варианта использования. - person schankam; 11.06.2019

Если вы используете флаттер, вы можете сделать следующее:

Firestore.instance.collection('your collection name').where(FieldPath.documentId, whereIn:[list containing multiple document IDs]).getDocuments();

Это вернет Future, содержащий List<DocumentSnapshot>, который вы можете повторять по своему усмотрению.

person Monis    schedule 02.06.2020
comment
"список, содержащий несколько идентификаторов документов" может содержать не более 10 элементов, верно? - person Krishna Shetty; 22.02.2021

Конечно, лучший способ сделать это - реализовать фактический запрос Firestore в облачной функции? Тогда будет только один звонок в оба конца от клиента к Firebase, что, похоже, именно то, что вы просите.

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

Внутри, вероятно, будет такое же количество вызовов самой Firebase, но все они будут осуществляться через сверхбыстрые межсоединения Google, а не через внешнюю сеть, и в сочетании с конвейерной обработкой, которую объяснил Фрэнк ван Пуффелен, вы должны получить отличную производительность от этот подход.

person Chris Wilson    schedule 04.03.2018
comment
Сохранение реализации в облачной функции - правильное решение в некоторых случаях, когда у вас сложная логика, но, вероятно, не в том случае, когда вы просто хотите объединить список с несколькими идентификаторами. Вы теряете кеширование на стороне клиента и стандартизированное форматирование возврата из обычных вызовов. Это вызвало больше проблем с производительностью, чем решило в некоторых случаях в моих приложениях, когда я использовал этот подход. - person Jeremiah; 07.03.2018

Вот как вы могли бы сделать что-то подобное в Kotlin с Android SDK.
Может не обязательно быть в одном цикле, но он эффективно группирует результат и позволяет избежать множества вложенных обратных вызовов.

val userIds = listOf("123", "456")
val userTasks = userIds.map { firestore.document("users/${it!!}").get() }

Tasks.whenAllSuccess<DocumentSnapshot>(userTasks).addOnSuccessListener { documentList ->
    //Do what you need to with the document list
}

Обратите внимание, что выборка определенных документов намного лучше, чем выборка всех документов и фильтрация результата. Это связано с тем, что Firestore взимает плату за набор результатов запроса.

person Markymark    schedule 20.08.2019
comment
Прекрасно работает, именно то, что я искал! - person Georgi; 22.09.2019

Надеюсь, это поможет вам, мне это поможет.

getCartGoodsData(id) {

    const goodsIDs: string[] = [];

    return new Promise((resolve) => {
      this.fs.firestore.collection(`users/${id}/cart`).get()
        .then(querySnapshot => {
          querySnapshot.forEach(doc => {
            goodsIDs.push(doc.id);
          });

          const getDocs = goodsIDs.map((id: string) => {
            return this.fs.firestore.collection('goods').doc(id).get()
              .then((docData) => {
                return docData.data();
              });
          });

          Promise.all(getDocs).then((goods: Goods[]) => {
            resolve(goods);
          });
        });
    });
  }
person Muhammad Mabrouk    schedule 07.11.2019

Для тех, кто хочет сделать это с помощью Angular, вот пример:

Сначала необходим импорт некоторых библиотек: (должен быть предварительно установлен)

import * as firebase from 'firebase/app'
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore'

Некоторая конфигурация для коллекции:

yourCollection: AngularFirestoreCollection;

constructor(
    private _db : AngularFirestore,
) { 
    // this is your firestore collection
    this.yourCollection = this._db.collection('collectionName');
}

Вот метод для выполнения запроса: ('products_id' - это массив идентификаторов)

getProducts(products_ids) {
    var queryId = firebase.firestore.FieldPath.documentId();
    this.yourCollection.ref.where(queryId, 'in', products_ids).get()
        .then(({ docs }) => {
            console.log(docs.map(doc => doc.data()))
        })
}
person Santi Nunez    schedule 25.02.2021

В настоящее время это невозможно в Firestore. Я не понимаю, почему ответ Александра принят, решение, которое он предлагает, просто возвращает все документы из коллекции «пользователей».

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

person Horea    schedule 23.12.2017

Да, это возможно. Пример в .NET SDK для Firestore:

/*List of document references, for example:
    FirestoreDb.Collection(ROOT_LEVEL_COLLECTION).Document(DOCUMENT_ID);*/
    List<DocumentReference> docRefList = YOUR_DOCUMENT_REFERENCE_LIST;
    
    // Required fields of documents, not necessary while fetching entire documents
    FieldMask fieldMask = new FieldMask(FIELD-1, FIELD-2, ...);
    
    // With field mask
    List<DocumentSnapshot> documentSnapshotsMasked = await FirestoreDb.GetAllSnapshotsAsync(docRefList, fieldMask);
    
    // Without field mask
    List<DocumentSnapshot>documentSnapshots = await FirestoreDb.GetAllSnapshotsAsync(docRefList);

Документация в .NET:

  1. Получить все снимки < / а>

  2. Маска поля

person Om Palsanawala    schedule 11.06.2021
comment
Хотя эта ссылка может дать ответ на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными, если ссылка на страницу изменится. - Из отзыва - person Amir Dora.; 11.06.2021
comment
Да, это полностью отвечает на вопрос. Я также добавил примерный фрагмент кода вместе со ссылочными ссылками. - person Om Palsanawala; 11.06.2021

Лучшее, что вы можете сделать, - это не использовать Promise.all в качестве клиента, а затем дождаться .all чтения, прежде чем продолжить.

Итерируйте чтения и позвольте им разрешиться самостоятельно. На стороне клиента это, вероятно, сводится к тому, что в пользовательском интерфейсе несколько изображений загрузчика выполнения разрешаются в значения независимо. Однако это лучше, чем замораживание всего клиента до тех пор, пока .all чтение не разрешится.

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

person Ronnie Royston    schedule 17.09.2018
comment
Он асинхронный, существует множество вариантов использования _1 _... он не обязательно должен что-либо замораживать - вам может потребоваться дождаться всех данных, прежде чем вы сможете сделать что-то значимое. - person Ryan Taylor; 08.03.2019
comment
Есть несколько вариантов использования, когда вам действительно нужно загрузить все ваши данные, поэтому ожидание (например, счетчик с соответствующим сообщением, нет необходимости замораживать какой-либо пользовательский интерфейс, как вы говорите) может быть полностью необходимо Promise.all ... Это просто действительно зависит от того, какие продукты вы здесь создаете. Подобные комментарии, на мой взгляд, неуместны, и в них не должно быть лучших слов. Это действительно зависит от различных вариантов использования, с которыми можно столкнуться, и от того, что ваше приложение делает для пользователя. - person schankam; 11.06.2019