Introdução à Linguagem SQL e Bancos de Dados
Introdução à Linguagem SQL e Bancos de Dados
BONATO, Antonio Sergio Ferreira. SQL - Structured Query Language. Rio de Janeiro:
FGV, 2024.
Todos os direitos reservados. Textos, vídeos, sons, imagens, gráficos e demais componentes
deste material são protegidos por direitos autorais e outros direitos de propriedade intelectual, de
forma que é proibida a reprodução no todo ou em parte, sem a devida autorização.
APRESENTAÇÃO DA LINGUAGEM
SQL
SQL, ou Structured Query Language, é uma linguagem de
programação utilizada para gerenciar e manipular dados em sistemas de
gerenciamento de banco de dados relacionais (RDBMS). Desde a sua
criação na década de 1970, a SQL se tornou uma ferramenta essencial para
qualquer pessoa envolvida no desenvolvimento ou administração de
bancos de dados.
O desenvolvimento recente da ciência de dados tem sido marcado
pela explosão de dados disponíveis e pela necessidade crescente de extrair
informações significativas desses volumes massivos de informações. Nesse
cenário, a SQL desempenha um papel crucial como uma linguagem
fundamental para manipular e analisar dados estruturados. Embora novas
ferramentas e linguagens tenham surgido para lidar com dados não
estruturados e complexos, a estrutura e a eficiência da SQL continuam
sendo inigualáveis para consultas precisas, transformações de dados e
preparação para análises avançadas. A sua capacidade de realizar operações
complexas de junção, agregação e filtragem em bancos de dados relacionais
e não relacionais não apenas facilita a exploração inicial de dados mas
também garante a integridade e a consistência dos conjuntos de dados,
sendo essencial para a criação de insights confiáveis na ciência de dados
moderna.
Com a SQL, os usuários podem executar uma variedade de
operações em um banco de dados, incluindo inserção, consulta,
atualização e exclusão de dados. A sua sintaxe simples e poderosa permite
que até mesmo iniciantes possam começar a trabalhar com bancos de
dados com relativa facilidade.
Existem diferentes dialetos de SQL, cada um adaptado para um
RDBMS específico, como MySQL, PostgreSQL, SQL Server, Oracle,
entre outros. Embora haja diferenças entre esses dialetos, os conceitos
fundamentais de SQL permanecem consistentes em todos eles.
As principais operações de SQL incluem:
1. consulta de dados (SELECT) – essa é uma das operações mais comuns na SQL, usada
para recuperar dados de uma ou mais tabelas em um banco de dados. Os usuários podem
especificar os critérios de seleção para filtrar e sumarizar os resultados conforme
necessário. O comando SELECT e todas as suas variações são um subconjunto da SQL
denominado DQL 1, ou linguagem de consulta de dados;
2. inserção de dados (INSERT) – essa operação permite adicionar novas linhas de dados a
uma tabela existente em um banco de dados;
3. atualização de dados (UPDATE) – a SQL oferece a capacidade de modificar os dados
existentes em uma tabela com base em critérios específicos;
4. exclusão de dados (DELETE) – essa operação permite remover linhas de dados de uma
tabela com base em determinados critérios;
Em resumo, a SQL é uma ferramenta poderosa e versátil para gerenciar dados em bancos de
dados relacionais. Com uma compreensão básica da sua sintaxe e funcionalidades principais, os
usuários podem realizar uma ampla variedade de tarefas relacionadas a dados de forma eficiente e
eficaz.
1
Data Query Language.
2
Data Manipulation Language.
3
Data Definition Language.
BREVE INTRODUÇÃO AOS BANCOS DE DADOS RELACIONAIS
Bancos de dados relacionais são uma ferramenta fundamental na organização e gestão de
informações estruturadas de maneira lógica e eficiente. Essa tecnologia tem as suas raízes no
desenvolvimento do modelo relacional por Edgar F. Codd, na década de 1970, um marco
importante na história da computação e gestão de dados.
Desde então, sistemas de gerenciamento de banco de dados (SGBD) relacionais como o
Oracle, MySQL, PostgreSQL e Microsoft SQL Server têm sido desenvolvidos e aprimorados
continuamente. Eles se tornaram fundamentais não apenas na informática mas também em áreas
como Economia, Administração, Biologia e muitas outras disciplinas acadêmicas e profissionais.
O modelo relacional propôs uma abordagem inovadora para armazenar dados em tabelas, de
modo que cada tabela representa uma entidade específica, como clientes, produtos ou pedidos.
Além disso, cada uma delas é composta por colunas que definem os tipos de informações que
podem ser armazenadas, como nomes, datas ou valores. Por exemplo, em uma tabela de clientes,
podemos ter colunas para nome, endereço e telefone.
Além da estrutura tabular, os bancos de dados relacionais introduzem o conceito de
relacionamento entre tabelas. Isso permite conectar informações entre diferentes tabelas por meio
de chaves. A chave primária é um campo (ou conjunto de campos) único em cada tabela, que
identifica exclusivamente cada registro. Por exemplo, em uma tabela de clientes, a chave primária
poderia ser um número de identificação único para cada cliente.
Já a chave estrangeira é um campo em uma tabela que estabelece uma relação com a chave
primária de outra tabela. Isso possibilita criar conexões entre tabelas, como associar pedidos a
clientes por meio das suas chaves primárias e estrangeiras correspondentes.
Esses conceitos são essenciais para garantir a integridade e consistência dos dados dentro de
um banco de dados relacional, facilitando operações como consultas complexas e análises
detalhadas, e garantindo que as informações sejam armazenadas de forma organizada e acessível.
SUMÁRIO
MÓDULO I – CONSULTANDO DADOS.............................................................................................. 9
PROFESSOR-AUTOR ....................................................................................................................... 97
MÓDULO I – CONSULTANDO DADOS
Neste módulo, veremos o essencial para a realização de consultas em uma tabela única. Para
isso, precisamos conhecer o comando SELECT, as cláusulas FROM e WHERE, bem como todos
os operadores envolvidos na comparação de resultados para a realização de filtros de consulta.
Veremos os tipos de dados e como ordenar os resultados, como eliminar linhas duplicadas e como
limitar a quantidade de linhas retornada em uma coluna.
Comando SELECT
O comando SELECT é utilizado para a leitura dos dados gravados nas tabelas do banco de
dados. Denominado consulta ou, mais habitualmente na sua versão inglesa query, tem, na sua
forma mais simples, a seguinte sintaxe:
SELECT * FROM tabela
Os comandos SQL podem ser escritos em letras maiúsculas ou minúsculas. A linguagem não
faz diferenciação de caso, isso é, não é case sensitive. Porém, é comum escrevermos as queries como
no exemplo anterior: nomes de campos e tabelas em letras minúsculas, os comandos SQL em letras
maiúsculas. Mas isso é apenas uma convenção.
O comando a seguir seleciona todas as linhas e todas as colunas de uma determinada tabela.
Por exemplo, observe a query apresentada a seguir:
SELECT *
FROM co2_emissions_pc;
Bloco de código 1.
Veja que ela retorna todas as linhas e colunas da tabela chamada co2_emissions_pc, que contém
as emissões de CO2 per capita por país em um determinado ano (veja o dicionário de dados do
banco utilizado como exemplo no Apêndice A).
É possível selecionar apenas algumas colunas da tabela, listando o nome das colunas no
comando SELECT.
SELECT coluna_1, coluna_2, coluna_n FROM tabela
Por exemplo, veja a query seguinte:
Bloco de código 2.
Observe que ela retorna todas as linhas, mas apenas as colunas co2_pc e country da tabela
co2_emissions_pc. Note que a ordem das colunas pode ser especificada em uma ordem diferente da
qual elas se encontram na tabela.
10
Figura 2 – Resultado do bloco de código 2 (apenas as 10 primeiras linhas)
SELECT *
FROM co2_emissions_pc
WHERE country = 'Brazil';
Bloco de código 3.
Bloco de código 4.
O resultado é o seguinte:
11
Figura 4 – Resultado do bloco de código 4 (apenas as 10 primeiras linhas)
Tipos de dados
Um aspecto importante em qualquer linguagem de programação são os tipos de dados que
ela consegue manipular. A gama de tipos é ampla e varia de uma implementação de bancos de dados
para outra implementação. Todavia, existe um conjunto básico de tipos de dados com os quais é
possível realizar quaisquer consultas em um banco de dados.
Os tipos, a sua descrição e como utilizá-los pode ser visto na tabela a seguir.
12
tipo significado como utilizar
timestamp Campo com data e hora. Digite a data e a hora juntas, como se
fosse uma string, utilizando as regras
da data e do tempo: '2023-12-01
23:12:15.123'.
* Às vezes, o editor vai pular linha se a string for muito grande, mas isso não causa problemas. O problema
ocorre quando se pressiona a tecla enter dentro da string.
** Embora a linguagem SQL não diferencie maiúsculas de minúsculas, isso faz diferença dentro de uma string;
se o valor 'Um texTo QualQuer' estiver escrito desta forma no conteúdo de uma coluna, a consulta precisa ser escrita
exatamente deste modo, caso contrário a linha desejada não será encontrada; no bloco de código 4, nenhuma linha
teria sido retornada caso o valor digitado tivesse sido 'brazil'.
Operadores relacionais
Para criarmos uma regra para um filtro, que chamamos de expressão relacional ou condição
lógica, utilizamos um operador relacional para comparar os dois elementos dessa condição, como o
operador = (igual). Esses operadores são semelhantes aos que estamos acostumados a usar na
matemática e tem o mesmo significado, como podemos ver na tabela a seguir:
13
Tabela 2 – Operadores relacionais
operador significado
= igual
> maior
< menor
Já usamos o operador = nos exemplos anteriores, mas podemos usar os outros operadores
para extrair diferentes informações do banco de dados. Por exemplo, se quisermos saber quais os
países que atingiram população superior a 1 bilhão de habitantes e em que ano isso aconteceu,
podemos usar o seguinte bloco de código:
Bloco de código 5.
14
Obs.: a query do bloco de código 5 não garante que os dados venham ordenados por país e
ano; para termos certeza que essa situação é verdadeira, é necessário indicar explicitamente no
comando de consulta que queremos os dados ordenados. Veremos essa cláusula de ordenação da
SQL mais adiante nesta apostila.
Operador IS NULL
Os campos que não contêm nenhum valor são tratados, pelos bancos de dados, como campos
nulos, ou seja, NULL. Não podemos comparar nada a NULL, de modo que os operadores = (igual)
e != (diferente) não funcionam com campos nulos.
Por exemplo, observe os comandos seguintes:
SELECT *
FROM population
WHERE tot_pop = NULL;
Bloco de código 6.
SELECT *
FROM population
WHERE tot_pop != NULL;
Bloco de código 7.
15
Veja que eles não retornam nenhuma linha. Ambas as expressões lógicas da cláusula WHERE
dos blocos de código 6 e 7 são avaliadas como falsas.
Para realizarmos esse tipo de consulta, precisamos utilizar um operador relacional especial
chamado IS NULL. Por exemplo, observe a consulta realizada com o bloco de código 8, cujo
resultado é apresentado na tabela da figura 6:
SELECT *
FROM population
WHERE tot_pop IS NULL;
Bloco de código 8.
SELECT *
FROM population
WHERE tot_pop IS NOT NULL;
Bloco de código 9.
Veja que ela resulta em todos os países e anos, exceto a Santa Sé nos anos em que o valor do
campo tot_pop é nulo, como apresentado na tabela da figura 7:
16
Figura 7 – Resultado do bloco de código 9 (apenas as 10 primeiras linhas)
Operador BETWEEN
Quando queremos fazer o filtro não por um valor, mas por uma faixa de valores, podemos
utilizar o operador BETWEEN.
Por exemplo, se for necessário consultar quais são os países e em quais anos a mortalidade
infantil se encontra no intervalo 600 ±5%, isto é, entre 570 e 630 mortes de crianças entre 0 e 5
anos a cada 1000 nascidos vivos. A consulta é esta:
Obtemos como resultado que Cuba, Irlanda, Islândia, no século XIX, e Barbados, Paquistão
e Ucrânia, no século XX, possuíam a elevada taxa de 60% de mortalidade infantil, como mostra a
tabela da figura a seguir:
17
O operador BETWEEN pode ser utilizado com qualquer tipo de dado: caractere, data e
numérico (inteiro e real). Esse operador também funciona com tipos de dados lógicos, aqueles que
assumem valores TRUE (verdadeiro) e FALSE (falso). Porém não faz sentido utilizá-lo para essa
operação, pois a avaliação da expressão retorna sempre falso, uma vez que não há nada entre TRUE
e FALSE.
Veja, a seguir, um exemplo de uso com campos do tipo data:
Observe que o campo "adj close" está entre aspas duplas. Elas são necessárias por causa do
espaço em branco dentro do nome do campo, que seria interpretado como um erro pela linguagem
SQL se não estivesse entre aspas. As aspas também devem ser utilizadas caso o editor de SQL
reconheça o nome do campo como sendo uma palavra reservada da SQL, como o campo "date",
pois essa é a palavra reservada para indicar o tipo data de calendário.
O resultado da consulta são todas as linhas que estão entre as datas da expressão lógica,
inclusive as próprias datas. Observe a queda do valor em USD das ações da Petrobrás na Nasdaq
neste período devido ao início dos lockdowns decorrentes da pandemia de Covid-19.
18
Operadores lógicos
Os operadores lógicos unem duas expressões relacionais, e, assim como estas, retornam
sempre um valor booleano (verdadeiro ou falso). Os operadores lógicos estão na tabela a seguir.
AND e
OR ou
NOT negação
O operador AND retorna verdadeiro se ambas as expressões lógicas forem verdadeiras. Caso
uma delas seja falsa, ou ambas, a expressão resultante retorna falso. Veja a tabela-verdade do AND:
Tabela 4 – Tabela-verdade do operador AND
Por exemplo, queremos selecionar todos os países que emitiram mais de 20 toneladas de CO2
per capita no ano de 2020:
19
Na figura 10, vemos que Brunei, Kuwait, Catar, três economias baseadas na extração de
petróleo, e Cingapura, foram os maiores emissores de CO2 no ano pesquisado.
Já o operador OR atua de maneira oposta ao AND. Ele retorna falso apenas se ambas as
expressões lógicas forem falsas. Caso contrário, retorna verdadeiro, como pode ser visto na tabela-
verdade a seguir:
OR VERDADEIRO FALSO
VERDADEIRO
verdadeiro verdadeiro
FALSO
verdadeiro falso
Suponha que precisemos selecionar os países das Américas segundo a classificação de regiões
do Banco Mundial:
20
O resultado é o seguinte:
Por sua vez, o operador NOT inverte o resultado da expressão lógica. Se ela for verdadeira,
esse operador a torna false, e vice-versa. Veja a tabela-verdade:
VERDADEIRO FALSO
NOT
falso verdadeiro
Se quisermos, agora, todos os países do mundo com exceção dos países das Américas, segundo
a mesma classificação do Banco Mundial, podemos fazer da seguinte forma:
Fazemos a mesma query, mas adicionamos NOT antes da expressão lógica. Observe que é
necessário colocar toda a expressão lógica entre parênteses, caso contrário apenas a parte wb_regions
= 'North America' seria negada e os países da América Latina e do Caribe seriam incluídos na
consulta.
21
Figura 12 – Resultado do bloco de código 14 (10 primeiras linhas)
Uma consulta SQL pode apresentar não apenas duas expressões lógicas mas uma série delas
conectadas por operadores lógicos. Nesse caso, a expressão é interpretada da esquerda para direita,
a não ser que o uso de parênteses altere essa ordem de interpretação, pois o que está entre parênteses
tem precedência. O bloco de código 14, apresentado anteriormente, é um exemplo disso.
Outro exemplo seria retomarmos a consulta do bloco de código 10, aquela sobre mortalidade
infantil, mas agora reduzindo o intervalo para 60 ±5%, isto é, entre 57 e 63 mortes de crianças
entre 0 e 5 anos a cada 1000 nascidos vivos e restringindo os países a China e Brasil. Queremos,
então, saber em quais anos esses países estiveram nessa faixa de mortalidade infantil.
Observe, como exemplo, o bloco de código a seguir:
Se fizermos essa consulta, iremos obter o resultado a seguir, que está claramente incorreto,
pois tot_deaths é 417, totalmente fora da faixa que pesquisamos. Veja:
22
Figura 13 – Resultado do bloco de código 15 (13 últimas linhas)
Então, o que pode ter acontecido de errado? É simples. Como a expressão é analisada da
esquerda para a direita, todas as linhas nas quais tot_deaths estava entre 57 e 63, e o país era China,
apareceram dentro do que esperávamos. Mas a outra expressão lógica foi analisada em separado, e
todas as linhas cujo país era Brasil apareceram também, mesmo com tot_deaths estando fora da
faixa.
Mas não é isso que queremos, e sim as linhas em que o país é Brasil ou China e tot_deaths
está entre 57 e 63. Para isso, temos que usar parênteses para mudar a ordem de avaliação e priorizar
a cláusula country = 'China' OR country = 'Brazil' e, para as linhas onde ela for verdadeira, trazer
somente as que estiverem dentro da faixa de mortes procurada, como ilustrado a seguir:
23
O resultado, então, passa a ser:
Desse modo, vemos que a China atingiu o valor de 60 ±5% mortes de crianças de 0 e 5 anos
a cada 1000 nascidos vivos 10 anos antes do Brasil.
Operador IN
O operador IN equivale ao operador ∈, utilizado para denotar que um elemento pertence a
um conjunto.
Sendo assim, ele pode ser utilizado para perguntar se o valor de uma coluna da tabela está em
uma lista de elementos. Por exemplo, a consulta do bloco 16 poderia ser escrita assim:
O resultado seria o mesmo da figura 14, mas dessa vez não foi necessário o uso de parênteses
para juntar as expressões lógicas, apenas para descrever a lista de países. Desse modo, o código SQL
fica muito mais legível, principalmente quando temos muitas condições lógicas na cláusula
WHERE.
Outro uso comum do operador IN é para substituir uma sequência de OR. Por exemplo, se
quisermos saber a emissão de CO2 per capita no ano de 2022 nos países do G7 podemos fazer assim:
24
SELECT country, co2_pc
FROM co2_emissions_pc
WHERE ref_year = 2022
AND (country = 'Canada'
OR country = 'France'
OR country = 'Germany'
OR country = 'Italy'
OR country = 'Japan'
OR country = 'UK'
OR country = 'USA');
No entanto, isso pode-se tornar uma tarefa tediosa se a lista for muito grande dada a
repetição de country e de OR. Nesse caso, podemos fazer da seguinte forma:
25
Operador LIKE
O operador LIKE procura por ocorrências de cadeias de caracteres dentro de campos do tipo
caractere (string). Associado ao operador LIKE, utilizamos o símbolo, também chamado token, %,
para indicar se queremos que a cadeia de caracteres seja procurada no início, no final ou em qualquer
posição do campo caractere-alvo.
Veja os exemplos:
a) Busca pela cadeia de caracteres 'Congo%' no início do nome do país na tabela de
fertilidade, que contém a média de filhos por mulher em um determinado ano e país:
Queremos saber a taxa de fertilidade nos dois Congos em 2021. O resultado é o seguinte:
26
Temos quatro países no mundo com Guiné no nome:
Observe que, ao contrário dos outros operadores, o LIKE é case insensitive, ou seja, não faz
diferença de letras maiúsculas e minúsculas. Por exemplo, na consulta do bloco de código 20, as
mesmas linhas seriam retornadas se tivéssemos usado 'congo%' em vez de 'Congo%'.
Dica de performance: evite utilizar o operador LIKE quando puder substituí-lo por um OR
ou um IN, pois além da busca linha a linha que é feita na tabela também é feita uma busca em cada
string para encontrar a sequência de caracteres procurada. Claro que para tabelas pequenas não há
impacto, mas quando existem milhões de linhas envolvidas, e a busca é repetitiva, o custo
computacional se torna proibitivo.
27
Na verdade, vale a pena usar o LIKE quando não conhecemos bem os valores do campo de
busca, como no exemplo que procuramos países terminados em '%land'. Seria bastante trabalhoso
escrever todos eles em uma lista para usar o IN e teríamos que sabê-los a priori. Porém as buscas
solicitadas nos exercícios a seguir são mais fáceis e efetivas usando o operador IN, como você irá
perceber ao fazê-los.
Ordenando dados
Como vimos na observação do bloco de código 5, o comando SELECT não garante a ordem
em que os dados são exibidos, a não ser que declaremos isso explicitamente por meio da cláusula
ORDER BY.
O ORDER BY ordena os dados em ordem crescente: números do menor para o maior,
caracteres em ordem alfabética do A para o Z, datas da mais antiga para a mais nova, booleano do
falso para o verdadeiro.
Retomando a consulta do bloco 5, podemos querer saber quais os países menos populosos
em um determinado ano, por exemplo, no ano 2000.
Vemos que o Vaticano é o país menos populoso, seguido de ilhas da Oceania, como Tuvalu,
Nauru e Palau. Mas dessa vez sabemos com certeza, pois o ORDER BY garante que os dados vem
ordenados em ordem crescente.
28
Figura 19 – Resultado do bloco de código 23 (10 primeiras linhas)
Podemos ainda querer saber os países mais populosos. A cláusula DESC inverte a ordem,
ordenando os números do maior para o menor, as strings de Z para A, as datas da mais nova para a
mais antiga e os booleanos do verdadeiro para o falso:
29
Retomemos agora a pergunta que originou a query do bloco 5: Quais os países que atingiram
população superior a 1 bilhão de habitantes e em que ano isso aconteceu?
Fazendo uma rolagem da tabela resultante, vemos que a China atingiu essa população em
1982, e a Índia em 1998. Note que agora utilizamos dois campos na cláusula ORDER BY.
Podemos usar quantos precisarmos, separando um do outro por vírgulas.
Figura 21 – Resultado do bloco de código 25 (excerto das 5 primeiras linhas da China e das 5
primeiras linhas da Índia)
Obs.: essa consulta ainda não é a ideal, pois temos que rolar a tabela para procurar as
informações, mas pelo menos estamos certos de que o primeiro ano que aparece é realmente o
primeiro; mais adiante, nesta apostila, veremos como filtrar somente a primeira linha de cada país.
Podemos utilizar a cláusula DESC para inverter a ordem de apenas um campo:
30
O resultado agora traz primeiro a Índia e depois a China.
Figura 22 – Resultado do bloco de código 26 (excerto das 5 primeiras linhas da Índia e das 5
primeiras linhas da China)
...
Se quisermos inverter a ordem dos dois campos, temos de utilizar DESC em cada um deles.
Figura 23 – Resultado do bloco de código 27 (excerto das 5 primeiras linhas da Índia e das 5
primeiras linhas da China)
...
31
Limitando os resultados
Nos exemplos e exercícios anteriores, quase sempre mostramos as 10 primeiras linhas ou as
cinco últimas linhas dos resultados obtidos. Mas isso foi feito usando-se o editor de imagens com a
intenção de mostrar os resultados nesta apostila, pois a consulta retornou milhares de linhas. A
tabela population, por exemplo, tem 59.297 linhas.
Entretanto, é possível limitar o número de linhas retornadas na consulta SQL por meio da
cláusula LIMIT. Por exemplo, se quisermos trazer apenas os 20 países mais populosos no ano 2000,
fazemos a seguinte query:
32
O LIMIT também é usado quando estamos explorando os dados e queremos dar uma olhada
no conteúdo de uma tabela muito grande. Nesse caso, para economizar tempo, limitamos o
resultado às cinco primeiras linhas, por exemplo:
SELECT *
FROM petrobras
LIMIT 5;
O resultado é:
Vemos que há duas para a África, Norte e Subsaariana; duas para as Américas, do Norte e do
Sul; duas para a Ásia, Ocidental e Oriental/Pacífico e duas para a Europa, Oeste e Leste.
33
Figura 26 – Resultado do bloco de código 30 (todas as linhas)
Desse modo, vemos que há sete classificações: três para a Ásia – Leste e Pacífico, Central e
Oriente Médio; mas a da Ásia Central também engloba a Europa, e a do Oriente Médio, o Norte
da África. As Américas também são divididas de maneira diferente, entre América do Norte e
América Latina e Caribe. Vemos também uma linha nula, que, se verificarmos, refere-se ao
Vaticano (Holy See), que o Banco Mundial não categorizou.
34
A cláusula DISTINCT se aplica à linha inteira retornada pela query, não é possível aplicá-la
a uma coluna específica. Veja o resultado da consulta que traz as combinações distintas entre
eight_regions e wb_regions na tabela country:
O resultado são 13 linhas, nas quais se pode notar diferentes combinações de eight_regions e
wb_regions.
Há, por exemplo, repetições no campo eight_regions que correspondem a diferentes valores
do campo wb_regions. Isso significa que há países classificados como asia_west pelo critério de
eight_regions, mas que podem pertencer a três diferentes regiões segundo o critério wb_regions.
Outro exemplo é o México, que pelo critério eight_regions pertence à América do Norte, mas pelo
critério wb_regions está na América Latina e Caribe.
35
MÓDULO II – JUNTANDO DADOS
Como vimos na Introdução, os bancos de dados relacionais são compostos de tabelas, e essas
tabelas se relacionam entre si por meio de chaves primárias e estrangeiras. Portanto, é necessário
haver um meio de juntá-las para extrair informações mais relevantes dos bancos de dados.
Uma consulta com junção une duas ou mais tabelas no sentido das colunas, trazendo as
colunas selecionadas de uma e da outra tabela, colocando-as lado a lado no resultado.
Ao fazermos junções, é necessário ter em mente a natureza das chaves dos bancos de dados
para que a junção seja correta:
as chaves primárias são identificadores unívocos de cada linha da tabela; podem ser
compostas de um ou mais campos;
as chaves estrangeiras de uma tabela estabelecem uma relação com a chave primária de
outra tabela; neste caso costumamos chamar, à luz de um determinado relacionamento, a
tabela que contém a chave primária de "tabela-pai" e a que contém a chave estrangeira de
"tabela-filha";
os relacionamentos devem ser sempre de 1 para n, isto é, uma linha na tabela-pai tem zero,
uma ou várias linhas relacionadas na tabela-filha; mas sob o ponto de vista da tabela-filha,
cada linha se relaciona com apenas uma linha da tabela-pai.
Tudo isso é importante para que não ocorra um produto cartesiano entre as linhas das tabelas
pai e filho. Pois se ao executar uma consulta, o banco encontra duas linhas na tabela-pai relacionadas
com uma mesma linha na tabela-filha, ele duplica o resultado, gerando uma linha que junta as
chaves de uma das linhas da tabela-pai com as chaves estrangeiras correspondentes na tabela-filha.
Depois gera outras linhas no resultado, juntando as chaves dessa segunda linha da tabela-pai com
as suas correspondentes na tabela-filha.
Vejamos cada tipo de junção na unidade a seguir.
UNIDADE 1 – JUNÇÕES INTERNAS E JUNÇÕES NATURAIS
As junções internas e as naturais são os tipos de junção mais utilizados na SQL. Com elas é
possível unir as colunas de duas ou mais tabelas e realizar consultas com maior complexidade e
riqueza de detalhes.
Junção interna
Suponha que nós queiramos saber os 10 países com maior PIB em 2019 e também a taxa de
natalidade desses 10 países. Fizemos isso nas questões da unidade 3 do módulo 1, mas ali nós
fizemos cinco consultas em separado e depois juntamos os dados manualmente. Agora queremos
trazer os resultados em uma única consulta. Vamos ver o exemplo e depois detalhamos a sintaxe.
SELECT [Link],
gp.gdp_pc,
f.mean_babies
FROM gdp_pc AS gp
INNER JOIN fertility AS f ON [Link] = [Link]
AND gp.ref_year = f.ref_year
WHERE gp.ref_year = 2019
ORDER BY gp.gdp_pc DESC
LIMIT 10;
As 10 linhas resultantes trazem os 10 países com maior PIB per capita, mas não os 10 países
com menor taxa de natalidade, em vez disso, mostram a taxa de natalidade dos referidos países.
Ocorre que esses países estão entre os de menor taxa de natalidade, que aliás está abaixo da taxa de
reposição, que é de 2.1 filhos por mulher (Ministério da Saúde, 2000).
38
No entanto, vamos dissecar o bloco de código 33. Veja que agora mencionamos duas tabelas,
gdp_pc e fertility. Para juntá-las, usamos a cláusula INNER JOIN, que é a junção interna, e
estabelecemos que os campos de união são country e ref_year em ambas as tabelas. Isso porque esses
campos são chaves primárias de ambas as tabelas. A chave primária não é só country, pois cada país
se repete várias vezes, e também não é ref_year, que também é uma informação que se repete várias
vezes. Mas as combinações de country e ref_year são únicas, isto é, para cada país existe apenas uma
linha que se refere a um determinado ano.
Veja que não soubemos isso de antemão a partir do dicionário de dados; somente após termos
analisado as tabelas, podemos chegar a essa conclusão. Aliás, a maioria das tabelas do nosso banco
de dados de exemplos tem country e ref_year como chave primária. Esses dois campos também
servem como chave estrangeira, pois eles são equivalentes (mas não totalmente iguais) em quase
todas as tabelas do banco de exemplos.
E como funciona o INNER JOIN? Ele une as duas tabelas de acordo com valores comuns a
ambas. Por exemplo, a linha Brazil-1970 da tabela gdp_pc vai se unir à linha Brazil-1970 da tabela
fertility. Se houver algum par country-ref_year na tabela gdp_pc que não exista na tabela fertility, a
linha que contém esse par será descartada. E a recíproca é verdadeira. Uma linha com um par
country-ref_year na tabela fertility sem equivalente na tabela gdp_pc será igualmente descartada.
Desse modo, podemos enxergar o INNER JOIN como uma operação de intersecção entre
dois conjuntos, ilustrada pela figura 29.
Note agora que estamos usando apelidos para as tabelas, ou table aliases. Esses apelidos foram
estabelecidos quando escrevemos gdp_pc AS pc e fertility AS f – caso você omita o AS nos apelidos
de tabelas, não tem problema, ele é opcional. E para que usamos os apelidos? Para explicitar, no
comando SQL, de onde vem cada campo. Por exemplo, quando estamos selecionando quais
colunas, escrevemos [Link], pc.gdp_pc, f.mean_babies. Com isso dizemos que o campo country
vem da tabela gdp_pc. Se não informássemos o apelido, a query não iria rodar porque existe um
campo com o mesmo nome na tabela fertility o que ocasionaria um erro de ambiguidade. Já nas
39
outras duas colunas, o apelido é opcional, pois os nomes só existem em uma ou outra tabela. Mas
usamos os apelidos para melhorar a legibilidade do comando. Imagine uma consulta que traga 50
colunas espalhadas em cinco tabelas que estão sendo juntadas. Fica difícil para quem está lendo a
query (mesmo para quem a escreveu) entender de onde está vindo o quê. O apelido ajuda com isso.
O uso de apelidos é feito em todas as cláusulas da consulta, não só na seleção das colunas.
Usamos também no INNER JOIN, no WHERE e até no ORDER BY.
Finalmente, na cláusula ON do INNER JOIN, especificamos exatamente quais os critérios
para fazer a junção: [Link] = [Link] AND gp.ref_year = f.ref_year. Por fim, arrematando a
nossa consulta, utilizamos a cláusula WHERE para filtrar pelo ano de 2019, o ORDER BY para
ordenar o PIB em ordem crescente, porque queremos os maiores, e o LIMIT para trazer apenas 10.
Para continuar a resolver as consultas da unidade 3 do módulo 1, vamos juntar a tabela
woman_years_at_school para listar o tempo médio na escola das mulheres de 25 anos em 2019. A
query agora fica assim:
SELECT [Link],
gp.gdp_pc,
f.mean_babies,
ws.mean_years
FROM gdp_pc AS gp
JOIN fertility AS f ON [Link] = [Link]
AND gp.ref_year = f.ref_year
JOIN women_years_at_school AS ws ON [Link] = [Link]
AND gp.ref_year = ws.ref_year
WHERE gp.ref_year = 2019
ORDER BY gp.gdp_pc DESC
LIMIT 10;
Só que não obtemos nenhuma linha como resultado. Isso acontece porque não há dados na
tabela woman_years_at_school além do ano de 2009. Como não há match das chaves das três tabelas
envolvidas, nenhuma linha é retornada.
40
Figura 30 – Resultado do bloco de código 34 (todas as linhas)
O uso da palavra INNER é opcional. Pode-se utilizar apenas JOIN que o banco de dados
entende que é um INNER JOIN.
Por exemplo, queremos trazer os cinco países com as maiores emissões de CO2 em 1970 e as
suas respectivas classificações geográficas segundo o critério do Banco Mundial.
SELECT [Link],
c.wb_regions,
co2.co2_pc
FROM co2_emissions_pc co2
JOIN country c ON [Link] = [Link]
WHERE co2.ref_year = 1970
ORDER BY co2.co2_pc DESC
LIMIT 5;
Note que também não utilizamos o AS para definir os apelidos das tabelas. O resultado é:
Existe um outro formato para fazer os INNER JOINS, no qual não se utiliza nem a palavra-
chave JOIN, apenas listam-se as tabelas e colocam-se os critérios de junção na cláusula WHERE.
41
Por exemplo, queremos saber os cinco países mais populosos da Europa no ano 2000. A query é:
SELECT [Link],
p.tot_pop
FROM population p,
country c
WHERE [Link] = [Link]
AND p.ref_year = 2000
AND c.four_regions = 'europe'
ORDER BY p.tot_pop DESC
LIMIT 5;
O resultado é:
O problema desse tipo de construção é que não distinguimos facilmente os critérios de junção
dos critérios de filtragem, pois todos ficam na cláusula WHERE. Portanto, prefira a construção
com a palavra JOIN.
Junção natural
A junção natural, ou NATURAL JOIN, é similar ao INNER JOIN, mas sem especificar
quais as colunas que irão fazer o match. Então, para que a junção ocorra, é necessário que os campos
de junção tenham o mesmo nome e tipo em ambas as tabelas; além disso, é importante que nenhum
campo que esteja fora da lista de campos de junção tenha o mesmo nome em ambas as tabelas, pois,
neste caso, ele também será usado para fazer a junção, e o resultado estará incorreto.
42
Suponha que queiramos saber os cinco países de maior taxa de mortalidade infantil
relacionada com a renda média diária per capita das famílias em 2019. A query pode ser:
SELECT [Link],
cm.tot_deaths,
inc.mean_usd
FROM child_mortality AS cm
NATURAL JOIN avg_income AS inc
WHERE cm.ref_year = 2019
ORDER BY cm.tot_deaths
LIMIT 5;
43
Em todos os casos, os campos da tabela não correspondida assumem valores nulos nos
resultados.
SELECT f.ref_year,
f.mean_babies,
wy.mean_years
FROM fertility f
INNER JOIN women_years_at_school wy ON [Link] = [Link]
AND f.ref_year = wy.ref_year
WHERE [Link] = 'Brazil'
AND f.ref_year BETWEEN 2001 AND 2020
ORDER BY f.ref_year;
44
O resultado dessa consulta traz apenas o período de 2001 a 2009, pois não há dados
posteriores a 2009 na tabela woman_years_at_school, como já vimos. Também se nota uma clara
correlação negativa entre as duas colunas.
Agora, se queremos trazer todas as linhas da tabela fertility que atenderem ao filtro de data da
consulta, fazemos um LEFT JOIN em vez de um INNER JOIN:
SELECT f.ref_year,
f.mean_babies,
wy.mean_years
FROM fertility f
LEFT JOIN women_years_at_school wy ON [Link] = [Link]
AND f.ref_year = wy.ref_year
WHERE [Link] = 'Brazil'
AND f.ref_year BETWEEN 2001 AND 2020
ORDER BY f.ref_year;
Agora, além das linhas de 2001 a 2009, que vêm com os valores de mean_years preenchidos,
temos também as linhas de 2010 a 2020, que vem com os valores de mean_years nulos.
45
Figura 36 – Resultado do bloco de código 39 (todas as linhas)
46
Suponha que queiramos investigar a emissão de CO2 per capita e o PIB per capita do Brasil
na última década. Utilizando-se INNER JOIN, a consulta é:
SELECT gp.ref_year,
cep.co2_pc,
gp.gdp_pc
FROM co2_emissions_pc cep
INNER JOIN gdp_pc gp ON [Link] = [Link]
AND gp.ref_year = cep.ref_year
WHERE [Link] = 'Brazil'
AND gp.ref_year BETWEEN 2014 AND 2023
ORDER BY gp.ref_year;
Nota-se que o ano de 2023 não aparece nos resultados. Se avaliarmos as tabelas, notamos que
o PIB vai até 2100, mas as emissões de CO2 vão apenas até 2022.
Se refizermos a consulta utilizando um RIGHT JOIN, pois é a da tabela gdp_pc, que vem
em segundo na lista da junção, queremos trazer todas as linhas.
SELECT gp.ref_year,
cep.co2_pc,
gp.gdp_pc
FROM co2_emissions_pc cep
RIGHT JOIN gdp_pc gp ON [Link] = [Link]
AND gp.ref_year = cep.ref_year
WHERE [Link] = 'Brazil'
47
AND gp.ref_year BETWEEN 2014 AND 2023
ORDER BY gp.ref_year;
Desse modo, a linha referente a 2023 aparece, trazendo o valor do PIB per capita e nulo no
lugar do valor do CO2 per capita.
Figura 38 – Diagrama de Venn representando um FULL JOIN. A área hachurada mostra que
todas as linhas, em ambas as tabelas, serão recuperadas na consulta. As que não tiverem
equivalência terão os seus valores preenchidos com NULL, em um lado ou no outro
48
Queremos investigar a população e a taxa de natalidade de algumas ilhas da Oceania: Fiji,
Guam, Nauru, Palau e Polinésia Francesa. Vamos começar fazendo um INNER JOIN:
SELECT [Link],
p.tot_pop,
f.mean_babies
FROM fertility f
INNER JOIN population p ON [Link] = [Link]
AND p.ref_year = f.ref_year
WHERE [Link] IN ('Fiji', 'Nauru', 'Palau',
'French Polynesia', 'Guam')
AND f.ref_year = 2018;
O resultado é o único país em comum, nas duas tabelas, entre os listados na cláusula
WHERE.
Vamos agora usar um LEFT JOIN para tentar trazer os dados existentes dos cinco países-
alvo:
SELECT [Link],
p.tot_pop,
f.mean_babies
FROM fertility f
LEFT JOIN population p ON [Link] = [Link]
AND p.ref_year = f.ref_year
WHERE [Link] IN ('Fiji', 'Nauru', 'Palau',
'French Polynesia', 'Guam')
AND f.ref_year = 2018;
49
Agora temos três países: Fiji, Guam e Polinésia Francesa. Eles não apareceram na consulta
anterior porque não existem dados referentes a eles na tabela population, pelo menos no ano de
2018. Mas existem na tabela fertility.
SELECT [Link],
p.tot_pop,
f.mean_babies
FROM fertility f
RIGHT JOIN population p ON [Link] = [Link]
AND p.ref_year = f.ref_year
WHERE [Link] IN ('Fiji', 'Nauru', 'Palau',
'French Polynesia', 'Guam')
AND p.ref_year = 2018;
Nessa consulta, aparecem Nauru e Palau, que não haviam aparecido na consulta anterior.
Isso acontece porque eles estão na tabela population, mas não na tabela fertility.
50
Vamos então fazer um FULL JOIN para que os cinco países apareçam na nossa consulta com
os dados existentes para cada um:
SELECT [Link],
[Link],
p.tot_pop,
f.mean_babies
FROM fertility f
FULL JOIN population p ON [Link] = [Link]
AND p.ref_year = f.ref_year
WHERE ([Link] IN ('Fiji', 'Nauru', 'Palau',
'French Polynesia', 'Guam')
OR [Link] IN ('Fiji', 'Nauru', 'Palau',
'French Polynesia', 'Guam') )
AND (f.ref_year = 2018
OR p.ref_year = 2018);
Pronto. Todos os países aparecem, como vemos na figura 42. Mas note que mudar a junção
para FULL JOIN não foi a única alteração que fizemos. Precisamos também incluir mais uma
coluna country no SELECT, pois temos que usar os apelidos das tabelas qualificando os campos
com nomes iguais para evitar a ambiguidade. No entanto, Guam e Polinésia Francesa não existem
na tabela population, então não tem como eles aparecerem na query se fazemos referência a [Link].
Por outro lado, Nauru e Palau não existem em fertility, então eles não são retornados em [Link].
Além disso, alteramos também a cláusula WHERE, consultando a lista de países nas duas
tabelas e unindo o resultado por meio de um OR. O mesmo foi feito para o ano de 2018.
51
Note que esses ajustes nas consultas vieram sendo feitos conforme mudávamos os JOINs.
Suponha duas tabelas, com apelidos A e B. No INNER JOIN, tanto faz o apelido da tabela que
usamos, o da tabela A ou o da tabela B, pois os valores retornados são valores comuns entre as duas
tabelas, eles aparecem dos dois lados. Mas sempre privilegiamos a tabela que vai retornar todos os
valores nos casos de OUTER JOINs. No caso do LEFT JOIN, usamos o apelido da tabela A,
supondo que ela seja listada primeiro na junção, ou seja, é a tabela que está no FROM. Se é um
RIGHT JOIN, usamos o apelido da tabela B. E, se for um FULL JOIN, temos que usar o apelido
das duas, fazendo a query de modo a listar os campos-chave das duas no SELECT e adequando o
filtro no WHERE usando OR para consultar os valores nas duas tabelas.
As colunas aparecem no resultado com os seus nomes novos. Aliás, não apenas nos resultados,
mas já podemos utilizar os novos nomes nas cláusulas seguintes da consulta, como fizemos no
WHERE e no ORDER BY.
52
Figura 43 – Resultado do bloco de código 46 (todas as linhas)
Note que o AS, que é opcional no apelido de tabelas, é obrigatório no apelido de colunas. Se
quisermos dar um nome composto para a coluna, separado por um espaço, é necessário utilizar
aspas. Caso contrário, estas são dispensáveis. No entanto, recomendamos que não se utilize nomes
separados por espaços e nem caracteres acentuados no nome das colunas. Às vezes, pode dar
problema de interpretação de codificação de caracteres 4.
Unindo dados
As operações UNION e UNION ALL têm por objetivo juntar linhas de duas ou mais
tabelas. Por exemplo, temos duas tabelas de tempo médio na escola, uma para homens e outra para
mulheres. Podemos juntar as duas tabelas para realizar, posteriormente, consultas nelas:
4
Veja mais sobre codificação de caracteres em [Link]
53
O resultado é:
Veja que criamos uma nova coluna, com um valor constante: male para os homens, e female
para mulheres. E batizamos a coluna de gender por meio de um apelido.
54
Figura 45 – Resultado do bloco de código 48 (10 primeiras linhas)
O resultado é:
A diferença entre o UNION e o UNION ALL é que o UNION faz um DISTINC das linhas
iguais. O UNION ALL mantém as linhas repetidas.
55
Veja o resultado da query com UNION:
56
O resultado são 80 linhas referentes ao Brasil, de 1970 a 2009, com cada ano aparecendo
duas vezes:
Figura 48 – Resultado do bloco de código 51 (5 primeiras linhas e mais 5 linhas quando estas
começam a se repetir: linha 41 a 45)
Subtraindo dados
Outra operação de conjuntos que podemos realizar sobre as linhas de duas tabelas é a
diferença, por meio do operador EXCEPT. Com ele, conseguimos descobrir linhas que existem
em uma determinada tabela mas não existem em outras; além disso, as operações de diferença de
conjuntos trazem os elementos que estão em um conjunto e não estão em outro. Assim como a
operação de diferença entre conjuntos, o EXCEPT não é transitivo, ou seja, a consulta "tabela alfa
EXCEPT tabela beta" traz resultados diferentes da consulta "tabela beta EXCEPT tabela alfa". A
não ser que as tabelas sejam iguais, situação em que a consulta com EXCEPT irá retornar zero
linhas em ambos os casos.
Queremos, por exemplo, saber todos os países que estão na tabela population e que não estão
na tabela fertility. Nesse caso, a consulta deverá ser feita assim:
SELECT [Link]
FROM population p
EXCEPT
SELECT [Link]
FROM fertility f;
57
O resultado é a lista de países da figura 49:
Por outro lado, se quisermos saber todos os países que estão na tabela fertility e que não estão
na tabela population, fazemos a consulta da seguinte forma:
SELECT [Link]
FROM fertility f
EXCEPT
SELECT [Link]
FROM population p;
58
Uma observação importante é que o EXCEPT compara as linhas da tabela. Então, para fazer
o EXCEPT, é necessário que as colunas dos dois SELECTs tenham o mesmo nome e o mesmo
tipo. Se utilizarmos várias colunas, a comparação será feita a partir da combinação dos valores de
todas as colunas. Dessa forma, se compararmos country e ref_year das tabelas population e fertility, e
se houver uma combinação de país e ano que existe em uma, mas não na outra, essa linha irá
aparecer no resultado.
Intersecção de dados
A última operação de conjuntos que podemos fazer sobre as linhas de duas tabelas é a
intersecção. Nesse caso, o operador é o INTERSECT, que traz as linhas em comum de duas
tabelas. Muito parecido com o INNER JOIN, exceto pelo fato de que atua sobre as linhas, e não
sobre as colunas, como as junções.
Assim como no EXCEPT, é necessário que as colunas dos dois SELECTs tenham as mesmas
colunas –mesmo nome e mesmo tipo –; a comparação é feita a partir da combinação dos valores de
todas as colunas. Entretanto, ao contrário do EXCEPT, tanto faz a ordem das tabelas. O resultado
será o mesmo, como acontece nas operações de intersecção de conjuntos.
Verificando agora quais são os países que estão nas tabelas population e fertility, fazemos:
SELECT [Link]
FROM population p
INTERSECT
SELECT [Link]
FROM fertility f;
59
60
MÓDULO III – AGRUPANDO OS DADOS
Neste módulo, iremos explorar as capacidades de consulta da linguagem SQL em todo o seu
potencial. Nele veremos como realizar operações matemáticas entre colunas e utilizar funções
sumarizadoras para fazer uma estatística descritiva das colunas.
Aprenderemos a agrupar os dados para sermos capazes de aplicar essas funções sumarizadoras
de acordo com diferentes critérios.
Veremos também todo o poder das subqueries, com as quais conseguimos relacionar
dinamicamente o resultado de duas ou mais consultas.
Operadores aritméticos
Os operadores aritméticos comuns da matemática podem ser usados na cláusula SELECT.
Eles estão listados na tabela a seguir:
Tabela 7 – Operadores aritméticos
operador significado
+ soma
- subtração
* multiplicação
/ divisão
Utilizando esses operadores, podemos realizar operações matemáticas entre valores literais (1
ou 0.75), entre colunas e entre valores literais e colunas.
Por exemplo, queremos saber, em termos percentuais, a taxa de mortalidade infantil do Brasil,
de 1910 a 2020, de 10 em 10 anos. Podemos fazer:
SELECT cm.ref_year,
cm.tot_deaths,
100 * cm.tot_deaths / 1000 AS percent_deaths
FROM child_mortality cm
WHERE cm.ref_year IN (1910, 1920, 1930, 1940,
1950, 1960, 1970, 1980,
1990, 2000, 2010, 2020)
AND [Link] = 'Brazil'
ORDER BY cm.ref_year;
Notamos, no resultado da figura 52, que a taxa de mortalidade infantil caiu de 41% dos
nascidos vivos em 1910 para 1,35% em 2020.
62
Figura 52 – Resultado do bloco de código 55 (todas as linhas)
Podemos também utilizar colunas de diferentes tabelas para fazer o cálculo. Vamos estimar o
PIB total dos 10 países mais ricos em trilhões de USD, usando para isso o PIB per capita da tabela
gdp_pc multiplicado pela população total do país da tabela population. Vamos fazer essa estimativa
para o ano de 2022:
SELECT [Link],
gp.gdp_pc,
p.tot_pop,
(gp.gdp_pc * p.tot_pop)/1E12 AS gdp_usd_tri
FROM gdp_pc gp
JOIN population p ON [Link] = [Link]
AND gp.ref_year = p.ref_year
WHERE gp.ref_year = 2022
ORDER BY gdp_usd_tri DESC
LIMIT 10;
Veja que, em termos de PIB per capita, a ordem dos países seria diferente. Os três primeiros
seriam EUA, Alemanha e Reino Unido, e não China, EUA e Índia. Mas o tamanho da população
fez bastante diferença. A Índia é o segundo colocado de acordo com o PIB total, mas seria o décimo
pelo critério PIB per capita. Os dados estão na figura 53.
63
Figura 53 – Resultado do bloco de código 56 (todas as linhas)
Também é possível utilizar uma operação aritmética na cláusula WHERE, desde que essa
operação seja comparada com um resultado e, dessa forma, retorne um valor lógico. Por exemplo,
queremos saber as emissões de CO2 per capita de Portugal em ano de Jogos Olímpicos de Verão,
de 1972 (Munique) até 2016 (Rio de Janeiro):
SELECT cep.ref_year,
cep.co2_pc
FROM co2_emissions_pc cep
WHERE cep.ref_year BETWEEN 1972 AND 2016
AND cep.ref_year % 4 = 0
AND [Link] = 'Portugal'
ORDER BY cep.ref_year;
Nós pegamos os anos cujo resto da divisão por quatro é igual a zero, expressão que retorna
verdadeiro ou falso, como condição de filtro.
64
As emissões aumentam até 2008 e depois caem abruptamente, mostrando que não há
correlação entre Jogos Olímpicos e emissões de CO2, lembrando que Portugal nunca sediou uma
olimpíada de verão.
Sumarizando dados
Uma ferramenta importante para a compreensão de uma base de dados é a estatística
descritiva. Com ela, por meio das medidas de posição e de dispersão, podemos sumarizar cada uma
das colunas numéricas da base de dados.
A linguagem SQL possui algumas funções de sumarização 5 de modo nativo, como a soma e
a média. Porém, não possui, nativamente, funções para medidas de dispersão, que podem ser
calculadas ou fornecidas pelo fabricante do banco de dados que estamos usando. Mas, nesse caso, é
necessário consultar a documentação elaborada pelo fabricante e ter em mente que a nossa consulta
pode não rodar em outro banco de dados, uma vez que estamos usando uma função não padrão.
A lista das funções de sumarização padrão está na tabela 8.
operador significado
5
Alguns autores se referem às funções de sumarização como funções de agregação.
65
SELECT AVG(gp.gdp_pc) AS avg_gdp_pc
FROM gdp_pc gp
JOIN country c ON [Link] = [Link]
WHERE gp.ref_year = 2020
AND c.four_regions = 'americas';
O valor é 15.199,12 USD, na média, para cada habitante do continente americano no ano
de 2020.
Podemos também aplicar todas as funções-sumário em uma tabela. Por exemplo, calcular,
em relação à renda per capita média dos habitantes da Europa, a soma, a média, o valor máximo, o
valor mínimo e a contagem de países no ano de 2020:
Veja que é bastante comum usarmos apelidos para as novas colunas que estamos criando com
a função de sumarização. Propositalmente, deixamos a coluna de MAX() sem apelido para você ver
como fica o nome da tabela. É usada a própria função e o nome da coluna.
66
Figura 56 – Resultado do bloco de código 59 (todas as linhas)
A soma das rendas médias diárias per capita dos países europeus é 1.880,93 USD; a média é
41,8 USD; o país com maior renda média tem um valor diário de 93,3 USD per capita, e o de
menor renda, 7.26 USD. Fizeram parte do cálculo 45 países, que é o resultado de COUNT(). Se
você fizer a conta, irá perceber que AVG() é SUM()/COUNT().
Comando GROUP BY
Na seção anterior vimos o PIB médio per capita das Américas no ano de 2020. Mas, e se
quisermos investigar a variação desse PIB ao longo de cinco anos, dois anos antes de 2020 e dois
anos depois? Nesse caso, utilizamos a cláusula GROUP BY:
SELECT gp.ref_year,
AVG(gp.gdp_pc) AS avg_gdp_pc
FROM gdp_pc gp
JOIN country c ON [Link] = [Link]
WHERE gp.ref_year BETWEEN 2018 AND 2022
AND c.four_regions = 'americas'
GROUP BY gp.ref_year;
Sempre colocamos o GROUP BY no final da query, mas não precisa ser necessariamente o
último. Podemos utilizar um ORDER BY para garantir que os dados apareçam em uma certa
ordem. No caso da nossa consulta, eles apareceram por ordem de ano, que é o que queríamos, mas
nem sempre o GROUP BY traz os dados ordenados. Além disso, há outras cláusulas SQL que
podemos usar depois do GROUP BY, como logo veremos.
67
Geralmente, os campos que colocamos no GROUP BY são os mesmos que colocamos no
SELECT, com exceção das funções de sumarização. Então, se queremos saber a média por ano,
colocamos ref_year no SELECT e ref_year no GROUP BY.
Notamos, nos resultados, o "efeito Covid-19" em 2020, que derrubou o PIB médio per
capita.
Podemos colocar mais de uma coluna para fazer o agrupamento. Investiguemos agora se esse
"efeito Covid-19" acontece também nos demais continentes no mesmo período:
SELECT c.four_regions,
gp.ref_year,
ROUND(AVG(gp.gdp_pc), 2) AS avg_gdp_pc
FROM gdp_pc gp
JOIN country c ON [Link] = [Link]
WHERE gp.ref_year BETWEEN 2018 AND 2022
GROUP BY c.four_regions,
gp.ref_year
ORDER BY c.four_regions,
gp.ref_year;
Agora, temos dois campos no SELECT, então repetimos esses mesmos dois campos no
GROUP BY. Para garantir que os dados venham ordenados do jeito que queremos, usamos um
ORDER BY com os mesmos dois campos. Note que utilizamos a função ROUND(valor, n), em
que valor é o valor que queremos arredondar e n é o número de casas decimais para tornar os
números mais fáceis de visualizar. ROUND() não é uma função-padrão da SQL, mas está presente
praticamente em todos os bancos de dados.
68
Figura 58 – Resultado do bloco de código 61 (todas as linhas)
Notamos nos resultados que o efeito Covid-19 acontece nas quatro regiões da classificação
da Gapminder. Além disso, podemos notar um ranking em termos de PIB médio per capita. Os
valores mais altos estão na Europa, em seguida vem Ásia, América e África. O PIB médio per capita
europeu é quase nove vezes maior que o da África.
SELECT c.four_regions,
gp.ref_year,
ROUND(AVG(gp.gdp_pc), 2) AS avg_gdp_pc
FROM gdp_pc gp
JOIN country c ON [Link] = [Link]
WHERE gp.ref_year BETWEEN 2018 AND 2022
GROUP BY c.four_regions,
gp.ref_year
HAVING avg_gdp_pc > 17000
ORDER BY c.four_regions,
gp.ref_year;
69
Na cláusula HAVING, colocamos o critério que queremos que as linhas resultantes tenham.
Podemos também colocar uma faixa de valores. Por exemplo, PIB médio per capita entre 17
mil e 20 mil USD por dia:
SELECT c.four_regions,
gp.ref_year,
ROUND(AVG(gp.gdp_pc), 2) AS avg_gdp_pc
FROM gdp_pc gp
JOIN country c ON [Link] = [Link]
WHERE gp.ref_year BETWEEN 2018 AND 2022
GROUP BY c.four_regions,
gp.ref_year
HAVING avg_gdp_pc BETWEEN 17000 AND 20000
ORDER BY c.four_regions,
gp.ref_year;
70
Figura 60 – Resultado do bloco de código 63 (todas as linhas)
Além disso, também podemos escolher as linhas que não queremos que venham na consulta.
Por exemplo, valores de PIB maiores do que 17 mil USD por dia:
SELECT c.four_regions,
gp.ref_year,
ROUND(AVG(gp.gdp_pc), 2) AS avg_gdp_pc
FROM gdp_pc gp
JOIN country c ON [Link] = [Link]
WHERE gp.ref_year BETWEEN 2018 AND 2022
GROUP BY c.four_regions,
gp.ref_year
HAVING NOT avg_gdp_pc > 17000
ORDER BY c.four_regions,
gp.ref_year;
Nesse caso, utilizamos HAVING NOT para negar a expressão lógica seguinte.
Contando linhas
É bem comum contarmos a quantidade de linhas em uma tabela. Para isso, utilizamos a
função de sumarização COUNT(). Nesse caso, as contagens podem ser de várias formas diferentes.
Começamos contando o total de linhas da tabela women_years_at_school.
71
SELECT count(*)
FROM women_years_at_school;
72
Existem 40 anos diferentes, como mostra a figura 64:
Poderíamos ter buscado nessa consulta também o ano mais antigo e o mais recente para os
quais temos dados:
Rolando a tabela toda, vemos que temos 40 linhas para cada um dos países. Isso nos mostra
que todos os países têm linhas para os 40 anos, que é a faixa de anos. Não podemos ainda falar que
temos dados para todos os anos e todos os países porque não contamos se há linhas nulas.
73
Figura 66 – Resultado do bloco de código 69 (10 primeiras linhas)
SELECT count(*)
FROM women_years_at_school
WHERE mean_years IS NULL;
Como o resultado foi zero, sabemos que não temos nenhuma linha com o valor de mean_years
nulo e podemos afirmar que temos dados para todos os anos em todos os países.
Podemos olhar a mesma coisa de outra perspectiva, contando o número de linhas por ano:
Bloco de código 71
Se visualizarmos todas as linhas retornadas na consulta, percebemos que há 175 linhas para
cada ano, que é o número de diferentes países. Portanto, vindo por outro caminho, chegamos à
conclusão de que temos dados para todos os 40 anos e para os 175 países.
74
Figura 68 – Resultado do bloco de código 71 (todas as linhas)
UNIDADE 3 – SUBQUERIES
Uma ferramenta que expande bastante o leque de consultas que podemos realizar são as
subqueries, que significa executar uma consulta baseado no resultado de outra consulta.
Por exemplo, queremos saber qual é o país que tem a menor expectativa de vida no ano 2000.
Podemos fazer uma consulta na tabela life_expectancy filtrando por ref_year = 2000, usar a função
de sumarização MIN() para pegar o menor valor de tot_years, anotar o resultado em um papel e
depois fazer outra consulta para pegar o lista de country cujo valor de tot_years é igual ao que
anotamos.
Ou podemos fazer direto usando uma subquery:
Primeiramente, o banco de dados executa a query que está entre parênteses, que chamamos
de subquery, e recebe um único resultado. Em seguida, o banco executa a query mais externa usando
esse valor que recebeu na sua cláusula WHERE.
Veja que tomamos o cuidado de utilizar apelidos diferentes para as duas queries, pois estamos
executando-as sobre a mesma tabela. Os apelidos diferentes evitam ambiguidades. Veja também
que filtramos por ref_year = 2000 nas duas consultas. Se não fizéssemos isso na subquery, iríamos
receber o valor mínimo global da tabela, independentemente do ano. Além disso, se não fizéssemos
isso na query principal, correríamos o risco de trazer também países que têm a expectativa de vida
igual à expectativa de vida mínima não só no ano 2000 mas também em outros anos. Os testes das
queries sem o filtro você pode fazer nos exercícios para ver o resultado.
75
Figura 69 – Resultado do bloco de código 72 (todas as linhas)
Na figura 69, temos o resultado da consulta como queríamos. Podemos ver não só a
expectativa de vida mínima mas também a máxima em uma mesma consulta fazendo duas
subqueries:
SELECT [Link],
le.ref_year,
le.tot_years
FROM life_expectancy le
WHERE le.ref_year = 2000
AND (
le.tot_years = (
SELECT MIN(le2.tot_years)
FROM life_expectancy le2
WHERE le2.ref_year = 2000
)
OR le.tot_years = (
SELECT MAX(le3.tot_years)
FROM life_expectancy le3
WHERE le3.ref_year = 2000
)
);
A consulta fica um pouco mais complicada. Veja que tomamos novamente o cuidado com
os apelidos, agora com as três queries, e tivemos que colocar o OR entre parênteses para agrupar
esses resultados, caso contrário a query seria executada da esquerda para a direita e o resultado estaria
errado.
76
Outra forma de usarmos subqueries é criando tabelas fictícias, denominadas visões (views),
para depois realizarmos consultas sobre essas visões. Por exemplo, as tabelas de tempo na escola
que temos estão separadas por gênero e já fizemos alguns exemplos juntando essas tabelas na seção
sobre UNION.
Podemos agora juntá-las novamente e calcular a escolaridade média dos homens e das
mulheres, de 2000 a 2009, nos países da região africana. Nesse caso, queremos saber o país com
menor tempo na escola para os homens e o com menor tempo na escola para as mulheres. A
consulta fica assim:
SELECT [Link],
[Link],
ROUND(AVG(tab.mean_years), 2) as avg_mean_years
FROM (
SELECT [Link],
m.ref_year,
'male' as gender,
m.mean_years
FROM men_years_at_school m
UNION ALL
SELECT [Link],
w.ref_year,
'female' as gender,
w.mean_years
FROM women_years_at_school w
) AS tab
JOIN country c ON [Link] = [Link]
AND c.four_regions = 'africa'
AND tab.ref_year BETWEEN 2000 AND 2009
GROUP BY [Link], [Link]
ORDER BY [Link], avg_mean_years;
Veja que realizamos a união das duas tabelas, colocamos essa união entre parênteses e a
apelidamos de tab. Depois fazemos a consulta em cima de tab, por meio de um INNER JOIN
com country, agrupando, calculando a média e ordenando os dados, como fazemos com uma tabela
normal.
77
Figura 71 – Resultado do bloco de código 74 (5 primeiras linhas do bloco feminino e 5
primeiras linhas do bloco masculino)
A figura 71 mostra o resultado. Notamos que são os mesmos países, em diferente ordem, e
que os homens ficam mais tempo na escola que as mulheres.
Cláusula EXISTS
As subqueries podem ser correlacionadas, de modo que utilizamos colunas da query no corpo
da subquery. Nesse caso, empregamos a cláusula EXISTS, que retorna verdadeiro se a subquery
trouxer alguma linha ou falso se a subquery voltar vazia.
Por exemplo, se quisermos encontrar, no ano 2000, todos os países com emissões de CO2 per
capita maiores do que as emissões per capita dos Estados Unidos, a consulta é realizada da seguinte
forma:
SELECT *
FROM co2_emissions_pc cep
WHERE cep.ref_year = 2000
AND EXISTS
(SELECT *
FROM co2_emissions_pc cep2
WHERE [Link] = 'USA'
AND cep2.ref_year = 2000
AND cep.co2_pc > cep2.co2_pc
)
Observe que fazemos, na subquery, co2_pc da tabela fora da subquery maior do que co2_pc da
tabela da subquery.
78
Figura 72 – Resultado do bloco de código 75 (todas as linhas)
Poderíamos ter feito a pergunta "ao contrário" e iríamos obter o mesmo resultado:
SELECT *
FROM co2_emissions_pc cep
WHERE cep.ref_year = 2000
AND NOT EXISTS
(SELECT *
FROM co2_emissions_pc cep2
WHERE [Link] = 'USA'
AND cep2.ref_year = 2000
AND cep.co2_pc <= cep2.co2_pc
)
Dessa vez, nós negamos o EXISTS e fazemos, na subquery, co2_pc da tabela fora da subquery
menor ou igual do que co2_pc da tabela da subquery.
79
MÓDULO IV – MANIPULANDO DADOS
6
Data Manipulation Language
7
Data Definition Language
Entretanto, pode ser o caso de termos que incluir manualmente novas linhas nas tabelas. Para
isso, utilizamos o comando INSERT.
A nossa base de cotações de ações da Petrobrás na Nasdaq vai até 29/12/2023. Suponha que
tenhamos obtido os dados dos primeiros cinco dias úteis de 2024, dos dias 2 a 5/1 e queiramos
inserir manualmente esses dias na base.
Figura 74 – Cotações das ações da Petrobrás (BR) em USD na Nyse de 2/1/2024 a 8/1/2024
8
Fonte: Yahoo! Finance .
O comando para inserir uma linha, a referente ao dia 2/1, fica assim:
Note que, após o nome da tabela, listamos todos os campos. A ordem não importa. Mas, na
cláusula VALUES, temos que colocar valores para todos os campos na ordem que os listamos entre
parênteses. O valor '2024-01-02' vai para o campo "Date", e o valor 16.00 vai para o campo "Close"
no exemplo do bloco de código 76.
Caso não saibamos algum valor, devemos escrever NULL no lugar dele. Porém, o campo
deve ter sido configurado para aceitar NULL, caso contrário ocorrerá um erro, e a nova linha não
será inserida.
No caso do nosso exemplo, o banco de dados retorna apenas que uma linha foi inserida. Mas
se fizermos uma query para conferir o resultado, teremos:
8
Disponível em: [Link] Acesso em:
5 jul. 2024.
82
SELECT *
FROM petrobras
WHERE "Date" > '2023-12-29';
Nesse caso, listamos os campos entre parênteses logo após o nome da tabela e, depois de
VALUES, colocamos várias sequências de valores entre parênteses, de modo análogo ao comando
com uma só linha (bloco de código 76). Veja que propositalmente inserimos NULL nos valores da
linha de 3/1/2024, mas logo consertaremos isso.
83
Rodando novamente a consulta do bloco de código 77, obtemos:
Alterando linhas
A atualização de valores em tabelas é feita por meio do comando UPDATE. É muito
importante que utilizemos a cláusula WHERE para atualizarmos somente as linhas de interesse. O
comando UPDATE sem WHERE executa normalmente, porém atualiza todas as linhas da tabela.
Para corrigirmos a linha referente à data de 3/1/2024 que inserimos com valores nulos no
comando INSERT anterior, fazemos o seguinte:
UPDATE petrobras
SET High = 16.64
WHERE "Date" = '2024-01-03';
Na cláusula SET, listamos as colunas que queremos alterar e os seus respectivos valores.
Rodando novamente a consulta do bloco de código 77, obtemos:
84
Note que o valor da coluna High para a linha de 3/1/2024 foi alterado conforme queríamos.
Para mudar várias colunas de uma vez fazemos da seguinte forma:
UPDATE petrobras
SET "Open" = 16.04,
Low = 16.00,
"Close" = 16.58,
"Adj Close" = 15.08,
Volume = 18366.8
WHERE "Date" = '2024-01-03';
Na cláusula SET, listamos as colunas que queremos alterar e os seus respectivos valores.
Rodando novamente a consulta do bloco de código 77, obtemos:
Removendo linhas
O comando para apagar linhas de uma tabela é o DELETE. Nesse comando, não é preciso
listar colunas, pois ele apaga a linha toda. Além disso, a mesma advertência feita com relação ao
UPDATE é feita para o DELETE: não execute esse comando sem a cláusula WHERE. Caso
contrário todas as linhas da tabela serão apagadas.
Nos exemplos anteriores inserimos as cotações da Petrobrás de 2/1 a 8/1/2024. Porém,
queríamos apenas ter inserido os dados da primeira semana de janeiro. O dia 8/1 então está
sobrando, e vamos apagá-lo:
DELETE
FROM petrobras
WHERE "Date" = '2024-01-08';
85
Rodando novamente a consulta do bloco de código 77, obtemos:
UNIDADE 2 – TRANSAÇÕES
O conceito de transação em SQL refere-se a uma sequência de comandos de manipulação de
dados que só devem existir no banco se todos eles ocorrerem com sucesso. Caso um deles falhe,
todos os outros devem ser desfeitos. Esse é apenas um dos aspectos de uma transação, mas é o
principal.
Alguns bancos, para iniciar uma transação, exigem que você execute o comando BEGIN
TRANSACTION. Outros bancos já estão com o modo de transação ligado, ou seja, você não
precisa iniciar a transação com o BEGIN TRANSACTION, mas somente o fato de ter executado
um comando DML é o suficiente para iniciar a transação.
O importante é que, se a sua sessão no banco está no modo de transação, os comandos DML
que você executar somente serão efetivados se você executar um COMMIT. Isso irá salvar tudo
que você fez no banco e encerrar a transação. Caso você tenha cometido algum erro ou algum
comando tenha dado errado, você pode desfazer tudo desde o BEGIN TRANSACTION com o
comando ROLLBACK. Esse comando irá apagar tudo o que você alterou, inclusive linhas
deletadas, e vai encerrar a transação.
Caso o banco com o qual você está trabalhando exija o BEGIN TRANSACTION para
começar uma transação, se você não o executar, todos os comandos que você executar serão
imediatamente efetivados, dispensando o uso do COMMIT. E, neste caso, o ROLLBACK não irá
funcionar. Aliás, é fundamental salientar que, depois de um COMMIT não é mais possível fazer
ROLLBACK. E vice-versa.
Importante: apenas comandos DML têm transação. Comandos DQL, isto é, os SELECTs
não tem controle de transação.
86
UNIDADE 3 – ALGUNS COMANDOS DDL
Os comandos DDL são utilizados para a definição e manutenção das estruturas de dados.
Servem para criar tabelas, para alterar a estrutura de tabelas, adicionando campos ou mudando os
seus nomes, e para remover tabelas que não são mais necessárias.
Esta unidade também apresenta um modo simples de limpar o conteúdo de uma tabela.
Criando tabelas
Uma das formas de se criar uma tabela é a partir da importação de um arquivo CSV 9. Porém,
você pode criar tabelas manualmente utilizando o comando CREATE TABLE.
Por exemplo, se quiséssemos ter criado a tabela child_mortality manualmente, o comando
seria:
Vamos criar agora duas tabelas com chaves primárias e um relacionamento entre elas. As
tabelas serão uf e cidade. Primeiramente, iremos criar a tabela uf, pois ela será necessária na hora de
criar cidade, pois uf fará referência à cidade.
9
Comma separated values, ou valores separados por vírgula (no formato brasileiro geralmente é ponto e vírgula).
87
CREATE TABLE "uf" (
"cd_uf" CHAR(2) NOT NULL PRIMARY KEY,
"descr_uf" VARCHAR(50) NOT NULL
);
Verificando a estrutura da tabela, vemos os campos com os seus diferentes tipos de caracteres
e observamos que ambos não aceitam valores nulos:
O campo CHAR tem tamanho fixo. No caso de cd_uf, irá ocupar o espaço de dois caracteres
mesmo que só seja inserido um. O campo VARCHAR também é caractere, mas é de tamanho
variável. Nós o criamos com tamanho 50, que é o tamanho máximo. Mas se inserirmos quatro
caracteres, o campo irá ocupar apenas o espaço de quatro caracteres.
Vamos criar agora a tabela cidade que terá um relacionamento com uf, isto é, receberá a chave
primária de uf como chave estrangeira.
88
Verificando a estrutura da tabela, vemos os campos com os seus diferentes tipos caracteres e
observamos que ambos não aceitam valores nulos:
Quando as tabelas estão relacionadas entre si, o banco de dados nos presta alguns serviços de
validação. Ele não deixa que cadastremos na tabela cidade uma cidade com cd_uf que não esteja
previamente cadastrado na tabela uf. Outro serviço importante para a manutenção da integridade
dos dados é não permitir que se apague uma UF da tabela uf caso alguma cidade esteja usando o
seu código.
E o uso de chaves primárias evita que sejam cadastradas UFs com o mesmo cd_uf na tabela
uf e cidades com o mesmo código IBGE (cd_ibge) na tabela cidade.
Alterando tabelas
Com o comando ALTER TABLE, é possível fazer uma série de alterações na estrutura de
uma tabela, como alterar o nome, adicionar ou remover colunas, alterar o nome e o tipo de colunas,
transformar um campo em chave primária, mudar uma coluna que aceita nulos para NOT NULL,
e assim por diante.
Existem algumas limitações importantes, entretanto, se já houver linhas cadastradas na tabela.
Por exemplo, se alguma coluna tiver um valor nulo cadastrado, tentar transformá-la em NOT
NULL irá causar um erro. Tentar transformar uma coluna em chave primária também irá causar
erro se ela contiver valores repetidos. Além disso, a adição de uma coluna NOT NULL em uma
tabela que possui linhas cadastradas tem de ser feita em três etapas: primeiro adicionar a coluna
como NULL, depois fazer UPDATE nas linhas preenchendo essa coluna com valores e, finalmente,
fazer outro ALTER TABLE para transformar a coluna em NOT NULL.
As operações relacionadas no parágrafo anterior não estão disponíveis em todos os bancos de
dados. Alguns não permitem que haja a remoção de colunas, por exemplo, e então é necessário criar
uma nova tabela sem a coluna indesejada e transferir os dados da antiga para a nova para depois
remover a antiga e renomear a nova. Bastante trabalhoso.
89
Vamos fazer um exemplo simples mas frequente, que é adicionar colunas. Se quisermos
adicionar na tabela cidade um campo para o número de habitantes e outro para a área em km2,
podemos fazer assim:
Removendo tabelas
Quando não queremos mais uma tabela em um banco de dados podemos simplesmente
apagá-la. O comando para isso é o DROP TABLE.
Por exemplo, vamos apagar as duas tabelas que criamos, cidade e uf. Os comandos para isso
são:
O banco simplesmente apaga as duas tabelas, sem fazer perguntas, eliminando todos os dados
e relacionamentos.
90
Cuidado: comandos DDL, como DROP TABLE, CREATE TABLE e ALTER TABLE
não são passíveis de ROLLBACK. Uma vez executados não é possível desfazê-los.
Limpando tabelas
Um meio rápido de se apagar todos os dados de uma tabela é o comando TRUNCATE
TABLE. Ele é melhor que o DELETE, pois é praticamente instantâneo, e libera o espaço alocado
pela tabela, coisa que o DELETE não faz. Mas ele tem um problema, pois como é um comando
DDL, ele não é passível de ROLLBACK. Já o DELETE é, se estiver sendo executado no contexto
de uma transação.
Para limpar a tabela petrobras poderíamos rodar:
Nesse caso, dizemos que poderíamos porque o SQLite, que estamos utilizando como SGBD, não
implementa o comando TRUNCATE, mas vários outros bancos de dados o implementam.
91
REFERÊNCIAS BIBLIOGRÁFICAS
CARDOSO, G. C.; CARDOSO, V. M. Linguagem SQL, fundamentos e práticas. São
Paulo: SRV Editora LTDA, 2013. ISBN 9788502200463.
DATE, C. J. Introdução a sistemas de bancos de dados. Rio de Janeiro: Grupo GEN,
2004. ISBN 8535212736.
ELMASRI, R.; NAVATHE, S. B. Sistemas de bancos de dados. São Paulo: Editora
Pearson Universidades, 2019. ISBN 8543025001.
MINISTÉRIO DA SAÚDE (Brasil). DATASUS. Qualificação de Indicadores do IDB-
1997: taxa de mortalidade infantil. [s. l.], 1997. Disponível em:
[Link] Acesso em: 19 jun. 2024.
MINISTÉRIO DA SAÚDE (Brasil). DATASUS. Qualificação de Indicadores do IDB-
2000: taxa de fecundidade total. [s. l.], 2000. Disponível em:
[Link] Acesso em: 25 jun. 2024.
NIELD, Thomas. Introdução à linguagem SQL: abordagem prática para iniciantes. São
Paulo: Novatec Editora, 2016. ISBN 8575225014.
RAMAKRISHNAN, R.; GEHRKE, J. Sistemas de gerenciamento de bancos de dados.
[s.l.]: Editora Grupo A, 2008. E-book. ISBN 8577260275.
ZHAO, A. SQL – Guia Prático: um guia para o uso de SQL. 4ª edição. São Paulo:
Novatec Editora LTDA, 2023. ISBN 8575228315.
Bases de dados
BANCO CENTRAL DO BRASIL. Sistema Gerenciador de Séries Temporais. Disponível
em: [Link] Acesso em 5. jul. 2024.
BANCO MUNDIAL. World Bank Open Data. Disponível em:
[Link] Acesso em 5. jul. 2024.
BRASIL. Governo Federal. Portal Brasileiro de Dados Abertos. Disponível em:
[Link] Acesso em 5. jul. 2024.
FMI. Fundo Monetário Internacional. IMF Data. Disponível em:
[Link] Acesso em 5. jul. 2024.
FEDERAL RESERVE BANK OF ST. LOUIS. FRED Economic Data. Disponível em:
[Link] Acesso em 5. jul. 2024.
GAPMINDER. Download the data. Disponível em: [Link]
Acesso em 23. jun. 2024.
IBGE. SIDRA – Sistema IBGE de Recuperação Automática. Disponível em:
[Link] Acesso em 5. jul. 2024.
92
IPEA. Ipeadata: coletânea de dados macroeconômicos, regionais e sociais. Disponível em:
[Link] Acesso em 5. jul. 2024.
OCDE. OECD Data. Disponível em: [Link] Acesso em 5. jul.
2024.
OXFORD UNIVERSITY. Our World in Data. Disponível em:
[Link] Acesso em 5. jul. 2024.
93
APÊNDICE I – DICIONÁRIO DE DADOS
Em todos os exemplos deste curso, utilizamos um conjunto de tabelas obtidas da base de
dados do Gapminder 10, uma organização sueca sem fins lucrativos que declara ter como missão
"lutar contra a ignorância devastadora com uma visão de mundo baseada em fatos que qualquer
um possa entender".
Para cumprir essa missão, a Gapminder reúne dados estatísticos, coletados das mais diversas
fontes, e os torna disponíveis para download gratuito no seu website.
Segue, na sequência, a lista das tabelas utilizadas.
Nota importante: a maioria as tabelas está organizada em um formato pouco adequado para
a consulta por SQL, por isso foi feito um tratamento desses dados antes da criação do banco de
dados de exemplo.
avg_income: renda média diária das famílias, USD/pessoa/dia, ajustados pela inflação
(paridade do poder de compra de 2017, em dólares internacionais constantes. Possui as
colunas:
country (texto): país a que se refere a métrica;
ref_year (inteiro): ano de referência da métrica;
mean_usd (real): valor da renda média;
link de acesso: [Link]
child_mortality: mortalidade das crianças de 0 a 5 anos de idade para cada 1000 nascidos
vivos. Possui as colunas:
country (texto): país a que se refere a métrica;
ref_year (inteiro): ano de referência da métrica;
tot_deaths (inteiro): número de crianças morta;
link de acesso: [Link]
co2_emissions_pc: emissão de CO2 per capita por ano em toneladas. Possui as colunas:
country (texto): país a que se refere a métrica;
ref_year (inteiro): ano de referência da métrica;
co2_pc (real): toneladas de CO2;
link de acesso: [Link]
country: lista de países e as suas diversas classificações utilizadas pela Gapminder. Possui as
colunas:
country (texto): país ao qual os dados se referem;
10
[Link]
94
four_regions (texto): classificação de quatro regiões (europe, americas, asia e africa) da
Gapminder;
six_regions (texto): classificação de seis regiões da Gapminder;
eight_regions (texto): classificação de oito regiões da Gapminder;
wb_regions (texto): classificação de regiões utilizada pelo Banco Mundial;
wb4income (texto): classificação de quatro perfis de renda utilizada pelo Banco
Mundial;
wb3income (texto): classificação de três perfis de renda utilizada pelo Banco Mundial;
link de acesso: [Link]
fertility: média do número de filhos por mulher em idade fértil. Possui as colunas:
country (texto): país a que se refere a métrica;
ref_year (inteiro): ano de referência da métrica;
mean_babies (real): quantidade média de filhos;
link de acesso: [Link]
gdp_pc: PIB per capita, em USD/pessoa, ajustados pela inflação (paridade do poder de
compra de 2017, em dólares internacionais constantes). Possui as colunas:
country (texto): país a que se refere a métrica;
ref_year (inteiro): ano de referência da métrica;
gdp_pc (real): valor do PIB per capita;
link de acesso: [Link]
life_expectancy: expectativa de vida do nascituro, em anos, se as taxas de mortalidade das
diferentes idades se mantiverem constantes ao longo da sua vida. Possui as colunas:
country (texto): país a que se refere a métrica;
ref_year (inteiro): ano de referência da métrica;
tot_years (inteiro): anos de vida;
link de acesso: [Link]
men_years_at_school: o número médio de anos que um homem de 25 ou mais anos de
idade passa na escola, incluindo ensino fundamental, médio e superior. Possui as colunas:
country (texto): país a que se refere a métrica;
ref_year (inteiro): ano de referência da métrica;
mean_years (real): média de anos na escola;
link de acesso: [Link]
petrobras: valor das cotações das ações da Petrobrás na Nyse Nasdaq em USD. Essa é a
única tabela do banco de dados que não foi obtida na Gapminder, mas sim no Yahoo!
Finance. Possui as colunas:
Date (tipo data): indica a data específica para a qual os dados de preço, volume e outras
métricas são apresentados;
95
Open (real): o preço da ação no momento em que o mercado abre naquela data
específica;
High (real): o preço mais alto alcançado pela ação durante o período de negociação
naquele dia;
Low (real): o preço mais baixo alcançado pela ação durante o período de negociação
naquele dia;
Close (real): o preço da ação no momento em que o mercado fecha naquela data
específica;
Adj Close (real): o preço de fechamento ajustado é o preço de fechamento da ação
ajustado para refletir eventos corporativos que afetam o preço da ação, como
dividendos, desdobramentos (splits) e agrupamentos (reverse splits);
Volume (inteiro): o número total de ações negociadas durante o dia de negociação;
link de acesso: [Link]
population: contagem do número total de habitantes vivendo em determinado território
em um determinado ano. Possui as colunas:
country (texto): país a que se refere a métrica;
ref_year (inteiro): ano de referência da métrica;
tot_years (inteiro): anos de vida;
link de acesso: [Link]
women_years_at_school: o número médio de anos que uma mulher de 25 ou mais anos de
idade passa na escola, incluindo ensino fundamental, médio e superior. Possui as colunas:
country (texto): país a que se refere a métrica;
ref_year (inteiro): ano de referência da métrica;
mean_years (real): média de anos na escola;
link de acesso: [Link]
96
PROFESSOR-AUTOR
ANTONIO SERGIO FERREIRA BONATO
97