Banco de Dados

13 nov, 2017

Principais diferenças entre consultas SQL e NoSQL no Firebase

Publicidade

O objetivo desse artigo é mostrar ao programador t-SQL, pl-SQL ou desenvolvedores em geral, como são feitas as consultas básicas numa base de dados NoSQL, mais especificamente o Firebase.

Será algo bem prático. Não entrarei em detalhes, pois acho que não serão necessários. Pelo menos não agora.

Tabela usuário

Id Nome Sobrenome E-mail Idade Cidade Estado
1 Evelyn Mendes evelyn@transnerd.com 35 Porto Alegre RS
2 John Doe john@doe.com 20 Rio de Janeiro RJ
3 Esmeralda Silva esmeralda@email.com 44 Florianópolis SC
4 João Silva joao@email.com 66 Bagé RS

Temos linhas e colunas bem definidas, tudo arrumadinho.

Vamos ver como fica no Firebase, vulgo json.

{
    "usuario" : {
        "1" :{
            "nome" : "Evelyn",
            "sobrenome" : "Mendes",
            "email": "evelyn@transnerd.com",
            "idade" : 35,
            "cidade":  "Porto Alegre",
            "estado" : "RS"
        },
        "2":{
            "nome" : "Jonh",
            "sobrenome" : "Doe",
            "email": "jonh@email.com",
            "idade" : 20,
            "cidade":  "Rio de Janeiro",
            "estado" : "RS"
        },
        "4":{
            "nome" : "Esmeralda",
            "sobrenome" : "Silva",
            "email": "esmeralda@email.com",
            "idade" : 44,
            "cidade":  "Florianópolis",
            "estado" : "SC"
        },
        "5":{
            "nome" : "João",
            "sobrenome" : "Silva",
            "email": "joao@email.com",
            "idade" : 66,
            "cidade":  "Bagé",
            "estado" : "RS"
        }
    }
}

Que bagunça!

Dentro desses dois contextos, vamos agora começar a fazer nossas consultas.

Obs: Irei supor que todas as configurações do Firebase de acesso e de referência raiz já estejam definidas como o “rootref”: const rootref = firebase.database().ref(); o que chamamos de root, é o que o Firebase entende por documento raiz.

Na imagem root, é “diversidade-6d120”. Tudo abaixo dele são os registros que serão utilizados, como o nó/tabela palavras.

1 – Recuperar um registro de um usuário pelo seu id

SQL

 SELECT * FROM usuario WHERE id = 1;

Resultado

Id  Nome Sobrenome E-mail Idade Cidade Estado
1 Evelyn Mendes evelyn@transnerd.com 35 Porto Alegre RS

Firebase

const usuarioRef = rootRef.child('usuario').child('1');

Resultado

"1" :{
    "nome" : "Evelyn",
    "sobrenome" : "Mendes",
    "email": "evelyn@transnerd.com",
    "idade" : 35,
    "cidade":  "Porto Alegre",
    "estado" : "RS"
}

2 – Recuperar um usuario pelo seu nome

SQL

select * from usuario where nome = 'John';

Resultado

Id Nome Sobrenome E-mail Idade  Cidade Estado
2 John Doe john@doe.com 20 Rio de Janeiro RJ

Firebase

const nomeRef = rootRef.child('usuario')
                       .orderByChild('nome')
                       .equalTo('John');

Resultado

"2":{
   "nome" : "Jonh",
   "sobrenome" : "Doe",
   "email": "jonh@email.com",
   "idade" : 20,
   "cidade":  "Rio de Janeiro",
   "estado" : "RS"
 }

3 – Limitar a quantidade de registros que devem retornar em 2

SQL

select top 2 * from usuario     
  --ou
 select * from usuario limit 2 
  --Obs: eu prefiro limit, top é muito heterocisnormativo, blergh!!

Resultado

Id Nome Sobrenome  E-mail Idade Cidade Estado
1 Evelyn Mendes evelyn@transnerd.com 35 Porto Alegre RS
2 John Doe john@doe.com 20 Rio de Janeiro RJ

Firebase

const limitRef = rootRef.child('usuario').limitToFirst(2);
 //firebase usa limit, não usa top.  Muito amor

Resultado

"1" :{
    "nome" : "Evelyn",
    "sobrenome" : "Mendes",
    "email": "evelyn@transnerd.com",
    "idade" : 35,
    "cidade":  "Porto Alegre",
    "estado" : "RS"
},
"2":{
    "nome" : "Jonh",
    "sobrenome" : "Doe",
    "email": "jonh@email.com",
    "idade" : 20,
    "cidade":  "Rio de Janeiro",
    "estado" : "RS"
}

4 – Pesquisar os usuarios aonde o nome comece com a letra e

SQL

select * from usuario where nome like 'E%';

Resultado

Id Nome Sobrenome  E-mail Idade  Cidade Estado
1 Evelyn Mendes evelyn@transnerd.com 35 Porto Alegre RS
4 Esmeralda Silva esmeralda@email.com 44 Florianópolis SC

Firebase

const startERef = rootRef.child('usuario')
                         .orderByChild('nome')
                         .startAt('E');

Resultado

"1" :{
    "nome" : "Evelyn",
    "sobrenome" : "Mendes",
    "email": "evelyn@transnerd.com",
    "idade" : 35,
    "cidade":  "Porto Alegre",
    "estado" : "RS"
}, 
"4":{
    "nome" : "Esmeralda",
    "sobrenome" : "Silva",
    "email": "esmeralda@email.com",
    "idade" : 44,
    "cidade":  "Florianópolis",
    "estado" : "SC"
}

5 – Pesquisar os usuários que tem idade menor que 50 anos

SQL

select * from usuario where idade < 50;

Resultado

Id Nome Sobrenome E-mail Idade Cidade Estado
1 Evelyn Mendes evelyn@transnerd.com 35 Porto Alegre RS
2 John Doe john@doe.com 20 Rio de Janeiro RJ
4 Esmeralda Silva esmeralda@email.com 44 Florianópolis SC

Firebase

const idade50Ref = rootRef.child('usuario')
                          .orderByChild('idade')
                          .endAt(49);

Resultado

"1" :{
    "nome" : "Evelyn",
    "sobrenome" : "Mendes",
    "email": "evelyn@transnerd.com.br",
    "idade" : 35,
    "cidade":  "Porto Alegre",
    "estado" : "RS"
},
"2":{
    "nome" : "Jonh",
    "sobrenome" : "Doe",
    "email": "jonh@email.com",
    "idade" : 20,
    "cidade":  "Rio de Janeiro",
    "estado" : "RS"
},
"4":{
    "nome" : "Esmeralda",
    "sobrenome" : "Silva",
    "email": "esmeralda@email.com",
    "idade" : 44,
    "cidade":  "Florianópolis",
    "estado" : "SC"
}

6 – Pesquisar os usuários que tem idade maior que 50 anos

SQL

 select * from usuario where idade > 50;

Resultado

Id Nome Sobrenome E-mail Idade Cidade Estado
5 João Silva joao@email.com 66 Bagé RS

Firebase

const idadeRef = rootRef.child('usuario')
                        .orderByChild('idade')
                        .startAt(51);

Resultado

"5":{
    "nome" : "João",
    "sobrenome" : "Silva",
    "email": "joao@email.com",
    "idade" : 66,
    "cidade":  "Bagé",
    "estado" : "RS"
}

7 – Agora vamos pesquisar os usuários que tem idade entre 21 e 50 anos

SQL

select * from usuario where idade >= 21 and idade <=50;

Resultado

Id Nome Sobrenome E-mail Idade Cidade Estado
1 Evelyn Mendes evelyn@transnerd.com 35 Porto Alegre RS
4 Esmeralda Silva esmeralda@email.com 44 Florianópolis SC

Firebase

const idadesRef = rootRef.child('usuario')
                         .orderByChild('idade')
                         .startAt(21)
                         .endAt(50);

Resultado

"1" :{
    "nome" : "Evelyn",
    "sobrenome" : "Mendes",
    "email": "evelyn@transnerd.com",
    "idade" : 35,
    "cidade":  "Porto Alegre",
    "estado" : "RS"
}
"4":{
    "nome" : "Esmeralda",
    "sobrenome" : "Silva",
    "email": "esmeralda@email.com",
    "idade" : 44,
    "cidade":  "Florianópolis",
    "estado" : "SC"
}

 

8 -Vamos complicar um pouco as coisas?

Vamos selecionar todos os usuários que têm 66 anos e moram no estado RS. No SQL é fácil.

SQL

select * from usuario where idade = 66 and estado ='RS';

Resultado

Id Nome Sobrenome E-mail Idade Cidade Estado
5 João Silva joao@email.com 66 Bagé RS

Como eu havia dito, no sql é facil ,e no firebase?

Vamos tentar. Parece fácil, né? Apenas adicionar mais e mais orders e equals. Porém:

const erroRef = rootRef.child('usuario')
                       .orderByChild('idade').equalTo(66)
                       .orderByChild('estado').equalTo('RS')

Porém, isso dará um erro, pois o Firebase só aceita uma função de ordenação por instrução. Ops.

Como vamos resolver isso?
Calma. Pra tudo tem um jeito!

Na realidade, uma das premissas de uma base NoSQL como o Firebase, é pensar na forma que você irá pesquisar ao imputar dados. Vamos fazer uma modificação no json e você entenderá. Insira essa chave no registro 5:

"idade_estado" : "66_RS"

Note que foi inserida uma nova chave concatenando dois valores, idade e estado. Isso você pode fazer usando o Firebase Functions, a cada iteração de insert e update na base de dados. Feito isso a instrução ficará simplificada.

Firebase

const idadeEstadoRef = rootRef.child('usuario')
                              .orderByChild('idade_estado')
                              .equalTo('66_RS');

Resultado

"5":{
   "nome" : "João",
   "sobrenome" : "Silva",
   "email": "joao@email.com",
   "idade" : 66,
   "cidade":  "Bagé",
   "estado" : "RS",
   "idade_estado" : "66_RS"
}

9 – Agora digamos que além da idade e do estado, eu também quero pesquisar pelo nome do usuário, mas de forma incremental

Novamente no SQL, é fácil.

SQL

select * from usuario where idade = 66 and estado ='RS' and nome like 'Jo%';

Resultado

Id Nome Sobrenome E-mail Idade Cidade Estado
5 João Silva joao@email.com 66 Bagé RS

E agora no Firebase?

Vamos começar aproveitando o que fizemos no registro 5 e acrescentar uma nova chave.

"idade_estado_nome" : "66_RS_João Silva"

Imagine três campos; Um input de idade, um select de estado e um input de nome. Você quer a idade de 66 anos, então no campo idade, você digita 66, procura por quem mora no estado do Rio Grande do Sul, e então escolhe RS no campo select. E além disso, você quer todos os nomes que comecem por J, Jo, Joã, João. Vamos criar uma variável para ficar mais descritivo. A forma como estou escrevendo é meramente ilustrativa.

let idade_estado_nome = idade.value + '-' + estado.value + '-' + nome.value; 
const idadeEstadoNomeRef = rootRef.child('usuario') 
                                  .orderByChild('idade_estado_nome') 
                                  .startAt(idade_estado_nome);

Note que idade já estará no valor e estado também. A função startAt() que irá trabalhar será somente o nome conforme for sendo pesquisado. Ficaria algo assim:

const idadeEstadoNomeRef = rootRef.child('usuario')
                                  .orderByChild('idade_estado_nome')
                                  .startAt('66_RS_J');
//----------------------------------------------------------------
const idadeEstadoNomeRef = rootRef.child('usuario')
                                  .orderByChild('idade_estado_nome')
                                  .startAt('66_RS_Jo');
//----------------------------------------------------------------
const idadeEstadoNomeRef = rootRef.child('usuario')
                                  .orderByChild('idade_estado_nome')
                                  .startAt('66_RS_Joã');
//----------------------------------------------------------------
const idadeEstadoNomeRef = rootRef.child('usuario')
                                  .orderByChild('idade_estado_nome')
                                  .startAt('66_RS_João');

Como temos apenas um João na nossa base de dados, o resultado será sempre o mesmo.

Resultado

"5":{
    "nome" : "João",
    "sobrenome" : "Silva",
    "email": "joao@email.com",
    "idade" : 66,
    "cidade":  "Bagé",
    "estado" : "RS",
    "idade_estado" : "66_RS",
    "idade_estado_nome" : "66_RS_João Silva"
}

Ponto de alerta: Eu não sei o custo que isso irá ter para o Firebase em bases muito grandes e com muitos registros. Outra questão é o unicode, que deve ser levado em consideração nas pesquisas por texto. À primeira vista parece que não se pode fazer muita coisa no Firebase, não se tem joins bem declarados, por exemplo, mas você tende a não precisar deles e demais funcionalidades de bancos relacionais, pois além do conceito de chaves compostas, ainda temos a desnormalização, onde os dados são replicados. Um exemplo seria um usuário inteiro, não somente seu uid estar dentro de registros de eventos que ele participará, e também estar dentro de registros de reuniões para esse usuário. Isso é normal e plenamente aceito no Firebase.

Gente, muito obrigada. No próximo artigo irei falar sobre regras (rules) da base de dados. Vocês irão se surpreender com o poder que o Firebase oferece para validações, tipagens, e pasmem, até “FKs”. Sim, tem. Porém, reparem que é entre aspas.

Beijos!