Subcolecciones de consultas de Firestore

125

Pensé haber leído que puedes consultar subcolecciones con el nuevo Firebase Firestore, pero no veo ningún ejemplo. Por ejemplo, tengo mi configuración de Firestore de la siguiente manera:

  • Danzas [colección]
    • danceName
    • Canciones [colección]
      • nombre de la cancion

¿Cómo podría consultar "Buscar todos los bailes donde songName == 'X'"

Nelson.b.austin
fuente
1
¿Es esto compatible todavía Firestore, año 2020?
sajanyamaha

Respuestas:

148

Actualización 2019-05-07

Hoy lanzamos consultas de grupos de colecciones , y estas le permiten realizar consultas entre subcolecciones.

Entonces, por ejemplo, en el SDK web:

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

Esto coincidiría con los documentos de cualquier colección donde la última parte de la ruta de la colección sea 'Canciones'.

Su pregunta original era sobre cómo encontrar bailes donde songName == 'X', y esto todavía no es posible directamente, sin embargo, para cada canción que coincida, puede cargar su padre.

Respuesta original

Esta es una característica que aún no existe. Se llama "consulta de grupo de colección" y te permitiría consultar todas las canciones independientemente de qué baile las contenga. Esto es algo que tenemos la intención de apoyar, pero no tenemos un cronograma concreto sobre cuándo llegará.

La estructura alternativa en este punto es hacer de las canciones una colección de alto nivel y hacer que el baile sea parte de una propiedad de la canción.

Gil Gilbert
fuente
147
Sería MUCHO mejor si el equipo de desarrollo de Firestore implementara consultas de subcolección lo antes posible. Después de todo, las 'consultas más poderosas' son uno de los principales puntos de venta según el manual de Firestore. En este momento, Firestore es como un Porsche sin ruedas.
Arne Wolframm
21
¡Estamos de acuerdo! Hay un número limitado de horas al día :-).
Gil Gilbert
20
No entiendo, ¿por qué paga la gente, si la base de fuego es limitada? Parece que incluso Backendless tiene más funciones que Firebase. ¿Y por qué Firebase es tan popular? Parece que la gente se volvió loca
nzackoya
15
Esta función es muy necesaria o, de lo contrario, la gente comenzará a encontrar alternativas, incluso si tenemos plazos que cumplir. : P
JD-V
13
Necesitamos esta característica. Al menos, el cronograma para publicar esto nos ayudará a estar preparados.
sanjaya panigrahy
22

ACTUALIZAR Ahora Firestore admite contenido de matrices

Tener estos documentos

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

hacerlo de esta forma

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

@ Nelson.b.austin Dado que Firestore aún no tiene eso, le sugiero que tenga una estructura plana, es decir:

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

Teniéndolo de esa manera, puede hacerlo:

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

Espero que esto ayude.

norgematos
fuente
2
para que songName_Title3: falsesirve si no me equivoco, solo se puede usar para buscar bailes que no tienen un nombre de canción específico, asumiendo que necesitamos un songName_Title3: falsepara dances.where("songName_"+songTitle, "==", false); devolver tales resultados, no tendría sentido que cada baile tenga banderas booleanas para cada canción posible nombre ...
epeleg
Esto es excelente, pero los documentos están limitados a 1 MB, por lo que si necesita asociar una lista larga de cadenas o lo que sea con un documento específico, no puede usar este enfoque.
Supertecnoboff
@Supertecnoboff Parece que tendría que ser una lista de cadenas muy grande y larga. ¿Qué rendimiento tiene esta consulta "array_contains" y cuáles son las alternativas más eficaces?
Jay Ordway
14

¿Qué pasa si almacena canciones como un objeto en lugar de como una colección? Cada baile como, con canciones como campo: escriba Objeto (no una colección)

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

entonces podrías consultar todos los bailes con 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);
    });
dmartins
fuente
3
Esta solución funcionaría, pero no es escalable en caso de que la cantidad de canciones sea grande o pueda crecer dinámicamente. Esto aumentaría el tamaño del documento y afectaría el rendimiento de lectura / escritura. Puede encontrar más información sobre esto en la documentación de Firebase vinculada a continuación (consulte la última sección 'Limitaciones' en la página) firebase.google.com/docs/firestore/solutions/arrays
Nouman Hanif
14

ACTUALIZACIÓN 2019

Firestore ha publicado consultas de grupos de recopilación. Consulte la respuesta de Gil anterior o la documentación oficial de consulta del grupo de recopilación


Respuesta anterior

Como dijo Gil Gilbert, parece que las consultas de grupos de colecciones están actualmente en proceso. Mientras tanto, probablemente sea mejor usar colecciones de nivel raíz y simplemente vincular estas colecciones usando los UID del documento.

Para aquellos que aún no lo saben, Jeff Delaney tiene algunas guías y recursos increíbles para cualquiera que trabaje con Firebase (y Angular) en AngularFirebase .

Modelado de datos relacionales de Firestore NoSQL : aquí analiza los conceptos básicos de la estructuración de NoSQL y Firestore DB

Modelado de datos avanzado con Firestore por ejemplo : estas son técnicas más avanzadas para tener en cuenta. Una gran lectura para aquellos que quieran llevar sus habilidades de Firestore al siguiente nivel.

Matthew Mullin
fuente
7

NUEVA ACTUALIZACIÓN 8 de julio de 2019:

db.collectionGroup('Songs')
  .where('songName', isEqualTo:'X')
  .get()
Nhật Trần
fuente
3

Siempre puedes buscar así: -

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;
      }
    })
);
Ankur Biswas
fuente
3

Limitaciones de consultas

Cloud Firestore no admite los siguientes tipos de consultas:

  1. Consultas con filtros de rango en diferentes campos.

  2. Consultas únicas en múltiples colecciones o subcolecciones. Cada consulta se ejecuta en una única colección de documentos. Para obtener más información sobre cómo su estructura de datos afecta sus consultas, consulte Elegir una estructura de datos .

  3. Consultas lógicas OR. En este caso, debe crear una consulta separada para cada condición OR y fusionar los resultados de la consulta en su aplicación.

  4. Consultas con una cláusula! =. En este caso, debe dividir la consulta en una consulta mayor que y una consulta menor que. Por ejemplo, aunque la cláusula de consulta where ("age", "! =", "30") no es compatible, puede obtener el mismo conjunto de resultados combinando dos consultas, una con la cláusula where ("age", "< "," 30 ") y uno con la cláusula where (" edad ","> ", 30).

ggDeGreat
fuente
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);
        });
Alok Prusty
fuente
1

Podría ser mejor utilizar una estructura de datos plana.
Los documentos especifican los pros y los contras de las diferentes estructuras de datos en esta página .

Específicamente sobre las limitaciones de las estructuras con subcolecciones:

No puede eliminar subcolecciones fácilmente ni realizar consultas compuestas en subcolecciones.

En contraste con las supuestas ventajas de una estructura de datos plana:

Las colecciones de nivel raíz ofrecen la mayor flexibilidad y escalabilidad, junto con potentes consultas dentro de cada colección.

MattCochrane
fuente
1

He encontrado una solución. Por favor, chequee esto.

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

Y luego puede ver las pestañas Datos, Reglas, Índices, Uso en su almacén de incendios en la nube desde console.firebase.google.com. Finalmente, debe configurar índices en la pestaña de índices.ingrese la descripción de la imagen aquí

Complete aquí el ID de la colección y algún valor de campo. Luego seleccione la opción de grupo de colección. Disfrútala. Gracias

Hijo feliz
fuente
Esto no responde a la pregunta. La consulta mencionada anteriormente solo obtiene todas las canciones con songName = 'X'. Esto no proporcionará los bailes donde songName = 'X'.
sachin rathod
0

Estoy trabajando con Observables aquí y el contenedor AngularFire, pero así es como logré hacerlo.

Es un poco loco, todavía estoy aprendiendo sobre los observables y posiblemente exagere. Pero fue un buen ejercicio.

Alguna explicación (no un experto en RxJS):

  • songId $ es un observable que emitirá identificadores
  • dance $ es un observable que lee ese id y luego obtiene solo el primer valor.
  • luego consulta el grupo de colección de todas las canciones para encontrar todas sus instancias.
  • Según las instancias, atraviesa las Danzas principales y obtiene sus identificadores.
  • Ahora que tenemos todos los ID de Dance, necesitamos consultarlos para obtener sus datos. Pero quería que funcionara bien, así que en lugar de consultar uno por uno, los agrupa en grupos de 10 (el ángulo máximo que se tomará para una inconsulta.
  • Terminamos con N cubos y necesitamos hacer N consultas en firestore para obtener sus valores.
  • una vez que hacemos las consultas en firestore, todavía necesitamos analizar los datos de eso.
  • y finalmente podemos fusionar todos los resultados de la consulta para obtener una única matriz con todos los Dances en ella.
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();

Copié, pegué mi código y cambié los nombres de las variables a los suyos, no estoy seguro de si hay algún error, pero funcionó bien para mí. Avíseme si encuentra algún error o si puede sugerir una mejor manera de probarlo, tal vez con algún firestore simulado.

Eduardo
fuente