DataARTIGO

Trabalhando com “JOINS” no Firebase

Neste artigo, o autor fala sobre a ausência das operações join, innerjoin e groupBy no Firebase e como contornar isso.

O fato do Firebase não ter operações como join, innerjoin e groupBy; pode ser meio frustrante em algumas situações. Neste artigo, vou descrever o que eu tenho feito para suprir as minhas necessidades.

O Firebase possui dois event listeners chamados on e once, onde podemos aplicar alguns valores, como: child_added, value, child_removed e child_changed. Pra ficar mais claro, vamos ver um exemplo prático:

let ref = firebase.database().ref();

ref.child('livros').on('child_added', => snapshot { console.log(snapshot.val()); })
ref.child('livros').on('value', snapshot  => { console.log(snapshot.val()); })
ref.child('livros').on('child_removed', snapshot => { console.log(snapshot.val()); })
ref.child('livros').on('child_changed', snapshot => { console.log(snapshot.val()); })

A diferença entre on e once é que o segundo escuta uma vez só, enquanto o primeiro fica escutando sempre. Agora imagine que temos uma lista de livros, e para cada um queremos trazer o autor e todos que já compraram esse livro. Com SQLSQL64 conteúdosSQL Server – Como evitar SQL Injection?Data · mai 2019Azure SQL DB Managed InstanceData · abr 2019SQL Server – Como evitar SQL Injection? Pare de utilizar Query Dinâmica como EXEC(@Query)Data · abr 2019Ver tudo em Data ou até mesmo com MongoDB, isso seria uma tarefa trivial, mas com Firebase pode ser tornar um pouco mais complicado.

No cenário mais simples, usamos o child_added e para cada livro consulta o autor e os clientes. Vamos ver como fica essa query!

ref.child('livros').on('child_added', => snapshot {
  let livro = snapshot.val();
  let authorId = livro.authorId;
  console.log('nome do livro: ' + livro.nome);
  ref.child('author').orderByChild("authorId").equalTo(authorId).on('value', author => {
    console.log('nome do author:' + author.val().nome;
  })
})

Sabe o que aconteceria nesse caso? Primeiro, ia dar o console.log() de todos os nomes dos nossos livros e depois iria dar o console do nome dos autores. Ex:

nome do livro é: SENHOR DOS ANÉIS
nome do livro é: O ALQUIMISTA
nome do livro é: TUDO É POSSÍVEL

nome do author: J. R. R. Tolkien
nome do author: Paulo Coelho
nome do author: Allan Percy

Mas o que queremos fazer é:

nome do livro é: SENHOR DOS ANEIS
nome do author: J. R. R. Tolkien

nome do livro é: O ALQUIMISTA
nome do author: Paulo Coelho

nome do livro é: TUDO É POSSÍVEL
nome do author: Allan Percy

Você pode pensar: “Ah, fácil de resolver! No retorno da segunda consulta eu mostro o nome do livro junto com o nome do autor!”. Vamos ver se essa abordagem funciona:

ref.child('livros').on('child_added', => snapshot {
  let livro = snapshot.val();
  let authorId = livro.authorId;

  ref.child('author').orderByChild("authorId").equalTo(authorId).on('value', author => {
    console.log('nome do livro: ' + livro.nome + '\n');
    console.log('nome do author:' + author.val().nome;
  })
})

Funciona! Mas e agora, se adicionarmos o terceiro listener – que é o caso dos clientes que compraram – o que podemos fazer? Ao mesmo tempo que você está consultando o autor, também está consultando os clientes, você não sabe qual vai ser o primeiro ou o último a responder. Por isso não pode jogar o console no listener do autor ou no listener do cliente, certo?

Nesse caso, usamos o conceito de Promise. Ao invés de usar o on, usamos o once que retorna uma Promise. Só uma observação: Quando a gente usa orderByChild, é como se fosse um filtro, então, ele retorna um array podendo conter 1 ou mais objetos.

A implementação fica da seguinte maneira:

let promises = [];

ref.child('livros').on('child_added', => snapshot {
  let livro = snapshot.val();
  let authorId = livro.authorId;

  let promise_author = ref.child('author').orderByChild("authorId").equalTo(authorId).once('value') //no caso do author, vai trazer 1 só
  let promise_pedidos = ref.child('pedidos').orderByChild("livroId").equalTo(livroId).once('value') //no caso de pedidos, vai trazer varios

  promises.push(promise_author, promise_pedidos) //inserindo as promises na fila

  // Quando terminar todas as minhas promises, ai sim eu monto a resposta final

  Promise.all(promisses).then(function(resp) {

	let nomeLivro = livro.nome;
	let nomeAuthor = resp[0].val().nome;
	let clientePedidos = resp[1].val().map(pedido => {
  	return pedido.nomeCliente
	});

	array.push(livro.nome + ' - ' + nomeAuthor + ' - ' + clientePedidos.join('\n'))

	console.log(array.join('\n'))

  })

})

Essa foi a saída para a implementação usando uma espécie de join. Claro que ao invés de colocar no array, eu fiz um objeto bonitinho, mapeado, e tal, mas isso fica a critério da sua implementação. Espero que sirva de base!

é analista de sistemas. Atualmente trabalha no desenvolvimento de ferramentas voltadas para o mercado financeiro.

Ver perfil