1.10. Consultas em SQL
Na linguagem SQL
, o comando SELECT
é utilizado para recuperação de dados das tabelas. A sintaxe geral deste comando é a seguinte:
SELECT [ ALL | DISTINCT [ ON ( expressão [, ...] ) ] ]
[ * | expressão [ [ AS ] rótulo ] [, ...] ]
[ FROM from_item [, ...] ]
[ WHERE condição ]
[ GROUP BY elemento_agrupamento [, ...] ]
[ HAVING condição [, ...] ]
[ ORDER BY expressão [ ASC | DESC ] [, ...] ]
[ LIMIT { quantidade | ALL } ]
[ OFFSET início [ ROW | ROWS ] ]
O comando SELECT
é formado por várias cláusulas: SELECT
, FROM
, WHERE
, GROUP BY
, HAVING
, ORDER BY
, LIMIT
e OFFSET
. Cada uma dessas cláusulas tem um papel importante na definição dos objetivos de uma consulta. Portanto, na sintaxe mostrada acima temos que:
Tudo que está entre os pares de colchetes (
[
e]
) é opcional.A cláusula
SELECT
permite especificar a lista de expressões, isto é, nomes de colunas ou fórmulas matemáticas ou chamadas de função ou até mesmo sub-consultas, que farão parte das linhas de saída da consulta. A palavra-chaveDISTINCT
faz com que linhas com valores duplicados sejam removidas do resultado, ficando apenas uma linha do grupo de linhas repetidas.DISTINCT ON ( expressão [, ...])
tem um comportamento semelhante, mas considerando apenas a lista de expressões fornecida. A palavra-chaveALL
inclui todas as linhas do resultado, que é o comportamento padrão e, portanto, pode ser omitida. A palavra-chaveAS
possibilita renomear uma coluna ou expressão com um novorótulo
. O caractere*
é uma abreviação para a lista de todas as colunas dos conjuntos de dados presentes na cláusulaFROM
.A cláusula
FROM
especifica uma ou mais tabelas como fonte dos dados da consulta. Caso múltiplas tabelas sejam especificadas, o resultado é um produto cartesiano (ouCROSS JOIN
) de todas as tabelas envolvidas. No entanto, é muito comum o uso de uma cláusulaWHERE
para restringir as linhas retornadas a um subconjunto menor desse produto cartesiano. Vale ressaltar que na cláusulaFROM
, ofrom_item
pode ser o nome de uma tabela do banco de dados, o nome de uma view (visão), uma sub-consulta, ou até mesmo uma chamada de função que produza valores que são tratados como um conjunto de linhas. Portanto, a cláusulaFROM
faz o produto cartesiano dos conjuntos de dados informados.A cláusula
WHERE
permite definir umacondição
, isto é, umaexpressão lógica
oupredicado
, para filtrar o conjunto de linhas da consulta. As linhas que não satisfaçam esse predicado serão eliminadas do resultado.A cláusula
GROUP BY
é utilizada para criar grupos de linhas que são condensadas em uma única linha através das operações de agregação tais como:SUM
,MIN
,MAX
,AVG
,COUNT
, entre outras. Oelemento_agrupamento
pode ser o nome de uma coluna ou uma expressão formada a partir das colunas. Também podemos usar os nomes das colunas de saída da consulta nessa cláusula ou até mesmo a posição ordinal da coluna de saída. Quando esta cláusula está presente, apenas as colunas listadas nela ou funções de agregação podem ser usadas na lista da cláusulaSELECT
.A cláusula
HAVING
permite definir umacondição
, isto é, umaexpressão lógica
oupredicado
, para filtrar o resutado dos grupos de linhas. Dessa maneira, as linhas resultantes de agrupamentos que não satisfaçam essa condição, são eliminadas do resultado final.A cláusula
ORDER BY
ordena o resultado final de acordo com a expressão fornecida. Lembrando que a expressão pode ser uma lista de colunas, expressões matemáticas, chamadas de função, ou a posição das colunas de saída de acordo com a lista da cláusulaSELECT
.As cláusulas
LIMIT
eOFFSET
permitem, respectivamente, definir o número máximo de linhas a serem retornadas e o ponto onde esta contagem começa a valer.
Nota
A sintaxe do comando de seleção apresentada acima foi feita de maneira simplificada. Para a versão completa, consulte o seguite tópico do manual do PostgreSQL: SELECT.
1.10.1. Consultas Simples
Nesta seção veremos exemplos de consultas que envolvem uma única tabela como entrada.
1. Recuperar os dados do contrantante Leonardo
:
Solução:
SELECT *
FROM contratante
WHERE nome = 'Leonardo';
Saída:
cpf | nome | genero | classificacao | data_cadastro
-------------+----------+--------+-----------------+---------------
55555555501 | Leonardo | m | grande produtor | 2024-10-10
(1 row)
Nessa consulta usamos três cláusulas:
SELECT
: Nesta cláusula usamos o caracter especial*
que é expandido para a lista de todas as colunas e, por isso, obtivemos uma linha com 05 valores como mostrado acima.FROM
: Nesta cláusula especificamos a tabelacontratante
como fonte da consulta.WHERE
: Nesta cláusula especificamos uma expressão lógica que realiza o filtro das linhas desejadas. No exemplo acima, temos apenas um contratante com o nomeLeonardo
e, portanto, somente uma linha satisfaz a condiçãonome = 'Leonardo'
.
2. Recuperar o CPF e nome dos contratantes classificados como “grande produtor” rural:
Solução:
SELECT cpf, nome
FROM contratante
WHERE classificacao = 'grande produtor';
Saída:
cpf | nome
-------------+----------
22222222201 | Karine
55555555501 | Leonardo
(2 rows)
A cláusula SELECT
da consulta acima contém um subconjunto das colunas da tabela contratante
: cpf
e nome
. Utilizamos o separador ,
para listar as colunas nessa cláusula. Essa cláusula nos permite, entre outra coisas, controlar a ordem de apresentação das colunas.
3. Recuperar o CPF e os três primeiros caracteres do nome dos contratantes registrados no ano de 2022 do gênero masculino (m
):
Solução:
SELECT cpf, left(nome, 3) AS iniciais_nome
FROM contratante
WHERE (genero = 'm') AND (extract(YEAR FROM data_cadastro) = 2022);
Saída:
cpf | iniciais_nome
-------------+---------------
11111111101 | Gil
44444444401 | Tha
(2 rows)
Na consulta acima:
O segundo elemento da cláusula
SELECT
utilizou uma função chamadaleft
para extrair no máximo três caracteres de um nome. Nesse elemento, também utilizamos a palavra-chaveAS
para criar um novo rótulo para a coluna de saída:iniciais_nome
.A clausula
WHERE
possui uma expressão usando o e-lógico (AND
). Portanto, apenas as linhas que tenham simultaneamente o caracterm
como gênero e a parte do ano da data de cadastro igual a 2022 serão selecionadas no resultado final.
1.10.2. Junção de Tabelas
Nesta seção veremos exemplos de consultas que envolvem duas ou mais tabelas como entrada.
4. Fazer o produto cartesiano entre as tabelas operacao
e gleba
:
Solução:
SELECT *
FROM operacao, gleba;
Saída:
numero | valor | data_inicio | data_fim | gid | geom | operacao_numero
--------+-----------+-------------+------------+-----+------------------------+-----------------
1 | 100000.54 | 2022-05-01 | 2022-11-30 | 1 | 0106000020421200000... | 1
1 | 100000.54 | 2022-05-01 | 2022-11-30 | 2 | 0106000020421200000... | 2
1 | 100000.54 | 2022-05-01 | 2022-11-30 | 3 | 0106000020421200000... | 3
1 | 100000.54 | 2022-05-01 | 2022-11-30 | 4 | 0106000020421200000... | 3
... | ... | ... | ... | ... | ... | ...
9 | 234000.56 | 2023-05-01 | 2023-11-30 | 8 | 0106000020421200000... | 7
9 | 234000.56 | 2023-05-01 | 2023-11-30 | 9 | 0106000020421200000... | 7
9 | 234000.56 | 2023-05-01 | 2023-11-30 | 10 | 0106000020421200000... | 8
9 | 234000.56 | 2023-05-01 | 2023-11-30 | 11 | 0106000020421200000... | 9
(99 rows)
A cláusula FROM
permite especificar uma lista de tabelas (ou itens de dados). Na consulta acima, a cláusula FROM
realizou o produto cartesiano entre as duas tabelas. Como temos 09 operações de crédito e 11 glebas, o resultado contém 99 linhas, isto é, todas as linhas da tabela operacao
pareadas com todas as linhas da tabela gleba
.
Repare também que por termos usado o caractere *
na cláusula SELECT
, todas as colunas das duas tabelas participaram do resultado final. Na cláusula SELECT
é possível controlar a lista de colunas de saída especificando o nome qualificado da coluna, isto é, o nome-tabela.nome-coluna
:
SELECT operacao.*, gleba.gid
FROM operacao, gleba;
Saída:
numero | valor | data_inicio | data_fim | gid
--------+-----------+-------------+------------+-----
1 | 100000.54 | 2022-05-01 | 2022-11-30 | 1
1 | 100000.54 | 2022-05-01 | 2022-11-30 | 2
1 | 100000.54 | 2022-05-01 | 2022-11-30 | 3
1 | 100000.54 | 2022-05-01 | 2022-11-30 | 4
... | ... | ... | ... | ...
9 | 234000.56 | 2023-05-01 | 2023-11-30 | 8
9 | 234000.56 | 2023-05-01 | 2023-11-30 | 9
9 | 234000.56 | 2023-05-01 | 2023-11-30 | 10
9 | 234000.56 | 2023-05-01 | 2023-11-30 | 11
(99 rows)
Nesse último exemplo, o item operacao.*
na cláusula SELECT
é expandido para todas as colunas da tabela operacao
, isto é, as colunas numero
, valor
, data_inicio
e data_fim
. Já a expressão gleba.gid
indica que queremos apenas a coluna gid
da tabela gleba
no resultado.
5. Juntar as linhas correlatas das tabelas operacao
e gleba
:
Nota
Chamamos este tipo de consulta de junção entre tabelas.
Solução:
SELECT *
FROM operacao, gleba
WHERE operacao.numero = gleba.operacao_numero;
Saída:
numero | valor | data_inicio | data_fim | gid | geom | operacao_numero
--------+-----------+-------------+------------+-----+------------------------+-----------------
1 | 100000.54 | 2022-05-01 | 2022-11-30 | 1 | 0106000020421200000... | 1
2 | 110000.45 | 2023-04-01 | 2023-10-30 | 2 | 0106000020421200000... | 2
3 | 730000.00 | 2022-05-01 | 2022-11-30 | 3 | 0106000020421200000... | 3
3 | 730000.00 | 2022-05-01 | 2022-11-30 | 4 | 0106000020421200000... | 3
4 | 200000.99 | 2021-05-01 | 2021-11-30 | 5 | 0106000020421200000... | 4
5 | 205000.98 | 2022-03-01 | 2022-12-31 | 6 | 0106000020421200000... | 5
6 | 243000.97 | 2023-06-01 | 2023-09-30 | 7 | 0106000020421200000... | 6
7 | 50000.12 | 2022-05-01 | 2022-11-30 | 8 | 0106000020421200000... | 7
7 | 50000.12 | 2022-05-01 | 2022-11-30 | 9 | 0106000020421200000... | 7
8 | 900000.73 | 2023-05-01 | 2023-11-30 | 10 | 0106000020421200000... | 8
9 | 234000.56 | 2023-05-01 | 2023-11-30 | 11 | 0106000020421200000... | 9
(11 rows)
Repare na saída acima que agora os valores nas linhas para a primeira coluna numero
são iguais na coluna operacao_numero
. Esse exemplo mostra como usar colunas relacionadas para busca da informação apropriada.
Outra forma de realizar a consulta acima é utilizar a palavra-chave INNER JOIN
, como indicado abaixo:
SELECT *
FROM operacao INNER JOIN gleba ON operacao.numero = gleba.operacao_numero;
Saída:
numero | valor | data_inicio | data_fim | gid | geom | operacao_numero
--------+-----------+-------------+------------+-----+------------------------+-----------------
1 | 100000.54 | 2022-05-01 | 2022-11-30 | 1 | 0106000020421200000... | 1
2 | 110000.45 | 2023-04-01 | 2023-10-30 | 2 | 0106000020421200000... | 2
3 | 730000.00 | 2022-05-01 | 2022-11-30 | 3 | 0106000020421200000... | 3
3 | 730000.00 | 2022-05-01 | 2022-11-30 | 4 | 0106000020421200000... | 3
4 | 200000.99 | 2021-05-01 | 2021-11-30 | 5 | 0106000020421200000... | 4
5 | 205000.98 | 2022-03-01 | 2022-12-31 | 6 | 0106000020421200000... | 5
6 | 243000.97 | 2023-06-01 | 2023-09-30 | 7 | 0106000020421200000... | 6
7 | 50000.12 | 2022-05-01 | 2022-11-30 | 8 | 0106000020421200000... | 7
7 | 50000.12 | 2022-05-01 | 2022-11-30 | 9 | 0106000020421200000... | 7
8 | 900000.73 | 2023-05-01 | 2023-11-30 | 10 | 0106000020421200000... | 8
9 | 234000.56 | 2023-05-01 | 2023-11-30 | 11 | 0106000020421200000... | 9
(11 rows)
6. Quais os contratos realizados no CPF 55555555501
?
Solução:
SELECT contratante.cpf, nome, operacao.numero, valor
FROM contratante, contratante_operacao, operacao
WHERE contratante.cpf = contratante_operacao.cpf
AND contratante_operacao.numero = operacao.numero
AND contratante.cpf = '55555555501';
Saída:
cpf | nome | numero | valor
-------------+----------+--------+-----------
55555555501 | Leonardo | 8 | 900000.73
(1 row)
Dica
Para mais detalhes das consultas com junção entre tabelas, consulte o manual do PostgreSQL nas seções 2.6. Joins Between Tables e 7.2. Table Expressions.
1.10.3. Consultas de Agregação
Temos vários operadores que trabalham com grupos de registros, sendo muito úteis para uso com a cláusula GROUP BY
:
AVG
: média dos valores da coluna.SUM
: soma dos valores da coluna.COUNT
: número de valores na coluna.MAX
: maior valor na coluna.MIN
: menor valor na coluna.
Nesta seção vamos explorar o uso das cláusulas GROUP BY
e HAVING
na construção de consultas que permitam realizar uma sumarização de valores a partir de grupos de linhas.
7. Quantos contratos existem?
Solução:
Neste caso precisamos apenas usar uma função de agregação que conte o número de linhas de uma tabela, tratando todas as linhas como um único grupo. A função de agregação COUNT
pode ser usada para esta finalidade:
SELECT COUNT(*) AS num_contratos
FROM operacao;
Saída:
num_contratos
---------------
9
(1 row)
8. Qual o número de operacões por ano?
Solução:
SELECT extract(YEAR FROM data_inicio) AS ano,
COUNT(*) AS num_operacoes
FROM operacao
GROUP BY extract(YEAR FROM data_inicio)
ORDER BY ano;
Saída:
ano | num_operacoes
------+---------------
2021 | 1
2022 | 4
2023 | 4
(3 rows)
9. Quantas operações cada CPF realizou?
Solução:
SELECT cpf, COUNT(*) AS num_operacoes
FROM contratante INNER JOIN contratante_operacao USING (cpf)
GROUP BY cpf
ORDER BY cpf;
Saída:
cpf | num_operacoes
-------------+---------------
11111111101 | 3
22222222201 | 1
33333333301 | 3
44444444401 | 1
55555555501 | 1
66666666601 | 1
(6 rows)
10. Quais CPF’s realizaram mais do que um empréstimo?
Solução:
Esta consulta precisará contar o número de operações para cada pessoa e em seguida aplicar um filtro no resultado do grupo. Isto indica que precisaremos utilizar a cláusula HAVING
como mostrado abaixo:
SELECT cpf, COUNT(*) AS num_operacoes
FROM contratante INNER JOIN contratante_operacao USING (cpf)
GROUP BY cpf
HAVING COUNT(*) > 1
ORDER BY cpf;
Resultado:
cpf | num_operacoes
-------------+---------------
11111111101 | 3
33333333301 | 3
(2 rows)
1.10.4. Funções de Janela (Window Functions)
Além das funções de agregação existem funções que operam em um conjunto relacionado de linhas. Essas funções são chamadas de funções de janela (window functions). Duas funções de janela muito úteis são: rank
e dense_rank
. Vamos explorar o uso da função dense_rank
nessa seção.
11. Qual a posição (ranking) de cada CPF em relação ao valor de empréstimos realizados ao longo dos anos?
Solução:
Vamos começar construindo uma consulta que liste o ano de matricula, número de matrícula, nome e total de créditos cursados por cada estudante:
SELECT cpf,
extract(YEAR FROM data_inicio) AS ano,
SUM(valor) AS total_emprestimo
FROM operacao INNER JOIN contratante_operacao USING (numero)
GROUP BY cpf, ano
ORDER BY cpf, ano;
Resultado:
cpf | ano | total_emprestimo
-------------+------+------------------
11111111101 | 2022 | 150000.66
11111111101 | 2023 | 110000.45
22222222201 | 2022 | 730000.00
33333333301 | 2021 | 200000.99
33333333301 | 2022 | 205000.98
33333333301 | 2023 | 243000.97
44444444401 | 2022 | 50000.12
55555555501 | 2023 | 900000.73
66666666601 | 2023 | 234000.56
(9 rows)
Em seguida, vamos construir uma nova consulta que utilize a consulta acima como subconsulta para podermos utilizar a função de janela dense_rank
para criar partições no resultado:
SELECT contratos.*,
dense_rank() OVER (PARTITION BY ano ORDER BY total_emprestimo DESC) AS rank_valor
FROM (
SELECT cpf,
extract(YEAR FROM data_inicio) AS ano,
SUM(valor) AS total_emprestimo
FROM operacao INNER JOIN contratante_operacao USING (numero)
GROUP BY cpf, ano
ORDER BY cpf, ano
) AS contratos
ORDER BY ano ASC, rank_valor ASC;
Resultado:
1 cpf | ano | total_emprestimo | rank_valor
2-------------+------+------------------+------------
3 33333333301 | 2021 | 200000.99 | 1
4 22222222201 | 2022 | 730000.00 | 1
5 33333333301 | 2022 | 205000.98 | 2
6 11111111101 | 2022 | 150000.66 | 3
7 44444444401 | 2022 | 50000.12 | 4
8 55555555501 | 2023 | 900000.73 | 1
9 33333333301 | 2023 | 243000.97 | 2
10 66666666601 | 2023 | 234000.56 | 3
11 11111111101 | 2023 | 110000.45 | 4
12(9 rows)
Repare na consulta que a função de janela dense_rank()
particiona as linhas resultantes da subconsulta pela coluna de saída ano
e cria um ranking baseado na ordem dentro de cada partição pelo valor da coluna total_emprestimo
.
Dica
Para mais detalhes das consultas de janela, consulte o manual do PostgreSQL nas seções 3.5. Window Functions e 9.22. Window Functions.
1.10.5. Consultas Espaciais
12. Qual a área em hectares de cada gleba?
Solução:
SELECT gid,
ST_Area(geom) AS area
FROM gleba;
Resultado:
gid | area
-----+------------------------
1 | 1.0874952000010346e-05
2 | 1.9811417999996206e-05
3 | 3.292848000000658e-05
4 | 2.306288000001502e-05
5 | 8.811599999997503e-06
6 | 3.23872467915163e-06
7 | 2.5555322999988482e-05
8 | 0.0001896014250000293
9 | 1.989994500000845e-05
10 | 3.0835349999948616e-06
11 | 1.85541250005088e-05
(11 rows)
INSERT INTO spatial_ref_sys (srid, proj4text)
VALUES (100000, '+proj=aea +lat_1=-2 +lat_2=-22 +lat_0=-12 +lon_0=-54 +x_0=5000000 +y_0=10000000 +ellps=GRS80 +units=m +no_defs ');
SELECT gid,
ST_Area(ST_Transform(geom, 100000)) / 10000.0 AS area
FROM gleba;
Resultado:
gid | area
-----+--------------------
1 | 12.072867606637685
2 | 21.993748629081985
3 | 36.67122515757885
4 | 25.759911019875354
5 | 9.845611033112005
6 | 3.616752242092826
7 | 28.555074776128993
8 | 209.41624609146064
9 | 21.965721476803974
10 | 3.436200736973672
11 | 20.679512542366254
(11 rows)
13. Listar o nome do município onde cada gleba encontra-se localizada.
Solução:
SELECT gleba.gid, geocodigo, nome
FROM municipio,
gleba
WHERE ST_Contains(municipio.geom, gleba.geom);
gid | geocodigo | nome
-----+-----------+------------------
1 | 4105409 | Chopinzinho
2 | 4105409 | Chopinzinho
3 | 4109401 | Guarapuava
4 | 4109401 | Guarapuava
5 | 4117057 | Nova Laranjeiras
6 | 4117057 | Nova Laranjeiras
7 | 4117057 | Nova Laranjeiras
8 | 4117602 | Palmas
9 | 4117602 | Palmas
10 | 4127007 | Teixeira Soares
11 | 4127007 | Teixeira Soares
(11 rows)
14. Qual o valor do financiamento anual em cada município?
Solução:
SELECT extract(YEAR FROM data_inicio) AS ano,
geocodigo,
nome,
SUM(valor) AS total,
COUNT(*) AS num_operacoes
FROM municipio,
gleba,
operacao
WHERE operacao.numero = gleba.operacao_numero
AND ST_Contains(municipio.geom, gleba.geom)
GROUP BY ano, geocodigo, nome
ORDER BY ano, geocodigo, nome;
ano | geocodigo | nome | total | num_operacoes
------+-----------+------------------+------------+---------------
2021 | 4117057 | Nova Laranjeiras | 200000.99 | 1
2022 | 4105409 | Chopinzinho | 100000.54 | 1
2022 | 4109401 | Guarapuava | 1460000.00 | 2
2022 | 4117057 | Nova Laranjeiras | 205000.98 | 1
2022 | 4117602 | Palmas | 100000.24 | 2
2023 | 4105409 | Chopinzinho | 110000.45 | 1
2023 | 4117057 | Nova Laranjeiras | 243000.97 | 1
2023 | 4127007 | Teixeira Soares | 1134001.29 | 2
(8 rows)
1.10.6. Criando Tabelas a partir de Consultas
Podemos utilizar os comandos CREATE TABLE
e SELECT
em conjunto para criar tabelas a partir do resultado de consultas.
A sintaxe básica desse comando é a seguinte:
CREATE TABLE nome-tabela AS consulta;
Vamos criar uma nova tabela chamada buffer_gleba
que conterá um buffer negativo de 30 metros das geometrias das glebas:
CREATE TABLE buffer_gleba AS
SELECT gid,
ST_Transform(ST_Buffer(ST_Transform(geom, 100000), -30), 4674)::geometry(multipolygon, 4674) AS geom
FROM gleba;
Saída:
SELECT 11