Підколекції запитів Firestore


125

Я думав, що прочитав, що ви можете запитувати підколекції за допомогою нового Firebase Firestore, але я не бачу прикладів. Наприклад, я налаштував Firestore таким чином:

  • Танці [колекція]
    • danceName
    • Пісні [колекція]
      • songName

Як би я міг запитувати "Знайти всі танці, де songName == 'X'"


1
це підтримується ще firestore, 2020 рік?
саджанямаха

Відповіді:


148

Оновити 2019-05-07

Сьогодні ми випустили запити групи колекцій , і вони дозволяють здійснювати запит у підколекціях.

Так, наприклад, у веб-SDK:

db.collectionGroup('Songs')
  .where('songName', '==', 'X')
  .get()

Це відповідатиме документам у будь-якій колекції, де остання частина шляху колекції - "Пісні".

Ваше первісне запитання стосувалося пошуку танців, де songName == 'X', і це все ще неможливо безпосередньо, однак для кожної пісні, що відповідає, ви можете завантажити її батьків.

Оригінальна відповідь

Це особливість, яка ще не існує. Це називається "груповий запит колекції" і дозволить вам запитувати всі пісні незалежно від того, який танець їх містив. Це те, що ми маємо намір підтримати, але не має конкретної строки, коли воно настане.

Альтернативна структура на даний момент полягає в тому, щоб зробити пісні колекцією вищого рівня і зробити те, який танець пісні є частиною властивості пісні.


147
Було б набагато краще, якби команда розробників Firestore реалізувала запити підколекції якнайшвидше. Зрештою, "більш потужні запити" є однією з головних точок продажу відповідно до посібника Firestore. Зараз Firestore схожий на Porsche без коліс.
Арн Вольфрам

21
Ми погоджуємось! За день існує лише обмежена кількість годин :-).
Гіл Гілберт

20
Я не розумію, за що платять люди, якщо вогнена база обмежена? Здається, навіть Backendless має більшу функціональність, ніж Firebase. І чому Firebase настільки популярний? Здається, люди з’їхали з розуму
nzackoya

15
Ця функція дуже потрібна, інакше люди почнуть шукати альтернативи, навіть у нас є терміни, яких ми повинні виконати. : P
JD-V

13
Нам потрібна ця функція. Орендуючи термін, щоб випустити це, допоможе нам бути готовими.
санджаї паніграхи

22

UPDATE Now Firestore підтримує масив, що містить

Маючи ці документи

    {danceName: 'Danca name 1', songName: ['Title1','Title2']}
    {danceName: 'Danca name 2', songName: ['Title3']}

зробіть це так

collection("Dances")
    .where("songName", "array-contains", "Title1")
    .get()...

@ Nelson.b.austin Оскільки у Firestore цього ще немає, я пропоную вам мати плоску структуру, тобто:

Dances = {
    danceName: 'Dance name 1',
    songName_Title1: true,
    songName_Title2: true,
    songName_Title3: false
}

Зробивши це таким чином, ви можете це зробити:

var songTitle = 'Title1';
var dances = db.collection("Dances");
var query = dances.where("songName_"+songTitle, "==", true);

Я сподіваюся, що це допомагає.


2
для чого songName_Title3: falseкорисний? якщо я не помиляюся, його можна використовувати лише для пошуку танців, у яких немає конкретної назви пісні, припускаючи, що нам потрібен результат, songName_Title3: falseщоб dances.where("songName_"+songTitle, "==", false); повернути такі результати, але не було б сенсу для кожного танцю мати булеві прапори для кожної можливої ​​пісні ім'я ...
epeleg

Це чудово, але документи обмежені 1МБ, тому якщо вам потрібно пов’язати довгий список рядків або будь-якого іншого із конкретним документом, ви не можете використовувати цей підхід.
Supertecnoboff

@Supertecnoboff Схоже, це мав би бути жахливо великий і довгий список рядків. Наскільки ефективним є цей запит "array_contains" та які ефективніші альтернативи?
Джей Ордвей

14

Що робити, якщо ви зберігаєте пісні як об’єкт, а не як колекцію? Кожен танець як, з піснями як поле: введіть Object (не колекцію)

{
  danceName: "My Dance",
  songs: {
    "aNameOfASong": true,
    "aNameOfAnotherSong": true,
  }
}

тоді ви можете запитувати всі танці з aNameOfASong:

db.collection('Dances')
  .where('songs.aNameOfASong', '==', true)
  .get()
  .then(function(querySnapshot) {
    querySnapshot.forEach(function(doc) {
      console.log(doc.id, " => ", doc.data());
    });
   })
   .catch(function(error) {
     console.log("Error getting documents: ", error);
    });

3
Це рішення спрацювало б, але воно не є масштабним, якщо кількість пісень велика або може динамічно зростати. Це збільшило б розмір документа і вплине на ефективність читання / запису. Більш докладно про це можна знайти в документації , пов'язаної Firebase нижче (див останній розділ «Обмеження» на сторінці) firebase.google.com/docs/firestore/solutions/arrays
Nouman Ханіф

14

ОНОВЛЕННЯ 2019 року

Firestore випустила колективні запити колекції. Дивіться відповідь Гіла вище або офіційну документацію щодо запиту групи колекцій


Попередній відповідь

Як заявив Гіл Гілберт, схоже, що зараз працює над запитами групи колекцій . Тим часом, ймовірно, краще використовувати колекції кореневого рівня та просто зв’язати між собою ці колекції за допомогою UID документа.

Для тих, хто ще не знає, Джефф Делані має кілька неймовірних посібників та ресурсів для тих, хто працює з Firebase (та Angular) на AngularFirebase .

Реляційне моделювання даних Firestore NoSQL - тут він розбиває основи структурування БД NoSQL та Firestore

Розширене моделювання даних за допомогою Firestore на прикладі - це більш вдосконалені методи, які слід тримати в задній частині розуму. Прекрасне читання для тих, хто хоче перенести свої навички Firestore на наступний рівень


7

НОВЕ ОНОВЛЕННЯ 8 липня 2019 року:

db.collectionGroup('Songs')
  .where('songName', isEqualTo:'X')
  .get()

3

Ви завжди можете шукати так: -

this.key$ = new BehaviorSubject(null);

return this.key$.switchMap(key =>
  this.angFirestore
    .collection("dances").doc("danceName").collections("songs", ref =>
      ref
        .where("songName", "==", X)
    )
    .snapshotChanges()
    .map(actions => {
      if (actions.toString()) {
        return actions.map(a => {
          const data = a.payload.doc.data() as Dance;
          const id = a.payload.doc.id;
          return { id, ...data };
        });
      } else {
        return false;
      }
    })
);

3

Обмеження в запитах

Cloud Firestore не підтримує такі типи запитів:

  1. Запити з діапазонами фільтрів у різних полях.

  2. Одиничні запити в декількох колекціях або підколекціях. Кожен запит суперечить одній колекції документів. Для отримання додаткової інформації про те, як ваша структура даних впливає на ваші запити, див. Вибір структури даних .

  3. Логічні АБО запити. У цьому випадку слід створити окремий запит для кожної умови АБО та об'єднати результати запиту у вашій програмі.

  4. Запити з пропозицією! = У цьому випадку слід розділити запит на запит, більший за запит, і запит, менший за показник. Наприклад, хоча пункт запиту, де ("age", "! =", "30") не підтримується, ви можете отримати однаковий результат, поєднавши два запити, один із пунктом where ("age", "< "," 30 ") і одне з пунктом де (" вік ","> ", 30).


2
var songs = []    
db.collection('Dances')
      .where('songs.aNameOfASong', '==', true)
      .get()
      .then(function(querySnapshot) {
        var songLength = querySnapshot.size
        var i=0;
        querySnapshot.forEach(function(doc) {
           songs.push(doc.data())
           i ++;
           if(songLength===i){
                console.log(songs
           }
          console.log(doc.id, " => ", doc.data());
        });
       })
       .catch(function(error) {
         console.log("Error getting documents: ", error);
        });

1

Можливо, краще використовувати плоску структуру даних.
Документи вказують плюси та мінуси різних структур даних на цій сторінці .

Зокрема про обмеження структур із підбірками:

Ви не можете легко видалити підколекції або виконувати складні запити в підколектах.

Суперечить передбачуваним перевагам плоскої структури даних:

Колекції на рівні коренів пропонують найбільшу гнучкість та масштабованість, а також потужний запит у кожній колекції.


1

Я знайшов рішення. Перевірте це.

var museums = Firestore.instance.collectionGroup('Songs').where('songName', isEqualTo: "X");
        museums.getDocuments().then((querySnapshot) {
            setState(() {
              songCounts= querySnapshot.documents.length.toString();
            });
        });

І тоді ви можете побачити вкладки "Дані", "Правила", "Покажчики", "Використання" у своєму хмарному каміні з console.firebase.google.com. Нарешті, слід встановити індекси на вкладці індексів.введіть тут опис зображення

Заповніть тут ідентифікатор колекції та деяке значення поля. Потім виберіть параметр групи колекцій. Насолоджуйся цим. Дякую


Це не дає відповіді на запитання. Зазначений вище запит просто отримує всі пісні з songName = 'X'. Це не забезпечить танці, де songName = 'X'.
sachin rathod

0

Я працюю з Observables тут і обгорткою AngularFire, але ось як мені це вдалося.

Це щось божевільне, я все ще дізнаюся про спостереження, і я, можливо, переоцінив це. Але це була приємна вправа.

Деякі пояснення (не експерт RxJS):

  • songId $ - це спостереження, яке випромінює ідентифікатори
  • dance $ - це спостережуване, яке читає цей ідентифікатор, а потім отримує лише перше значення.
  • Потім він запитує collectionGroup всіх пісень, щоб знайти всі його примірники.
  • Виходячи з примірників, він переходить до батьківських танців і отримує їхні ідентифікатори.
  • Тепер, коли у нас є всі танці танцю, нам потрібно запитати їх, щоб отримати їх дані. Але я хотів, щоб це було добре, тому замість того, щоб запитувати по черзі, я збираю їх у відра з 10 (максимальний кутовий буде потрібен для inзапиту.
  • Ми закінчуємо N відрами і нам потрібно виконати N запитів на firestore, щоб отримати їх значення.
  • як тільки ми робимо запити на firestore, нам ще потрібно насправді проаналізувати дані з цього.
  • і, нарешті, ми можемо об'єднати всі результати запитів, щоб отримати єдиний масив із усіма танцями в ньому.
type Song = {id: string, name: string};
type Dance = {id: string, name: string, songs: Song[]};

const songId$: Observable<Song> = new Observable();
const dance$ = songId$.pipe(
  take(1), // Only take 1 song name
  switchMap( v =>
    // Query across collectionGroup to get all instances.
    this.db.collectionGroup('songs', ref =>
      ref.where('id', '==', v.id)).get()
  ),
  switchMap( v => {
    // map the Song to the parent Dance, return the Dance ids
    const obs: string[] = [];
    v.docs.forEach(docRef => {
      // We invoke parent twice to go from doc->collection->doc
      obs.push(docRef.ref.parent.parent.id);
    });
    // Because we return an array here this one emit becomes N
    return obs;
  }),
  // Firebase IN support up to 10 values so we partition the data to query the Dances
  bufferCount(10),
  mergeMap( v => { // query every partition in parallel
    return this.db.collection('dances', ref => {
      return ref.where( firebase.firestore.FieldPath.documentId(), 'in', v);
    }).get();
  }),
  switchMap( v => {
    // Almost there now just need to extract the data from the QuerySnapshots
    const obs: Dance[] = [];
    v.docs.forEach(docRef => {
      obs.push({
        ...docRef.data(),
        id: docRef.id
      } as Dance);
    });
    return of(obs);
  }),
  // And finally we reduce the docs fetched into a single array.
  reduce((acc, value) => acc.concat(value), []),
);
const parentDances = await dance$.toPromise();

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

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