Подколлекции запросов Firestore


125

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

  • Танцы [сборник]
    • danceName
    • Песни [сборник]
      • название песни

Как я могу запросить «Найти все танцы, где songName == 'X'»


1
Поддерживается ли это пожарный склад, 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

ОБНОВЛЕНИЕ Теперь 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); возвращение таких результатов это не имело бы Sence для каждого танца , чтобы иметь логические флаги для каждой возможной песни name ...
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. Запросы с предложением! =. В этом случае следует разделить запрос на запрос «больше» и «меньше». Например, хотя предложение запроса where ("age", "! =", "30") не поддерживается, вы можете получить тот же набор результатов, объединив два запроса, один с предложением where ("age", "< "," 30 ") и один с предложением where (" возраст ","> ", 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'.
Сачин Ратод,

0

Я работаю с Observables здесь и AngularFire оболочкой но вот как мне это удалось.

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

Некоторое объяснение (не эксперт RxJS):

  • songId $ - это наблюдаемое, которое будет выдавать идентификаторы
  • dance $ - это наблюдаемое, которое считывает этот идентификатор и затем получает только первое значение.
  • Затем он запрашивает collectionGroup всех песен, чтобы найти все ее экземпляры.
  • На основе экземпляров он переходит к родительским Dances и получает их идентификаторы.
  • Теперь, когда у нас есть все идентификаторы Dance, нам нужно запросить их, чтобы получить их данные. Но я хотел, чтобы он работал хорошо, поэтому вместо того, чтобы запрашивать один за другим, я группирую их в группы по 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.