Aula 01 — Revisão de SQL: do modelo relacional ao banco funcionando
Projeto de Banco de Dados
Curso
Engenharia de Software
Carga horária
80 horas
SGBD
PostgreSQL
Encontros
Sextas-feiras
1. Como a disciplina funciona
Até aqui, banco de dados apareceu como ferramenta: modelar tabelas, escrever consultas, usar o ORM. Esta disciplina abre a caixa. O objeto de estudo passa a ser o próprio sistema gerenciador: como ele guarda os dados em disco, como garante que uma transferência bancária não perca dinheiro no meio, e por que a mesma consulta às vezes leva 3 milissegundos e às vezes 3 segundos.
É uma disciplina prática. A maior parte dos encontros tem metade ou mais do tempo em laboratório, e a exposição serve para dar sentido ao que será feito no mesmo dia.
1 · Arquitetura do SGBD
2 · Transações e concorrência
3 · Processamento de consultas
4 · Novas tecnologias
Avaliação
| Componente | Como se compõe |
|---|---|
| MB1 | P1 (0 a 7,0) + MT1 (0 a 3,0) |
| MB2 | P2 (0 a 7,0) + MT2 (0 a 3,0) |
| Média final | (MB1 + MB2) ÷ 2, com frequência mínima de 75% |
MT1 e MT2 vêm dos trabalhos e das atividades entregues ao fim de cada encontro.
Toda aula tem entrega
Na próxima aula cada um vai montar a própria máquina virtual Linux com PostgreSQL instalado, configurar o acesso externo e conectar de fora. Hoje não se instala nada: o pgAdmin já está nas máquinas do laboratório.
Antes de começar: o ambiente
Todo o SQL desta página é executado no pgAdmin 4, já instalado nas máquinas do laboratório. Esta página é o material de consulta: o comando fica aqui, com botão de copiar, e você executa lá.
Tutorial do ambiente
Guia do desafio
Script do banco
Os quatro primeiros passos
- Abra o pgAdmin 4 pelo menu. A primeira abertura demora alguns segundos e a janela pode ficar em branco por um instante: é normal, não clique várias vezes no ícone. Na primeira vez ele pede uma senha mestra, que não é a senha do banco — é só para ele guardar as suas conexões. Anote a que você escolher.
- Conecte no servidor. Na árvore à esquerda, clique no servidor listado em
Serverse informe a senha do usuáriopostgres. Se não houver servidor nenhum, chame o professor: o pgAdmin sozinho é só o painel, e precisa de um PostgreSQL rodando para conversar. - Crie o seu banco. Botão direito em
Databases→Create→Database…, com o nomebiblioteca. Não trabalhe dentro do bancopostgres, que é o de manutenção do servidor. - Abra a janela de consulta. Com o banco
bibliotecaselecionado, useTools → Query ToolouAlt+Shift+Q. É aqui que você escreve SQL.
| O que fazer | Como |
|---|---|
| Executar tudo o que está no editor | F5, ou o botão ▶ |
| Executar só um trecho | selecione com o mouse e aperte F5 |
| Salvar o seu arquivo | Ctrl + S |
| Abrir um arquivo .sql | Ctrl + O, ou o ícone de pasta |
A linha destacada é a mais útil: você vai acumular comandos na mesma janela, e selecionar só o que quer rodar evita reexecutar tudo a cada teste.
Confira em qual banco você está
Salve desde o começo, não no fim
Ctrl+S como aula01-seusobrenome.sql. Esse arquivo é a sua entrega de hoje, e é o que você vai rodar dentro da máquina virtual na aula 02. Máquina de laboratório trava e é usada por outra turma depois de você: guarde uma cópia em pendrive, e-mail ou Drive.Se você faltou, chegou atrasado ou quer refazer em casa
O script abaixo cria as tabelas e os dados usados das seções 7 em diante, de uma vez só. Ele também está disponível como arquivo: baixar banco-aula01.sql. No pgAdmin, abra com Ctrl+O e execute com F5.
DROP TABLE IF EXISTS livro;
DROP TABLE IF EXISTS autor;
CREATE TABLE autor (
id serial PRIMARY KEY,
nome text NOT NULL,
pais varchar(50),
nascimento date
);
CREATE TABLE livro (
id serial PRIMARY KEY,
titulo text NOT NULL,
isbn varchar(20) UNIQUE,
ano integer CHECK (ano > 1400),
disponivel boolean DEFAULT true,
autor_id integer NOT NULL REFERENCES autor(id)
);
INSERT INTO autor (nome, pais, nascimento) VALUES
('Machado de Assis', 'Brasil', '1839-06-21'),
('Clarice Lispector', 'Brasil', '1920-12-10'),
('Graciliano Ramos', 'Brasil', '1892-10-27'),
('Jorge Amado', 'Brasil', '1912-08-10'),
('Gabriel García Márquez', 'Colômbia', '1927-03-06'),
('Autor Sem Livro', 'Brasil', NULL);
INSERT INTO livro (titulo, isbn, ano, autor_id) VALUES
('Dom Casmurro', '978-85-01', 1899, 1),
('Memórias Póstumas', '978-85-02', 1881, 1),
('Quincas Borba', NULL, 1891, 1),
('A Hora da Estrela', '978-85-03', 1977, 2),
('Vidas Secas', '978-85-04', 1938, 3),
('Capitães da Areia', '978-85-05', 1937, 4),
('Cem Anos de Solidão', '978-85-06', 1967, 5);Só use o script se precisar
2. Antes do SQL: o que é uma tabela
Uma tabela é um conjunto de linhas que compartilham as mesmas colunas. Os nomes formais aparecem na bibliografia e vale reconhecê-los quando surgirem.
| Termo do dia a dia | Termo formal | O que é |
|---|---|---|
| Tabela | Relação | a estrutura inteira |
| Linha | Tupla | um registro individual |
| Coluna | Atributo | um campo, com tipo fixo |
| Tipo | Domínio | o conjunto de valores que a coluna aceita |
Todos os valores de uma coluna são do mesmo tipo. Essa garantia é o que separa um banco de uma planilha, e é o que permite comparar, ordenar e indexar sem precisar verificar valor por valor.
As duas chaves
Chave primária
Chave estrangeira
Por que a chave estrangeira importa
3. Criando o banco e a primeira tabela
O comando que cria um banco é direto, e é o único da aula que você não vai executar aqui: o executor já coloca você dentro de um banco pronto.
CREATE DATABASE biblioteca;A tabela é onde as decisões começam. Copie o bloco abaixo para o pgAdmin, execute, e depois leia cada linha com calma: cada pedaço dela é uma escolha de projeto.
DROP TABLE IF EXISTS livro;
DROP TABLE IF EXISTS autor;
CREATE TABLE autor (
id serial PRIMARY KEY,
nome text NOT NULL,
pais varchar(50),
nascimento date
);
SELECT * FROM autor;| Trecho | O que significa |
|---|---|
| id | o nome da coluna |
| serial | inteiro que se numera sozinho a cada inserção: 1, 2, 3… |
| PRIMARY KEY | identifica a linha; já implica NOT NULL e UNIQUE |
| text | texto de tamanho livre |
| NOT NULL | não aceita vazio; inserir sem nome dá erro |
| varchar(50) | texto com limite de 50 caracteres |
| date | só data, sem hora |
Zero linhas, e está certo
SELECT devolveu nenhuma linha, mas a estrutura existe. Criar a tabela e povoar a tabela são operações separadas, e essa distinção vai reaparecer o semestre inteiro.4. Escolhendo o tipo de cada coluna
O tipo não é detalhe de sintaxe. Ele define o que a coluna aceita, quanto ocupa e quais operações fazem sentido sobre ela.
| Tipo | Para quê | Cuidado |
|---|---|---|
| integer | números inteiros | |
| serial | inteiro com numeração automática | use apenas na chave primária |
| numeric(10,2) | dinheiro e medidas exatas | 10 dígitos, 2 depois da vírgula |
| real / float | ponto flutuante | nunca para dinheiro |
| text | texto sem limite | é o padrão no PostgreSQL |
| varchar(n) | texto com limite | o limite é regra de negócio, não desempenho |
| boolean | verdadeiro ou falso | aceita true, false e NULL |
| date | data | |
| timestamp | data e hora |
A linha destacada merece demonstração. Rode isto no pgAdmin e olhe o resultado da soma:
SELECT (0.1::float8 + 0.2::float8)::text AS soma_float,
(0.1::numeric + 0.2::numeric)::text AS soma_numeric,
(0.1::float8 + 0.2::float8) = 0.3::float8 AS float_bate;O float devolve 0.30000000000000004, e a comparação com 0.3 dá falso. Num cálculo isolado essa diferença é invisível. Veja o que ela faz somada, em mil lançamentos de um centavo:
SELECT sum(0.01::float8)::text AS total_float,
sum(0.01::numeric)::text AS total_numeric
FROM generate_series(1, 1000);O centavo que ninguém acha
float sai 9.999999999999831. Repare que não existe um lançamento errado para procurar: o erro está espalhado por todos os mil. É por isso que, em sistema financeiro, numeric é a única opção.E não adianta trocar por
real, que é menor — ele só demora mais a aparecer. sum(0.1::real) cem vezes dá 10.000002, e não 10.5. Restrições: o banco defendendo os dados
Agora uma tabela com vínculo e com regras. Cada restrição declarada aqui é um tipo de dado errado que deixa de existir na base.
CREATE TABLE livro (
id serial PRIMARY KEY,
titulo text NOT NULL,
isbn varchar(20) UNIQUE,
ano integer CHECK (ano > 1400),
disponivel boolean DEFAULT true,
autor_id integer NOT NULL REFERENCES autor(id)
);
INSERT INTO autor (nome, pais) VALUES ('Machado de Assis', 'Brasil');
SELECT id, nome FROM autor;| Restrição | O que garante |
|---|---|
| PRIMARY KEY | identifica a linha; não repete e não é nula |
| UNIQUE | não repete, mas aceita nulo |
| NOT NULL | não aceita vazio |
| CHECK (…) | a condição precisa ser verdadeira para a linha entrar |
| DEFAULT | valor usado quando você não informa nada |
| REFERENCES | chave estrangeira: o valor precisa existir na outra tabela |
Três tentativas que precisam falhar
No pgAdmin, selecione uma linha por vez e aperte F5, mantendo as outras duas comentadas. O erro é o resultado esperado: é o banco fazendo o trabalho dele.
-- 1) não existe autor com id 999
INSERT INTO livro (titulo, ano, autor_id) VALUES ('Livro sem autor', 2020, 999);
-- 2) 1200 não passa no CHECK
-- INSERT INTO livro (titulo, ano, autor_id) VALUES ('Livro antigo', 1200, 1);
-- 3) falta o título, que é NOT NULL
-- INSERT INTO livro (ano, autor_id) VALUES (2020, 1);Cada erro acima é um problema que não aconteceu
6. Inserindo dados
Uma linha por vez, ou várias de uma vez. Repare que ninguém informa o id: o serial cuida disso.
INSERT INTO autor (nome, pais, nascimento) VALUES
('Clarice Lispector', 'Brasil', '1920-12-10'),
('Graciliano Ramos', 'Brasil', '1892-10-27'),
('Jorge Amado', 'Brasil', '1912-08-10'),
('Gabriel García Márquez', 'Colômbia', '1927-03-06');
INSERT INTO livro (titulo, isbn, ano, autor_id) VALUES
('Dom Casmurro', '978-85-01', 1899, 1),
('A Hora da Estrela', '978-85-03', 1977, 2),
('Vidas Secas', '978-85-04', 1938, 3),
('Capitães da Areia', '978-85-05', 1937, 4),
('Cem Anos de Solidão', '978-85-06', 1967, 5);
SELECT id, nome, pais FROM autor ORDER BY id;7. Consultando
O SELECT é o comando que você mais vai escrever na vida profissional, e o único que ganha uma unidade inteira desta disciplina.
SELECT titulo, ano FROM livro WHERE ano > 1950;
-- experimente trocar por uma destas:
-- SELECT titulo FROM livro WHERE ano BETWEEN 1900 AND 1950;
-- SELECT titulo FROM livro WHERE titulo LIKE '%Solidão%';
-- SELECT titulo FROM livro WHERE autor_id IN (1, 2);
-- SELECT titulo FROM livro WHERE isbn IS NULL;| Operador | Uso |
|---|---|
| = <> > < >= <= | comparação |
| BETWEEN a AND b | faixa, incluindo os dois extremos |
| LIKE | padrão de texto: % é qualquer coisa, _ é um caractere |
| IN (…) | está na lista |
| IS NULL / IS NOT NULL | vazio ou preenchido |
| AND OR NOT | combinação de condições |
= NULL nunca funciona
NULL não é um valor: é a ausência de um. Nada é igual a uma ausência, nem outra ausência. A comparação devolve nulo em vez de verdadeiro ou falso, então a linha jamais aparece no resultado, e você recebe uma lista vazia sem nenhuma mensagem de erro. Use sempre IS NULL. É o erro de SQL mais comum que existe, e o mais silencioso.SELECT count(*) AS com_igual FROM livro WHERE isbn = NULL;
SELECT count(*) AS com_is_null FROM livro WHERE isbn IS NULL;Ordenar e limitar fecham o repertório básico. Sem ORDER BY não existe garantia nenhuma de ordem, mesmo que o resultado pareça consistente nos seus testes.
SELECT titulo, ano FROM livro ORDER BY ano DESC LIMIT 3;8. JOIN: juntando as tabelas
O autor_id do livro é um número, e ninguém quer ler número em relatório. O JOIN troca o número pelo dado.
SELECT l.titulo, a.nome AS autor, l.ano
FROM livro l
JOIN autor a ON a.id = l.autor_id
ORDER BY a.nome;A palavra INNER é opcional: JOIN sozinho já significa isso. Os apelidos de tabela, l e a, são como se escreve no dia a dia.
A diferença que decide o relatório
O cenário tem um autor cadastrado sem nenhum livro. Rode o bloco abaixo, depois troque LEFT JOIN por JOIN e execute de novo. Conte as linhas nas duas versões.
SELECT a.nome, l.titulo
FROM autor a
LEFT JOIN livro l ON l.autor_id = a.id
ORDER BY a.nome;| INNER JOIN | LEFT JOIN | |
|---|---|---|
| Traz | só o que casa dos dois lados | tudo da esquerda, casando ou não |
| Quem não casa | desaparece do resultado | aparece com NULL nas colunas da direita |
| Serve para | cruzar dados existentes | encontrar o que está faltando |
Onde isso vira erro de verdade
INNER JOIN esconde justamente os autores com zero livro, que muitas vezes são exatamente os que a pergunta queria encontrar. O resultado sai sem erro, com aparência correta, e a decisão tomada em cima dele está errada. A pergunta de negócio é que determina qual dos dois usar.9. Agrupando e contando
SELECT count(*) AS total_livros,
min(ano) AS mais_antigo,
max(ano) AS mais_recente,
round(avg(ano)) AS ano_medio
FROM livro;O GROUP BY faz a mesma conta, mas por grupo. Note o LEFT JOIN: sem ele, o autor sem livro sumiria da lista em vez de aparecer com zero.
SELECT a.nome, count(l.id) AS quantos_livros
FROM autor a
LEFT JOIN livro l ON l.autor_id = a.id
GROUP BY a.nome
ORDER BY quantos_livros DESC;SELECT a.nome, count(l.id) AS quantos
FROM autor a
JOIN livro l ON l.autor_id = a.id
GROUP BY a.nome
HAVING count(l.id) > 1;WHERE e HAVING é questão de momento
WHERE filtra linhas antes do agrupamento. O HAVING filtra grupos depois dele. É por isso que não se usa count() dentro do WHERE: no momento em que ele roda, o grupo ainda não existe.Duas contagens que parecem iguais e não são. A diferença entre elas é exatamente a quantidade de nulos da coluna:
SELECT count(*) AS todas_as_linhas,
count(isbn) AS com_isbn
FROM livro;10. Alterando e apagando
UPDATE livro SET disponivel = false WHERE id = 1;
SELECT id, titulo, disponivel FROM livro WHERE id = 1;O erro mais caro do SQL
Os dois comandos abaixo são válidos:
UPDATE livro SET disponivel = false; -- muda TODAS as linhas
DELETE FROM livro; -- apaga TODAS as linhasO banco não pergunta nada, não avisa e faz exatamente o que foi mandado. O hábito que evita o prejuízo é simples e vale para o resto da carreira: escreva primeiro o SELECT com o mesmo WHERE, confira quais linhas voltaram, e só então troque o SELECT * por UPDATE ou DELETE.
Mudanças de estrutura usam ALTER TABLE. E apagar tabela tem ordem: primeiro quem aponta, depois quem é apontado. É a inversa da ordem de criação.
ALTER TABLE livro ADD COLUMN paginas integer;
ALTER TABLE livro DROP COLUMN paginas;
-- esta linha falha: livro depende de autor
DROP TABLE autor;
-- a ordem correta seria:
-- DROP TABLE livro;
-- DROP TABLE autor;11. As cinco famílias do SQL
Os nomes valem porque a disciplina se organiza em torno deles. Hoje você usou as três primeiras; as outras duas são assunto do semestre.
| Sigla | Função | Comandos | Onde volta |
|---|---|---|---|
| DDL | Define a estrutura | CREATE · ALTER · DROP · TRUNCATE | Unidade 1 |
| DML | Mexe nos dados | INSERT · UPDATE · DELETE | Unidade 2 |
| DQL | Consulta | SELECT | Unidade 3 |
| DCL | Controla acesso | GRANT · REVOKE | Aula 05 |
| TCL | Controla transação | BEGIN · COMMIT · ROLLBACK | Unidade 2 |
Na próxima aula
postgresql.conf e pg_hba.conf, e conecta nesse servidor a partir de outra máquina. É o primeiro passo para administrar um banco em servidor de verdade, inclusive em nuvem.12. Desafio: sistema de chamados
Uma empresa de manutenção de equipamentos controla os chamados dos clientes numa planilha. Três coisas já deram errado: chamado atribuído a técnico que não trabalha mais lá, cliente cadastrado duas vezes com grafias diferentes, e um relatório de produtividade que ninguém consegue fechar porque os valores estão escritos em texto livre.
Modele isso como banco de dados.
Parte 1 · Modelagem
cliente
tecnico
chamado
O conjunto precisa ter, no mínimo, um NOT NULL, um UNIQUE, um CHECK e um DEFAULT, além das chaves.
Parte 2 · Dados
Quatro clientes em pelo menos duas cidades, três técnicos sendo um inativo e um sem nenhum chamado, e oito chamados distribuídos de forma desigual, com pelo menos um sem valor preenchido. A distribuição desigual não é capricho: sem ela as consultas da parte 3 devolvem tudo igual e não provam nada.
Parte 3 · Consultas
- Todos os chamados com o nome do cliente e o nome do técnico, em vez dos números.
- Quantos chamados cada técnico atendeu. O técnico sem chamado nenhum precisa aparecer na lista, com zero.
- O valor total cobrado por cliente, do maior para o menor.
- Os chamados abertos por clientes de uma cidade específica, à sua escolha.
- Os técnicos com mais de dois chamados atendidos.
- Quantos chamados estão sem valor preenchido, em uma única consulta.
- O chamado mais caro, com o nome do cliente.
- A média de valor por técnico, considerando apenas os técnicos ativos.
Parte 4 · Três perguntas
- Na questão 2, qual tipo de JOIN você usou e por quê? O que aconteceria com o resultado se você usasse o outro?
- Volte ao problema da empresa. Qual restrição que você criou impede cada um dos três erros que eles tiveram? Se algum não for impedido pelo banco, diga qual e explique por quê.
- Um colega sugeriu guardar o valor cobrado como text, porque assim daria para escrever "a combinar" quando não há preço fechado. Responda a ele.
.sql pelo Classroom com os comandos na ordem em que rodaram e as respostas em comentário, nomeado aula01-seusobrenome.sql. Entrega no fim da aula: quem não terminar envia o que fez.-- Parte 1: crie as três tabelas aqui, na ordem certa.
-- Lembre: chamado referencia as outras duas, então vem por último.
CREATE TABLE cliente (
id serial PRIMARY KEY
-- continue
);
Onde a turma costuma travar
- Questão 2 resolvida com
INNER JOIN: o técnico sem chamado desaparece e a lista sai com um técnico a menos, sem erro nenhum. WHERE count(...)na questão 5. Agregação filtra comHAVING.- Criar
chamadoantes declienteetecnico: a chave estrangeira precisa que a tabela apontada já exista.
13. Perguntas para fixação e debate
- Uma coluna UNIQUE e uma coluna PRIMARY KEY parecem fazer a mesma coisa. Qual é a diferença, e em que situação ela aparece na prática?
- Por que o resultado de uma consulta sem ORDER BY não pode ser considerado ordenado, mesmo quando parece consistente em todos os seus testes?
- Se uma tabela tem 100 linhas e a coluna email tem 30 valores nulos, quanto devolvem count(*) e count(email)? Por que a diferença existe?
- Você precisa apagar um autor que tem livros cadastrados. O banco recusa. Quais são as saídas possíveis, e qual delas você escolheria num sistema em produção?
- Em que situação usar varchar(50) em vez de text é uma boa decisão de projeto? E em que situação é apenas hábito herdado de outro banco?
- O comando DELETE FROM cliente; foi executado por engano numa base de produção. O que aconteceu, e o que precisaria existir para recuperar os dados? Guarde a resposta: o assunto volta na aula 09.
📚 Estudo desta aula
- MACHADO, Felipe Nery R. Banco de Dados: Projeto e Implementação. São Paulo: Saraiva, 2020. Modelo relacional e linguagem SQL
- CARDOSO, G. C.; CARDOSO, V. M. Linguagem SQL, fundamentos e práticas. São Paulo: Saraiva, 2013. DDL, DML e consultas
- HEUSER, Carlos A. Projeto de banco de dados. Porto Alegre: Grupo A, 2011. Modelo relacional e restrições de integridade
📖 Leitura complementar
- SILBERSCHATZ, Abraham. Sistema de Banco de Dados. Rio de Janeiro: Grupo GEN, 2020.
- POSTGRESQL GLOBAL DEVELOPMENT GROUP. PostgreSQL Documentation. Disponível em postgresql.org/docs. The SQL Language; Data Types
O executor de SQL desta página roda um PostgreSQL real compilado para WebAssembly, dentro do seu navegador. Nada é enviado a servidor, e o banco desaparece quando a aba é fechada.