Subcoleções de consulta do Firestore

125

Achei que li que você pode consultar subcoleções com o novo Firebase Firestore, mas não vejo nenhum exemplo. Por exemplo, tenho meu Firestore configurado da seguinte maneira:

  • Danças [coleção]
    • danceName
    • Músicas [coleção]
      • nome da música

Como eu poderia consultar "Find all dances where songName == 'X'"

Nelson.b.austin
fonte
1
isso ainda é compatível com o firestore, ano 2020?
sajanyamaha

Respostas:

148

Atualização 07/05/2019

Hoje lançamos consultas de grupo de coleção , e elas permitem que você consulte subcoleções.

Portanto, por exemplo, no SDK da web:

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

Isso corresponderia a documentos em qualquer coleção em que a última parte do caminho da coleção fosse 'Músicas'.

Sua pergunta original era sobre como encontrar danças em que songName == 'X', e isso ainda não é possível diretamente, no entanto, para cada música correspondente, você pode carregar seu pai.

Resposta original

Este é um recurso que ainda não existe. É chamado de "consulta de grupo de coleção" e permite que você consulte todas as músicas, independentemente de qual dança as contém. É algo que pretendemos apoiar, mas não temos um cronograma concreto de quando acontecerá.

A estrutura alternativa neste ponto é fazer das músicas uma coleção de nível superior e fazer com que a dança da música seja parte de uma propriedade da música.

Gil Gilbert
fonte
147
Seria MUITO melhor se a equipe de desenvolvimento do Firestore implementasse consultas de subcoleção o mais rápido possível. Afinal, 'consultas mais poderosas' é um dos principais argumentos de venda de acordo com o manual do Firestore. Agora, o Firestore é como um Porsche sem rodas.
Arne Wolframm
21
Nós concordamos! Há apenas um número limitado de horas no dia :-).
Gil Gilbert
20
Não entendo, quanto é que as pessoas pagam, se o firebase é limitado? Parece que até o Backendless tem mais funcionalidades do que o Firebase. E por que o Firebase é tão popular? Parece que as pessoas ficaram doidas
nzackoya
15
Esse recurso é muito necessário ou as pessoas vão começar a encontrar alternativas, mesmo que tenhamos prazos a cumprir. : P
JD-V
13
Precisamos desse recurso. Pelo menos o cronograma para liberar isso nos ajudará a estarmos preparados.
sanjaya panigrahy
22

ATUALIZAR Agora o Firestore suporta array-contém

Ter esses documentos

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

faça assim

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

@ Nelson.b.austin Como a firestore ainda não tem, sugiro que você tenha uma estrutura plana, ou seja:

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

Assim feito, você pode fazer:

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

Eu espero que isso ajude.

norgematos
fonte
2
para que songName_Title3: falseserve? se não estou errado, ele só pode ser usado para pesquisar danças que não tenham um nome de música específico, supondo que precisamos de um songName_Title3: falsepara dances.where("songName_"+songTitle, "==", false); retornar tais resultados, não faria sentido para cada dança ter bandeiras booleanas para cada música possível nome ...
epeleg
Isso é ótimo, mas os documentos são limitados a 1 MB, então se você precisar associar uma longa lista de strings ou qualquer outra coisa a um documento específico, você não pode usar essa abordagem.
Supertecnoboff
@Supertecnoboff Isso parece que teria que ser uma lista terrivelmente longa e longa de strings. Qual é o desempenho desta consulta "array_contains" e quais são as alternativas de melhor desempenho?
Jay Ordway
14

E se você armazenar músicas como um objeto em vez de uma coleção? Cada dança como, com canções como um campo: digite Objeto (não uma coleção)

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

então você pode consultar todas as danças com 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
fonte
3
Esta solução funcionaria, mas não é escalonável caso o número de músicas seja grande ou possa aumentar dinamicamente. Isso aumentaria o tamanho do documento e afetaria o desempenho de leitura / gravação. Mais sobre este podem ser encontradas na documentação Firebase link abaixo (veja a última seção 'Limitações' na página) firebase.google.com/docs/firestore/solutions/arrays
Nouman Hanif
14

ATUALIZAÇÃO 2019

A Firestore lançou Consultas de grupo de coleção. Veja a resposta de Gil acima ou a documentação oficial de consulta do grupo de coleções


Resposta Anterior

Conforme afirma Gil Gilbert, parece que as consultas de grupos de coleções estão atualmente em andamento. Enquanto isso, provavelmente é melhor usar coleções de nível raiz e apenas vincular entre essas coleções usando os UIDs do documento.

Para quem ainda não sabe, Jeff Delaney tem alguns guias e recursos incríveis para quem trabalha com Firebase (e Angular) no AngularFirebase .

Modelagem de dados relacionais Firestore NoSQL - Aqui ele detalha os fundamentos da estruturação de NoSQL e Firestore DB

Modelagem de dados avançada com Firestore por exemplo - Estas são técnicas mais avançadas para manter em mente. Uma ótima leitura para aqueles que desejam levar suas habilidades Firestore para o próximo nível

Matthew Mullin
fonte
7

NOVA ATUALIZAÇÃO 8 de julho de 2019:

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

Você sempre pode pesquisar assim: -

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
fonte
3

Limitações de consulta

O Cloud Firestore não oferece suporte para os seguintes tipos de consulta:

  1. Consultas com filtros de intervalo em diferentes campos.

  2. Consultas únicas em várias coleções ou subcoleções. Cada consulta é executada em uma única coleção de documentos. Para obter mais informações sobre como sua estrutura de dados afeta suas consultas, consulte Escolher uma estrutura de dados .

  3. Consultas OR lógicas. Nesse caso, você deve criar uma consulta separada para cada condição OR e mesclar os resultados da consulta em seu aplicativo.

  4. Consultas com uma cláusula! =. Nesse caso, você deve dividir a consulta em uma consulta maior que e uma consulta menor que. Por exemplo, embora a cláusula de consulta onde ("idade", "! =", "30") não seja suportada, você pode obter o mesmo conjunto de resultados combinando duas consultas, uma com a cláusula onde ("idade", "< "," 30 ") e um com a cláusula onde (" idade ","> ", 30).

ggDeGreat
fonte
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
fonte
1

Poderia ser melhor usar uma estrutura de dados simples.
Os documentos especificam os prós e os contras das diferentes estruturas de dados nesta página .

Especificamente sobre as limitações de estruturas com sub-coleções:

Você não pode excluir facilmente subcoleções ou realizar consultas compostas entre elas.

Comparado com as supostas vantagens de uma estrutura de dados simples:

As coleções de nível raiz oferecem a maior flexibilidade e escalabilidade, junto com consultas poderosas dentro de cada coleção.

MattCochrane
fonte
1

Eu encontrei uma solução. Por favor, checar isto.

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

E então você pode ver as guias Dados, Regras, Índices e Uso em seu Cloud Firestore em console.firebase.google.com. Finalmente, você deve definir índices na guia índices.insira a descrição da imagem aqui

Preencha o ID da coleção e algum valor de campo aqui. Em seguida, selecione a opção de grupo de coleta. Aproveite. obrigado

Filho feliz
fonte
Isso não responde à pergunta. A consulta mencionada acima apenas busca todas as músicas com songName = 'X'. Isso não fornecerá as danças em que songName = 'X'.
sachin rathod
0

Estou trabalhando com Observables aqui e o wrapper AngularFire, mas foi assim que consegui fazer isso.

É meio louco, ainda estou aprendendo sobre observáveis ​​e possivelmente exagerei. Mas foi um bom exercício.

Alguma explicação (não é um especialista RxJS):

  • songId $ é um observável que emitirá ids
  • dance $ é um observável que lê aquele id e obtém apenas o primeiro valor.
  • ele então consulta o collectionGroup de todas as canções para encontrar todas as instâncias dele.
  • Com base nas instâncias que atravessa para o pai Danças e obtém seus ids.
  • Agora que temos todos os ids de dança, precisamos consultá-los para obter seus dados. Mas eu queria que ele tivesse um bom desempenho, então, em vez de consultar um por um, agrupei-os em grupos de 10 (o angular máximo necessário para uma inconsulta.
  • Acabamos com N depósitos e precisamos fazer N consultas no firestore para obter seus valores.
  • depois de fazermos as consultas no firestore, ainda precisamos realmente analisar os dados desse.
  • e, finalmente, podemos mesclar todos os resultados da consulta para obter um único array com todas as Danças nele.
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();

Copiei, colei meu código e alterei os nomes das variáveis ​​para o seu, não tenho certeza se há algum erro, mas funcionou bem para mim. Avise-me se encontrar algum erro ou se puder sugerir uma maneira melhor de testá-lo com talvez algum Firestore simulado.

Eduardo
fonte