SQL – Aprendendo a usar o Comando SELECT

Por: Rodrigo Borges

Introdução

Neste artigo, pretendo abordar os seguintes temas da Data Manipulation Language – Linguagem de manipulação de dados (DML), mais precisamente sobe o comando SELECT e os tópicos que mais caem em concurso sobre ele. Para tal, irei falar sobre:

  1. Sintaxe básica
  2. Funções de agregação (count, avg, min, max, sum, …)
  3. Agrupamento (group by)
  4. Join

Sintaxe básica do SELECT

A sintaxe básica é similar a “exiba <colunas> da <tabela> quando <condição>” e podemos imaginar um exemplo como sendo “exiba nome e nota da tabela alunos quando nota < 7”. A sintaxe exata para isso é:

SELECT <colunas>

FROM <tabela>

WHERE <condição ou condições>

O campo colunas pode ter * se for para exibir todas ou uma lista separada por ‘,’. A cláusula WHERE é a única que não é obrigatória. Ela pode ter mais de uma condição separada por AND (operação de E lógico) ou OR (operação de OU lógico) e ainda é possível usar parêntesis para separar condições.

Cada condição pode usar os operadores:

  • Igual: “=”
  • Diferente: “!=”
  • Maior: “>”
  • Menor: “<”
  • Maior ou igual: “>=”
  • Menor ou igual: “<=”
  • Existem outros não abordados como o between, not in, in, exists, not exists, …

SELECT nome

FROM Professor

WHERE matricula > 7 AND idMateria = 106

Os parêntesis podem ser usados para uma simples estética, o que garante uma facilidade de leitura ou ainda para garantir a precedência correta. A precedência é analisada na seguinte ordem:

  1. Parêntesis ( )

  2. Divisão (/), Multiplicação (*)
  3. Adição (+), Subtração (-)
  4. NOT
  5. AND
  6. ALL, ANY, BETWEEN, IN, LIKE, OR

Para ilustrar o uso de parêntesis como forma de forçar a precedência, fiz um exemplo onde eu quero as matrículas maiores que 7 e que tenham a matéria de id 106 a 109 (106 inclusive a 109 inclusive), ou as matrículas menores ou iguais a 7 e que tenham a matéria de id 101, bastando que uma dessas condições sejam verdade.

SELECT nome

FROM Professor

WHERE (matricula > 7 AND idMateria BETWEEN 106, 109) OR (matricula <= 7 AND idMateria = 101)

ou

SELECT nome

FROM Professor

WHERE (matricula > 7 AND idMateria IN (106, 107, 108, 109) ) OR (matricula <= 7 AND idMateria = 101)

Usei 2 operadores para poder explicá-los rapidamente:

  • BETWEEN: pode ser usado com textos, valores numéricos ou datas e desem ser passados o menor e o maior valor aceitos no retorno.
  • IN: permite buscar por uma lista de valores. Os bancos de dados costumam limitar a quantidade de valores dentro do “IN” a 1.000 por questões de desempenho.

Vou usar as tabelas expressas pelo Modelo de Entidade-relacionamento, ambos exibidos abaixo, para nos guiar na explicação:

Modelo de Entidade Relacionamento (MER) das tabelas usando a notação “pé de galinha”. Fonte: (o autor, 2020)

Tabelas Aluno, Matéria, Cursa e Professor. Fonte: (o autor, 2020)

Consultando mais de uma tabela em um comando

É possível acessar mais de uma tabela e juntar a resposta delas através da sintaxe a seguir.

SELECT <colunas>

FROM <tabela1>, <tabela2>

WHERE <condição ou condições>

Quando se usa mais de uma tabela, deve-se informar o relacionamento esperado entre elas, ou seja, quando os dados de uma tabela se relacionam com o da outra. Se isso não for feito, o resultado será o chamado produto cartesiano: uma combinação de todos os elementos de 1 tabela com todos de outra, mesmo que não haja relacionamento entre elas.

Usando as tabelas acima como exemplo, se fizéssemos o seguinte SQL “SELECT * FROM Aluno, Materia”, o resultado seria a tabela abaixo.


Produto Cartesiano entre aluno e matéria. Fonte: (o autor, 2020)

Para evitar isso, é preciso ligar as tabelas por meio da cláusula WHERE, informando as colunas que se relacionam. Na verdade, existem 2 formas de se fazer isso a depender de como as tabelas foram modeladas. Repare que as tabelas aluno e matéria têm um relacionamento diferente de matéria e professor e podemos ver isso melhor graficamente por meio do MER apresentado no início do artigo. Enquanto o primeiro relacionamento envolve o chamado muitos para muitos ou “N para N”, o que obriga a criação de uma tabela relacional chamada de Cursa e que será usada na consulta, o segundo relacionamento se basta pelas 2 tabelas.

Para esse relacionamento mais simples, envolvendo apenas as 2 tabelas, podemos fazer a consulta assim:

SELECT *

FROM Materia, Professor

WHERE id = idMateria

Se houvesse repetição de nome de coluna entre as tabelas, teríamos que usar o nome da tabela antes da coluna a que ela pertence, não havendo repetição, é facultativo. Podemos também criar um apelido (alias) para a tabela de forma a facilitar o entendimento, de modo que a consulta acima ficará assim com nome da tabela e com apelido:

SELECT *

FROM Materia, Professor

WHERE Materia.id = Professor.idMateria

ou

SELECT *

FROM Materia M, Professor P

WHERE M.id = P.idMateria

Outra forma de fazer o relacionamento ocorre quando ele tem cardinalidade “n para n”, ou seja, 1 linha da tabela A pode estar associada a n linhas da tabela B e 1 linha da tabela B pode estar associada a n linhas da tabela A. Repare que no nosso exemplo, 1 aluno pode cursar várias matérias e 1 matéria pode ser cursada por vários alunos. Esse tipo de relacionamento é resolvido por meio de uma tabela intermediária – a chamada tabela relacional – que serve para fazer a ligação entre as 2 tabelas e no exemplo é a tabela “Cursa”. A diferença, nesse caso, será que precisaremos usá-la para fazer a ligação entre as 2 outras tabelas.

SELECT A.*, M.*

FROM Materia M, Cursa C, Aluno A

WHERE A.Matricula = C. Matricula

AND C.idMateria = M.id

Consultas Avançadas

As consultas básicas nos ajudam muito no dia a dia, mas na hora de responder questões, normalmente o examinador quer analisar o conhecimento do “join” e o uso das funções de agregação.

Funções de Agregação

As funções de agregação servem para resumir uma coluna do banco de dados e podem ser diferentes de acordo com o SGBD utilizado. Algumas das funções mais utilizadas são:

  • AVG: calcula a média entre todos os valores.
  • COUNT: conta a quantidade de linhas.
  • MIN: obtém o valor mínimo.
  • MAX: obtém o valor máximo.
  • SUM: soma todos os valores.

No Oracle, essas funções somente recebem valores que não estejam nulos. Isso significa que se eu quiser saber a quantidade de linhas da tabela Professor, eu executo:

SELECT COUNT(*)

FROM Professor

ou

SELECT COUNT(matricula)

FROM Professor

Mas se eu executar o comando abaixo, só teremos a quantidade de linhas que tenham o valor da coluna idMateria diferente de nulo. Como no MER, eu permiti que essa coluna seja nula, pode gerar um retorno diferente da quantidade total.

SELECT COUNT(idMateria)

FROM Professor

As funções de agregação são muito usadas em conjunto com o agrupamento, conforme veremos no tópico a seguir.

Finalizando o tópico de agregação, deixo um link para as listas de funções de agregação de alguns dos SGBD mais cobrados:

Agrupamento (Group By)

O agrupamento é uma forma de fazermos uma síntese do resultado, algo como sintetizar as notas dos alunos para mostrar a média de cada turma. E como isso funciona no banco de dados?

Na linguagem SQL, nós agrupamos com a cláusula “GROUP BY” e informamos qual a coluna agrupadora. Se queremos agrupar por turma, dizemos “GROUP BY <coluna referente a turma>”, conforme exemplo a seguir que usa o MER do início do artigo.

SELECT C.idMateria, AVG (nota)

FROM Materia M, Cursa C, Aluno A

WHERE A.Matricula = C. Matricula

AND C.idMateria = M.id

GROUP BY C.idMateria

Perceba que eu usei a cláusula “WHERE” também, pois ainda não expliquei o “JOIN” e usei em conjunto com o “GROUP BY”. O SGBD vai analisar o WHERE para filtrar o conjunto resposta e, desse conjunto, vai agrupar.

Outro detalhe é que a projeção (nome dado pela álgebra relacional quando excluímos algumas colunas) só pode ter as colunas agrupadoras. Isso pode soar difícil, mas é lógico. Imagine que nós executamos o select acima, onde agrupamos pelo idMateria (logo, pela matéria) e obtivemos a média por matéria. Se eu tentar colocar a coluna matriculaAluno na resposta, terei n valores a serem exibidos para cada matéria, ou seja, para cada linha; e isso não pode!


Resultado do “GROUP BY”. Fonte: (o autor: 2020).

Como falei no início do tópico, o uso do “WHERE” junto com o “GROUP BY“ serve para que seja feito um filtro antes da síntese de agrupamento. Mas e se eu quiser filtrar depois do agrupamento!? Para isso, eu tenho a cláusula “HAVING”. Resgatando a ideia do comando anterior, se eu só quiser saber as turmas com média menor que 7, eu uso o “HAVING” para isso.

SELECT C.idMateria, AVG (nota)

FROM Materia M, Cursa C, Aluno A

WHERE A.Matricula = C. Matricula

AND C.idMateria = M.id

GROUP BY C.idMateria

HAVING AVG(nota) < 7

Entendendo o Join e seus tipos

O join é outra cláusula que parece confusa para muita gente, mas ela busca simplificar a sintaxe na união de tabelas e o tipo de join (INNER, FULL OUTER, LEFT, RIGHT) informa que conjunto de dados desejamos, isso poderá ser visto melhor na figura abaixo. Uma consulta com join é similar a um SELECT como o que vemos a seguir, dependendo do tipo de join colocamos outros testes no WHERE.

SELECT *

FROM A, B

WHERE A.id= B.id

Muitas questões de SQL podem ser analisadas por meio de conjuntos matemáticos, pois cada tabela seria um conjunto que tem em sua interseção com outra tabela a representação de um relacionamento entre os 2 conjuntos. Antes de explicar, vou traduzir do inglês para que facilite o entendimento do join (junção). O outer join é junção externa e o inner join é uma junção interna, o left é esquerda e o right é direita. Após a explicação de cada join, fiz um exemplo comparando a consulta com o INNER e o LEFT JOIN e seus resultados e, na figura seguinte, uma comparação de cada tipo e subtipo de join usando conjuntos.

Existem alguns tipos, mas a ideia básica pode ser tirada de 2 deles:

  • Apenas as linhas das 2 tabelas que possuem relacionamentoINNER JOIN. Um join entre professor e matéria só exibiria os professores associados a matéria e matéria com associação com professor.
  • Incluir linhas que não tenham relacionamento e pode ter as com relacionamento também se for preciso – OUTER JOIN.
    • Todos os professores e suas matérias (matérias relacionadas ao professor), se existir relacionamento – LEFT JOIN ou LEFT OUTER JOIN. Exibe todos os professores e suas associações com matéria e exibe também os professores que não têm associação.
    • O RIGHT JOIN ou RIGHT OUTER JOIN é igual ao left (esquerda), só que usando a tabela da direita. Isso significa que vai obter exatamente o mesmo que o inner + as linhas da direita que não se relacionam com as da esquerda (esse trecho final que é o inverso do LEFT).
    • A operação de FULL OUTER é a combinação das linhas que não têm relacionamento, como inclui também as que têm relacionamento (é a união do resultado do LEFT OUTER com o RIGHT OUTER), possui todos os elementos da coluna da esquerda + todos os elementos da coluna da direita.


Resultado do INNER e do LEFT JOINS. Fonte: (o autor, 2020)

Faço 2 observações sobre a figura anterior para que não fique nenhuma dúvida:

  1. No select eu coloquei “SELECT p.nome Professor, m.nome Materia”, os textos em negrito são apelidos ou alias, nome em inglês, já que o nome das colunas são iguais, para que possa ser exibido de forma mais fácil.

  2. O texto “<nulo>” foi só para chamar a atenção, o campo está em vazio ou nulo.

Como veremos na figura abaixo, os joins podem ter filtros para limitar o retorno a algumas partes do conjunto e isso é feito por meio da clausula WHERE depois do “SELECT … JOIN … ON …”. Se a consulta faz um LEFT JOIN, ela vai retornar o subconjunto da esquerda (os que têm relação com a tabela da direita + os elementos ou linhas da esquerda que não têm relação com a direita), mas posso só retornar os que não têm relação com a direita se adicionar o WHERE com a coluna da esquerda responsável pelo join sendo nula. A consulta a seguir é a anterior com essa alteração, o que nos daria somente a última linha (Professor será “Aguiar” e o campo matéria será nulo).

SELECT p.nome Professor, m.nome Materia

FROM Professor p

INNER JOIN Materia m

ON m.id = p.idMateria

WHERE p.idMateria is null


Fonte: (Arbeck, 2013) disponível na Wikimedia sob a licença https://creativecommons.org/licenses/by/3.0

Existe ainda o natural join que é um join onde não se usa as colunas de relacionamento, pois o banco de dados é quem detectará que colunas são essas. A escolha da relação pode ser feita de forma errada delo banco, o que pode nos mostrar informações erradas, por isso o natural join deve ser usado com cuidado. O natural join pode ser:

  • INNER: SELECT * FROM Professor NATURAL INNER JOIN Materia ou SELECT * FROM Professor NATURAL JOIN Materia
  • LEFT: SELECT * FROM Professor NATURAL LEFT JOIN Materia
  • RIGHT: SELECT * FROM Professor NATURAL RIGHT JOIN Materia

Para informar a(s) coluna(s) a ser(em) relacionada(s), pode-se usar a cláusula “USING” – o que exclui a possibilidade de fazer um natural join – sendo usado quando:

  • As tabelas tiverem uma coluna com o mesmo nome, mas tipos diferentes. Nesse caso basta 1 nome dentro da cláusula “USING”.
  • Várias colunas tiverem o mesmo nome entre as tabelas, mas o objetivo for unir por 1 ou algumas dessas colunas.

O “USING” é parecido com o “ON” do join, sendo, inclusive, convertido internamente para um cláusula “ON”. Assim, um comando

SELECT *

FROM A

INNER JOIN B

USING (id)

seria igual a

SELECT *

FROM A x

INNER JOIN B y

ON x.id = y.id

Por último, temos um tipo de join que nunca vi em concurso, o CROSS JOIN. Ele é um produto cartesiano, ou seja, a combinação de cada linha da tabela da esquerda com todas as linhas da tabela da direita e a quantidade total de linhas será igual a quantidade de linhas da tabela da esquerda X a quantidade de linhas da tabela da direita.

Referências Bibliográficas:

NAVATHE; ELMASRI. Sistemas de Banco de Dados, Pearson, 6° Edição, 2010.

USING clause. Disponível em: <https://docs.oracle.com/javadb/10.8.3.0/ref/rrefsqljusing.html>. Acesso em: 05 abr. 2020.

SQL Logical Operators and Operator Precedence. Disponível em: <http://www.sqlqueryexamples.com/sql-logical-operators-and-operator-precedence.htm >. Acesso em 05 abr. 2020.

Rodrigo Borges

Bacharel em Ciência da Computação (PUC-Rio) 2003 Pós Graduação em Criptografia e Segurança em Redes (UFF) 2009 MBA Gerenciamento de Projetos (FGV-Rio) 2012 Aprovado e classificado nos seguintes concursos: EAOT-FAB/2006, Dataprev/2012, TRE-MT/2015, TRT-MT/2016, TRE-Pe/2017, TRE-Pr/2017, TRE-RJ/2017, STM/2018, TRT-SP, entre outros.