Google Firestore - як отримати документ за кількома ідентифікаторами за один рейс?


99

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


4
Ви, схоже, припускаєте, що зворотні поїздки спричиняють проблеми з продуктивністю у вашому додатку. Я б не припускав цього. У таких випадках Firebase чудово працює, оскільки він конвеєрує запити . Хоча я не перевіряв, як Firestore поводиться в цьому сценарії, я хотів би переглянути доказ проблеми з продуктивністю, перш ніж припустити, що вона існує.
Frank van Puffelen,

1
Припустимо, мені потрібно документи a, b, cщоб зробити що - то. Я запитую всі три паралельно в окремих запитах. aзаймає 100 мс, bзаймає 150 мс і cзаймає 3000 мс. Як результат, мені потрібно зачекати 3000 мс, щоб виконати завдання. Це буде maxз них. Коли кількість документів для отримання буде великою, це буде ризикованіше. Залежно від статусу мережі, я думаю, це може стати проблемою.
Joon

1
Хіба не надсилання їх усіх як одиночних SELECT * FROM docs WHERE id IN (a,b,c)займе однакову кількість часу? Я не бачу різниці, оскільки зв’язок встановлюється один раз, а решта конвеєризується над цим. Час (після початкового встановлення з'єднання) - це час завантаження всіх документів + ​​1 зворотній рейс, однаковий для обох підходів. Якщо воно поводиться для вас інакше, чи можете ви поділитися зразком (як у моєму зв’язаному запитанні)?
Frank van Puffelen

Я думаю, що я загубив тебе. Коли ви говорите, що це конвеєр, ви маєте на увазі, що Firestore автоматично групує та надсилає запити на свій сервер за один зворотній шлях до бази даних?
Джун

FYI, що я маю на увазі під зворотною поїздкою - це один мережевий дзвінок до бази даних від клієнта. Я запитую, чи декілька запитів автоматично згруповані як одна поїздка в обидва кінці Firestore, чи кілька запитів виконуються як багаторазові поїздки паралельно.
Джун,

Відповіді:


90

якщо ви знаходитесь у Node:

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

/**
* 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] тепер підтримує запити!"

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

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


29
Для тих, хто хоче викликати цей метод з динамічно сформованим масивом посилань на документи, ви можете зробити це так: firestore.getAll (... arrayOfReferences). Then ()
Хорея

1
Вибачте, @KamanaKisinga ... Майже рік я не робив жодної речі у Firebase і зараз не можу допомогти (привіт, я відповів цю відповідь рік тому сьогодні!)
Нік Франческа з

2
Зараз на базі клієнта SDK також пропонують цю функцію. див. приклад Jeodonara для прикладу: stackoverflow.com/a/58780369
Frank van

4
попередження: вхідний фільтр наразі обмежений 10 елементами. Тож ви, мабуть, дізнаєтесь, що це марно, коли ви збираєтеся почати виробництво.
Мартін Кремер,

6
насправді вам потрібно використовувати, firebase.firestore.FieldPath.documentId()а ні'id'
Maddocks

20

Вони щойно оголосили про цю функціональність, https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html .

Тепер ви можете використовувати такі запити, але пам’ятайте, що розмір вводу не може бути більше 10.

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


Існує запит whereIn, а не where. І я не знаю, як розробити запит для декількох документів зі списку ідентифікаторів документів, який належить до певної колекції. Будь ласка, допоможіть.
Помилка компіляції, закінчення

17
@Compileerrorend не могли б ви спробувати це? db.collection('users').where(firebase.firestore.FieldPath.documentId(), 'in',["123","345","111"]).get()
jeadonara

спасибі, особливо заfirebase.firestore.FieldPath.documentId()
Cherniv

10

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

Однак, як сказав Франк ван Пуффелен у коментарях вище, це не означає, що отримання 3 документів буде втричі меншим, ніж отримання одного документа. Найкраще проводити власні вимірювання, перш ніж дійти висновку тут.


1
Річ у тім, що я хочу знати теоретичні межі продуктивності Firestore перед тим, як перейти до Firestore. Я не хочу мігрувати, а потім усвідомлюю, що це недостатньо добре для мого випадку використання.
Joon

2
Привіт, тут також є розбір козе. Скажімо, я зберіг список усіх ідентифікаційних даних мого друга, і їх число становить 500. Я можу отримати список за 1 вартість читання, але для того, щоб відобразити їх ім’я та фотоURL, це буде коштувати мені 500 читань.
Тапас Мукерджі

1
Якщо ви намагаєтеся прочитати 500 документів, потрібно 500 прочитань. Якщо об’єднати потрібну інформацію з усіх 500 документів в один додатковий документ, потрібно лише одне прочитання. Так зване копіювання даних є цілком нормальним для більшості баз даних NoSQL, включаючи Cloud Firestore.
Франк ван Пуффен

1
@FrankvanPuffelen Наприклад, у mongoDb ви можете використовувати ObjectId так stackoverflow.com/a/32264630/648851 .
Sitian Liu

2
Як сказав @FrankvanPuffelen, дублювання даних є досить поширеним явищем у базі даних NoSQL. Тут ви повинні запитати себе, як часто ці дані потрібно читати, і наскільки вони повинні бути сучасними. Якщо ви зберігаєте інформацію про 500 користувачів, скажімо, їх ім’я + фото + ідентифікатор, ви можете отримати їх за одне прочитання. Але якщо вони вам потрібні в актуальному стані, вам, мабуть, доведеться використовувати хмарну функцію для оновлення цих посилань кожного разу, коли користувач оновлює своє ім’я / фото, тому запускає хмарну функцію +, виконуючи деякі операції запису. Немає "правильної" / "кращої" реалізації, це лише залежить від вашого варіанту використання.
счанкам

10

На практиці ви б використовували 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())))
}

3
це дійсно має бути вибрана відповідь, оскільки вона дозволяє використовувати більше 10 ідентифікаторів
sshah98

9

Ви можете використовувати таку функцію:

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'])

5

Безумовно, найкращий спосіб це зробити, реалізувавши фактичний запит Firestore у хмарній функції? Тоді буде лише один дзвінок у обидва кінці від клієнта до Firebase, який, здається, є тим, про що ви просите.

Ви справді хочете зберегти всю свою логіку доступу до даних, як ця сторона сервера.

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


3
Зберігання реалізації у хмарній функції є правильним рішенням у деяких випадках, коли у вас складна логіка, але, мабуть, не у випадку, коли ви просто хочете об’єднати список із кількома ідентифікаторами. Що ви втрачаєте - це кешування на стороні клієнта та стандартизоване форматування зворотного зв'язку із звичайних дзвінків. Це спричинило більше проблем із продуктивністю, ніж це вирішувало в деяких випадках у моїх програмах, коли я використовував підхід.
Єремія

3

Якщо ви використовуєте флаттер, ви можете зробити наступне:

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

Це поверне майбутнє, що містить, List<DocumentSnapshot>яке ви можете повторити, як вам зручно.


2

Ось як ви могли б зробити щось подібне у Котліні за допомогою 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 бере з вас плату за набір результатів запиту.


1
Працює гарно, саме те, що я шукав!
Георгі

0

На даний момент це неможливо у Firestore. Я не розумію, чому приймається відповідь Олександра, рішення, яке він пропонує, просто повертає всі документи в колекції "користувачі".

Залежно від того, що вам потрібно зробити, вам слід вивчити копію відповідних даних, які вам потрібно відобразити, і вимагати повний документ лише тоді, коли це потрібно.


0

Найкраще, що ви можете зробити, це не використовувати Promise.allяк ваш клієнт, то .allперед тим, як продовжувати, слід дочекатися прочитання.

Повторіть прочитане і дайте їм вирішити самостійно. На стороні клієнта це, мабуть, зводиться до того, що інтерфейс користувача має кілька зображень завантажувача прогресу, які самостійно вирішують значення. Однак це краще, ніж заморожувати весь клієнт, доки .allчитання не вирішиться.

Тому негайно скиньте всі синхронні результати до подання, а потім нехай асинхронні результати надходять, коли вони вирішуються, окремо. Це може здатися дрібною відмінністю, але якщо у вашого клієнта поганий зв’язок з Інтернетом (такий, як я зараз у цій кав’ярні), заморожування всього досвіду клієнта на кілька секунд, швидше за все, призведе до досвіду „ця програма відмовна”.


2
Це асинхронно, існує безліч випадків використання Promise.all... не обов’язково щось „заморожувати” - можливо, вам доведеться почекати всі дані, перш ніж ви зможете зробити щось значуще
Райан Тейлор

Існує декілька випадків використання, коли вам потрібно завантажити всі ваші дані, тому очікування (наприклад, обертач із відповідним повідомленням, не потрібно "заморожувати" будь-який інтерфейс, як ви кажете) може бути цілком необхідним Promise.all .. Це просто насправді залежить від того, яку продукцію ви тут будуєте. Такі коментарі, на мій погляд, дуже неактуальні, і в них не повинно бути "найкращих" слів. Це насправді залежить від різних випадків використання, з якими можна зіткнутися, і від того, що ваш додаток робить для користувача.
счанкам

0

Сподіваюся, це вам допоможе, це працює для мене.

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);
          });
        });
    });
  }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.