Вложенные коллекции запросов Firestore

#firebase #google-cloud-firestore

#firebase #google-облако-firestore

Вопрос:

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

  • Танцы [коллекция]
    • Имя танца
    • Песни [коллекция]
      • Название песни

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

Ответ №1:

Обновление 2019-05-07

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

Так, например, в web SDK:

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

Это соответствовало бы документам в любой коллекции, где последняя часть пути к коллекции — ‘Songs’.

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

Оригинальный ответ

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

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

Комментарии:

1. Было бы НАМНОГО лучше, если бы команда разработчиков Firestore внедрила запросы вложенных коллекций как можно скорее. В конце концов, согласно руководству Firestore, «более мощные запросы» являются одним из основных преимуществ. Прямо сейчас Firestore похож на Porsche без колес.

2. Мы согласны! В сутках всего лишь ограниченное количество часов :-).

3. Я не понимаю, за что люди платят, если firebase ограничена? Похоже, что даже Backendless обладает большей функциональностью, чем Firebase. И почему Firebase так популярна? Кажется, люди сошли с ума

4. Эта функция крайне необходима, иначе люди начнут искать альтернативы, даже если нам нужно уложиться в сроки. 😛

5. Нам нужна эта функция. По крайней мере, временные рамки выпуска помогут нам подготовиться.

Ответ №2:

ОБНОВЛЕНИЕ Теперь 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);
  

Я надеюсь, что это поможет.

Комментарии:

1. для чего это songName_Title3: false полезно? если я не ошибаюсь, его можно использовать только для поиска танцев, у которых нет определенного названия песни, предполагая, что нам нужно songName_Title3: false заставить dances.where("songName_" songTitle, "==", false); возвращать такие результаты, было бы бессмысленно для каждого танца иметь логические флаги для каждого возможного названия песни…

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

3. @Supertecnoboff Похоже, что это должен быть ужасно большой и длинный список строк. Насколько производителен этот запрос «array_contains» и каковы более производительные альтернативы?

Ответ №3:

ОБНОВЛЕНИЕ 2019

Firestore выпустил запросы к группам коллекций. Смотрите ответ Gil выше или официальную документацию по запросу группы коллекций


Предыдущий ответ

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

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

Моделирование реляционных данных Firestore NoSQL — Здесь он разбивает основы структурирования NoSQL и Firestore DB

Расширенное моделирование данных на примере Firestore — это более продвинутые методы, которые следует держать в уме. Отличное чтение для тех, кто хочет поднять свои навыки работы с Firestore на новый уровень

Ответ №4:

Что, если хранить песни как объект, а не как коллекцию? Каждый танец как, с песнями в качестве поля: тип объекта (не коллекция)

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

Комментарии:

1. Это решение могло бы работать, но оно не масштабируется в случае, если количество композиций велико или может динамически расти. Это увеличило бы размер документа и повлияло бы на производительность чтения / записи. Подробнее об этом можно прочитать в документации Firebase, приведенной по ссылке ниже (см. Последний раздел «Ограничения» на странице) firebase.google.com/docs/firestore/solutions/arrays

Ответ №5:

НОВОЕ ОБНОВЛЕНИЕ от 8 июля 2019 года:

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

Ответ №6:

Я нашел решение. Пожалуйста, проверьте это.

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

И тогда вы сможете просматривать данные, правила, индексы, вкладки использования в вашем облачном firestore из консоли.firebase.google.com .
Наконец, вы должны установить индексы на вкладке индексы.введите описание изображения здесь

Введите здесь идентификатор коллекции и некоторое значение поля. Затем выберите опцию группы коллекций. Наслаждайтесь этим. Спасибо

Комментарии:

1. Это не отвечает на вопрос. Запрос, упомянутый выше, просто извлекает все песни с songName = ‘X’ . Это не приведет к появлению танцев, где songName = ‘X’ .

Ответ №7:

Вы всегда можете выполнить поиск следующим образом:-

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

Комментарии:

1. Что такое BehaviorSubject?

2. @Cassini Это RxJS-вещь

Ответ №8:

Ограничения запросов

Облачный Firestore не поддерживает следующие типы запросов:

  1. Запросы с фильтрами диапазона для разных полей.

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

  3. Логические запросы OR. В этом случае вам следует создать отдельный запрос для каждого условия ИЛИ и объединить результаты запроса в вашем приложении.

  4. Запросы с предложением !=. В этом случае вам следует разделить запрос на запрос «больше, чем» и запрос «меньше, чем». Например, хотя предложение запроса where(«возраст», «!=», «30») не поддерживается, вы можете получить тот же набор результатов, объединив два запроса, один с предложением where(«возраст», «<«, «30») и одна с предложением where(«возраст», «>», 30).

Ответ №9:

Здесь я работаю с наблюдаемыми и оболочкой AngularFire, но вот как мне удалось это сделать.

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

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

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

  

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

Ответ №10:

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

Ответ №11:

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

В частности, об ограничениях структур с вложенными коллекциями:

Вы не можете легко удалять вложенные коллекции или выполнять составные запросы между вложенными коллекциями.

В отличие от предполагаемых преимуществ плоской структуры данных:

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