#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 не поддерживает следующие типы запросов:
Запросы с фильтрами диапазона для разных полей.
Отдельные запросы к нескольким коллекциям или вложенным коллекциям. Каждый запрос выполняется для одной коллекции документов. Дополнительные сведения о том, как ваша структура данных влияет на ваши запросы, см. в разделе Выбор структуры данных.
Логические запросы OR. В этом случае вам следует создать отдельный запрос для каждого условия ИЛИ и объединить результаты запроса в вашем приложении.
Запросы с предложением !=. В этом случае вам следует разделить запрос на запрос «больше, чем» и запрос «меньше, чем». Например, хотя предложение запроса 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:
Могло бы быть лучше использовать плоскую структуру данных.
В документах указаны плюсы и минусы различных структур данных на этой странице.
В частности, об ограничениях структур с вложенными коллекциями:
Вы не можете легко удалять вложенные коллекции или выполнять составные запросы между вложенными коллекциями.
В отличие от предполагаемых преимуществ плоской структуры данных:
Коллекции корневого уровня обеспечивают наибольшую гибкость и масштабируемость наряду с мощными запросами внутри каждой коллекции.