100% acharam este documento útil (2 votos)
2K visualizações392 páginas

Aprendendo SQL

Enviado por

Lucas Diniz
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
100% acharam este documento útil (2 votos)
2K visualizações392 páginas

Aprendendo SQL

Enviado por

Lucas Diniz
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 392

Alan Beaulieu

Novatec
Authorized translation of the English edition of Learning SQL 2nd Edition ISBN 978-0-596-52083-
0
© 2009, Alan Beaulieu. This translation is published and sold by permission of O'Reilly Media,
Inc., the owner of all rights to publish and sell the same.
Tradução autorizada da edição em inglês da obra Learning SQL 2nd Edition ISBN 978-0-596-
52083-0
© 2009, Alan Beaulieu. Esta tradução é publicada e vendida com a permissão da O'Reilly Media,
Inc., detentora de todos os direitos para publicação e venda desta obra.
© Novatec Editora Ltda. 2010.
Todos os direitos reservados e protegidos pela Lei 9.610 de 19/02/1998.
É proibida a reprodução desta obra, mesmo parcial, por qualquer processo, sem prévia autorização,
por escrito, do autor e da Editora.
Editor: Rubens Prates
Tradução: Edgard B.Damiani
Revisão gramatical: Lia Gabriele Regius
Editoração eletrônica: Camila Kuwabata
ISBN: 978-85-7522-743-5
Histórico de edições impressas:
Novembro/2015 Terceira reimpressão
Dezembro/2013 Segunda reimpressão
Junho/2012 Primeira reimpressão
Janeiro/2010 Primeira edição
Novatec Editora Ltda.
Rua Luís Antônio dos Santos 110
02460-000 – São Paulo, SP – Brasil
Tel.: +55 11 2959-6529
E-mail: [email protected]
Site: www.novatec.com.br
Twitter: twitter.com/novateceditora
Facebook: facebook.com/novatec
LinkedIn: linkedin.com/in/novatec
Sumário

Prefácio
CAPÍTULO 1 ■ Uma breve introdução
Introdução aos bancos de dados
Sistemas de banco de dados não-relacional
O modelo relacional
Um pouco de terminologia
O que é SQL?
Classes de instruções SQL
SQL: uma linguagem não-procedural
Exemplos em SQL
O que é MySQL?
O que vem em seguida
CAPÍTULO 2 ■ Criando e populando um banco de dados
Criando um banco de dados MySQL
Usando a ferramenta de linha de comando mysql
Tipos de dados do MySQL
Dados do tipo caractere
Dados numéricos
Dados temporais
Criação de tabelas
Passo 1: Projeto
Passo 2: Re namento
Passo 3: Construindo instruções de esquema SQL
Povoando e modi cando tabelas
Inserindo dados
Atualizando dados
Exclusão de dados
Quando boas instruções se dão mal
Chaves primárias não-únicas
Chave estrangeira não-existente
Violações dos valores das colunas
Conversões de data inválidas
Esquema bank (bank schema)
CAPÍTULO 3 ■ Introdução a consultas
Funcionamento das consultas
Cláusulas de consulta
Cláusula select
Aliases de colunas
Removendo linhas duplicadas
Cláusula from
Tabelas
Vínculos de tabela
De nindo aliases de tabela
Cláusula where
Cláusulas group by e having
Cláusula order by
Ordenação ascendente versus ordenação descendente
Ordenação por meio de expressões
Ordenando por meio de referências numéricas
Teste seu conhecimento
CAPÍTULO 4 ■ Filtragem
Avaliação de condições
Usando parênteses
Usando o operador not
Construindo uma condição
Tipos de condições
Condições de igualdade
Condições de intervalo
Condições de adesão
Condições de correspondência
Null: aquela palavra de quatro letras
Teste seu conhecimento
CAPÍTULO 5 ■ Consultando múltiplas tabelas
O que é uma junção?
Produto cartesiano
Junções internas
A sintaxe ANSI de junção
Juntando três ou mais tabelas
Usando subconsultas como tabelas
Usando a mesma tabela duas vezes
Autojunções
Junções equivalentes versus não-equivalentes
Condições de junção versus condições de ltro
Teste seu conhecimento
CAPÍTULO 6 ■ Trabalhando com conjuntos
Introdução à teoria dos conjuntos
Teoria dos conjuntos na prática
Operadores de conjunto
Operador union
Operador intersect
Operador except
Regras das operações de conjunto
Ordenando resultados de consultas compostas
Precedência das operações de conjunto
Teste seu conhecimento
CAPÍTULO 7 ■ Geração, conversão e manipulação de dados
Trabalhando com strings
Geração de strings
Manipulação de strings
Trabalhando com dados numéricos
Realizando funções aritméticas
Controlando a precisão numérica
Tratando dados sinalizados
Trabalhando com dados temporais
Trabalhando com fusos horários
Gerando dados temporais
Manipulando dados temporais
Funções de conversão
Teste seu conhecimento
CAPÍTULO 8 ■ Agrupamentos e agregações
Conceitos de agrupamento
Funções de agregação
Grupos implícitos versus grupos explícitos
Contando valores distintos
Usando expressões
Como nulls são tratados
Gerando grupos
Agrupamento por uma coluna
Agrupamento por múltiplas colunas
Agrupamento por meio de expressões
Gerando resumos (rollups)
Condições de ltro de grupo
Teste seu conhecimento
CAPÍTULO 9 ■ Subconsultas
O que é uma subconsulta?
Tipos de subconsultas
Subconsultas não-correlatas
Subconsultas de linhas múltiplas e coluna única
Subconsultas de múltiplas colunas
Subconsultas correlatas
Operador exists
Manipulação de dados usando subconsultas correlatas
Quando usar subconsultas
Subconsultas como fontes de dados
Subconsultas em condições de ltro
Subconsultas como geradoras de expressões
Resumo das subconsultas
Teste seu conhecimento
CAPÍTULO 10 ■ Junções revisitadas
Junções externas
Junção externa esquerda versus junção externa direita
Junções externas de três tabelas
Autojunções externas
Junções cruzadas
Junções naturais
Teste seu conhecimento
CAPÍTULO 11 ■ Lógica condicional
O que é lógica condicional?
Expressão case
Expressões case pesquisadas
Expressões case simples
Exemplos de expressões case
Transformações de conjuntos-resultados
Agregação seletiva
Veri cando a existência
Erros de divisão por zero
Atualizações condicionais
Tratando valores null
Teste seu conhecimento
CAPÍTULO 12 ■ Transações
Bancos de dados multiusuários
Locking
Granularidade dos bloqueios
O que é uma transação?
Iniciando uma transação
Finalizando uma transação
Pontos de gravação de transações
Teste seu conhecimento
CAPÍTULO 13 ■ Índices e restrições
Índices
Criação de índices
Tipos de índices
Como os índices são usados
O lado negativo dos índices
Restrições
Criação de restrições
Restrições e índices
Restrições em cascata
Teste seu conhecimento
CAPÍTULO 14 ■ Views
O que são views?
Por que usar as views?
Segurança de dados
Agregação de dados
Escondendo a complexidade
Juntando dados particionados
Views atualizáveis
Atualizando views simples
Atualizando views complexas
Teste seu conhecimento
CAPÍTULO 15 ■ Metadados
Dados sobre dados
Information_schema
Trabalhando com metadados
Scripts de geração de esquemas
Veri cação de implantação
Geração dinâmica de SQL
Teste seu conhecimento
APÊNDICE A ■ Diagrama ER do banco de dados de exemplo
APÊNDICE B ■ Extensões do MySQL para a linguagem SQL
Extensões para a instrução select
Cláusula limit
Cláusula into out le
Instruções insert/update combinadas
Atualizações e exclusões ordenadas
Atualizações e exclusões em múltiplas tabelas
APÊNDICE C ■ Soluções dos exercícios
Prefácio

Linguagens de programação vêm e vão constantemente, e poucas


linguagens em uso atualmente têm raízes que remontam a mais de uma
década. Alguns exemplos são o Cobol, que ainda é amplamente usado em
ambientes de mainframe, e a linguagem C, que é muito popular no
desenvolvimento de sistemas operacionais e de servidores, além de ser
usada em sistemas embarcados.
SQL é a linguagem para geração, manipulação e recuperação de dados de
um banco de dados relacional. Uma das razões da popularidade dos
bancos de dados relacionais é que bancos relacionais corretamente
projetados podem tratar quantidades enormes de dados. Ao trabalhar
com grandes conjuntos de dados, a linguagem SQL é semelhante a uma
daquelas câmeras digitais incrementadas equipadas com lentes de zoom
potentes, pois você pode usar a SQL para observar grandes conjuntos de
dados, ou você pode dar um zoom em linhas individuais (ou em qualquer
nível entre esses extremos). Outros sistemas de gerenciamento de banco
de dados tendem a entregar os pontos quando colocados sob cargas
pesadas, pois seu foco é muito estreito (o zoom está travado na ampliação
máxima), e é por isso que falharam todas as tentativas de destronar os
bancos de dados relacionais e a SQL. Portanto, mesmo que a SQL seja
uma linguagem antiga, ela continuará nas redondezas por muito mais
tempo, e ainda tem uma longa carreira à sua frente.

Por que aprender SQL?


Se você pretende trabalhar com um banco de dados relacional, seja
desenvolvendo aplicações, realizando tarefas administrativas ou gerando
relatórios, você precisará saber como interagir com os dados em seu
banco de dados. Mesmo que você esteja usando uma ferramenta que gere
o código SQL para você, como uma ferramenta de relatórios, poderá
haver momentos em que você precisará ignorar o sistema de geração
automática para escrever suas próprias instruções SQL.
Aprender SQL tem o benefício adicional de forçá-lo a confrontar e
compreender as estruturas de dados usadas para armazenar informações
sobre sua própria empresa ou organização. Conforme vai se sentindo mais
confortável com as tabelas em seu banco de dados, você pode se colocar
na posição de propor modi cações ou adições a seu esquema de banco de
dados.

Por que usar este livro para aprender SQL?


A linguagem SQL é dividida em várias categorias. Instruções usadas para
criar objetos de banco de dados (tabelas, índices, restrições etc.) são
coletivamente conhecidas como instruções de esquema SQL (schema
statements). As instruções usadas para criar, manipular e recuperar dados
armazenados em um banco de dados são conhecidas como instruções de
dados SQL (data statements). Se você for um administrador, usará tanto os
esquemas SQL como as instruções de dados SQL. Se for um programador
ou autor de relatórios, provavelmente você precisará usar (ou receberá
permissão para usar) apenas instruções de dados SQL. Apesar de este
livro demonstrar várias instruções de esquema SQL, seu foco principal
será nas características de programação.
Com apenas alguns comandos, as instruções de dados SQL parecem
enganosamente simples. Em minha opinião, muitos dos livros disponíveis
sobre SQL ajudam a perpetuar essa noção, pois apenas tocam a superfície
daquilo que é possível fazer com a linguagem. No entanto, se você vai
trabalhar com SQL, cabe a você entender completamente os recursos da
linguagem e como características diferentes podem ser combinadas para
produzir resultados poderosos. Sinto que este é o único livro que fornece
uma abordagem detalhada da linguagem SQL sem o “benefício” adicional
de imitar um “peso de porta” (você sabe, aquelas “referências completas”
de 1.250 páginas que tendem a acumular poeira nas prateleiras dos
cubículos das pessoas).
Apesar de os exemplos deste livro rodarem em MySQL, Oracle Database e
SQL Server, tive que escolher um desses produtos para hospedar meu
banco de dados de exemplo e formatar os conjuntos de resultados
retornados pelas consultas de exemplo. Dos três, escolhi o MySQL porque
ele é obtido livremente, é fácil de instalar e simples de administrar. Para
aqueles leitores que estiverem usando um servidor diferente, peço que
baixem e instalem o MySQL e carreguem o banco de dados de exemplo
para que possam executar os exemplos e realizar testes com os dados.

Estrutura deste livro


Este livro é dividido em 15 capítulos e três apêndices:
O capítulo 1, Uma breve introdução, explora a história dos bancos de
dados computadorizados, incluindo o surgimento do modelo relacional e
da linguagem SQL.
O capítulo 2, Criando e populando um banco de dados, demonstra como
criar um banco de dados MySQL, como criar as tabelas usadas pelos
exemplos do livro e populá-las com dados.
O capítulo 3, Introdução a consultas, apresenta a instrução select, além
de demonstrar as cláusulas mais comuns (select, from, where).
O capítulo 4, Filtragem, demonstra os diferentes tipos de condições que
podem ser usados na cláusula where de uma instrução select, update ou
delete.

O capítulo 5, Pesquisando múltiplas tabelas, mostra como as consultas


podem utilizar múltiplas tabelas por meio de junção (join) de tabelas.
O capítulo 6, Trabalhando com conjuntos, aborda tudo o que diz respeito a
conjuntos de dados e como eles interagem dentro de consultas.
O capítulo 7, Geração de dados, conversão e manipulação, demonstra
várias funções nativas usadas para manipular ou converter dados.
O capítulo 8, Agrupamento e agregados, mostra como os dados podem ser
agregados e onde podem ser utilizados.
O capítulo 9, Subconsultas, apresenta a subconsulta (uma das minhas
favoritas) e mostra como e onde pode ser utilizada.
O capítulo 10, Junções revisitadas, explora mais a fundo os vários tipos de
junções de tabelas.
O capítulo 11, Lógica condicional, explora como a lógica condicional (ou
seja, if-then-else) pode ser utilizada em instruções select, insert,
update e delete.
O capítulo 12, Transações, apresenta transações e mostra como usá-las.
O capítulo 13, Índices e restrições, explora índices e restrições.
O capítulo 14, Views, mostra como construir uma interface que protege os
usuários da complexidade dos dados.
O capítulo 15, Metadados, demonstra a utilidade do dicionário de dados.
O apêndice A, Diagrama E-R do banco de dados de exemplo, mostra o
esquema de banco de dados usado por todos os exemplos deste livro.
O apêndice B, Extensões MySQL à linguagem SQL, demonstra algumas
das características não-ANSI interessantes da implementação SQL do
MySQL.
O apêndice C, Soluções dos exercícios, mostra as soluções dos exercícios
dos capítulos.

Convenções adotadas neste livro


As seguintes convenções tipográ cas foram adotadas neste livro:
Itálico
Usado em nomes de arquivos, nomes de diretórios e URLs. Também é
usado para ênfase e para indicar a primeira ocorrência de um termo
técnico.
Monoespaçado

Usado nos códigos-exemplo e para indicar palavras-chave SQL dentro


do texto.
Monoespaçado itálico
Usada para indicar termos de nidos pelo usuário.
CAIXA ALTA
Usada para indicar palavras-chave SQL dentro dos códigos-exemplo.
Monoespaçado negrito
Indica entradas do usuário em exemplos que demonstrem uma
interação. Também indica elementos de código enfatizados aos quais
você deve prestar atenção redobrada.
Indica uma dica, sugestão ou nota geral. Por exemplo, usarei
notas para indicar recursos novos e úteis do Oracle9i.
Indica um aviso ou alerta. Por exemplo, direi a você se uma
cláusula SQL em particular pode ter consequências adversas
se não forem usadas com cuidado.

Como entrar em contato conosco


Envie comentários e dúvidas sobre este livro para:
[email protected]

Temos uma página da web para este livro, onde incluímos a lista de
erratas, exemplos e qualquer outra informação adicional.
• Página da edição traduzida, em português
http://www.novatec.com.br/
• Página da edição original, em inglês
http://www.oreilly.com/catalog/9780596520830

Para obter mais informações sobre livros da Novatec, acesse nosso site
em:
http://www.novatec.com.br

Usando os códigos de exemplo


(texto da edição original, em inglês)
Este livro está aqui para ajudá-lo a fazer seu trabalho. Em geral, você pode
usar o código deste livro em seus programas e documentações. Não é
preciso entrar em contato conosco para obter permissão, a não ser que
você esteja reproduzindo uma parte signi cativa do código. Por exemplo,
escrever um programa que usa vários blocos de código presentes neste
livro não requer qualquer tipo de permissão. Vender ou distribuir um CD-
ROM composto por exemplos dos livros da O’Reilly requer permissão.
Responder uma pergunta citando este livro e o código de exemplo não
requer permissão. Incorporar uma quantidade signi cativa de código de
amostra deste livro na documentação de seu produto requer permissão.
Agradecemos, mas não exigimos, a atribuição de créditos. Uma atribuição
geralmente inclui o título, o autor, a editora e o ISBN. Por exemplo,
“Aprendendo SQL, por Alan Beaulieu. Copyright 2009, O’Reilly Media,
Inc., 978-0-596-52083-0”.
Se acreditar que seu uso de exemplos de código ultrapassa o uso
permitido ou a permissão concedida aqui, sinta-se à vontade para entrar
em contato pelo e-mail [email protected].

Agradecimentos
Gostaria de agradecer a minha editora, Mary Treseler, por ajudar a tornar
essa segunda edição uma realidade, e a Kevin Kline, Roy Owens, Richard
Sonen e Matthew Russell, que foram muito gentis ao revisar o livro para
mim durante os feriados de m de ano. Também gostaria de agradecer
aos leitores que atenciosamente enviaram questões, comentários e
correções. Por m, agradeço a minha esposa, Nancy, e a minhas lhas,
Michelle e Nicole, por seu encorajamento e inspiração.

Sobre o autor
Alan Beaulieu tem projetado, construído e implementado aplicações de
banco de dados customizadas há cerca de 15 anos. Atualmente possui sua
própria empresa de consultoria que se especializou em projetar bancos de
dados Oracle e dar suporte a serviços nos campos de nanças e
telecomunicações. Ao construir grandes bancos de dados para os
ambientes OLTP e OLAP, Alan utilizou funcionalidades do Oracle como
Consultas Paralelas, Particionamento, e Servidores Paralelos. Alan é
bacharel em Pesquisas Operacionais pela Escola de Engenharia da
Universidade de Cornell. Ele vive em Massachusetts com sua esposa e
duas lhas, e pode ser encontrado no endereço
[email protected].
CAPÍTULO 1
Uma breve introdução

Antes de arregaçarmos as mangas e começarmos a trabalhar, pode ser útil


apresentar alguns conceitos básicos de banco de dados, além de aprender
um pouco mais sobre a história do armazenamento e da recuperação de
dados em computadores.

Introdução aos bancos de dados


Um banco de dados é nada além de um conjunto de informações
relacionadas. Uma lista telefônica, por exemplo, é um banco de dados dos
nomes, números de telefones e endereços de todas as pessoas que moram
em uma região especí ca. Apesar de a lista telefônica ser um banco de
dados presente em todos os lugares e frequentemente utilizado, ela
apresenta os seguintes problemas:
• Encontrar o número de telefone de uma pessoa pode demorar muito
tempo, principalmente se a lista telefônica contiver um número muito
grande de entradas.
• Uma lista telefônica é indexada apenas pelo sobrenome e nome do
titular da conta telefônica – portanto, pesquisar o nome de todas as
pessoas que morem em um endereço em particular, apesar de ser
teoricamente possível, não é um exemplo de uso prático desse banco
de dados.
• A partir do momento em que a lista telefônica é impressa, as
informações tornam-se cada vez menos precisas, pois as pessoas
mudam-se de/para uma região, mudam seus números de telefone ou
mudam-se para outros endereços dentro de uma mesma região.
Os mesmos pontos fracos atribuídos à lista telefônica também podem ser
aplicados a qualquer sistema manual de armazenamento de dados, tais
como os registros de pacientes armazenados em um arquivo. Devido à
natureza inconveniente dos bancos de dados em papel, algumas das
primeiras aplicações desenvolvidas para os computadores foram sistemas
de banco de dados, que são mecanismos de armazenamento e
recuperação de dados computadorizados.
Por armazenar dados eletronicamente, em vez de armazenar em papel, um
sistema de banco de dados é capaz de recuperar dados mais rapidamente,
indexar dados de diversas maneiras e fornecer informações atualizadas
para sua comunidade de usuários.
Os primeiros sistemas de banco de dados gerenciavam dados
armazenados em tas magnéticas. Como normalmente existiam bem mais
tas magnéticas do que leitores de tas, os técnicos tinham a tarefa de
carregar e descarregar tas conforme os dados eram solicitados. Como os
computadores daquela época continham pouca memória, múltiplas
requisições dos mesmos dados geralmente exigiam que os dados fossem
lidos da ta várias vezes. Apesar desses sistemas de banco de dados
representarem uma melhoria signi cativa em relação aos bancos de dados
em papel, eles são um eco distante daquilo que é possível realizar com a
tecnologia atual. (Sistemas modernos de banco de dados podem gerenciar
terabytes de dados espalhados em inúmeros dispositivos de
armazenamento de acesso rápido, mantendo dezenas de gigabytes desses
dados em memória de alta velocidade, mas isso está um pouco além da
discussão atual.)

Sistemas de banco de dados não-relacional


Esta seção contém informações históricas sobre sistemas de
banco de dados pré-relacionais. Para os leitores ávidos por
mergulhar direto na linguagem SQL, sintam-se à vontade para
pular algumas páginas até a próxima seção.
Ao longo das primeiras décadas dos sistemas de banco de dados
computadorizados, dados foram armazenados e representados aos
usuários de diversas maneiras. Em um sistema de banco de dados
hierárquico, por exemplo, os dados são representados como uma ou mais
estruturas em árvore. A gura 1.1 mostra como os dados bancários
relacionados às contas bancárias de George Blake e Sue Smith poderiam
ser representados por meio de estruturas em árvore.
Tanto George quanto Sue possuem sua própria árvore contendo suas
contas e as transações dessas mesmas contas. O sistema de banco de
dados hierárquico fornece ferramentas para localizar uma árvore de um
cliente em particular e, então, percorrê-la para encontrar as contas e/ou
transações desejadas. Cada nó da árvore pode ter nenhum ou um nó-pai,
e nenhum, um ou mais nós- lhos. Essa con guração é conhecida como
uma hierarquia de pai único.
Outra abordagem comum, denominada sistema de banco de dados em
rede, expõe conjuntos de registros e conjuntos de ligações que de nem
relacionamentos entre diferentes registros. A gura 1.2 mostra como as
contas bancárias de George e Sue, previamente apresentadas, seriam
representadas em tal sistema.

Figura 1.1 – Exibição hierárquica dos dados de contas bancárias.


Figura 1.2 – Exibição em rede dos dados das contas.
Para encontrar as transações ligadas à conta de aplicações de Sue, você
precisaria realizar os seguintes passos:
1. Encontrar o registro de cliente de Sue Smith.
2. Seguir a conexão do registro de cliente de Sue Smith até sua lista de
contas.
3. Percorrer a cadeia de contas até encontrar a conta de aplicações.
4. Seguir a conexão do registro de aplicações até sua lista de transações.
Uma característica interessante dos sistemas de banco de dados em rede é
demonstrada pelo conjunto de registros de produtos na extrema direita da
gura 1.2. Perceba que cada registro de produto (conta-corrente, conta-
poupança etc.) aponta para uma lista de registros de conta que são
daquele tipo de produto. Registros de conta, portanto, podem ser
acessados de vários lugares (tanto dos registros de cliente quanto dos
registros de produtos), permitindo que um banco de dados em rede aja
como uma hierarquia de múltiplos pais.
Tanto o sistema hierárquico quanto o sistema em rede de banco de dados
estão ativos e em bom funcionamento atualmente, apesar de
normalmente existirem apenas no mundo dos mainframes. Além disso,
sistemas de banco de dados hierárquicos desfrutaram de um
renascimento no reino dos serviços de diretórios, tais como o Active
Directory, da Microsoft, e o Red Hat Directory Server, bem como por
meio da Extensible Markup Language (XML).
No entanto, iniciando na década de 1970, uma nova forma de representar
dados começou a ncar suas raízes – uma forma que era mais rigorosa e,
ao mesmo tempo, fácil de entender e de implementar.

O modelo relacional
Em 1970, o Dr. E. F. Codd, do laboratório de pesquisas da IBM, publicou
um artigo intitulado “A Relational Model of Data for Large Shared Data
Banks” (“Um modelo relacional de dados para bancos de dados
volumosos compartilhados”) que propunha a representação dos dados
como conjuntos de tabelas. Em vez de usar ponteiros para navegar entre
entidades relacionadas, dados redundantes seriam usados para conectar
registros em diferentes tabelas. A gura 1.3 mostra como as informações
das contas de George e Sue seriam representadas nesse contexto.
Há quatro tabelas na gura 1.3 representando as quatro entidades
discutidas até aqui: customer (cliente), product (produto), account (conta
bancária) e transaction (transação). Olhando ao longo do topo da tabela
customer (cliente), na gura 1.3, você verá três colunas: cust_id (que
contém o número identi cador do cliente), fname (que contém o prenome
do cliente) e lname (que contém o último sobrenome do cliente). A seguir
na tabela customer (cliente), olhando na horizontal, você verá duas linhas,
uma contendo os dados de George Blake e a outra contendo os de Sue
Smith. O número de colunas que uma tabela pode conter difere de
servidor para servidor, mas costuma ser grande o su ciente a ponto de
não ser uma preocupação (o Microsoft SQL Server, por exemplo, permite
até 1.024 colunas por tabela). O número de linhas que uma tabela pode
conter é mais uma questão de limites físicos (ou seja, quanto espaço em
disco está disponível) e de manutenibilidade (ou seja, o quão grande uma
tabela pode car antes de car difícil de trabalhar com ela) do que de
limitações do servidor de banco de dados.

Figura 1.3 – Exibição relacional dos dados de conta.


Cada tabela em um banco de dados relacional inclui informações que
identi cam de forma única uma linha em uma tabela (conhecida como
chave primária), além de informações adicionais necessárias para
descrever completamente a entidade. Olhando novamente para a tabela
customer, a coluna cust_id tem um número diferente para cada cliente;
George Blake, por exemplo, pode ser identi cado de forma inequívoca por
meio do ID de cliente #1. Nenhum outro cliente terá aquele identi cador
e nenhuma outra informação é necessária para localizar os dados de
George Blake na tabela customer.
Cada servidor de banco de dados fornece um mecanismo para
gerar conjuntos únicos de números a serem usados como
valores de chave primária, então você não precisa se preocupar
em rastrear quais números já foram utilizados.
Embora eu pudesse ter optado por usar a combinação das colunas fname e
lname como chave primária (uma chave primária consistindo em duas ou
mais colunas é denominada chave composta), pode muito bem acontecer
de duas ou mais pessoas com os mesmos nomes e sobrenomes possuírem
conta no banco. Portanto, optei por incluir a coluna cust_id na tabela
customer especi camente para ser usada como uma coluna de chave
primária.
Nesse exemplo, a escolha do par fname/lname como chave
primária poderia ser referida como uma chave natural, ao
passo que a escolha de cust_id poderia ser referida como uma
chave substituta. A decisão de empregar chaves naturais ou
substitutas é um tópico de amplo debate, mas nesse caso
particular a escolha é clara, já que o sobrenome de uma pessoa
pode mudar (tal como quando uma pessoa adota o sobrenome
do cônjuge), e as colunas da chave primária nunca devem ter
seus valores modi cados após a atribuição inicial.
Algumas das tabelas também contêm informações usadas para navegar
para outras tabelas; é aqui que entram os “dados redundantes”
mencionados anteriormente. Por exemplo, a tabela account inclui uma
coluna denominada cust_id, que contém o identi cador único do cliente
que abriu a conta, junto com a coluna denominada product_cd, que
contém o identi cador único do produto ao qual a conta corresponderá.
Essas colunas são conhecidas como chaves estrangeiras e servem ao
mesmo propósito das linhas que conectam as entidades nas versões
hierárquicas e em rede das informações de conta. Se estiver procurando
um registro de conta especí co e desejar saber mais informações sobre o
cliente que abriu a conta, você poderia pegar o valor da coluna cust_id e
usá-lo para encontrar a linha apropriada na tabela customer (esse processo
é conhecido, no jargão do banco de dados relacional, como uma junção
[join]; as junções são introduzidas no capítulo 3 e são analisadas em
profundidade nos capítulos 5 e 10).
Pode parecer um desperdício armazenar os mesmos dados várias vezes,
mas o modelo relacional é bem claro sobre quais dados redundantes
devem ser armazenados. Por exemplo, é apropriado para a tabela account
incluir uma coluna do identi cador único do cliente que abriu a conta,
mas não é adequado incluir também o nome e o sobrenome do cliente
nessa tabela. Se um cliente mudar de sobrenome, por exemplo, você vai
querer ter certeza de que existe apenas um lugar no banco de dados que
armazena o nome do cliente; de outra forma, o dado poderia ser
modi cado em um lugar, mas não no outro, prejudicando a
con abilidade dos dados no banco de dados. O lugar apropriado para
esses dados é a tabela customer, e apenas o valor de cust_id deveria ser
incluído em outras tabelas. Também não é indicado para uma coluna
conter vários segmentos de informação, tal como uma coluna de nome
que contenha tanto o nome quanto o sobrenome, ou uma coluna de
endereço que contenha informações de rua, cidade, estado e CEP. O
processo de re namento do projeto de banco de dados com o objetivo de
garantir que cada peça de informação independente esteja em apenas um
lugar (com exceção das chaves estrangeiras) é conhecido como
normalização.
Voltando às quatro tabelas da gura 1.3, você deve estar se perguntando
como poderia usá-las para encontrar as transações de conta corrente de
George Blake. Primeiro, você encontraria o identi cador único de George
Blake na tabela customer. Então, você encontraria a linha da tabela
account cuja coluna cust_id contenha o identi cador único do George e
cuja coluna product_cd corresponda à linha da tabela product cuja coluna
name seja igual a "CHK". Por m, você localizaria as linhas na tabela
transaction cuja coluna account_iid corresponda ao identi cador único
da tabela account. Isso pode soar complicado, mas você pode fazê-lo com
apenas um comando usando a linguagem SQL, como veremos em breve.

Um pouco de terminologia
Introduzi algumas terminologias novas nas seções anteriores, então talvez
agora seja o momento para algumas de nições formais. A tabela 1.1
mostra os termos que usaremos no restante do livro, junto com suas
de nições.
Tabela 1.1 – Termos e de nições
Termo De nição

Algo de interesse para a comunidade de usuários do banco de dados. Exemplos incluem


Entidade
clientes, partes, localizações geográficas etc.

Coluna Um dado armazenado em uma tabela.

Um conjunto de colunas que, coletivamente, descreve de forma completa uma entidade ou


Linha
alguma ação em uma entidade. Também chamada de registro.

Um conjunto de linhas, mantidas em memória volátil (não-persistente) ou em armazenamento


Tabela
permanente (persistente).

Conjunto-
Outro nome para uma tabela não-persistente, geralmente o resultado de uma consulta SQL.
resultado

Chave Uma ou mais colunas que podem ser usadas como um identificador único de cada linha em uma
primária tabela.

Chave Uma ou mais colunas que podem ser usadas em conjunto para identificar uma única linha em
estrangeira outra tabela.

O que é SQL?
Junto com a de nição de Codd do modelo relacional, ele propôs uma
linguagem chamada DSL/Alpha para manipular os dados em tabelas
relacionais. Logo após a publicação do artigo de Codd, a IBM
comissionou um grupo para construir um protótipo de suas ideias. Esse
grupo criou uma versão simpli cada da DSL/Alpha, que foi chamada de
SQUARE. Re namentos da SQUARE levaram a uma linguagem
denominada SEQUEL, que foi, nalmente, renomeada para SQL.
A SQL está entrando na meia-idade (assim como este autor...) e ela passou
por um grande número de mudanças ao longo do caminho. Em meados
da década de 1980, o American National Standars Institute (ANSI)
começou a trabalhar no primeiro padrão da linguagem SQL, que foi
publicado em 1986. Re namentos subsequentes levaram a novos
lançamentos do padrão SQL em 1989, 1992, 1999, 2003 e 2006. Junto com
os re namentos do núcleo da linguagem, novas funções foram
adicionadas a ela para incorporar funcionalidades orientadas a objeto,
entre outras coisas. O último padrão, SQL 2006, focou na integração entre
SQL e XML e de ne a linguagem chamada Xquery, que é usada para
consultar dados em documentos XML.
A SQL anda de mãos dadas com o modelo relacional porque o resultado
de uma consulta SQL é uma tabela (também chamada, nesse contexto, de
conjunto-resultado ou, em inglês, result set). Portanto, uma nova tabela
permanente pode ser criada em um banco de dados relacional
simplesmente armazenando o conjunto-resultado de uma consulta. De
maneira semelhante, uma consulta pode usar tanto as tabelas
permanentes quanto os conjuntos-resultados de outras consultas como
entradas (exploraremos esse fato em detalhes no capítulo 9).
Uma última nota: SQL não é um acrônimo de qualquer coisa (apesar de
muitas pessoas insistirem que signi ca “Structured Query Language”). Ao
se referir à linguagem, é igualmente aceitável dizer as letras
individualmente (ou seja, S. Q. L.) ou usar a palavra sequel (pronuncia-se
“síquel”).

Classes de instruções SQL


A linguagem SQL é dividida em várias partes distintas: as partes que
exploraremos neste livro incluem as instruções de esquema SQL, que são
usadas para de nir as estruturas de dados armazenadas em um banco de
dados; instruções de dados SQL, que são usadas para manipular as
estruturas de dados previamente de nidas com as instruções de esquema
SQL; e as instruções de transação SQL, que são usadas para iniciar,
encerrar e desfazer transações (explicadas no capítulo 12). Por exemplo,
para criar uma nova tabela em seu banco de dados, você poderia usar a
instrução de esquema SQL create table, ao passo que o processo de
população de sua nova tabela com dados necessitaria da instrução de
dados SQL insert.
Para lhe dar um aperitivo da aparência dessas instruções, aqui está uma
instrução de esquema SQL que cria uma tabela chamada corporation:
CREATE TABLE corporation
(corp_id SMALLINT,
name VARCHAR(30),
CONSTRAINT pk_corporation PRIMARY KEY (corp_id)
);

Essa instrução cria uma tabela com duas colunas, corp_id e name, com a
coluna corp_id identi cada como a chave primária da tabela.
Analisaremos os detalhes dessa instrução, tais como os diferentes tipos de
dados disponíveis no MySQL, no capítulo 2. A seguir, veja uma instrução
de dados SQL que insere uma linha na tabela corporation para a empresa
Acme Paper Corporation:
INSERT INTO corporation (corp_id, name)
VALUES (27, 'Acme Paper Corporation');

Essa instrução adiciona uma linha à tabela corporation com o valor 27 na


coluna corp_id e o valor Acme Paper Corporation na coluna name.
Finalmente, aqui está uma instrução select simples que retorna os dados
recém-criados:
mysql< SELECT name
-> FROM corporation
-> WHERE corp_id = 27;
+------------------------+
| name |
+------------------------+
| Acme Paper Corporation |
+------------------------+

Todos os elementos do banco de dados criados por meio de instruções de


esquema SQL são armazenados em um conjunto especial de tabelas
denominado dicionário de dados. Esses “dados sobre o banco de dados”
são conhecidos coletivamente como metadados e serão explorados no
capítulo 15. Assim como acontece com as tabelas que você mesmo cria, as
tabelas do dicionário de dados podem ser consultadas por meio de uma
instrução select, permitindo, assim, descobrir em tempo de execução
quais estruturas de dados existem atualmente no banco de dados. Por
exemplo, se pedirem a você que gere um relatório mostrando as novas
contas bancárias criadas no mês passado, você poderia codi car na unha
os nomes das colunas da tabela account que eram conhecidos por você no
momento em que gerou o relatório, ou você poderia consultar o
dicionário de dados para determinar o conjunto atual de colunas e gerar
dinamicamente o relatório cada vez em que a consulta fosse executada.
A maior parte deste livro concentra-se nas instruções de dados da
linguagem SQL, que consistem nos comandos select, update, insert e
delete. Instruções de esquema SQL serão demonstradas no capítulo 2, no
qual o banco de dados de exemplo, usado ao longo de todo o livro, será
gerado. Em geral, instruções de esquema SQL não necessitam de muita
discussão além de sua sintaxe, ao passo que instruções de dados SQL,
mesmo sendo em um número pequeno, oferecem inúmeras
oportunidades para estudos detalhados. Portanto, embora eu procure
apresentar várias instruções de esquema SQL, a maioria dos capítulos
deste livro concentra-se nas instruções de dados SQL.

SQL: uma linguagem não-procedural


Se você já trabalhou com linguagens de programação, deve estar
acostumado a de nir variáveis e estruturas de dados, a usar lógica
condicional (ou seja, if-then-else) e construções de laço (ou seja, do
while ... end) e a dividir seu código em blocos pequenos e reutilizáveis
(ou seja, objetos, funções, procedimentos). Seu código é enviado a um
compilador, e o executável que resulta disso faz exatamente (bem, nem
sempre exatamente) aquilo que você o programou para fazer.
Independentemente de você trabalhar com Java, C#, C, Visual Basic ou
alguma outra linguagem procedural, você tem total controle sobre o que o
programa faz.
Uma linguagem procedural de ne os resultados desejados e o
mecanismo, ou processo, pelo qual os resultados serão gerados.
Linguagens não-procedurais também de nem os resultados
desejados, mas o processo pelo qual os resultados são gerados é
deixado nas mãos de um agente externo.
Com SQL, no entanto, você precisará abrir mão de parte do controle ao
qual está acostumado, pois as instruções SQL de nem as entradas e
saídas necessárias, mas a forma como uma instrução é executada é
deixada para um componente do seu mecanismo de banco de dados
conhecido como otimizador. A função do otimizador é olhar para suas
instruções SQL e, considerando como suas tabelas estão con guradas e
quais índices estão disponíveis, decidir o caminho de execução mais
e ciente (bem, nem sempre o mais e ciente). A maioria dos mecanismos
de banco de dados permitirá que você in uencie as decisões do
otimizador especi cando dicas, tal como sugerir que um índice em
particular seja usado; a maioria dos usuários de SQL, no entanto, nunca
chegará a esse nível de so sticação e deixará tais ajustes nos para seus
administradores de banco de dados ou para especialistas em desempenho.
Com SQL, portanto, você não será capaz de escrever aplicações
completas. A menos que você esteja escrevendo um script simples para
manipular certos dados, você precisará integrar a SQL com sua linguagem
de programação favorita. Alguns fabricantes de banco de dados já zeram
isso para você, tais como a linguagem PL/SQL da Oracle, a linguagem de
procedimentos armazenados (stored procedures) do MySQL e a
linguagem Transact-SQL, da Microsoft.
Tabela 1.2 – Kits de ferramentas de integração de SQL
Linguagem Kit de ferramentas

Java JDBC (Java Database Connectivity; JavaSoft)

Rogue Wave SourcePro DB (ferramenta de terceiros para se conectar a bancos de dados Oracle,
C++
SQL Server, MySQL, Informix, DB2, Sybase, e PostgreSQL)

C/C++ Pro*C (Oracle), MySQL C API (código aberto) e DB2 Call Level Interface (IBM)

C# ADO.NET (Microsoft)

Perl Perl DBI

Python Python DB

Visual
ADO.NET (Microsoft)
Basic

Se você precisa apenas executar comandos SQL de forma interativa, cada


fabricante de banco de dados fornece pelo menos uma ferramenta simples
de linha de comando para enviar comandos SQL ao mecanismo de banco
de dados e para inspecionar os resultados. A maioria dos fabricantes
também fornece uma ferramenta grá ca que inclui duas janelas, uma
mostrando seus comandos SQL e a outra mostrando os resultados de
seus comandos SQL. Como os exemplos deste livro serão executados em
um banco de dados MySQL, usarei a ferramenta de linha de comando
mysql, incluída como parte da instalação do MySQL, para executar os
exemplos e para formatar os resultados.

Exemplos em SQL
Anteriormente neste capítulo, prometi que mostraria a você uma
instrução SQL que retornaria todas as transações da conta corrente de
George Blake. Sem mais enrolação, aqui está ela:

Sem entrar muito nos detalhes por enquanto, essa consulta identi ca a
linha na tabela individual relativa a George Blake e a linha na tabela
product relativa ao produto "CHK", encontra a linha na tabela account
relativa a essa combinação individual/product e retorna quatro colunas da
tabela transaction relacionadas a todas as transações ocorridas nessa
conta. Se por acaso você souber que o ID de George Blake é 8 e que a
conta é designada pelo código ‘CHK’, você pode simplesmente encontrar a
conta corrente de George Blake na tabela account baseado no ID de
cliente e usar o ID de conta para encontrar as transações apropriadas:
SELECT t.txn_id, t.txn_type_cd, t.txn_date, t.amount
FROM account a
INNER JOIN transaction t ON t.account_id = a.account_id
WHERE a.cust_id = 8 AND a.product_cd = 'CHK';

Falarei sobre os conceitos nessas consultas (e muito mais) nos capítulos


seguintes, mas eu quis pelo menos mostrar qual é a aparência geral delas.
As consultas anteriores contêm três cláusulas diferentes: select, from e
where. Quase toda consulta que você encontrará inclui pelo menos essas
três cláusulas, apesar de haver muitas outras que podem ser usadas para
propósitos mais especializados. O papel de cada uma dessas três cláusulas
é demonstrado a seguir:
SELECT /* selecione uma ou mais coisas */ ...
FROM /* de um ou mais lugares */ ...
WHERE /* onde uma ou mais condições se apliquem */ ...

A maioria das implementações de SQL trata qualquer texto


entre as tags /* e */ como comentários.
Ao construir sua consulta, sua primeira tarefa geralmente será determinar
qual ou quais tabelas serão necessárias e, então, adicioná-las à sua
cláusula from. Em seguida, você precisará adicionar condições à sua
cláusula where para ltrar os dados dessas tabelas que não lhe interessam.
Por m, você decidirá quais colunas das diferentes tabelas precisam ser
recuperadas e irá adicioná-las à sua cláusula select. Aqui está um
exemplo simples que mostra como você encontraria todos os clientes com
o sobrenome “Smith”:
SELECT cust_id, fname
FROM individual
WHERE lname = 'Smith';

Essa consulta pesquisa a tabela individual por todas as linhas cuja


coluna lname corresponda à string ‘Smith’ e retorna as colunas cust_id e
fname dessas linhas.

Além de consultar seu banco de dados, muito provavelmente você se verá


envolvido no processo de popular e modi car seus dados. Aqui está um
exemplo de como você poderia inserir uma nova linha na tabela product:
INSERT INTO product (product_cd, name)
VALUES ('CD', 'Certificate of Depysit')

Opa, parece que você digitou a palavra “Deposit” errado. Sem problemas
– você pode arrumá-la com uma instrução update:
UPDATE product
SET name = 'Certificate of Deposit'
WHERE product_cd = 'CD';

Perceba que a instrução update também contém uma cláusula where,


exatamente como a instrução select. Isso acontece porque uma instrução
update deve identi car as linhas que serão modi cadas – nesse caso, você
está especi cando que apenas as linhas cuja coluna product_cd
corresponda à string ‘CD’ deverão ser modi cadas. Como a coluna
product_cd é a chave primária da tabela product, você deve esperar que
sua instrução update modi que exatamente uma linha (ou nenhuma,
caso o valor não exista na tabela). Sempre que você executar uma
instrução de dados SQL, você receberá um feedback do mecanismo de
banco de dados sobre quantas linhas foram afetadas por sua instrução. Se
você estiver usando uma ferramenta interativa, como a ferramenta de
linha de comando do MySQL mencionada anteriormente, você receberá
um feedback relativo a quantas linhas foram:
• Retornadas por sua instrução select
• Criadas por sua instrução insert
• Modi cadas por sua instrução update
• Removidas por sua instrução delete
Se você estiver usando uma linguagem procedural com um dos kits de
ferramentas mencionados anteriormente, o kit de ferramentas incluirá
uma chamada para obter essa informação após a execução de sua
instrução SQL. Em geral, é uma boa ideia veri car essa informação para
ter certeza de que sua instrução não fez algo inesperado (como quando
você se esquece de colocar uma cláusula where na instrução delete e
acaba apagando todas as linhas da tabela!).

O que é MySQL?
Bancos de dados relacionais estão disponíveis comercialmente há pelo
menos duas décadas. Alguns dos produtos comerciais mais consolidados
e difundidos incluem:
• Oracle Database, da Oracle Corporation
• SQL Server, da Microsoft
• DB2 Universal Database, da IBM
• Sybase Adaptive Server, da Sybase
Todos esses servidores de banco de dados fazem aproximadamente a
mesma coisa, apesar de alguns deles estarem melhores equipados para
executar bancos de dados muito grandes ou com taxas de transferência
muito altas. Outros são melhores para tratar objetos, arquivos muito
grandes ou arquivos XML, e assim por diante. Independentemente desses
fatores, todos eles fazem um bom trabalho em se conformarem ao padrão
ANSI SQL mais recente. Isso é uma coisa boa, e cito isso para mostrar a
você que é possível escrever instruções SQL que serão executadas em
qualquer uma dessas plataformas com pouca ou nenhuma modi cação.
Em paralelo aos servidores de banco de dados comerciais, tem havido
muita atividade na comunidade open source nos últimos cinco anos, com
o objetivo de criar alternativas viáveis aos servidores comerciais. Dois dos
servidores open source mais comumente utilizados são o PostgreSQL e o
MySQL. O website do MySQL (http://www.mysql.com) atualmente a rma
que ele já foi instalado em cerca de 10 milhões de computadores, é
disponibilizado gratuitamente, e descobri que ele é extremamente simples
de baixar e instalar. Por essas razões, decidi que todos os exemplos deste
livro seriam executados em um banco de dados MySQL1 (versão 5.1), e
que a ferramenta de linha de comando mysql seria usada para formatar os
resultados das consultas. Mesmo que você já esteja usando outro servidor
e não planeje usar o MySQL, eu o encorajo a instalar a última versão do
servidor MySQl, carregar o esquema e os dados de exemplo e a
experimentar com os dados e os exemplos deste livro.
No entanto, tenha em mente a seguinte advertência:
Este não é um livro sobre a implementação SQL do MySQL.
Em vez disso, este livro foi planejado para ensiná-lo a construir instruções
SQL que executarão no MySQL sem quaisquer modi cações, e que
executarão em versões recentes do Oracle Database, Sybase Adaptive
Server e SQL Server com pouca ou nenhuma modi cação.
Para manter o código deste livro o mais neutro possível, vou me abster de
demonstrar algumas das coisas interessantes que os implementadores da
linguagem SQL do MySQL decidiram fazer e que não podem ser feitas em
outras implementações de bancos de dados. Em vez disso, o apêndice B
cobrirá algumas dessas funções tendo em vista os leitores que planejam
continuar usando o MySQL.
O que vem em seguida
O objetivo geral dos próximos quatro capítulos é introduzir as instruções
de dados SQL, com uma ênfase especial nas três cláusulas principais da
instrução select. Além disso, você verá muitos exemplos que usam o
esquema de banco de dados (apresentado no próximo capítulo), que será
usado em todos os exemplos do livro. Espero que a familiaridade com um
único banco de dados permita a você captar a essência de um exemplo
sem ter que parar toda vez para examinar as tabelas que estão sendo
utilizadas. Se porventura o trabalho com um mesmo conjunto de tabelas
se tornar tedioso, sinta-se à vontade para ampliar o banco de dados com
tabelas adicionais, ou invente seu próprio banco de dados, com o qual
possa fazer experimentos.
Depois que você adquirir um bom traquejo das questões básicas, os
capítulos restantes irão mergulhar a fundo em conceitos adicionais, a
maioria dos quais são independentes uns dos outros. Portanto, se você se
sentir confuso com um tópico, você poderá seguir adiante e retornar mais
tarde para revisar um capítulo. Após a conclusão da leitura do livro e a
exploração de todos os exemplos, você estará bem encaminhado para se
tornar um pro ssional tarimbado em SQL.
Para os leitores interessados em aprender mais sobre bancos de dados
relacionais, sobre a história dos sistemas de banco de dados
computadorizados ou sobre o tanto da linguagem SQL que foi abordado
nesta breve introdução, aqui estão alguns recursos que vale a pena
conferir:
• Database in Depth: Relational Theory for Practitioners
(http://oreilly.com/catalog/9780596100124/) (O’Reilly), de C. J. Date.
• An Introduction to Database Systems, Eighth Edition (Addison-Wesley),
de C. J. Date.
• The Database Relational Model: A Retrospective Review and Analysis: A
Historical Account and Assessment of E. F. Codd’s Contribution to the
Field of Database Technology (Addison-Wesley), de C. J. Date.
• http://en.wikipedia.org/wiki/Database_management_system
• http://www.mcjones.org/System_R/
1 NOTA: Visando uma maior longevidade da Segunda Edição, a versão em inglês do livro utilizou
a versão 6.0 do MySQL, que, à época, encontrava-se no estágio Alpha de desenvolvimento. No
entanto, tal versão nunca passou do estágio Alpha e, devido a uma reestruturação no ciclo de
desenvolvimento, ela foi completamente removida do site o cial do MySQL. Por isso,
decidimos utilizar a versão 5.1 no livro em português, por ser uma versão estável e que funciona
perfeitamente com todos os exemplos do livro.
CAPÍTULO 2
Criando e populando um banco de dados

Este capítulo fornecerá as informações necessárias para criar seu primeiro


banco de dados e para criar as tabelas e dados associados utilizados pelos
exemplos deste livro. Você também aprenderá sobre vários tipos de dados
e verá como criar tabelas com eles. Como os exemplos deste livro são
executados em um banco de dados MySQL, este capítulo é, de certa
forma, direcionado às características e sintaxe do MySQL, mas a maioria
dos conceitos são aplicáveis a qualquer servidor.

Criando um banco de dados MySQL


Se você já conta com um banco de dados MySQL disponível para uso,
você pode pular as instruções de instalação e iniciar pelas instruções da
tabela 2.1. Tenha em mente, no entanto, que este livro presume que você
esteja usando a versão 5.1 ou posterior do MySQL – por isso, talvez seja
necessário considerar a atualização de seu servidor ou a instalação de
outro servidor, caso você esteja usando uma versão mais antiga.
As seguintes instruções mostram os passos mínimos necessários para
instalar um servidor MySQL 5.1 em um computador Windows:
1. Vá até a página de download do MySQL Database Server em
http://dev.mysql.com/downloads. Se desejar especi camente a versão 5.1,
o endereço completo é http://dev.mysql.com/downloads/mysql/5.1.html.
2. Baixe o pacote Windows Essentials (x86), que inclui apenas as
ferramentas mais comumente usadas.
3. Quando solicitado se “Deseja executar ou salvar esse arquivo?”, clique
em Executar.
4. A caixa de diálogo MySQL 5.1 – Setup Wizard aparecerá. Clique em
Next.
5. Ative a opção Typical e clique em Next.
6. Clique em Install.
7. A caixa de diálogo MySQL Enterprise será exibida. Clique em Next
duas vezes.
8. Quando a instalação for concluída, certi que-se de que a opção
“Con gure the MySQL Server now” esteja marcada e clique em Finish.
Isso abrirá o Con guration Wizard.
9. Quando o Con guration Wizard abrir, clique em Next, ative a opção
Standard Con guration e clique em Next novamente. Selecione as
opções “Install as Windows Service” e “Include Bin Directory in
Windows PATH”. Clique em Next.
10. Selecione a opção Modify Security Settings, insira uma senha para o
usuário root (certi que-se de anotar a senha, pois você precisará dela
em breve!) e clique em Next.
11. Clique em Execute.
A essa altura, se tudo correu bem, o servidor MySQL está instalado e
rodando. Se não estiver, sugiro que você desinstale o servidor e leia o guia
“Troubleshooting a MySQL Installation Under Windows” (que você pode
encontrar em http://dev.mysql.com/doc/refman/5.1/en/windows-
troubleshooting.html).
Se você desinstalou uma versão mais antiga do MySQL antes de
carregar a versão 5.1, talvez seja preciso fazer uma faxina extra (eu tive
que limpar algumas entradas antigas do Registro) antes de conseguir
executar corretamente o Con guration Wizard.
Em seguida, será necessário abrir uma janela de comando do Windows,
executar a ferramenta mysql e criar seu banco de dados e usuário do
banco de dados. A tabela 2.1 descreve os passos necessários. No passo 5,
sinta-se à vontade para escolher sua própria senha para o usuário
lrngsql, em vez de usar “xyz” (mas não se esqueça de anotá-la!).

Tabela 2.1 – Criando o banco de dados de exemplo


Passo Descrição Ação

Abra a caixa de diálogo Executar a Clique em Iniciar e, em seguida,


1
partir do menu Iniciar em Executar.
2 Abra uma janela de comando Digite cmd e clique em OK
3 Faça o login no MySQL como root mysql -u root -p

Crie um banco de dados para os


4 create database bank;
dados de exemplo
Crie o usuário do banco de dados grant all privileges on bank.*
5. lrngsql com privilégios totais sobre o to 'lrngsql'@'localhost'
banco de dados bank identified by 'xyz';

6 Saia da ferramenta mysql quit;

Faça o login no MySQL como o


7 mysql -u lrngsql -p;
usuário lrngsql
8 Conecte-se ao banco de dados bank use bank;

Agora você tem um servidor MySQL, um banco de dados e um usuário


do banco de dados; – a única coisa que falta fazer é criar as tabelas do
banco de dados e populá-las com dados de exemplo. Para tanto, faça o
download do script em http://examples.oreilly.com/learningsql/ e execute-o
a partir do utilitário mysql. Se você salvou o arquivo como
c:\temp\LearningSQLExample.sql, será preciso fazer o seguinte:
1. Se você efetuou o logout na ferramenta mysql, repita os passos 7 e 8
da tabela 2.1.
2. Digite source c:/temp/LearningSQLExample.sql; e pressione Enter.
Você deve ter agora um banco de dados funcional, populado com todos os
dados necessários para os exemplos deste livro.

Usando a ferramenta de linha de comando mysql


Sempre que você invocar a ferramenta de linha de comando mysql, você
pode especi car o nome de usuário e o banco de dados que será usado,
tal como no exemplo seguinte:
mysql -u lrngsql -p bank
Isso irá poupá-lo de ter que digitar use bank; toda vez que iniciar a
ferramenta. A senha será solicitada, e então o prompt mysql> surgirá, por
meio do qual será possível escrever instruções SQL e veri car os
resultados. Por exemplo, se você quiser saber a data e o horário atuais, é
possível escrever a seguinte consulta:
mysql> SELECT now();
+---------------------+
| now() |
+---------------------+
| 2008-02-19 16:48:46 |
+---------------------+
1 row in set (0.01 sec)

A função now() é uma função nativa do MySQL que retorna a data e o


horário atuais. Como você pode ver, a ferramenta de linha de comando
mysql formata os resultados de suas consultas dentro de um retângulo
limitado por caracteres +, - e |. Após retornar todos os resultados (nesse
caso, há apenas uma única linha de resultado), a ferramenta de linha de
comando mysql mostra quantas linhas foram retornadas e quanto tempo a
instrução SQL levou para ser executada.

Sobre a omissão da cláusula from


No caso de alguns servidores de banco de dados, não será possível
executar uma consulta sem uma cláusula from que nomeie pelo menos
uma tabela. O Oracle Database é um servidor comumente utilizado
para o qual essa a rmação é verdadeira. Para os casos em que você
queira apenas chamar uma função, o Oracle fornece uma tabela
chamada dual, que consiste em uma única coluna denominada dummy
e que contém uma única linha de dados. Para se manter compatível
com o Oracle Database, o MySQL também fornece uma tabela dual. A
consulta anterior, que determinava a data e o horário atuais, poderia,
portanto, ser escrita assim:
mysql> SELECT now()
FROM dual;
+---------------------+
| now() |
+---------------------+
| 2005-05-06 16:48:46 |
+---------------------+
1 row in set (0.01 sec)

Se você não estiver usando o Oracle e não tiver a necessidade de


manter seu código compatível com ele, você poderá ignorar por
completo a tabela dual e apenas usar a cláusula select sem a cláusula
from.

Quando você tiver terminado de usar a ferramenta de linha de comando


mysql, apenas digite quit; ou exit; para retornar ao prompt de comando
do Windows.

Tipos de dados do MySQL


Em geral, todos os servidores de banco de dados mais difundidos têm a
capacidade de armazenar os mesmos tipos de dados, tais como strings,
datas e números. Eles costumam diferir nos tipos de dados
especializados, como documentos XML, textos muito grandes ou
documentos binários. Como este é um livro introdutório de SQL, e como
98% das colunas que você encontrará serão de tipos de dados simples,
este livro aborda apenas os tipos de dados de caracteres, data e numéricos.

Dados do tipo caractere


Dados do tipo caractere podem ser armazenados como strings de
tamanho xo ou variável: a diferença é que strings de tamanho xo são
preenchidas à direita com espaços e sempre consomem o mesmo número
de bytes, e strings de tamanho variável não são preenchidas à direita com
espaços e nem sempre consomem a mesma quantidade de bytes. Ao
de nir uma coluna de caracteres, você deverá especi car o tamanho
máximo que uma string a ser armazenada na coluna poderá ter. Por
exemplo, se você deseja armazenar strings que contenham até 20
caracteres de extensão, você poderia usar uma das seguintes de nições:
char(20) /* tamanho fixo */
varchar(20) /* tamanho variável */

O tamanho máximo de colunas do tipo char é atualmente de 255 bytes,


enquanto colunas varchar podem ter até 65.535 bytes. Se você precisa
armazenar strings mais longas (como e-mails, documentos XML etc.),
você precisará usar um dos tipos de dados de texto (mediumtext e
longtext), que abordarei mais tarde nesta seção. Em geral, você deve usar
o tipo char quando todas as strings que serão armazenadas na coluna têm
o mesmo tamanho, como abreviações de estados, e o tipo varchar quando
as strings armazenadas na coluna têm tamanho variável. Tanto char
quanto varchar são usadas de forma semelhante em todos os principais
servidores de banco de dados.
O Oracle Database é uma exceção quando se fala em usar o tipo
varchar. Usuários do Oracle devem usar o tipo varchar2 quando
forem de nir colunas de caracteres com tamanho variável.

Conjuntos de caracteres
Para as línguas que usam o alfabeto latino, como o inglês e o português,
há um número su cientemente pequeno de caracteres usados de forma a
se necessitar de apenas um byte para armazenar cada um dos caracteres.
Outras línguas, como o japonês e o coreano, contêm um número muito
grande de caracteres, necessitando, assim, de múltiplos bytes de
armazenamento para cada um deles. Tais conjuntos de caracteres são,
portanto, denominados conjuntos de caracteres multibytes.
O MySQL pode armazenar dados usando vários conjuntos de caracteres,
tanto de byte único quanto multibyte. Para exibir os conjuntos de
caracteres suportados por seu servidor, você pode usar o comando show
da seguinte maneira:

Se o valor da quarta coluna, maxlen, for maior do que 1, o conjunto de


caracteres é um conjunto multibyte.
Quando instalei o servidor MySQL, o conjunto de caracteres latin1 foi
automaticamente selecionado como o conjunto de caracteres padrão. No
entanto, você pode optar por usar um conjunto de caracteres diferente
para cada coluna de caracteres em seu banco de dados, e pode até
armazenar conjuntos de caracteres diferentes dentro de uma mesma
tabela. Para escolher um conjunto de caracteres diferente do padrão ao
de nir uma coluna, basta nomear um dos conjuntos de caracteres
suportados após a de nição do tipo, como em:
varchar(20) character set utf8

Com o MySQL, você também pode atribuir um conjunto de caracteres


padrão para todo o seu banco de dados:
create database foreign_sales character set utf8;

Apesar de isso ser tudo que gostaria de discutir a respeito de conjuntos de


caracteres em um livro introdutório, há muito mais sobre o tópico de
internacionalização do que é mostrado aqui. Se você pretende trabalhar
com vários conjuntos de caracteres ou com conjuntos não-familiares,
talvez você queira consultar um livro como Java Internationalization, de
Andy Deitsch e David Czarnecki
(http://oreilly.com/catalog/9780596000196/)(O’Reilly), ou Unicode
Demysti ed: A Practical Programmer’s Guide to the Encoding Standard, de
Richard Gillam (Addison-Wesley).

Dados de texto
Se for necessário armazenar dados que possam exceder o limite de 64 KB
das colunas varchar, você precisará usar um dos tipos de texto.
A tabela 2.2 mostra os tipos de texto disponíveis e seus tamanhos
máximos.
Tabela 2.2 – Tipos de texto do MySQL
Tipo de texto Número máximo de bytes

Tinytext 255

Text 65.535

Mediumtext 16.777.215

Longtext 4.294.967.295

Ao escolher utilizar um dos tipos de texto, tenha em mente o seguinte:


• Se os dados que estiverem sendo carregados em uma coluna de texto
excederem o tamanho máximo daquele tipo, os dados serão
truncados.
• Espaços iniciais não serão removidos quando dados são carregados na
coluna.
• Ao usar colunas de texto para ordenar ou agrupar, apenas os
primeiros 1.024 bytes serão utilizados, apesar de esse limite poder ser
aumentado, caso haja necessidade.
• Os diferentes tipos de texto são exclusivos do MySQL. O SQL Server
tem apenas um tipo de texto para dados longos de caracteres,
enquanto o DB2 e o Oracle usam um tipo de dados chamado clob, um
acrônimo de Character Large Object.
• Agora que o MySQL permite até 65.535 bytes em colunas varchar (elas
eram limitadas a 255 bytes na versão 4), não há nenhuma razão em
particular para usar o tipo de texto tinytext.
Se você estiver criando uma coluna para entrada de dados livres, como
uma coluna de notas que armazena dados sobre as interações dos clientes
com o serviço de atendimento ao cliente de sua empresa, o varchar
provavelmente será o mais adequado. No entanto, se você estiver
armazenando documentos, você deve escolher o tipo mediumtext ou
longtext.

O Oracle Database permite até 2.000 bytes para colunas char e


4.000 bytes para colunas varchar2. O SQL Server armazena até
8.000 bytes tanto para o tipo char quanto para o tipo varchar.

Dados numéricos
Apesar de parecer razoável ter apenas um tipo de dados numérico
denominado “numérico”, existem, na verdade, vários tipos de dados
numéricos que re etem as várias maneiras nas quais os números são
utilizados, tal como ilustrado aqui:
Uma coluna indicando se o pedido de um cliente foi enviado
Esse tipo de coluna, referenciado como um Booleano, poderia conter
um 0 para indicar false e um 1 para indicar true.
Uma chave primária gerada pelo sistema para uma tabela de transações
Esse dado iniciaria geralmente em 1 e cresceria em incrementos de um
até um número potencialmente muito grande.
Um número de um item em uma cesta de compras eletrônica de um cliente
Os valores para esse tipo de coluna seriam números inteiros positivos
entre 1 e, no máximo, 200 (para compradores compulsivos).
Dados posicionais para uma máquina perfuradora de circuito impresso
Dados cientí cos ou de manufatura de alta precisão normalmente
necessitam de uma precisão de oito casas decimais.
Para tratar esses tipos de dados (entre outros), o MySQL tem diferentes
tipos de dados numéricos. Os tipos de dados mais comuns são aqueles
usados para armazenar números inteiros. Ao especi car um desses tipos,
você também deve especi car que os dados são unsigned (sem sinal),
indicando ao servidor que todos os dados armazenados nessa coluna
serão maiores ou iguais a zero. A tabela 2.3 mostra cinco tipos de dados
diferentes utilizados no armazenamento de números inteiros.
Tabela 2.3 – Tipos inteiros do MySQL1
Tipo Escopo com sinal Escopo sem sinal

Tinyint −128 a 127 0 a 255

Smallint −32.768 a 32.767 0 a 65.535

Mediumint −8.388.608 a 8.388.607 0 a 16.777.215

Int −2.147.483.648 a 2.147.483.647 0 a 4.294.967.295

Bigint −9.223.372.036.854.775.808 a 9.223.372.036.854,775.807 0 a 18.446.744.073.709.551.615

Quando você cria uma coluna usando um dos tipos inteiros, o MySQL
alocará uma quantidade de espaço apropriado para armazenar os dados,
variando de um byte para um tinyint até oito bytes para um bigint.
Portanto, você deve tentar escolher um tipo que seja grande o su ciente
para armazenar o maior número previsto para a coluna, sem desperdiçar
espaço de armazenamento de forma desnecessária.
Para números de ponto utuante (como 3,1415927), você deve optar por
um dos tipos numéricos mostrados na tabela 2.4.
Tabela 2.4 – Tipos de ponto utuante do MySQL
Tipo Escopo numérico

Float(p,e) −3,402823466E+38 a −1,175494351E-38 e de 1,175494351E-38 a 3,402823466E+38

-1,7976931348623157E+308 a -2,2250738585072014E-308 e de 2,2250738585072014E-


Double(p,e)
308 a 1,7976931348623157E+308

Ao usar um tipo de ponto utuante, você pode especi car uma precisão
(o número total de dígitos permitidos tanto à esquerda quanto à direita
do ponto decimal) e uma escala (o número de dígitos permitidos à direita
do ponto decimal), mas isso não é obrigatório. Esses valores estão
representados na tabela 2.4 como p e e. Se você especi car uma precisão e
uma escala para sua coluna de ponto utuante, lembre-se de que os
dados armazenados na coluna serão arredondados se o número de dígitos
exceder a escala e/ou a precisão da coluna. Por exemplo, uma coluna
de nida como float(4,2) armazenará um total de quatro dígitos, dois à
esquerda e dois à direita do ponto decimal. Portanto, uma coluna desse
tipo trataria os números 27.44 e 8.19 sem problemas, mas o número
17.8675 seria arredondado para 17.87, e a tentativa de armazenar o número
178.375 na sua coluna oat(4, 2) geraria um erro.
Tais como os tipos inteiros, as colunas de ponto utuante podem ser
de nidas como unsigned, mas essa designação apenas evita que números
negativos sejam armazenados, não modi cando o escopo dos dados que
podem ser armazenados na coluna.

Dados temporais
Além de strings e números, muito provavelmente você também se verá
trabalhando com datas e/ou horários. Esse tipo de dados é conhecido
como temporal, e alguns exemplos de dados temporais em um banco de
dados incluem:
• A data futura em que se espera a ocorrência de um evento especí co,
como o envio do pedido de um cliente.
• A data em que um pedido de um cliente foi enviado.
• A data e o horário em que um usuário modi cou uma linha em
particular em uma tabela.
• A data de nascimento de um funcionário.
• O ano correspondente a uma linha de uma tabela de fatos
vendas_anuais em um data warehouse (depósito de dados).

• O tempo necessário para armar a ação em uma linha de montagem


de automóveis.
O MySQL inclui tipos de dados que tratam todas essas situações. A tabela
2.5 mostra os tipos de dados temporais suportados pelo MySQL.
Tabela 2.5 – Tipos de dados temporais
Tipo Formato padrão Valores permitidos

Date AAAA-MM-DD 1000-01-01 a 9999-12-31

Datetime AAAA-MM-DD HH:MI:SS 1000-01-01 00:00:00 a 9999-12-31 23:59:59

Timestamp AAAA-MM-DD HH:MI:SS 1970-01-01 00:00:00 a 2037-12-31 23:59:59

Year AAAA 1901 a 2155

Time HHH:MI:SS −838:59:59 a 838:59:59

Apesar de os servidores de banco de dados armazenarem dados temporais


de diversas maneiras, o propósito da string de formato (segunda coluna
da tabela 2.5) é mostrar como os dados serão representados quando
retornados, junto com a forma em que uma string de data deve ser
construída ao inserir ou atualizar uma coluna temporal. Portanto, se você
quisesse inserir a data 23 de março de 2005 em uma coluna de data
usando o formato AAAA-MM-DD, você usaria a string ‘2005-03-23’. O
capítulo 7 explora como os dados temporais são construídos e
apresentados.
Cada servidor de banco de dados permite um escopo diferente de
datas em colunas temporais. O Oracle Database aceita datas de
4172 A.C. até 9999, enquanto o SQL Server trabalha com datas que
vão de 1753 até 9999 (a menos que você esteja usando o novo tipo
de dados datetime2 do SQL Server, que permite datas entre 1 e
9999). O MySQL ca entre o Oracle e o SQL Server, podendo
armazenar datas entre 1000 A.C. e 9999. Apesar disso não fazer
diferença para a maioria dos sistemas que registram eventos atuais e
futuros, é importante ter isso em mente caso você esteja
armazenando datas históricas.
A tabela 2.6 descreve os vários componentes dos formatos de data
mostrados na tabela 2.5.
Tabela 2.6 – Componentes dos formatos de data
Componente De nição Escopo

AAAA Ano, incluindo século 1000 a 9999

MM Mês 01 (Janeiro) a 12 (Dezembro)

DD Dia 01 a 31

HH Hora 00 a 23

HHH Horas (transcorridas) −838 a 838

MI Minuto 00 a 59

SS Segundo 00 a 59

Aqui está como os vários tipos temporais seriam usados para


implementar os exemplos mostrados anteriormente:
• Colunas que armazenam a data futura esperada de envio do pedido
de um cliente e a data de aniversário de um funcionário usariam o
tipo date, já que é desnecessário saber a que horas uma pessoa nasceu
e irreal agendar um envio futuro com precisão de segundos.
• Uma coluna que armazena informação sobre quando o pedido de um
cliente foi realmente enviado usaria o tipo datetime, pois ele é
importante para registrar não só a data em que o envio ocorreu, mas
também o horário em que ele ocorreu.
• Uma coluna que registra a última vez que um usuário modi cou uma
linha especí ca de uma tabela usaria o tipo timestamp. O tipo
timestamp armazena a mesma informação que o tipo datetime (ano,
mês, dia, hora, minuto, segundo), mas uma coluna timestamp será
automaticamente povoada com a data e a hora atuais pelo servidor
MySQL no momento em que uma linha é adicionada a uma tabela ou
quando uma linha é modi cada posteriormente.
• Uma coluna que armazene apenas dados de ano usaria o tipo year.
• Colunas que armazenam dados relativos a um período de tempo
necessário para se completar uma tarefa usariam o tipo time. Para esse
tipo de dado, seria desnecessário e confuso armazenar um
componente de data, pois você está interessado apenas no número de
horas/minutos/segundos necessários para completar a tarefa. Essa
informação pode ser derivada utilizando-se duas colunas datetime
(uma para a data/hora inicial da tarefa e outra para a data/hora de
término) e subtraindo uma da outra, mas é mais simples utilizar uma
única coluna time.
O capítulo 7 explora como trabalhar com cada um dos tipos de dados
temporais.

Criação de tabelas
Agora que temos um conhecimento sólido de quais tipos de dados podem
ser armazenados em um banco de dados SQL, chegou a hora de vermos
como usar esses tipos em de nições de tabelas. Vamos começar de nindo
uma tabela que armazenará informações sobre uma pessoa.

Passo 1: Projeto
Uma boa forma de começar a projetar uma tabela é fazer um
brainstorming para descobrir que tipo de informação seria interessante
incluir. Aqui está o que marquei depois de re etir por instantes sobre os
tipos de informações que de nem uma pessoa:
• Nome
• Sexo
• Data de nascimento
• Endereço
• Comidas favoritas
Certamente isso não é uma lista completa, mas é boa o su ciente por
enquanto. O próximo passo é atribuir nomes de colunas e tipos de dados.
A tabela 2.7 mostra minha tentativa inicial.
Tabela 2.7 – Tabela person, primeiro passo
Coluna Tipo Valores permitidos

Name Varchar(40)

Gender Char(1) M, F

Birth_date Date

Address Varchar(100)

Favorite_foods Varchar(200)

As colunas name, address e favorite_foods são do tipo varchar e


permitem a entrada de dados de forma livre. A coluna gender permite um
único caractere, que deve ser igual a M ou F. A coluna birth_date é do tipo
date, já que um componente de horário não é necessário.

Passo 2: Re namento
No capítulo 1, você teve acesso ao conceito de normalização, que é o
processo de garantir que não haja colunas duplicadas (além das chaves
estrangeiras) nem colunas compostas em seu projeto de banco de dados.
Ao olhar para as colunas da tabela person uma segunda vez, as seguintes
questões surgem:
• A coluna name é, na verdade, um objeto composto consistindo em um
prenome e um sobrenome.
• Como várias pessoas podem ter o mesmo nome, sexo, data de
nascimento e assim por diante, não há colunas na tabela person que
garantam unicidade.
• A coluna address também é um objeto composto, consistindo em rua,
cidade, estado/província, país e código postal.
• A coluna favorite_foods é uma lista, contendo 0, 1 ou mais itens
independentes. Seria melhor criar uma tabela separada para esse dado
que inclua uma chave estrangeira da tabela person, para você poder
saber a qual pessoa uma comida determinada deve ser atribuída.
Depois de levar todas essas questões em consideração, a tabela 2.8 mostra
a versão normalizada da tabela person.
Tabela 2.8 – Tabela Person, segundo passo
Coluna Tipo Valores permitidos

Person_id Smallint (unsigned)

First_name Varchar(20)

Last_name Varchar(20)

Gender Char(1) M, F

Birth_date Date

Street Varchar(30)

City Varchar(20)

State Varchar(20)

Country Varchar(20)

Postal_code Varchar(20)

Agora que a tabela person tem uma chave primária (person_id) para
garantir a unicidade, o próximo passo é construir uma tabela
favorite_food que inclua uma chave estrangeira para a tabela person. A
tabela 2.9 mostra o resultado.
Tabela 2.9 – Tabela favorite_food
Coluna Tipo

Person_id Smallint (unsigned)

Food Varchar(20)

As colunas person_id e food formam a chave primária da tabela


favorite_food, e a coluna person_id também é uma chave estrangeira
para a tabela person.

Quanto é su ciente?
Retirar a coluna favorite_foods da tabela person certamente foi uma
boa ideia, mas será que não há mais nada a fazer? O que acontece, por
exemplo, se uma pessoa lista “massa” como uma comida favorita
enquanto outra lista “espaguete”? Elas são a mesma coisa? Para evitar
esse problema, você pode querer que as pessoas escolham suas
comidas favoritas de uma lista de opções. Nesse caso, você deverá criar
uma tabela food com as colunas food_id e food_name e, então, alterar a
tabela favorite_food para conter uma chave estrangeira para a tabela
food. Apesar de essa abordagem ser totalmente normalizada, você
pode decidir que é melhor apenas armazenar os valores que os
usuários inserirem, deixando a tabela do jeito que está.

Passo 3: Construindo instruções de esquema SQL


Agora que o projeto está completo para as duas tabelas de informações a
respeito de pessoas e suas comidas favoritas, o próximo passo é gerar
instruções SQL para criar as tabelas no banco de dados. Aqui está a
instrução que cria a tabela person:
CREATE TABLE person
(person_id SMALLINT UNSIGNED,
fname VARCHAR(20),
lname VARCHAR(20),
gender CHAR(1),
birth_date DATE,
street VARCHAR(30),
city VARCHAR(20),
state VARCHAR(20),
country VARCHAR(20),
postal_code VARCHAR(20),
CONSTRAINT pk_person PRIMARY KEY (person_id)
);

Tudo nessa instrução deve ser autoexplicativo, exceto pelo último item: ao
de nir sua tabela, você precisa informar ao servidor de banco de dados
qual ou quais colunas servirão de chave primária para a tabela. Você faz
isso criando uma restrição (constraint) na tabela. Você pode adicionar
vários tipos de restrições em uma de nição de tabela. Essa restrição é uma
restrição de chave primária. Ela é criada sobre a coluna person_id e recebe
o nome de pk_person.
Ainda falando sobre restrições, existe outro tipo de restrição que pode ser
útil para a tabela person. Na tabela 2.7, adicionei uma terceira coluna para
mostrar os valores permitidos para certas colunas (tais como ‘M’ e ‘F’ para
a coluna gender). Outro tipo de restrição, denominado restrição de
veri cação (check constraint), restringe os valores permitidos para uma
coluna especí ca. O MySQL permite que uma restrição de veri cação seja
anexada a uma de nição de coluna, como mostrado a seguir:
gender CHAR(1) CHECK (gender IN ('M','F')),

Apesar de as restrições de veri cação operarem como esperado nos


servidores de banco de dados, o servidor MySQL permite que restrições
sejam de nidas, mas não força seu uso. No entanto, o MySQL fornece
outro tipo de dados de caracteres chamado enum, que une a veri cação de
restrição à de nição de dados. Con ra como caria a de nição da coluna
gender:
gender ENUM('M','F'),

Aqui está como a tabela person caria com um tipo de dados enum para a
coluna gender:
CREATE TABLE person
(person_id SMALLINT UNSIGNED,
fname VARCHAR(20),
lname VARCHAR(20),
gender ENUM('M','F'),
birth_date DATE,
street VARCHAR(30),
city VARCHAR(20),
state VARCHAR(20),
country VARCHAR(20),
postal_code VARCHAR(20),
CONSTRAINT pk_person PRIMARY KEY (person_id)
);

Mais tarde neste capítulo, você verá o que acontece caso tente adicionar
dados em uma coluna que violem sua restrição de veri cação (ou, no
caso, do MySQL, seus valores de enumeração).
Agora você está pronto para executar a instrução create table usando a
ferramenta de linha de comandos mysql. Ela caria assim:
mysql> CREATE TABLE person
-> (person_id SMALLINT UNSIGNED,
-> fname VARCHAR(20),
-> lname VARCHAR(20),
-> gender ENUM('M','F'),
-> birth_date DATE,
-> street VARCHAR(30),
-> city VARCHAR(20),
-> state VARCHAR(20),
-> country VARCHAR(20),
-> postal_code VARCHAR(20),
-> CONSTRAINT pk_person PRIMARY KEY (person_id)
-> );
Query OK, 0 rows affected (0.27 sec)

Depois de processar a instrução create table, o servidor MySQL retorna


a mensagem “Query OK, 0 rows a ected”, dizendo que a instrução não
tinha erros de sintaxe. Se você quiser se certi car de que a tabela existe de
fato, você pode usar o comando describe (ou sua forma abreviada, desc)
para observar a de nição da tabela:

As colunas 1 e 2 da saída do comando describe são autoexplicativas. A


coluna 3 mostra se uma coluna em particular pode ser omitida quando os
dados são inseridos na tabela. Deixei esse tópico de fora da discussão
propositalmente (veja o quadro “O que é Null?” abaixo para uma breve
explicação), mas o exploraremos a fundo no capítulo 4. A quarta coluna
mostra se uma coluna faz parte de alguma chave (primária ou estrangeira)
– nesse caso, a coluna person_id está marcada como chave primária. A
coluna 5 mostra se uma coluna em particular será populada com um
valor padrão caso seja omitida no momento da inserção de dados na
tabela. A sexta coluna (chamada “Extra”) exibe qualquer outra
informação relevante que possa ser aplicável a uma coluna.

O que é Null?
Em alguns casos, não é possível ou aplicável fornecer um valor para
uma coluna especí ca em uma tabela. Por exemplo, ao adicionar
dados de um novo pedido de cliente, a coluna ship_date ainda não
pode ser determinada. Nesse caso, diz-se que a coluna é nula (perceba
que eu não disse que seu valor é nulo), o que indica a ausência de um
valor. Null é usado em inúmeros casos em que um valor não pode ser
fornecido, tais como:
• Não aplicável
• Desconhecido
• Conjunto vazio
Ao projetar uma tabela, você deve especi car quais colunas têm
permissão para serem nulas (o padrão) e quais colunas não têm
permissão para isso (por meio da adição das palvras-chaves not null
após a de nição de tipo).
Agora que você criou a tabela person, seu próximo passo é criar a tabela
favorite_food:
mysql> CREATE TABLE favorite_food
-> (person_id SMALLINT UNSIGNED,
-> food VARCHAR(20),
-> CONSTRAINT pk_favorite_food PRIMARY KEY (person_id, food),
-> CONSTRAINT fk_fav_food_person_id FOREIGN KEY (person_id)
-> REFERENCES person (person_id)
-> );
Query OK, 0 rows affected (0.10 sec)

Isso deve parecer bem similar à instrução create table da tabela person,
com as seguintes exceções:
• Como uma pessoa pode ter mais de uma comida favorita (que é a
razão pela qual essa tabela foi criada), é preciso mais do que a coluna
person_id para garantir unicidade na tabela. Essa tabela, portanto,
tem uma chave primária de duas colunas: person_id e food.
• A tabela favorite_food contém outro tipo de restrição denominada
restrição de chave estrangeira (foreign key constraint). Ela restringe os
valores da coluna person_id na tabela favorite_food para que sejam
incluídos apenas valores existentes na tabela person. Com essa
restrição de nida, não conseguirei adicionar uma linha na tabela
favorite_food indicando que person_id 27 gosta de pizza se ainda não
houver uma linha na tabela person cujo person_id seja 27.
Caso se esqueça de criar a chave estrangeira no momento em que
cria a tabela, é possível adicioná-la mais tarde por meio da
instrução alter table.
A instrução describe mostra o seguinte após a execução da instrução
alter table:

Agora que as tabelas foram criadas, o próximo passo lógico é adicionar


alguns dados.

Povoando e modi cando tabelas


Com as tabelas person e favorite_food em seus devidos lugares, agora
você pode iniciar a exploração das quatro instruções de dados do SQL:
insert, update, delete e select.

Inserindo dados
Como ainda não há dados nas tabelas person e favorite_food, a primeira
das quatro instruções de dados SQL a ser explorada será a instrução
insert. Existem três componentes principais em uma instrução insert:
• O nome da tabela na qual serão adicionados os dados.
• Os nomes das colunas que serão populadas dentro da tabela.
• Os valores que serão usados para popular as colunas.
Você não é obrigado a fornecer dados para todas as colunas da tabela (a
menos que todas as colunas estejam de nidas como not null). Em alguns
casos, aquelas colunas que não foram incluídas na instrução insert inicial
receberão valores posteriormente por meio de uma instrução update. Em
outros casos, uma coluna pode nunca receber um valor para uma linha de
dados especí ca (como um pedido de cliente que é cancelado antes de ser
enviado, tornando a coluna ship_date inaplicável).

Gerando dados de chave numérica


Antes de inserir dados na tabela person, seria interessante discutir como
valores são gerados para chaves primárias numéricas. Além de escolher
um número do nada, você tem duas outras opções:
• Observar o maior número que atualmente se encontra na tabela e
adicionar um a ele.
• Deixar que o servidor de banco de dados forneça um valor para você.
Apesar de a primeira opção ser válida, ela é comprovadamente
problemática em um ambiente multiusuário, pois dois usuários podem
olhar na tabela ao mesmo tempo e gerar o mesmo valor para a chave
primária. Em vez disso, todos os servidores de banco de dados disponíveis
atualmente fornecem um método seguro e robusto de gerar chaves
numéricas. Em alguns servidores, como o Oracle Database, um objeto de
esquema separado é usado (chamado de sequência) – no caso do MySQL,
no entanto, você precisa apenas ativar o atributo de autoincremento para
sua coluna de chave primária. Normalmente isso é feito no momento de
criação da tabela, mas fazê-lo agora fornecerá a oportunidade de aprender
outra instrução de esquema SQL, alter table, que é usada para
modi car a de nição de uma tabela existente:
ALTER TABLE person MODIFY person_id SMALLINT UNSIGNED
AUTO_INCREMENT;

Essencialmente, essa instrução rede ne a coluna person_id da tabela


person. Se você pedir para descrever a tabela, você verá o atributo de
autoincremento listado na coluna “Extra” para person_id:

Ao inserir dados na tabela person_id, apenas forneça null para a coluna


person_id e o MySQL populará a coluna com o próximo valor disponível
(por padrão, o MySQL inicia em 1 para coluna de autoincremento).

Instrução insert
Agora que todas as peças estão posicionadas, é hora de adicionar alguns
dados. A instrução seguinte cria uma linha na tabela person para William
Turner:
mysql> INSERT INTO person
-> (person_id, fname, lname, gender, birth_date)
-> VALUES (null, 'William', 'Turner', 'M', '1972-05-27');
Query OK, 1 row affected (0.01 sec)

O feedback (“Query OK, 1 row a ected”) está lhe dizendo que a sintaxe
da sua instrução estava correta e que uma linha foi adicionada ao banco
de dados (já que foi uma instrução insert). Você pode observar os dados
recém-adicionados à tabela por meio de uma instrução select:

Como você pode ver, o servidor MySQL gerou um valor de 1 para a chave
primária. Já que existe apenas uma única linha na tabela person, não me
preocupei em especi car qual linha me interessava e simplesmente
recuperei todas as linhas da tabela. No entanto, se houvesse mais de uma
linha na tabela eu poderia adicionar uma cláusula where para especi car
que queria apenas os dados da linha cuja coluna person_id tivesse o valor
1:

Apesar de essa consulta especi car um valor de chave primária, você pode
usar qualquer coluna da tabela como critério de pesquisa de linhas, como
mostra a consulta a seguir, que pesquisa todas as linhas cuja coluna
lname tenha o valor ‘Turner’:

Antes de seguir em frente, vale mencionar mais algumas coisas a respeito


da instrução insert que construímos anteriormente:
• Não foram fornecidos valores para as colunas de endereço. Isso não é
um problema, pois é permitido que essas colunas sejam nulas.
• O valor fornecido para a coluna birth_date foi uma string. Desde que
você respeite o formato mostrado na tabela 2.5, o MySQL converterá a
string em uma data para você.
• Os nomes de colunas e os valores fornecidos devem corresponder em
número e tipo. Se você nomear sete colunas e fornecer apenas seis
valores, ou se fornecer valores que não possam ser convertidos ao tipo
de dados apropriado à coluna correspondente, você receberá um erro.
William também forneceu informações sobre seus três pratos favoritos.
Veja as três instruções insert que armazenam suas preferências:
mysql> INSERT INTO favorite_food (person_id, food)
-> VALUES (1, 'pizza');
Query OK, 1 row affected (0.01 sec)
mysql> INSERT INTO favorite_food (person_id, food)
-> VALUES (1, 'cookies');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO favorite_food (person_id, food)
-> VALUES (1, 'nachos');
Query OK, 1 row affected (0.01 sec)

Aqui está uma consulta que retorna os pratos favoritos de William em


ordem alfabética usando uma cláusula order by:
mysql> SELECT food
-> FROM favorite_food
-> WHERE person_id = 1
-> ORDER BY food;
+---------+
| food |
+---------+
| cookies |
| nachos |
| pizza |
+---------+
3 rows in set (0.02 sec)

A cláusula order by diz ao servidor como ordenar os dados retornados


pela consulta. Sem a cláusula order by, não há garantias de que os dados
da tabela serão retornados em alguma ordem especí ca.
Para que William não se sinta sozinho, você pode executar outra
instrução insert para adicionar Susan Smith à tabela person:
mysql> INSERT INTO person
-> (person_id, fname, lname, gender, birth_date,
-> street, city, state, country, postal_code)
-> VALUES (null, 'Susan','Smith', 'F', '1975-11-02',
-> '23 Maple St.', 'Arlington', 'VA', 'USA', '20220');
Query OK, 1 row affected (0.01 sec)

Como Susan foi gentil o su ciente ao fornecer seu endereço, incluímos


cinco colunas a mais do que quando os dados de William foram
inseridos. Se você consultar a tabela novamente, verá que a linha de Susan
teve o valor 2 atribuído à sua chave primária:
Posso ter essas informações em XML?
Se você vai trabalhar com dados XML, cará feliz em saber que a
maioria dos servidores de banco de dados fornece uma maneira
simples de gerar saída XML a partir de uma consulta. Com o MySQL,
por exemplo, você pode usar a opção --xml ao invocar a ferramenta
mysql, e toda sua saída será automaticamente formatada usando XML.
Aqui está como os dados de favorite_food cariam no formato de
documento XML:
C:\database> mysql -u lrngsql -p --xml bank
Enter password: xxxxxx
Welcome to the MySQL Monitor...
Mysql> SELECT * FROM favorite_food;
<?xml version=”1.0”?>
<resultset statement=”select * from favorite_food”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”>
<row>
<field name=”person_id”>1</field>
<field name=”food”>cookies</field>
</row>
<row>
<field name=”person_id”>1</field>
<field name=”food”>nachos</field>
</row>
<row>
<field name=”person_id”>1</field>
<field name=”food”>pizza</field>
</row>
</resultset>
3 rows in set (0.00 sec)
Com o SQL Server, você não precisa con gurar sua ferramenta de
linha de comando: basta apenas adicionar a cláusula for xml no m
de sua consulta, como em:
SELECT * FROM favorite_food
FOR XML AUTO, ELEMENTS

Atualizando dados
Quando os dados de William Turner foram inicialmente adicionados à
tabela, várias colunas de endereço foram omitidas da instrução insert. A
próxima instrução mostra como essas colunas podem ser populadas por
meio de uma instrução update:
mysql> UPDATE person
-> SET street = '1225 Tremont St.',
-> city = 'Boston',
-> state = 'MA',
-> country = 'USA',
-> postal_code = '02138'
-> WHERE person_id = 1;
Query OK, 1 row affected (0.04 sec)
Rows matched: 1 Changed: 1 Warnings: 0

O servidor respondeu com uma mensagem de duas linhas: o item “Rows


matched: 1” informa que a condição na cláusula where correspondeu a
uma única linha da tabela, e o item “Changed: 1” diz que uma única
linha da tabela foi modi cada. Como a cláusula where especi ca a chave
primária da linha de William, isso é exatamente o que você esperaria que
acontecesse.
Dependendo das condições em sua cláusula where, também é possível
modi car mais de uma linha com uma única instrução. Considere, por
exemplo, o que aconteceria se sua cláusula where casse assim:
WHERE person_id < 10

Como William e Susan têm um valor em person_id menor do que 10, as


linhas de ambos seriam modi cadas. Se você omitir completamente a
cláusula where, sua instrução update modi cará todas as linhas da tabela.

Exclusão de dados
Parece que William e Susan não estão se dando muito bem, então um
deles terá que ir embora. Como William foi o primeiro, Susan terá a
honra de ser chutada pela instrução delete:
mysql> DELETE FROM person
-> WHERE person_id = 2;
Query OK, 1 row affected (0.01 sec)

Mais uma vez, a chave primária foi usada para isolar a linha de interesse,
de forma que uma única linha seja apagada da tabela. Semelhantemente à
instrução update, mais de uma linha pode ser apagada dependendo das
condições da cláusula where, e todas as linhas serão apagadas se a
cláusula where for omitida.

Quando boas instruções se dão mal


Até agora, todas as instruções de dados SQL mostradas neste capítulo têm
sido bem-formadas e seguiram corretamente todas as regras. No entanto,
baseado nas de nições das tabelas person e favorite_food, existem várias
maneiras com as quais você pode gerar con itos ao inserir ou modi car
dados. Esta seção mostrará a você alguns dos erros mais comuns que
podem surgir à sua frente e como o servidor MySQL responderá a eles.

Chaves primárias não-únicas


Como a de nição das tabelas incluiu a criação de restrições de chave
primária, o MySQL irá se certi car de que valores duplicados de chaves
não serão inseridos nas tabelas. A instrução a seguir tenta burlar o
atributo de autoincremento da coluna person_id para criar outra linha na
tabela person com o valor 1 em person_id:
mysql> INSERT INTO person
-> (person_id, fname, lname, gender, birth_date)
-> VALUES (1, 'Charles','Fulton', 'M', '1968-01-15');
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'

Nada o impedirá (pelo menos com os objetos de esquema atuais) de criar


duas linhas com nomes idênticos, endereços idênticos, datas de
nascimento idênticas e assim por diante, desde que as linhas tenham
valores diferentes na coluna person_id.
Chave estrangeira não-existente
A de nição da tabela favorite_food inclui a criação de uma restrição de
chave estrangeira na coluna person_id. Essa restrição garante que todos os
valores entrados em person_id na tabela favorite_food existem na tabela
person. Veja o que aconteceria caso você tentasse criar uma linha que
violasse essa restrição:
mysql> INSERT INTO favorite_food (person_id, food)
-> VALUES (999, 'lasagna');
ERROR 1452 (23000): Cannot add or update a child row: a foreign
key constraint
fails ('bank'.'favorite_food', CONSTRAINT 'fk_fav_food_person_id'
FOREIGN KEY
('person_id') REFERENCES 'person' ('person_id'))

Nesse caso, a tabela favorite_food é considerada a lha, e a tabela person,


a mãe, já que a tabela favorite_food depende da tabela person para
preencher alguns de seus dados. Se você planeja inserir dados em ambas
as tabelas, será necessário primeiro criar uma linha na tabela-mãe antes
de inserir dados em favorite_food.
Restrições de chave estrangeira são impostas apenas quando as
tabelas são criadas usando o mecanismo de armazenamento
InnoDB. Discutiremos os mecanismos de armazenamento do
MySQL no capítulo 12.

Violações dos valores das colunas


A coluna gender da tabela person é restrita aos valores ‘M’ para masculino
e ‘F’ para feminino. Se você tentar por engano atribuir à coluna qualquer
outro valor, receberá a seguinte resposta:
mysql> UPDATE person
-> SET gender = 'Z'
-> WHERE person_id = 1;
ERROR 1265 (01000): Data truncated for column 'gender' at row 1

A mensagem de erro é um pouco confusa, mas ela dá uma ideia geral de


que o servidor não cou feliz com o valor fornecido para a coluna gender.

Conversões de data inválidas


Se você construir uma string com a qual pretende popular uma coluna de
data, e essa string não corresponder ao formato esperado, você receberá
outro erro. Aqui está um exemplo que usa um formato de data que não
corresponde ao formato de data padrão “AAAA-MM-DD”:
mysql> UPDATE person
-> SET birth_date = 'DEC-21-1980'
-> WHERE person_id = 1;
ERROR 1292 (22007): Incorrect date value: 'DEC-21-1980' for column
'birth_date'
at row 1

Em geral, é sempre uma boa ideia especi car explicitamente o formato da


string em vez de con ar no formato padrão. Aqui está outra versão da
instrução que usa a função str_to_date para especi car qual formato de
string será usado:
mysql> UPDATE person
-> SET birth_date = str_to_date('DEC-21-1980' , '%b-%d-%Y')
-> WHERE person_id = 1;
Query OK, 1 row affected (0.12 sec)
Rows matched: 1 Changed: 1 Warnings: 0

Não só o servidor de banco de dados cou feliz, como William também


cou feliz (nós o deixamos oito anos mais jovem, sem precisar fazer uma
cirurgia estética caríssima!).
Anteriormente no capítulo, quando comentei a respeito dos vários
tipos de dados temporais, mostrei strings de formatação de datas do
tipo “AAAA-MM-DD”. Enquanto vários servidores de banco de
dados usam esse estilo de formatação, o MySQL usa %Y para indicar
um ano com quatro caracteres. Aqui estão mais alguns formatadores
que você talvez possa precisar ao converter strings em datas/horas
no MySQL:
%aO nome do dia da semana abreviado: Sun, Mon, ...
%b O nome do mês abreviado: Jan, Feb, ...
%c O número correspondente ao mês (0..12)
%d Os dias do mês (00..31)
%f Quantidade de microssegundos (000000..999999)
%H A hora do dia, no formato de 24 horas (00..23)
%h A hora do dia, no formato de 12 horas (01..12)
%i Os minutos dentro de uma hora (00..59)
%M O nome completo do mês (January..December)
%m O número correspondente ao mês (01..12)
%p AM ou PM
%s A quantidade de segundos (00..59)
%W O nome completo do dia da semana (Sunday..Saturday)
%w O número correspondente ao dia da semana
(0=Domingo..6=Sábado)
%Y O ano com quatro dígitos

Esquema bank (bank schema)


No restante do livro, você usará um grupo de tabelas que modelam um
banco comunitário. Algumas das tabelas incluem employee (funcionário),
branch (a liada), account (conta), customer (cliente), product (produto) e
transaction (transação). O esquema completo e os dados de exemplo
devem ter sido criados depois de realizar os passos nais do carregamento
do servidor MySQL, no início do capítulo, e da geração dos dados de
exemplo. Para ver um diagrama das tabelas e de suas colunas e
relacionamentos, veja o apêndice A.
A tabela 2.10 mostra todas as tabelas utilizadas no esquema bank, junto
com suas breves de nições.
Tabela 2.10 – De nições de esquema bank
Nome da tabela De nição

account Um produto específico aberto para um cliente em particular

branch Uma localização em que transações bancárias são conduzidas

business Um cliente corporativo (subtipo da tabela customer)


customer Uma pessoa ou empresa reconhecida pelo banco

department Um grupo de funcionários do banco que executa uma determinada função bancária

employee Uma pessoa que trabalha para o banco

individual Um cliente não-corporativo (subtipo da tabela customer)

officer Uma pessoa autorizada a conduzir negócios para um cliente corporativo

product Um serviço bancário oferecido aos clientes

product_type Um grupo de produtos que têm funções similares

transaction Uma mudança realizada no balanço de uma conta

Sinta-se à vontade para fazer quantas experiências quiser com as tabelas,


inclusive adicionar suas próprias tabelas para expandir as funções de
negócio do banco. Você também pode descartar uma tabela e recriá-la a
partir do arquivo que você baixou para garantir que seus dados de
exemplo estejam corretos.
Se quiser ver quais tabelas estão disponíveis em seu banco de dados, você
pode usar o comando show tables, assim:
mysql> SHOW TABLES;
+----------------+
| Tables_in_bank |
+----------------+
| account |
| branch |
| business |
| customer |
| department |
| employee |
| favorite_food |
| individual |
| officer |
| person |
| product |
| product_type |
| transaction |
+----------------+
13 rows in set (0.10 sec)

Junto com as 11 tabelas do esquema de banco, a listagem de tabelas


também inclui as duas tabelas criadas neste capítulo: person e
favorite_food. Essas tabelas não serão usadas nos próximos capítulos,
então sinta-se à vontade para descartá-las com os seguintes comandos:
mysql> DROP TABLE favorite_food;
Query OK, 0 rows affected (0.56 sec)
mysql> DROP TABLE person;
Query OK, 0 rows affected (0.05 sec)

Se quiser olhar as colunas de uma tabela, você pode usar o comando


describe. Aqui está um exemplo da saída de describe para a tabela
customer:

Quanto mais confortável você estiver com o banco de dados de exemplo,


melhor você entenderá os exemplos e, consequentemente, maior será sua
compreensão dos conceitos dos próximos capítulos.

1 N. de T.: apesar de estarmos usando o padrão brasileiro de formatação de números para indicar
os escopos dos tipos numéricos, não se esqueça de que os bancos de dados utilizam o ponto (.)
como separador decimal.
CAPÍTULO 3
Introdução a consultas

Até agora, você viu alguns poucos exemplos de consultas de banco de


dados (também conhecidas como instruções select) espalhadas ao longo
dos primeiros dois capítulos. Agora chegou o momento de observar mais
de perto as diferentes partes da instrução select e como elas interagem
entre si.

Funcionamento das consultas


Antes de dissecar a instrução select, pode ser interessante observar como
as consultas são executadas pelo servidor MySQL (ou, no caso, por
qualquer servidor de banco de dados). Se você estiver usando a
ferramenta de linha de comando mysql (e presumo que esteja), você já
logou no servidor MySQL fornecendo um nome de usuário e uma senha
(e possivelmente um nome de host, caso o MySQL esteja sendo executado
em outro computador). Uma vez que o servidor tenha veri cado que seu
nome de usuário e senha estão corretos, uma conexão de banco de dados é
gerada para uso. Essa conexão é mantida pela aplicação que a solicitou
(que, nesse caso, é a ferramenta mysql) até que a aplicação libere a
conexão (ou seja, como resultado de você digitar quit) ou que o servidor
feche a conexão (ou seja, quando o servidor é desligado). Cada conexão
com o servidor MySQL recebe um identi cador, que é mostrado a você
logo após o login:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 11
Server version: 6.0.3-alpha-community MySQL Community Server (GPL)
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

Nesse caso, meu ID de conexão é 11. Essa informação pode ser útil para
seu administrador de banco de dados caso algo saia errado, como uma
consulta malformada que ca executando por horas, então provavelmente
você vai querer anotá-lo.
Uma vez que o servidor tenha veri cado seu nome de usuário e senha e
tenha retornado uma conexão, você está pronto para executar consultas
(junto com outras instruções SQL). Cada vez que uma consulta é enviada
ao servidor, ele veri ca os seguintes itens antes de executá-la:
• Você tem permissão para executar a instrução?
• Você tem permissão para acessar os dados desejados?
• A sintaxe de sua instrução está correta?
Se sua instrução passar por esses testes, a consulta será passada para o
otimizador de consultas, cujo trabalho é determinar a maneira mais
e ciente de executá-la. O otimizador observará coisas como a ordem em
que deve juntar as tabelas nomeadas na cláusula from e quais índices
estão disponíveis, então ele traçará um plano de execução que o servidor
usará para executar sua consulta.
Compreender e in uenciar a forma que seu servidor de banco
de dados escolhe planos de execução é um tópico fascinante
que muitos de vocês desejarão explorar. Os leitores que
estiverem utilizando o MySQL deveriam considerar ler o livro
High Performance MySQL, de Baron Scwartz e colaboradores.
(http://oreilly.com/catalog/9780596101718/) (O’Reilly). Entre
outras coisas, você aprenderá a gerar índices, analisar planos de
execução, in uenciar o otimizador por meio de dicas de
consulta e ajustar os parâmetros de inicialização de seu
servidor. Se estiver usando o Oracle Database ou o SQL Server,
dúzias de livros de otimização estão disponíveis no mercado.
Uma vez que o servidor tenha terminado de executar sua consulta, o
conjunto-resultado (result set) é retornado à aplicação que o invocou (que
é, mais uma vez, a ferramenta mysql). Como mencionei no capítulo 1, um
conjunto-resultado é nada além de outra tabela contendo linhas e
colunas. Se sua consulta falhar em gerar quaisquer resultados, a
ferramenta mysql mostrará a mensagem que se encontra no nal do
seguinte exemplo:
mysql> SELECT emp_id, fname, lname
-> FROM employee
-> WHERE lname = 'Bkadfl';
Empty set(0.00 sec)

Se a consulta retornar uma ou mais linhas, a ferramenta mysql formatará


os resultados adicionando cabeçalhos de coluna e construindo caixas em
torno das colunas usando os símbolos -, | e +, como mostra o próximo
exemplo:
mysql> SELECT fname, lname
-> FROM employee;
+----------+-----------+
| fname | lname |
+----------+-----------+
| Michael | Smith |
| Susan | Barker |
| Robert | Tyler |
| Susan | Hawthorne |
| John | Gooding |
| Helen | Fleming |
| Chris | Tucker |
| Sarah | Parker |
| Jane | Grossman |
| Paula | Roberts |
| Thomas | Ziegler |
| Samantha | Jameson |
| John | Blake |
| Cindy | Mason |
| Frank | Portman |
| Theresa | Markham |
| Beth | Fowler |
| Rick | Tulman |
+----------+-----------+
18 rows in set (0.00 sec)

Essa consulta retornou o primeiro e o último nome de todos os


funcionários da tabela employee. Após a exibição da última linha de
dados, a ferramenta mysql exibe uma mensagem informando-lhe quantas
linhas foram retornadas, que, nesse caso, foram 18.

Cláusulas de consulta
Vários componentes ou cláusulas formam a instrução select. Enquanto
apenas uma delas é obrigatória no MySQL (a cláusula select),
normalmente você incluirá pelo menos duas ou três das seis cláusulas
disponíveis. A tabela 3.1 mostra as diferentes cláusulas e suas nalidades.
Tabela 3.1 – Cláusulas de consulta
Nome da
Finalidade
cláusula

Select Determina quais colunas serão incluídas no conjunto-resultado da consulta

Identifica as tabelas de onde serão retirados os dados e como as tabelas deverão ser
From
juntadas

Where Filtra os dados indesejados

Group by Usada para agrupar linhas por meio de valores comuns de colunas

Having Filtra grupos indesejados

Order by Ordena as linhas do conjunto-resultado final usando uma ou mais colunas

Todas as cláusulas mostradas na tabela 3.1 estão incluídas na


especi cação ANSI. Além disso, existem várias outras cláusulas exclusivas
do MySQL que exploraremos no apêndice B. As seções seguintes
aprofundam o uso das seis principais cláusulas de consulta.

Cláusula select
Apesar de a cláusula select ser a primeira cláusula de uma instrução
select, ela é uma das últimas a ser avaliada pelo servidor de banco de
dados. A razão para isso é que, antes de poder determinar o que incluir
no conjunto-resultado nal, é necessário conhecer de antemão todas as
colunas que podem ser incluídas nele. Portanto, para entender
completamente o papel da cláusula select, você precisará conhecer um
pouco sobre a cláusula from. Para começar, aqui vai uma consulta:
mysql> SELECT *
-> FROM department;
+---------+----------------+
| dept_id | name |
+---------+----------------+
| 1 | Operations |
| 2 | Loans |
| 3 | Administration |
+---------+----------------+
3 rows in set (0.04 sec)
Nessa consulta, a cláusula from lista uma única tabela (department), e a
cláusula select indica que todas as colunas (referenciadas pelo *) na
tabela department devem ser incluídas no conjunto-resultado. Essa
consulta poderia ser descrita em português da seguinte maneira:
Mostre todas as colunas e todas as linhas da tabela departamento.
Além de especi car todas as colunas por meio do caractere asterisco, você
pode nomear explicitamente as colunas nas quais está interessado, como
em:
mysql> SELECT dept_id, name
-> FROM department;
+---------+----------------+
| dept_id | name |
+---------+----------------+
| 1 | Operations |
| 2 | Loans |
| 3 | Administration |
+---------+----------------+
3 rows in set (0.01 sec)

Os resultados são idênticos aos da primeira consulta, já que todas as


colunas da tabela department (dept_id e name) foram nomeadas na
cláusula select. Você também pode optar por incluir apenas um
subconjunto das colunas da tabela department:
mysql> SELECT name
-> FROM department;
+----------------+
| name |
+----------------+
| Operations |
| Loans |
| Administration |
+----------------+
3 rows in set (0.00 sec)

A função da cláusula select, portanto, é a seguinte:


A cláusula select determina quais entre todas as colunas possíveis deverão
ser incluídas no conjunto-resultado da consulta.
Se você estivesse limitado a incluir apenas as colunas da tabela ou das
tabelas nomeadas na cláusula from, as coisas seriam bem chatas. No
entanto, você pode apimentar as coisas incluindo em sua cláusula select
o seguinte:
• Literais, como números ou strings.
• Expressões, como transaction.amount * -1.
• Funções nativas, como ROUND(transaction.amount, 2).
• Chamada a funções de nidas pelo usuário.
A próxima consulta demonstra o uso de uma coluna da tabela, um literal,
uma expressão e uma chamada a uma função nativa em uma única
consulta à tabela employee:

Falaremos sobre expressões e funções nativas de forma mais detalhada


posteriormente, mas eu quis lhe dar um aperitivo das coisas que podem
ser incluídas em uma cláusula select. Se você precisa apenas executar
uma função nativa ou avaliar uma expressão simples, você pode omitir a
cláusula select por completo. Aqui está um exemplo:
Como essa consulta apenas chama três funções nativas e não retorna
dados de quaisquer tabelas, não há necessidade de uma cláusula from.

Aliases de colunas
Apesar de a ferramenta mysql gerar rótulos (cabeçalhos) para as colunas
retornadas por suas consultas, você pode querer atribuir seus próprios
rótulos. Embora você possa desejar atribuir um novo rótulo para uma
coluna de uma tabela (se ela for nomeada de forma pouco clara ou
ambígua), você certamente desejará atribuir seus próprios rótulos para
aquelas colunas de seu conjunto-resultado que são geradas por expressões
ou chamadas a funções nativas. Você pode fazer isso adicionando um alias
(apelido) de coluna após cada elemento de sua cláusula select. Aqui está
a consulta anterior à tabela employee com aliases de colunas aplicados a
três delas:
Se você olhar no cabeçalho das colunas, verá que a segunda, terceira e
quarta colunas agora têm nomes razoáveis, em vez de simplesmente serem
rotuladas com a função ou expressão que as gerou. Se você observar a
cláusula select, verá como os aliases de coluna status, empid_x_pi e
last_name_upper são adicionados à segunda, terceira e quarta colunas.
Creio que você concordará comigo que ca mais fácil de entender a saída
da instrução com o uso dos aliases de coluna e que caria mais fácil de
trabalhar com ela programaticamente se você estiver requisitando a
consulta de dentro do Java ou do C# em vez da ferramenta mysql. Para
destacar mais ainda seus aliases de coluna, você também tem a opção de
usar a palavra-chave as antes do nome do alias, assim:
mysql> SELECT emp_id,
-> 'ACTIVE' AS status,
-> emp_id * 3.14159 AS empid_x_pi,
-> UPPER(lname) AS last_name_upper
-> FROM employee;
Muitas pessoas sentem que a inclusão da palavra-chave opcional as
melhora a legibilidade, apesar de eu ter optado por não usá-la nos
exemplos deste livro.

Removendo linhas duplicadas


Em alguns casos, uma consulta pode retornar linhas de dados duplicadas.
Por exemplo, se você fosse retornar todos os IDs de todos os clientes que
possuem contas, você faria o seguinte:
mysql> SELECT cust_id
-> FROM account;
+---------+
| cust_id |
+---------+
| 1 |
| 1 |
| 1 |
| 2 |
| 2 |
| 3 |
| 3 |
| 4 |
| 4 |
| 4 |
| 5 |
| 6 |
| 6 |
| 7 |
| 8 |
| 8 |
| 9 |
| 9 |
| 9 |
| 10 |
| 10 |
| 11 |
| 12 |
| 13 |
+---------+
24 rows in set (0.00 sec)

Como alguns clientes têm mais de uma conta, você verá o mesmo ID de
cliente uma vez para cada conta mantida por cada cliente. O que você
provavelmente deseja nesse caso é o conjunto especí co de clientes que
tenham contas, em vez de visualizar o ID de cliente de cada linha da
tabela account. Você pode conseguir isso adicionando a palavra-chave
distinct diretamente após a palavra-chave select, como demonstrado a
seguir:
mysql> SELECT DISTINCT cust_id
-> FROM account;
+---------+
| cust_id |
+---------+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
| 11 |
| 12 |
| 13 |
+---------+
13 rows in set (0.01 sec)

O conjunto-resultado agora contém 13 linhas, uma para cada cliente


especí co, em vez de 24 linhas, uma para cada conta.
Se não quiser que o servidor remova dados duplicados do resultado ou se
tiver certeza de que não há duplicatas em seu conjunto-resultado, você
pode especi car a palavra-chave ALL em vez de especi car DISTINCT. No
entanto, a palavra-chave ALL é o padrão e nunca precisa ser nomeada
explicitamente, então a maioria dos programadores não inclui ALL em
suas consultas.
Tenha em mente que a geração de um conjunto-resultado
DISTINCT requer a ordenação dos dados, o que pode demorar
muito para grandes conjuntos-resultados. Não caia na
armadilha de usar DISTINCT apenas para ter certeza de que não
haverá duplicatas; em vez disso, gaste um pouco de tempo
entendendo os dados com os quais está trabalhando, para saber
se duplicatas poderão, de fato, ocorrer.
Cláusula from
Até aqui, você viu consultas cujas cláusulas from continham apenas uma
única tabela. Apesar de a maioria dos livros de SQL de nir a cláusula
from simplesmente como uma lista de uma ou mais tabelas, gostaria de
ampliar a de nição da seguinte maneira:
A cláusula from de ne as tabelas usadas em uma consulta, junto com os
meios de vincular essas tabelas.
Essa de nição é composta por dois conceitos separados, mas
relacionados, que exploraremos nas seções seguintes.

Tabelas
Quando confrontadas com o termo tabela, a maioria das pessoas pensa
em um conjunto de linhas relacionadas armazenadas em um banco de
dados. Enquanto isso realmente descreve um tipo de tabela, eu gostaria
de usar a palavra de forma mais geral, removendo qualquer noção de
como os dados podem ser armazenados e enfocando apenas o conjunto
de linhas relacionadas. Três tipos diferentes de tabelas se enquadram
nessa de nição menos restrita:
• Tabelas permanentes (ou seja, criadas usando a instrução create
table).
• Tabelas temporárias (ou seja, linhas retornadas por uma subconsulta).
• Tabelas virtuais (ou seja, criadas usando a instrução create view).
Cada um desses tipos de tabelas pode ser incluído em uma cláusula from
de uma consulta. Por ora, você deve estar confortável com a inclusão de
tabelas permanentes em uma cláusula from, então descreverei brevemente
os outros tipos de tabelas que podem ser referenciados em uma cláusula
from.

Tabelas geradas por subconsultas


Uma subconsulta é uma consulta contida dentro de outra consulta.
Subconsultas são englobadas por parênteses e podem ser encontradas em
inúmeras partes de uma instrução select, dentro da cláusula from, no
entanto, uma subconsulta tem o papel de gerar uma tabela temporária
visível por todas as outras cláusulas da consulta e que pode interagir com
outras tabelas nomeadas na cláusula from. Aqui está um exemplo simples:

Nesse exemplo, uma subconsulta à tabela employee retorna cinco colunas,


e a consulta que a contém referencia três das cinco colunas disponíveis. A
subconsulta é referenciada pela consulta-contêiner por meio de seu alias,
que, nesse caso, é e. Este foi um exemplo simplista e pouco útil de uma
subconsulta em uma cláusula from; você encontrará uma abordagem
detalhadas de subconsultas no capítulo 9.

Views
Uma view é uma consulta armazenada no dicionário de dados. Ela se
parece com, e age como uma tabela, mas não há dados associados a uma
view (é por isso que eu a chamo de tabela virtual). Quando você realiza
uma consulta em uma view, sua consulta é mesclada com a de nição da
view para criar uma consulta nal a ser executada.
Como demonstração, aqui está uma de nição de view que consulta a
tabela employee e inclui uma chamada a uma função nativa:
mysql> CREATE VIEW employee_vw AS
-> SELECT emp_id, fname, lname,
-> YEAR(start_date) start_year
-> FROM employee;
Query OK, 0 rows affected (0.10 sec)

Quando a view é criada, nenhum dado adicional é gerado ou


armazenado: o servidor simplesmente esconde a instrução select no
bolso para uso futuro. Agora que a view existe, você pode usá-la em
consultas, como em:
mysql> SELECT emp_id, start_year
-> FROM employee_vw;
+--------+------------+
| emp_id | start_year |
+--------+------------+
| 1 | 2005 |
| 2 | 2006 |
| 3 | 2005 |
| 4 | 2006 |
| 5 | 2007 |
| 6 | 2008 |
| 7 | 2008 |
| 8 | 2006 |
| 9 | 2006 |
| 10 | 2006 |
| 11 | 2004 |
| 12 | 2007 |
| 13 | 2004 |
| 14 | 2006 |
| 15 | 2007 |
| 16 | 2005 |
| 17 | 2006 |
| 18 | 2006 |
+--------+------------+
18 rows in set (0.07 sec)

As views são criadas por inúmeras razões, inclusive para esconder colunas
dos usuários e simpli car projetos de banco de dados complexos.

Vínculos de tabela
O segundo desvio da de nição simples que z da cláusula from é a regra
obrigatória que diz que, caso mais de uma tabela apareça na cláusula
from, as condições para vincular as tabelas também devem ser incluídas.
Isso não é um requisito do MySQL ou de qualquer outro servidor de
banco de dados, e sim um método aprovado pelo ANSI de junção de
múltiplas tabelas, e é o que apresenta a melhor portabilidade entre os
vários servidores de banco de dados. Exploraremos a junção de múltiplas
tabelas mais a fundo nos capítulos 5 e 10, mas aqui vai um exemplo
simples no caso de eu ter aguçado sua curiosidade:

A consulta anterior exibe dados tanto da tabela employee (emp_id, fname,


lname) quanto da tabela department (name), então ambas as tabelas são
incluídas na cláusula from. O mecanismo de vinculação das duas tabelas
(referido como uma junção ou join) é a a liação do funcionário a um
departamento armazenada na tabela employee. Então, o servidor de banco
de dados é instruído a usar o valor da coluna dept_id na tabela employee
para procurar pelo nome do departamento associado na tabela
department. As condições de junção das duas tabelas são encontradas na
subcláusula on da cláusula from; nesse caso, a condição de junção é ON
employee.dept_id = department.dept_id. Novamente, por favor, consulte o
capítulo 5 para uma discussão mais aprofundada sobre junção de
múltiplas tabelas.

De nindo aliases de tabela


Quando múltiplas tabelas são juntadas em uma única consulta, você
precisa de uma maneira de identi car a qual tabela você está se referindo
quando referencia colunas nas cláusulas select, where, group by, having e
order by. Você tem duas escolhas ao referenciar uma tabela fora da
cláusula from:
• Usar o nome completo da tabela, como em employee.emp_id.
• Atribuir um alias a cada uma das tabelas e usá-lo ao longo da
consulta.
Na consulta anterior, optei por usar o nome completo da tabela nas
cláusulas select e on. Aqui está como a mesma consulta caria usando
aliases:
SELECT e.emp_id, e.fname, e.lname,
d.name dept_name
FROM employee e INNER JOIN department d
ON e.dept_id = d.dept_id;

Se você olhar atentamente a cláusula from, verá que foi atribuído o alias e
à tabela employee, e o alias d à tabela department. Esses aliases são, então,
utilizados na cláusula on para de nir a condição de junção, bem como na
cláusula select ao especi car as colunas que serão incluídas no conjunto-
resultado. Creio que você concordará que o uso de aliases torna a
instrução mais compacta sem causar confusão (contanto que suas
escolhas de nomes de aliases sejam razoáveis). Além disso, você pode usar
a palavra-chave as com seus aliases de tabela, de forma similar à que foi
demonstrada anteriormente com os aliases de coluna:
SELECT e.emp_id, e.fname, e.lname,
d.name dept_name
FROM employee AS e INNER JOIN department AS d
ON e.dept_id = d.dept_id;

Descobri que aproximadamente metade dos desenvolvedores de banco de


dados com os quais trabalhei usam a palavra-chave as com seus aliases de
coluna e tabela, e metade não a utiliza.

Cláusula where
As consultas mostradas até agora neste capítulo selecionaram todas as
linhas da tabela employee, department ou account (exceto pela
demonstração de distinct no início do capítulo). Na maior parte do
tempo, no entanto, você não vai querer recuperar todas as linhas de uma
tabela, mas vai querer uma maneira de ltrar aquelas linhas que não são
de seu interesse. Isso é um trabalho para a cláusula where.
A cláusula where é o mecanismo de ltragem de linhas indesejadas de seu
conjunto-resultado.
Por exemplo, talvez você esteja interessado em recuperar dados da tabela
employee, mas apenas daqueles funcionários que estão empregados como
gerentes de caixa. A consulta a seguir utiliza uma cláusula where para
recuperar apenas os quatro gerentes de caixa (head teller):

Nesse caso, a cláusula where ltrou 14 das 18 linhas de funcionários. Essa


cláusula where contém uma única condição de ltragem, mas você pode
incluir quantas condições forem necessárias – condições individuais são
separadas usando operadores do tipo and, or e not (veja o capítulo 4
para uma discussão completa da cláusula where e de condições de
ltragem). Aqui está uma extensão da consulta anterior que inclui uma
segunda condição, indicando que apenas os funcionários com data de
admissão após 1º de janeiro de 2006 devem ser incluídos:
A primeira condição (title = 'Head Teller') ltrou 14 das 18 linhas de
funcionários, e a segunda (start_date > '2006-01-01') ltrou mais duas
linhas, deixando apenas duas linhas no conjunto-resultado nal. Vejamos
o que aconteceria se você mudasse o operador and, que separa as duas
condições, para or:

Olhando a saída, você pode perceber que os quatro gerentes foram


incluídos no conjunto-resultado, junto com todos os outros funcionários
que começaram a trabalhar após 1º de janeiro de 2006. Pelo menos uma
das duas condições é verdade para 15 dos 18 funcionários da tabela
employee. Portanto, quando você separa as condições com um operador
and, todas as condições devem ser verdadeiras para que uma linha seja
incluída no conjunto-resultado; quando você usa o operador or, no
entanto, apenas uma das condições precisa ser verdadeira para que a linha
seja incluída.
Então, o que você deve fazer caso precise usar os operadores and e or na
mesma cláusula where? Fico feliz que tenha perguntado. Você deve usar
parênteses para agrupar condições. A próxima consulta especi ca que
apenas os funcionários que são gerentes de caixa e começaram a trabalhar
na empresa depois do dia 1º de janeiro de 2006, ou os funcionários que
são caixas de banco e começaram a trabalhar após 1º de janeiro de 2007,
devem ser incluídos no conjunto-resultado:

Você deve sempre usar os parênteses para separar grupos de condições


que misturem diferentes operadores, de forma que você, o servidor de
banco de dados e qualquer pessoa que posteriormente venha a modi car
seu código entendam a mesma coisa.

Cláusulas group by e having


Todas as consultas até agora recuperaram dados brutos sem qualquer tipo
de manipulação. Às vezes, no entanto, você precisará localizar detalhes em
seus dados que exigirão do servidor de banco de dados certo tratamento
dos dados antes de retornar o conjunto-resultado. Um desses mecanismos
é a cláusula group by, que é usada para agrupar dados por valores de
colunas. Por exemplo, em vez de olhar a lista de funcionários e de
departamentos nos quais estão empregados, você pode querer olhar para
a lista de departamentos, junto com o número de funcionários
empregados em cada departamento. Ao usar a cláusula group by, você
também pode usar a cláusula having, que permite ltrar dados agrupados
da mesma forma que a cláusula where permite ltrar dados brutos.
Aqui está uma consulta que conta todos os funcionários de cada
departamento e que retorna os nomes dos departamentos que contenham
mais de dois funcionários:
mysql> SELECT d.name, count(e.emp_id) num_employees
-> FROM department d INNER JOIN employee e
-> ON d.dept_id = e.dept_id
-> GROUP BY d.name
-> HAVING count(e.emp_id) > 2;
+----------------+---------------+
| name | num_employees |
+----------------+---------------+
| Administration | 3 |
| Operations | 14 |
+----------------+---------------+
2 rows in set (0.00 sec)

Mencionei brevemente essas duas cláusulas para que elas não lhe peguem
de surpresa mais adiante no livro, mas elas são um pouco mais avançadas
do que as outras quatro cláusulas da instrução select. Portanto, peço que
você espere até o capítulo 8 para ver uma descrição completa de como e
quando usar group by e having.

Cláusula order by
Em geral, as linhas de um conjunto-resultado retornado de uma consulta
não estão em nenhuma ordem especí ca. Se desejar que seu conjunto-
resultado retorne em uma ordem especí ca, você precisará instruir o
servidor a ordenar os resultados por meio da cláusula order by:
A cláusula order by é o mecanismo que ordena seus conjuntos-resultados
usando dados de coluna brutos ou expressões baseadas em dados de coluna.
Por exemplo, vamos olhar uma consulta feita à tabela account:
mysql> SELECT open_emp_id, product_cd
-> FROM account;
+-------------+------------+
| open_emp_id | product_cd |
+-------------+------------+
| 10 | CHK |
| 10 | SAV |
| 10 | CD |
| 10 | CHK |
| 10 | SAV |
| 13 | CHK |
| 13 | MM |
| 1 | CHK |
| 1 | SAV |
| 1 | MM |
| 16 | CHK |
| 1 | CHK |
| 1 | CD |
| 10 | CD |
| 16 | CHK |
| 16 | SAV |
| 1 | CHK |
| 1 | MM |
| 1 | CD |
| 16 | CHK |
| 16 | BUS |
| 10 | BUS |
| 16 | CHK |
| 13 | SBL |
+-------------+------------+
24 rows in set (0.00 sec)

Se você estiver tentando analisar os dados de cada funcionário, seria


interessante ordenar os resultados pela coluna open_emp_id - para isso,
basta adicionar essa coluna à cláusula order by:
mysql> SELECT open_emp_id, product_cd
-> FROM account
-> ORDER BY open_emp_id;
+-------------+------------+
| open_emp_id | product_cd |
+-------------+------------+
| 1 | CHK |
| 1 | SAV |
| 1 | MM |
| 1 | CHK |
| 1 | CD |
| 1 | CHK |
| 1 | MM |
| 1 | CD |
| 10 | CHK |
| 10 | SAV |
| 10 | CD |
| 10 | CHK |
| 10 | SAV |
| 10 | CD |
| 10 | BUS |
| 13 | CHK |
| 13 | MM |
| 13 | SBL |
| 16 | CHK |
| 16 | CHK |
| 16 | SAV |
| 16 | CHK |
| 16 | BUS |
| 16 | CHK |
+-------------+------------+
24 rows in set (0.00 sec)

Agora cou mais fácil visualizar quais tipos de contas cada funcionário
abriu. No entanto, seria melhor ainda se você pudesse garantir que os
tipos de contas fossem mostrados na mesma ordem para cada funcionário
- isso é possível pela adição da coluna product_cd após a coluna
open_emp_id na cláusula order by:
mysql> SELECT open_emp_id, product_cd
-> FROM account
-> ORDER BY open_emp_id, product_cd;
+-------------+------------+
| open_emp_id | product_cd |
+-------------+------------+
| 1 | CD |
| 1 | CD |
| 1 | CHK |
| 1 | CHK |
| 1 | CHK |
| 1 | MM |
| 1 | MM |
| 1 | SAV |
| 10 | BUS |
| 10 | CD |
| 10 | CD |
| 10 | CHK |
| 10 | CHK |
| 10 | SAV |
| 10 | SAV |
| 13 | CHK |
| 13 | MM |
| 13 | SBL |
| 16 | BUS |
| 16 | CHK |
| 16 | CHK |
| 16 | CHK |
| 16 | CHK |
| 16 | SAV |
+-------------+------------+
24 rows in set (0.00 sec)

O conjunto-resultado agora foi ordenado primeiro pelo ID do


funcionário e depois pelo tipo de conta. A ordem em que as colunas
aparecem na sua cláusula order by faz diferença.

Ordenação ascendente versus ordenação descendente


Ao ordenar, você tem a opção de especi car ordenação ascendente ou
descendente por meio das palavras-chave asc e desc. Como o padrão de
ordenação é ascendente, será necessário adicionar uma palavra-chave
(desc, no caso) apenas se você quiser uma ordenação descendente. Por
exemplo, a consulta a seguir lista todas as contas ordenadas pelo saldo
disponível com o maior saldo listado no topo:
Ordenações descendentes costumam ser usadas em consultas de
ranqueamento, do tipo “mostre-me os cinco maiores saldos”. O MySQL
inclui uma cláusula limit que permite ordenar os dados e, em seguida,
descartar tudo menos as primeiras X linhas – veja o apêndice B para uma
discussão acerca da cláusula limit, junto com outras extensões não-ANSI.

Ordenação por meio de expressões


Ordenar os resultados usando dados de coluna é muito bom, mas às vezes
você precisará ordenar por algo que não está armazenado no banco de
dados e que possivelmente não aparecerá em lugar nenhum de sua
consulta. Você pode adicionar uma expressão em sua cláusula order by
para lidar com esse tipo de situação. Por exemplo, talvez você queira
ordenar seus dados de cliente pelos últimos três dígitos do número de
identi cação federal do cliente (que é um número de previdência social
para pessoa física ou um ID corporativo para pessoa jurídica):

Essa consulta usa a função nativa right() para extrair os três últimos
caracteres da coluna fed_id e, então, ordena as linhas baseada nesse valor.

Ordenando por meio de referências numéricas


Se estiver ordenando usando as colunas da sua cláusula select, você pode
optar por referenciar as colunas por suas posições dentro da cláusula
select em vez de usar seus nomes. Por exemplo, se você quiser ordenar
usando a segunda e quinta colunas retornadas por uma consulta, poderia
fazer o seguinte:
Você deve usar essa opção de forma moderada, pois a adição de uma
coluna na cláusula select sem a modi cação dos números na cláusula
order by pode causar resultados inesperados. Pessoalmente, posso até
referenciar colunas de forma posicional ao escrever consultas ad hoc, mas
sempre referencio as colunas pelo nome quando escrevo meu código.

Teste seu conhecimento


Os seguintes exercícios foram projetados para fortalecer sua compreensão
da instrução select e de suas várias cláusulas. Por favor, consulte o
apêndice C para veri car as soluções.

Exercício 3.1
Recupere o ID de funcionário, o nome e o sobrenome de todos os
funcionários do banco. Ordene pelo sobrenome e, então, pelo nome.

Exercício 3.2
Recupere o ID de conta, o ID de cliente e o saldo disponível de todas as
contas cujo status seja igual a 'ACTIVE' e cujo saldo disponível seja maior
que U$2.500.

Exercício 3.3
Escreva uma consulta à tabela account que retorne os IDs dos
empregados que abriram as contas (use a coluna account.open_emp_id).
Inclua uma única linha para cada funcionário especí co.

Exercício 3.4
Preencha as lacunas (denotadas por <#>) dessa consulta para conseguir os
seguintes resultados:
mysql> SELECT p.product_cd, a.cust_id, a.avail_balance
-> FROM product p INNER JOIN account <1>
-> ON p.product_cd = <2>
-> WHERE p.<3> = 'ACCOUNT'
-> ORDER BY <4>, <5>;
+------------+---------+---------------+
| product_cd | cust_id | avail_balance |
+------------+---------+---------------+
| CD | 1 | 3000.00 |
| CD | 6 | 10000.00 |
| CD | 7 | 5000.00 |
| CD | 9 | 1500.00 |
| CHK | 1 | 1057.75 |
| CHK | 2 | 2258.02 |
| CHK | 3 | 1057.75 |
| CHK | 4 | 534.12 |
| CHK | 5 | 2237.97 |
| CHK | 6 | 122.37 |
| CHK | 8 | 3487.19 |
| CHK | 9 | 125.67 |
| CHK | 10 | 23575.12 |
| CHK | 12 | 38552.05 |
| MM | 3 | 2212.50 |
| MM | 4 | 5487.09 |
| MM | 9 | 9345.55 |
| SAV | 1 | 500.00 |
| SAV | 2 | 200.00 |
| SAV | 4 | 767.77 |
| SAV | 8 | 387.99 |
+------------+---------+---------------+
21 rows in set (0.09 sec)
CAPÍTULO 4
Filtragem

Haverá momentos em que você vai querer trabalhar com todas as linhas
de uma tabela, tais como:
• Limpar todos os dados de uma tabela usada para alimentar um data
warehouse.
• Modi car todas as linhas de uma tabela após a adição de uma nova
coluna.
• Recuperar todas as linhas de uma tabela de la de mensagens.
Em casos como esses, suas instruções SQL não precisarão de uma
cláusula where, já que você não precisará desconsiderar nenhuma linha.
Na maior parte do tempo, entretanto, você precisará estreitar seu foco para
um subconjunto de uma tabela. Portanto, todas as instruções de dados
SQL (com exceção da instrução insert) incluem uma cláusula opcional
where para lidar com condições de ltro usadas para restringir o número de
linhas afetadas pela instrução SQL. Além disso, a instrução select inclui
uma cláusula having na qual podem ser incluídas condições de ltro
relativas a dados agrupados. Este capítulo explora os vários tipos de
condições de ltro que você pode empregar nas cláusulas where das
instruções select, update e delete – exploraremos o uso das condições de
ltro na cláusula having de uma instrução select no capítulo 8.

Avaliação de condições
Uma cláusula where pode conter uma ou mais condições, separadas pelos
operadores and e or. Se múltiplas condições forem separadas apenas por
operadores and, todas as condições devem ser verdadeiras para que a
linha seja incluída no conjunto-resultado. Considere a seguinte cláusula
where:
WHERE title = 'Teller' AND start_date < '2007-01-01'

Dadas essas duas condições, apenas os caixas que começaram a trabalhar


no banco antes de 2007 serão incluídos (ou, olhando de outra forma,
qualquer funcionário que não seja caixa ou que tenha começado a
trabalhar no banco de 2007 em diante será desconsiderado). Apesar de
esse exemplo usar apenas duas cláusulas, independentemente de quantas
condições tenha a sua cláusula where, se elas estiverem separadas por
operadores and, todas devem ser verdadeiras para que a linha seja
incluída no conjunto-resultado.
No entanto, se todas as condições da cláusula where estiverem separadas
por operadores or, apenas uma das condições deve ser verdadeira para
que a linha seja incluída no conjunto-resultado. Considere as duas
condições mostradas a seguir:
WHERE title = 'Teller' OR start_date < '2007-01-01'

Agora há várias maneiras de uma linha de um funcionário especí co ser


incluída em um conjunto-resultado:
• O funcionário é um caixa e foi empregado antes de 2007.
• O funcionário é um caixa e foi empregado a partir de 1º de janeiro de
2007.
• O empregado não é um caixa, mas foi empregado antes de 2007.
A tabela 4.1 mostra os resultados possíveis de uma cláusula where que
contenha duas condições separadas por um operador or.
Tabela 4.1 – Resultado de duas condições usando or
Resultado intermediário Resultado nal

WHERE true OR true True

WHERE true OR false True

WHERE false OR true True

WHERE false OR false False

No caso do exemplo anterior, a única maneira de uma linha ser excluída


do conjunto-resultado é o caso do funcionário não ser caixa e ter sido
empregado a partir de 1º de janeiro de 2007.
Usando parênteses
Se a sua cláusula where incluir três ou mais condições separadas tanto
pelo operador or quanto pelo operador and, você deve usar parênteses
para deixar claro qual é sua intenção, tanto para o servidor de banco de
dados quanto para qualquer outra pessoa que leia seu código. Aqui está
uma cláusula where que estende o exemplo anterior, veri cando se um
funcionário ainda está empregado no banco:
WHERE end_date IS NULL
AND (title = 'Teller' OR start_date < '2007-01-01')

Agora existem três condições: para uma linha constar no conjunto-


resultado nal, a primeira condição deve ser verdadeira, e uma entre a
segunda e a terceira condições (ou ambas) deve ser verdadeira. A tabela
4.2 mostra os resultados possíveis dessa cláusula where.
Tabela 4.2 – Resultado de três condições usando and, or
Resultado intermediário Resultado nal

WHERE true AND (true OR true) True

WHERE true AND (true OR false) True

WHERE true AND (false OR true) True

WHERE true AND (false OR false) False

WHERE false AND (true OR true) False

WHERE false AND (true OR false) False

WHERE false AND (false OR true) False

WHERE false AND (false OR false) False

Como você pode ver, quanto mais condições tiver sua cláusula where,
mais combinações haverá para o servidor avaliar. Nesse caso, apenas três
das oito combinações geram um resultado nal verdadeiro.

Usando o operador not


Felizmente, o exemplo anterior de três condições foi fácil de entender. No
entanto, considere a seguinte modi cação:
WHERE end_date IS NULL
AND NOT (title = 'Teller' OR start_date < '2007-01-01')

Encontrou a mudança relativa ao exemplo anterior? Eu adicionei o


operador not após o operador or na segunda linha. Agora, em vez de
procurar por funcionários ativos que sejam caixas ou que tenham
começado a trabalhar no banco antes de 2007, estou procurando por
funcionários ativos que não sejam caixas e que tenham começado a
trabalhar no banco de 2007 em diante. A tabela 4.3 mostra os resultados
possíveis desse exemplo.
Tabela 4.3 – Resultado de três condições usando and, or e not
Resultado intermediário Resultado nal

WHERE true AND NOT (true OR true) False

WHERE true AND NOT (true OR false) False

WHERE true AND NOT (false OR true) False

WHERE true AND NOT (false OR false) True

WHERE false AND NOT (true OR true) False

WHERE false AND NOT (true OR false) False

WHERE false AND NOT (false OR true) False

WHERE false AND NOT (false OR false) False

Apesar de ser fácil para o servidor de banco de dados lidar com esse tipo
de condição, costuma ser difícil para uma pessoa avaliar uma cláusula
where que inclua operadores not, e é por isso que você não os encontrará
com muita frequência. Nesse caso, você pode reescrever a cláusula where
de forma a evitar o uso do operador not:
WHERE end_date IS NULL
AND title != 'Teller' AND start_date >= '2007-01-01'

Mesmo tendo certeza de que o servidor não tem uma preferência, você
provavelmente entenderá mais facilmente essa versão da cláusula where.

Construindo uma condição


Agora que você já viu como o servidor avalia múltiplas condições, vamos
voltar um passo e estudar o que compõe uma única condição. Uma
condição é composta por uma ou mais expressões unidas por um ou mais
operadores. Uma expressão pode ser:
• Um número.
• Uma coluna de uma tabela ou de uma view.
• Uma string literal, como 'Teller'.
• Uma função nativa, como concat('Learning', ' ', 'SQL').
• Uma subconsulta.
• Uma lista de expressões, como ('Teller', 'Head Teller',
'Operations Manager').

Os operadores usados dentro das condições incluem:


• Operadores de comparação, como =, !=, <, >, <>, LIKE, IN e
BETWEEN

• Operadores aritméticos, como +, -, * e /

A próxima seção demonstra como você pode combinar essas expressões e


operadores para construir os vários tipos de condições.

Tipos de condições
Há várias maneiras diferentes de se ltrar dados indesejados. Você pode
procurar por valores especí cos, conjuntos de valores ou intervalos de
valores que serão incluídos ou excluídos, ou então você pode usar várias
técnicas de pesquisa de padrões para pesquisar correspondências parciais
ao lidar com dados do tipo string. As quatro próximas subseções
exploram cada um desses tipos de condições detalhadamente.

Condições de igualdade
Uma grande porcentagem das condições de ltro que você escreverá ou
com as quais irá se deparar tem o formato 'coluna = expressão', como
em:
title = 'Teller'
fed_id = '111-11-1111'
amount = 375.25
dept_id = (SELECT dept_id FROM department WHERE name = 'Loans')

Condições como essas são chamadas de condições de igualdade porque


igualam uma expressão à outra. Os primeiros três exemplos igualam uma
coluna a um literal (duas strings e um número), e o quarto exemplo
iguala uma coluna a um valor retornado por uma subconsulta. A consulta
a seguir usa duas condições de igualdade – uma na cláusula on (uma
condição de junção) e outra na cláusula where (uma condição de ltro):

Essa consulta mostra todos os produtos que sejam tipos de conta de


clientes.

Condições de desigualdade
Outro tipo de condição muito comum é a condição de desigualdade, que
a rma que duas expressões não são iguais. Aqui está a consulta anterior
com a condição de ltro na cláusula where modi cada para uma condição
de desigualdade:
Essa consulta mostra todos os produtos que não são tipos de conta de
clientes (customer accounts). Ao construir condições de desigualdade,
você pode optar por usar o operador != ou o operador <>.

Modi cação de dados usando condições de igualdade


Condições de igualdade/desigualdade são muito usadas em modi cação
de dados. Por exemplo, digamos que o banco tenha uma política de
remover linhas de contas antigas uma vez por ano. Sua tarefa é remover
linhas da tabela account que tenham sido fechadas em 2002. Aqui está
uma maneira de fazer isso:
DELETE FROM account
WHERE status = 'CLOSED' AND YEAR(close_date) = 2002;

Essa instrução inclui duas condições de desigualdade: uma para


encontrar apenas contas fechadas e outra para buscar as contas fechadas
em 2002.
Ao criar exemplos de instruções delete e update, eu procuro
escrever cada instrução de tal maneira que nenhuma linha seja
modi cada. Dessa forma, ao executar essas instruções, seus
dados não serão modi cados, e a saída das instruções select
sempre re etirá o que é mostrado no livro.
Como as seções do MySQL estão no modo de autocomissão
(auto-commit) por padrão (veja o capítulo 12), você não
conseguirá retroceder (roll back) quaisquer mudanças feitas
nos dados de exemplo caso uma de minhas instruções
modi que os dados. Você pode, claro, fazer o que quiser com
os dados de exemplo, inclusive removê-los por completo e
executar novamente os scripts que forneci, mas eu tento mantê-
los intactos.

Condições de intervalo
Junto com a veri cação de que uma expressão é igual (ou desigual) a
outra expressão, você pode construir condições que veri cam se uma
expressão está dentro de certo intervalo. Esse tipo de condição é comum
quando se trabalha com dados numéricos ou temporais. Considere a
seguinte consulta:

Essa consulta pesquisa todos os funcionários contratados antes de 2007.


Junto com a especi cação de um limite superior para a data de início,
você também pode querer especi car um limite inferior para ela:
Essa versão da consulta retorna todos os funcionários contratados em
2005 ou 2006.

Operador between
Quando você tem tanto o limite superior quanto o inferior de seu
intervalo, você pode optar por usar uma única condição que utilize o
operador between em vez de usar duas condições separadas, como em:
Ao usar o operador between, há duas coisas que você deve ter em mente.
Você deve sempre especi car o limite inferior do intervalo primeiro (após
between) e o limite superior do intervalo em segundo lugar (após and).
Veja o que acontece caso você especi que sem querer o limite superior em
primeiro lugar:
mysql> SELECT emp_id, fname, lname, start_date
-> FROM employee
-> WHERE start_date BETWEEN '2007-01-01' AND '2005-01-01';
Empty set (0.00 sec)

Como você pode ver, nenhum dado é retornado. Isso acontece porque o
servidor está, de fato, gerando duas condições a partir de sua condição
única usando os operadores >= e <=, assim:
mysql> SELECT emp_id, fname, lname, start_date
-> FROM employee
-> WHERE start_date >= '2007-01-01'
-> AND start_date <= '2005-01-01';
Empty set (0.00 sec)

Como é impossível ter uma data que seja, ao mesmo tempo, posterior a 1º
de janeiro de 2007 e anterior a 1º de janeiro de 2005, a consulta retorna
um conjunto vazio. Isso nos leva à segunda armadilha do operador
between, que é se lembrar de que os limites inferior e superior são
inclusivos – isso signi ca que os valores fornecidos por você serão
incluídos nos limites do intervalo. Nesse caso, eu quero especi car 2005-
01-01 como o limite inferior do intervalo e 2006-12-31 como o limite
superior, em vez de 2007-01-01. Mesmo que seja improvável que algum
funcionário tenha sido contratado no Ano Novo de 2007, é melhor
especi car exatamente aquilo que você quer.
Assim como com as datas, você também pode construir condições que
especi quem intervalos de números. Intervalos numéricos são fáceis de
entender, como demonstrado a seguir:
Todas as contas com saldo disponível entre $ 3.000 e $ 5.000 foram
retornadas. Novamente, certi que-se de que você especi cou o limite
inferior primeiro.

Intervalos de string
Assim como você pode construir intervalos de datas e números, que são
fáceis de entender, você também pode construir condições que pesquisem
intervalos de strings, que são um pouco mais difíceis de visualizar.
Digamos, por exemplo, que você esteja pesquisando os clientes cujo
número de Seguro Social esteja dentro de certo intervalo. O formato de
um número de Seguro Social é “XXX-XX-XXXX”, em que X indica um
número entre 0 e 9, e você quer encontrar todos os clientes cujo número
de Seguro esteja entre “500-00-0000” e “999-99-9999”. A instrução caria
assim:
mysql> SELECT cust_id, fed_id
-> FROM customer
-> WHERE cust_type_cd = 'I'
-> AND fed_id BETWEEN '500-00-0000' AND '999-99-9999';
+---------+-------------+
| cust_id | fed_id |
+---------+-------------+
| 5 | 555-55-5555 |
| 6 | 666-66-6666 |
| 7 | 777-77-7777 |
| 8 | 888-88-8888 |
| 9 | 999-99-9999 |
+---------+-------------+
5 rows in set (0.01 sec)

Para trabalhar com intervalos de strings, você precisa saber a ordem dos
caracteres dentro de seu conjunto de caracteres (a ordem em que os
caracteres estão dispostos dentro de um conjunto de caracteres é chamada
de collation).

Condições de adesão
Em alguns casos, você não irá restringir uma expressão para um único
valor ou um intervalo de valores, e sim para um conjunto nito de valores.
Por exemplo, você pode desejar localizar todas as contas cujo código de
produto seja 'CHK', 'SAV', 'CD' ou 'MM':

Embora não tenha sido muito cansativo gerar essa cláusula where (quatro
condições unidas por operadores or), imagine se o conjunto de expressões
tivesse 10 ou 20 membros. Para essas situações, você pode usar o operador
in:
SELECT account_id, product_cd, cust_id, avail_balance
FROM account
WHERE product_cd IN ('CHK','SAV','CD','MM');

Com o operador in, você pode escrever uma única condição


independentemente do número de expressões existentes no conjunto.

Usando subconsultas
Além de de nir seu próprio conjunto de expressões, como
('CHK','SAV','CD','MM'), você também pode usar uma subconsulta para
gerar um conjunto na hora. Por exemplo, todos os quatro tipos de
produtos usados na consulta anterior têm seu campo product_type_cd
de nido como 'ACCOUNT', então por que não usar uma subconsulta à
tabela product para recuperar os códigos dos quatro produtos, em vez de
nomeá-los explicitamente:

A subconsulta retorna um conjunto de quatro valores, e a consulta


principal veri ca se o valor da coluna product_cd pode ser encontrado no
conjunto que a subconsulta retornou.

Usando not in
Por vezes, você vai precisar saber se uma expressão em particular existe
dentro de um conjunto de expressões, e às vezes você terá que saber se ela
não existe. Nessas situações, você pode usar o operador not in:

Essa consulta pesquisa todas as contas que não sejam conta corrente
(CHK), poupança (SAV), certi cado de depósito (CD) ou fundo de
investimento (MM).

Condições de correspondência
Até agora, você conheceu condições que identi cam uma string exata, um
intervalo de strings ou um conjunto de strings; o último tipo de condição
lida com correspondências parciais de strings. Você pode, por exemplo,
querer encontrar todos os funcionários cujo sobrenome comece com T.
Você poderia usar uma função nativa para extrair a primeira letra da
coluna lname, como em:
mysql> SELECT emp_id, fname, lname
-> FROM employee
-> WHERE LEFT(lname, 1) = 'T';
+--------+--------+--------+
| emp_id | fname | lname |
+--------+--------+--------+
| 3 | Robert | Tyler |
| 7 | Chris | Tucker |
| 18 | Rick | Tulman |
+--------+--------+--------+
3 rows in set (0.01 sec)

Apesar de a função nativa left() realizar bem seu trabalho, ela não
oferece muita exibilidade. Em vez disso, você pode usar caracteres-
curinga para construir expressões de pesquisa, como será demonstrado
na próxima seção.

Usando curingas
Ao pesquisar por correspondências parciais de strings, você pode estar
interessado em:
• Strings iniciadas/terminadas por um caractere especí co.
• Strings iniciadas/terminadas por uma substring.
• Strings contendo um caractere especí co em qualquer posição dentro
da string em que será feita a pesquisa.
• Strings contendo uma substring em qualquer posição dentro da string
em que será feita a pesquisa.
• Strings com um formato especí co, independentemente de caracteres
individuais.
Você pode construir expressões de pesquisa para identi car essas e muitas
outras correspondências parciais de strings usando os caracteres-curinga
mostrados na tabela 4.4.
Tabela 4.4 – Caracteres-curinga
Caractere-curinga Correspondências

_ Exatamente um caractere

% Qualquer número de caracteres (incluindo 0)

O caractere underscore assume o lugar de um único caractere, enquanto o


caractere de porcentagem pode assumir o lugar de um número variável de
caracteres. Para construir condições que utilizem expressões de pesquisa
você deve usar o operador like, como em:
mysql> SELECT lname
-> FROM employee
-> WHERE lname LIKE '_a%e%';
+-----------+
| lname |
+-----------+
| Barker |
| Hawthorne |
| Parker |
| Jameson |
+-----------+
4 rows in set (0.00 sec)

A expressão de pesquisa do exemplo anterior especi ca strings que


contenham um a na segunda posição, seguido por um e em qualquer
outra posição na string (incluindo a última posição). A tabela 4.5 mostra
mais algumas expressões de pesquisa e suas interpretações.
Tabela 4.5 – Exemplos de expressões de pesquisa
Expressão de pesquisa Interpretação

F% Strings iniciadas por F

%t Strings terminadas por t

%bas% Strings que contenham a substring ‘bas’

_ _t_ String de quatro caracteres com um t na terceira posição

_ _ _-_ _-_ _ _ _ Strings de 11 caracteres com hífens na quarta e na sétima posições

Você poderia usar o último exemplo da tabela 4.5 para encontrar clientes
cuja identi cação federal corresponda ao formato usado em números de
Seguro Social, como em:
mysql> SELECT cust_id, fed_id
-> FROM customer
-> WHERE fed_id LIKE '___-__-____';
+---------+-------------+
| cust_id | fed_id |
+---------+-------------+
| 1 | 111-11-1111 |
| 2 | 222-22-2222 |
| 3 | 333-33-3333 |
| 4 | 444-44-4444 |
| 5 | 555-55-5555 |
| 6 | 666-66-6666 |
| 7 | 777-77-7777 |
| 8 | 888-88-8888 |
| 9 | 999-99-9999 |
+---------+-------------+
9 rows in set (0.02 sec)

Os caracteres-curinga funcionam bem na construção de expressões de


pesquisa simples, no entanto, se você necessita de algo mais so sticado,
pode usar múltiplas expressões de pesquisa, como demonstrado a seguir:
mysql> SELECT emp_id, fname, lname
-> FROM employee
-> WHERE lname LIKE 'F%' OR lname LIKE 'G%';
+--------+-------+----------+
| emp_id | fname | lname |
+--------+-------+----------+
| 5 | John | Gooding |
| 6 | Helen | Fleming |
| 9 | Jane | Grossman |
| 17 | Beth | Fowler |
+--------+-------+----------+
4 rows in set (0.00 sec)

Essa consulta identi ca todos os funcionários cujo sobrenome inicie com


F ou G.

Usando expressões regulares


Se você achar que os caracteres-curinga não fornecem exibilidade
su ciente, você pode usar expressões regulares para construir expressões
de pesquisa. Uma expressão regular é, essencialmente, uma expressão de
pesquisa com esteróides. Se você é novo no mundo do SQL, mas já
codi cou usando linguagens de programação como Perl, então você já
deve estar intimamente familiarizado com expressões regulares. Se nunca
usou expressões regulares, talvez seja útil consultar o livro Mastering
Regular Expressions, de Je rey E. F. Friedl
(http://oreilly.com/catalog/9780596528126/) (O’Reilly), já que este é um
tópico grande demais para abordar neste livro.
Veja como a consulta anterior (encontrar todos os funcionários cujo
sobrenome inicie com F ou G) caria usando a implementação do
MySQL de expressões regulares:
mysql> SELECT emp_id, fname, lname
-> FROM employee
-> WHERE lname REGEXP '^[FG]';
+--------+-------+----------+
| emp_id | fname | lname |
+--------+-------+----------+
| 5 | John | Gooding |
| 6 | Helen | Fleming |
| 9 | Jane | Grossman |
| 17 | Beth | Fowler |
+--------+-------+----------+
4 rows in set (0.00 sec)
O operador regexp pega uma expressão regular ('^[FG]', nesse exemplo) e
a aplica na expressão à esquerda da condição (a coluna lname). A consulta
agora contém uma única condição que usa uma expressão regular, em vez
de duas condições que usam caracteres-curinga.
O Oracle Database e o Microsoft SQL Server também suportam
expressões regulares. No Oracle Database, você usaria a função
regexp_like em vez do operador regexp mostrado no exemplo anterior, ao
passo que o SQL Server permite que expressões regulares sejam usadas
com o operador like.

Null: aquela palavra de quatro letras


Evitei o assunto enquanto pude, mas chegou a hora de discutir um tópico
que costuma ser encarado com medo, incerteza e horror: o valor null
(nulo). Null é a ausência de valor – antes de um funcionário ser mandado
embora, por exemplo, sua coluna end_date na tabela employee deve ser
nula. Não há valor que possa ser atribuído à coluna end_date e que faça
sentido nessa situação. Mas null é um tanto evasivo, pois pode aparecer
em diversos sabores:
Não aplicável
Tal como a coluna de ID de funcionário em uma transação que
ocorreu em uma máquina de autoatendimento.
Valor ainda não conhecido
Como no caso da identi cação federal que não é conhecida no
momento em que uma linha de cliente é criada
Valor inde nido
Como quando uma conta é criada para um produto que ainda não foi
adicionado ao banco de dados
Alguns teóricos argumentam que devia haver uma expressão
diferente para cada uma dessas (entre outras) situações, mas a
maioria dos pro ssionais concorda que ter vários tipos de
valores null seria algo extremamente confuso.
Ao trabalhar com null, você deve se lembrar que:
• Uma expressão pode ser nula, mas nunca pode ser igual a null.
• Dois nulls nunca são equivalentes.
Para testar se uma expressão é nula, você precisa usar o operador is null,
como demonstrado a seguir:

Essa consulta retorna todos os funcionários que não tenham um chefe (já
pensou que maravilha?). Aqui está a mesma consulta, usando = null em
vez de is null:
mysql> SELECT emp_id, fname, lname, superior_emp_id
-> FROM employee
-> WHERE superior_emp_id = NULL;
Empty set (0.01 sec)

Como você pode ver, a consulta é analisada e executada, mas não retorna
nenhuma linha. Isso é um erro comum cometido por programadores SQL
inexperientes, e o servidor de banco de dados não o alertará sobre seu
erro, então seja cuidadoso ao construir condições que testem valores
nulos.
Se você quiser saber se algum valor foi atribuído a uma coluna, você pode
usar o operador is not null, como em:

Essa versão da consulta retorna os outros 17 funcionários que, ao


contrário de Michael Smith, têm um chefe.
Antes de colocar o null de lado por um momento, seria interessante
investigar mais uma armadilha em potencial. Suponha que alguém solicite
que você identi que todos os funcionários que não sejam gerenciados por
Helen Fleming (cujo ID de funcionário é 6). Seu primeiro impulso
poderia ser fazer o seguinte:

Apesar de ser verdade que esses 14 funcionários não trabalham para


Helen Fleming, se você olhar atentamente para os dados verá que existe
mais um funcionário que não trabalha para Helen e que não está listado
aqui. Esse funcionário é Michael Smith, cuja coluna superior_emp_id é
nula (porque ele é o chefão). Portanto, para responder corretamente à
questão, você precisa levar em conta a possibilidade de que algumas
linhas podem conter um null na coluna superior_emp_id:

O conjunto-resultado agora inclui todos os 15 funcionários que não


trabalham para Helen. Ao trabalhar com um banco de dados com o qual
você não está familiarizado, é sempre uma boa ideia descobrir quais
colunas em uma tabela permitem nulls, para que você possa tomar as
medidas apropriadas em suas condições de ltro, evitando que dados
escapem pelas rachaduras.

Teste seu conhecimento


Os seguintes exercícios testam sua compreensão de condições de ltro. Por
favor, consulte o apêndice C para ver as soluções.
Os seguintes dados de transação são usados nos dois primeiros exercícios:
Txn_id Txn_date Account_id Txn_type_cd Amount

1 2005-02-22 101 CDT 1000.00

2 2005-02-23 102 CDT 525.75

3 2005-02-24 101 DBT 100.00

4 2005-02-24 103 CDT 55

5 2005-02-25 101 DBT 50

6 2005-02-25 103 DBT 25

7 2005-02-25 102 CDT 125.37

8 2005-02-26 103 DBT 10

9 2005-02-27 101 CDT 75

Exercício 4.1
Quais IDs de transação seriam retornados pelas seguintes condições de
ltro?
txn_date < '2005-02-26' AND (txn_type_cd = 'DBT' OR amount > 100)
Exercício 4.2
Quais IDs de transação seriam retornados pelas seguintes condições de
ltro?
account_id IN (101,103) AND NOT (txn_type_cd = 'DBT' OR amount >
100)

Exercício 4.3
Construa uma consulta que recupere todas as contas abertas em 2002.

Exercício 4.4
Construa uma consulta que encontre todos os clientes não-corporativos
cujo sobrenome contenha um a na segunda posição e um e em qualquer
lugar após a.
CAPÍTULO 5
Consultando múltiplas tabelas

No capítulo 2, eu mostrei como conceitos relacionados são divididos em


fragmentos independentes por meio de um processo chamado
normalização. O resultado nal daquele exercício foram duas tabelas:
person e favorite_food. Se, no entanto, você precisar gerar um único
relatório que mostra o nome, o endereço e os pratos favoritos de uma
pessoa, será necessário um mecanismo que reúna os dados dessas duas
tabelas. Esse mecanismo é conhecido como junção (join), e este capítulo
se concentra na junção mais simples e mais comum que existe, a junção
interna (inner join). O capítulo 10 ilustrará todos os diferentes tipos de
junções.

O que é uma junção?


Consultas a uma única tabela certamente não são raras, mas você
descobrirá que a maioria de suas consultas requer duas, três ou mais
tabelas. A título de ilustração, vamos dar uma olhada nas de nições das
tabelas employee e department e, então, de nir uma consulta que recupere
dados de ambas as tabelas:
Digamos que você deseje recuperar o nome e o sobrenome de cada
funcionário, junto com o nome do departamento em que cada um deles
trabalha. Sua consulta, portanto, precisa recuperar as colunas
employee.fname, employee.lname e department.name. Mas como você
recuperaria dados de ambas as tabelas na mesma consulta? A resposta
está na coluna employee.dept_id, que armazena o ID do departamento ao
qual cada funcionário está vinculado (em termos mais formais, a coluna
employee.dept_id é a chave estrangeira para a tabela department). A
consulta, que veremos em breve, instrui o servidor a usar a coluna
employee.dept_id como ponte entre as tabelas employee e department,
permitindo, assim, que colunas de ambas as tabelas sejam incluídas no
conjunto-resultado da consulta. Esse tipo de operação é conhecido como
junção.

Produto cartesiano
A maneira mais fácil de começar é colocando as tabelas employee e
department na cláusula from de uma consulta e ver o que acontece. Aqui
está uma consulta que recupera o nome e sobrenome de cada funcionário
junto com o nome do departamento, por meio de uma cláusula from que
nomeia ambas as tabelas separadas pela palavra-chave join:
mysql> SELECT e.fname, e.lname, d.name
-> FROM employee e JOIN department d;
+----------+-----------+----------------+
| fname | lname | name |
+----------+-----------+----------------+
| Michael | Smith | Operations |
| Michael | Smith | Loans |
| Michael | Smith | Administration |
| Susan | Barker | Operations |
| Susan | Barker | Loans |
| Susan | Barker | Administration |
| Robert | Tyler | Operations |
| Robert | Tyler | Loans |
| Robert | Tyler | Administration |
| Susan | Hawthorne | Operations |
| Susan | Hawthorne | Loans |
| Susan | Hawthorne | Administration |
| John | Gooding | Operations |
| John | Gooding | Loans |
| John | Gooding | Administration |
| Helen | Fleming | Operations |
| Helen | Fleming | Loans |
| Helen | Fleming | Administration |
| Chris | Tucker | Operations |
| Chris | Tucker | Loans |
| Chris | Tucker | Administration |
| Sarah | Parker | Operations |
| Sarah | Parker | Loans |
| Sarah | Parker | Administration |
| Jane | Grossman | Operations |
| Jane | Grossman | Loans |
| Jane | Grossman | Administration |
| Paula | Roberts | Operations |
| Paula | Roberts | Loans |
| Paula | Roberts | Administration |
| Thomas | Ziegler | Operations |
| Thomas | Ziegler | Loans |
| Thomas | Ziegler | Administration |
| Samantha | Jameson | Operations |
| Samantha | Jameson | Loans |
| Samantha | Jameson | Administration |
| John | Blake | Operations |
| John | Blake | Loans |
| John | Blake | Administration |
| Cindy | Mason | Operations |
| Cindy | Mason | Loans |
| Cindy | Mason | Administration |
| Frank | Portman | Operations |
| Frank | Portman | Loans |
| Frank | Portman | Administration |
| Theresa | Markham | Operations |
| Theresa | Markham | Loans |
| Theresa | Markham | Administration |
| Beth | Fowler | Operations |
| Beth | Fowler | Loans |
| Beth | Fowler | Administration |
| Rick | Tulman | Operations |
| Rick | Tulman | Loans |
| Rick | Tulman | Administration |
+----------+-----------+----------------+
54 rows in set (0.23 sec)

Hummm... Existem apenas 18 funcionários e três departamentos


diferentes, então como o conjunto-resultado resultou em 54 linhas?
Olhando mais atentamente, você verá que o conjunto de 18 funcionários
foi repetido três vezes, com todos os dados idênticos, exceto pelo nome de
departamento. Como a consulta não especi cou a forma de junção das
duas tabelas, o servidor de banco de dados gerou o produto cartesiano,
que é conjunto das combinações entre os elementos das duas tabelas (18
funcionários x 3 departamentos = 54 combinações). Esse tipo de junção é
conhecido como uma junção cruzada (cross join), e é raramente usado
(intencionalmente, pelo menos). Junções cruzadas são um dos tipos de
junções que estudaremos no capítulo 10.

Junções internas
Para modi car a consulta anterior de forma que apenas 18 linhas sejam
incluídas no conjunto-resultado (uma para cada funcionário), você
precisa descrever como as duas tabelas se relacionam. Anteriormente,
mencionei que a coluna employee.dept_id serviria de vínculo entre as
duas tabelas, então essa informação precisa ser adicionada à subcláusula
on da cláusula from:
mysql> SELECT e.fname, e.lname, d.name
-> FROM employee e JOIN department d
-> ON e.dept_id = d.dept_id;
+----------+-----------+----------------+
| fname | lname | name |
+----------+-----------+----------------+
| Michael | Smith | Administration |
| Susan | Barker | Administration |
| Robert | Tyler | Administration |
| Susan | Hawthorne | Operations |
| John | Gooding | Loans |
| Helen | Fleming | Operations |
| Chris | Tucker | Operations |
| Sarah | Parker | Operations |
| Jane | Grossman | Operations |
| Paula | Roberts | Operations |
| Thomas | Ziegler | Operations |
| Samantha | Jameson | Operations |
| John | Blake | Operations |
| Cindy | Mason | Operations |
| Frank | Portman | Operations |
| Theresa | Markham | Operations |
| Beth | Fowler | Operations |
| Rick | Tulman | Operations |
+----------+-----------+----------------+
18 rows in set (0.00 sec)

Em vez de 54 linhas, agora você tem as 18 linhas esperadas devido à


adição da subcláusula on, que instrui o servidor a juntar as tabelas
employee e department utilizando a coluna dept_id para percorrer de uma
tabela a outra. Por exemplo, a linha de Susan Hawthorne na tabela
employee contém um valor de 1 na coluna dept_id (que não é mostrada no
exemplo). O servidor usa esse valor para procurar a linha da tabela
department que tenha um valor de 1 em sua coluna department e, então,
recupera o valor ‘Operations’ da coluna name daquela linha.
Se um valor existe na coluna dept_id de uma tabela mas não existe na
outra, a junção falha para as linhas que contenham esse valor, e tais
linhas são excluídas do conjunto-resultado. Esse tipo de junção é
conhecido como junção interna (inner join) e é o tipo de junção mais
utilizado. Para esclarecer as coisas: se, por exemplo, a tabela department
tivesse uma quarta linha para o departamento de marketing, mas nenhum
funcionário tivesse sido alocado nele, o departamento de marketing não
seria incluído no conjunto-resultado. De forma semelhante, se um ou
mais funcionários tivessem sido alocados ao departamento de ID 99, que
não existe na tabela department, esses funcionários cariam de fora do
conjunto-resultado. Se você quiser incluir todas as linhas de uma das
tabelas, independentemente de existirem ou não correspondências, você
precisará de nir uma junção externa (outer join), mas falaremos sobre isso
posteriormente.
No exemplo anterior, não especi quei na cláusula from o tipo de junção a
ser usado. No entanto, quando você deseja juntar duas tabelas por meio
de uma junção interna, você deve especi car isso explicitamente em sua
cláusula from; aqui está o mesmo exemplo, com a adição do tipo de
junção (perceba a palavra-chave INNER):
mysql> SELECT e.fname, e.lname, d.name
-> FROM employee e INNER JOIN department d
-> ON e.dept_id = d.dept_id;
+----------+-----------+----------------+
| fname | lname | name |
+----------+-----------+----------------+
| Michael | Smith | Administration |
| Susan | Barker | Administration |
| Robert | Tyler | Administration |
| Susan | Hawthorne | Operations |
| John | Gooding | Loans |
| Helen | Fleming | Operations |
| Chris | Tucker | Operations |
| Sarah | Parker | Operations |
| Jane | Grossman | Operations |
| Paula | Roberts | Operations |
| Thomas | Ziegler | Operations |
| Samantha | Jameson | Operations |
| John | Blake | Operations |
| Cindy | Mason | Operations |
| Frank | Portman | Operations |
| Theresa | Markham | Operations |
| Beth | Fowler | Operations |
| Rick | Tulman | Operations |
+----------+-----------+----------------+
18 rows in set (0.00 sec)

Se você não especi car o tipo de junção, o servidor fará uma junção
interna por padrão. No entanto, como você verá mais tarde no livro, há
vários tipos de junções, então você deve cultivar o hábito de especi car o
tipo exato de junção pretendida.
Se os nomes das colunas usadas para juntar as duas tabelas forem
idênticos, o que é o caso da consulta anterior, você pode usar a
subcláusula using em vez da subcláusula on, como em:
mysql> SELECT e.fname, e.lname, d.name
-> FROM employee e INNER JOIN department d
-> USING (dept_id);
+----------+-----------+----------------+
| fname | lname | name |
+----------+-----------+----------------+
| Michael | Smith | Administration |
| Susan | Barker | Administration |
| Robert | Tyler | Administration |
| Susan | Hawthorne | Operations |
| John | Gooding | Loans |
| Helen | Fleming | Operations |
| Chris | Tucker | Operations |
| Sarah | Parker | Operations |
| Jane | Grossman | Operations |
| Paula | Roberts | Operations |
| Thomas | Ziegler | Operations |
| Samantha | Jameson | Operations |
| John | Blake | Operations |
| Cindy | Mason | Operations |
| Frank | Portman | Operations |
| Theresa | Markham | Operations |
| Beth | Fowler | Operations |
| Rick | Tulman | Operations |
+----------+-----------+----------------+
18 rows in set (0.01 sec)

Como using é uma notação compacta que pode ser usada em apenas uma
situação especí ca, pre ro sempre usar a subcláusula on para evitar
confusão.

A sintaxe ANSI de junção


A notação usada ao longo deste livro para juntar tabelas foi introduzida
na versão SQL92 do padrão ANSI SQL. Todos os principais bancos de
dados (Oracle Database, Microsoft SQL Server, MySQL, IBM DB2
Universal Database e Sybase Adaptive Server) adotaram a sintaxe de
junção do SQL92. Como a maioria desses servidores já estava no mercado
antes do lançamento da especi cação SQL92, todos eles também incluem
uma sintaxe de junção mais antiga. Por exemplo, todos esses servidores
entenderiam a seguinte variação da consulta anterior:
mysql> SELECT e.fname, e.lname, d.name
-> FROM employee e, department d
-> WHERE e.dept_id = d.dept_id;
+----------+-----------+----------------+
| fname | lname | name |
+----------+-----------+----------------+
| Michael | Smith | Administration |
| Susan | Barker | Administration |
| Robert | Tyler | Administration |
| Susan | Hawthorne | Operations |
| John | Gooding | Loans |
| Helen | Fleming | Operations |
| Chris | Tucker | Operations |
| Sarah | Parker | Operations |
| Jane | Grossman | Operations |
| Paula | Roberts | Operations |
| Thomas | Ziegler | Operations |
| Samantha | Jameson | Operations |
| John | Blake | Operations |
| Cindy | Mason | Operations |
| Frank | Portman | Operations |
| Theresa | Markham | Operations |
| Beth | Fowler | Operations |
| Rick | Tulman | Operations |
+----------+-----------+----------------+
18 rows in set (0.01 sec)

Esse método mais antigo de especi car junções não inclui a subcláusula
on – em vez disso, as tabelas são nomeadas na cláusula from separados por
vírgulas, e as condições de junção são incluídas na cláusula where. Apesar
de você poder decidir ignorar a sintaxe do SQL92 em favor da sintaxe de
junção mais antiga, a sintaxe de junção ANSI tem as seguintes vantagens:
• Condições de junção e condições de ltro são separadas em duas
cláusulas diferentes (a subcláusula on e a cláusula where,
respectivamente), tornando a consulta mais fácil de entender.
• As condições de junção para cada par de tabelas estão contidas em
suas próprias cláusulas on, tornando menos provável que parte de uma
junção seja omitida por engano.
• Consultas que usam a sintaxe de junção do SQL92 são portáveis entre
servidores de banco de dados, ao passo que a sintaxe mais antiga é
ligeiramente diferente entre os diferentes servidores.
Os benefícios da sintaxe de junção do SQL92 são visualizados mais
facilmente no caso de consultas complexas que incluam tanto condições
de junção quanto de ltro. Considere a seguinte consulta, que retorna
todas as contas abertas por caixas experientes (contratados antes de 2007)
que estejam atualmente alocados na lial de Woburn:

No caso dessa consulta, não é tão fácil assim determinar quais condições
da cláusula where são condições de junção e quais são condições de ltro.
Também não ca tão aparente que tipo de junção está sendo empregado
(para identi car o tipo de junção, você teria que observar mais de perto as
condições de junção na cláusula where para ver se algum tipo de caractere
especial foi utilizado), e não é fácil determinar se alguma condição de
junção foi deixada de fora por engano. Aqui está a mesma consulta
usando a sintaxe de junção do SQL92:

Creio que você concordará que a versão utilizando a sintaxe de junção do


SQL92 é a mais fácil de entender.

Juntando três ou mais tabelas


Juntar três tabelas é semelhante a juntar duas tabelas, mas tem um
pequeno truque. Em uma junção de duas tabelas, existem duas tabelas e
um tipo de junção na cláusula from, além de uma única subcláusula on
que de ne como as tabelas serão juntadas. Em uma junção de três
tabelas, existem três tabelas e dois tipos de junção na cláusula from, além
de duas subcláusulas on. Aqui está outro exemplo de uma consulta com
uma junção de duas tabelas:
mysql> SELECT a.account_id, c.fed_id
-> FROM account a INNER JOIN customer c
-> ON a.cust_id = c.cust_id
-> WHERE c.cust_type_cd = 'B';
+------------+------------+
| account_id | fed_id |
+------------+------------+
| 24 | 04-1111111 |
| 25 | 04-1111111 |
| 27 | 04-2222222 |
| 28 | 04-3333333 |
| 29 | 04-4444444 |
+------------+------------+
5 rows in set (0.15 sec)

Essa consulta, que retorna o ID da conta e o número de imposto federal


de todas as contas corporativas, deve parecer bem simples para você a essa
altura. Se, no entanto, você adicionar a tabela employee à consulta para
também recuperar o nome do caixa que abriu cada conta, a consulta
cará assim:

Agora três tabelas, dois tipos de junção e duas subcláusulas on estão


listadas na cláusula from, tornando as coisas um pouco mais complexas. À
primeira vista, a ordem em que as tabelas são nomeadas pode fazer você
pensar que a tabela employee está sendo juntada à tabela customer, pois a
tabela account é nomeada primeiro, seguida pela tabela customer e, então,
pela tabela employee. No entanto, se você trocar a ordem em que as
primeiras duas tabelas aparecem, você obterá exatamente o mesmo
resultado:

A tabela customer agora está listada primeiro, seguida pela tabela account
e, então, pela tabela employee. Como as subcláusulas on não mudaram, os
resultados são os mesmos. Para esgotar a questão, aqui está a mesma
consulta uma última vez, mas com uma ordem de tabelas completamente
invertida (de employee para account para customer):
A ordem das junções importa?
Se você estiver confuso sobre o porquê de as três versões da consulta a
account/employee/customer terem dado o mesmo resultado, tenha em
mente que SQL é uma linguagem não-procedural, o que signi ca que
você descreve o que quer recuperar e quais objetos do banco de dados
precisam estar envolvidos, mas ca a critério do servidor de banco de
dados determinar a melhor maneira de executar sua consulta. Usando
estatísticas reunidas a partir de seus objetos de banco de dados, o
servidor deve pegar uma das três tabelas como ponto de partida (a
tabela escolhida ca conhecida como tabela condutora), e então decide
em qual ordem juntar as tabelas restantes. Portanto, a ordem em que
as tabelas aparecem em sua cláusula from é irrelevante.
Se, no entanto, você acredita que as tabelas em sua consulta devem ser
juntadas sempre em uma ordem especí ca, você pode colocar as
tabelas na ordem desejada e, então, especi car a palavra-chave
STRAIGHT_JOIN no MySQL, requisitar a opção FORCE ORDER no SQL
Server ou usar a dica de otimização ORDERED ou a dica LEADING no
Oracle Database. Por exemplo, para dizer ao servidor MySQL para
usar a tabela customer como a tabela condutora e, então, juntar as
tabelas account e employee, você poderia fazer o seguinte:
mysql> SELECT STRAIGHT_JOIN a.account_id, c.fed_id, e.fname,
e.lname
-> FROM customer c INNER JOIN account a
-> ON a.cust_id = c.cust_id
-> INNER JOIN employee e
-> ON a.open_emp_id = e.emp_id
-> WHERE c.cust_type_cd = ‘B’;

Uma maneira de pensar a respeito de uma consulta que use três ou mais
tabelas seria como uma bola de neve rolando morro abaixo. As primeiras
duas tabelas rolam a bola, e cada tabela subsequente se anexa à bola de
neve conforme ela desce o morro. Você pode imaginar a bola de neve
como um conjunto-resultado intermediário, que vai reunindo mais e mais
colunas conforme tabelas subsequentes vão sendo juntadas. Portanto, a
tabela employee não está realmente sendo juntada à tabela account, e sim
ao conjunto intermediário que foi criado quando as tabelas customer e
account foram juntadas. (No caso de você estar imaginando por que
escolhi a analogia da bola de neve, é porque escrevi este capítulo no meio
de um inverno na Nova Inglaterra: 280 centímetros de neve até agora, e
mais chegando amanhã. Que maravilha.)

Usando subconsultas como tabelas


Você já viu vários exemplos de consultas que usam três tabelas, mas existe
uma variante que vale a pena mencionar: o que fazer no caso de um ou
mais conjuntos de dados serem gerados por subconsultas. Subconsultas
será o foco do capítulo 9, mas já introduzi o conceito de uma subconsulta
dentro da cláusula from no capítulo anterior. Aqui está outra versão da
consulta anterior (encontrar todas as contas abertas por caixas
experientes atualmente alocados na lial Woburn) que une a tabela
account às subconsultas feitas às tabelas branch e employee:
1 SELECT a.account_id, a.cust_id, a.open_date, a.product_cd
2 FROM account a INNER JOIN
3 (SELECT emp_id, assigned_branch_id
4 FROM employee
5 WHERE start_date < '2007-01-01'
6 AND (title = 'Teller' OR title = 'Head Teller')) e
7 ON a.open_emp_id = e.emp_id
8 INNER JOIN
9 (SELECT branch_id
10 FROM branch
11 WHERE name = 'Woburn Branch') b
12 ON e.assigned_branch_id = b.branch_id;

A primeira subconsulta, que inicia na linha 3 e recebe o alias e, encontra


todos os caixas experientes. A segunda subconsulta, que inicia na linha 9
e que recebe o alias b, encontra o ID da lial Woburn. Primeiro, a tabela
account é juntada à subconsulta dos caixas experientes usando o ID de
funcionário e, então, a tabela resultante é juntada à subconsulta da lial
Woburn usando o ID de lial. Os resultados são os mesmos da versão
anterior da consulta (tente e comprove por si mesmo), mas as consultas
parecem bem diferentes uma da outra.
Não há algo realmente chocante aqui, mas pode levar um minuto para
entender o que está acontecendo. Repare, por exemplo, na ausência da
cláusula where na consulta principal – como todas as condições de ltro
acontecem nas tabelas employee e branch, as condições de ltro estão
todas dentro das subconsultas, então não há a necessidade de ltrar
quaisquer condições na consulta principal. Uma maneira de visualizar o
que está acontecendo seria executando as subconsultas separadamente e
observando os conjuntos-resultados. Aqui está o resultado da primeira
subconsulta feita à tabela employee:
mysql> SELECT emp_id, assigned_branch_id
-> FROM employee
-> WHERE start_date < '2007-01-01'
-> AND (title = 'Teller' OR title = 'Head Teller');
+--------+--------------------+
| emp_id | assigned_branch_id |
+--------+--------------------+
| 8 | 1 |
| 9 | 1 |
| 10 | 2 |
| 11 | 2 |
| 13 | 3 |
| 14 | 3 |
| 16 | 4 |
| 17 | 4 |
| 18 | 4 |
+--------+--------------------+
9 rows in set (0.03 sec)

Assim, esse conjunto-resultado consiste em um conjunto de IDs de


funcionário e de seus IDs de lial correspondentes. Ao serem juntados à
tabela account por meio da coluna emp_id, você tem um conjunto-
resultado intermediário consistindo em todas as linhas da tabelas account
mais uma coluna adicional que retorna o ID de lial do funcionário que
abriu cada conta. Aqui estão os resultados da segunda subconsulta feita à
tabela branch:
mysql> SELECT branch_id
-> FROM branch
-> WHERE name = 'Woburn Branch';
+-----------+
| branch_id |
+-----------+
| 2 |
+-----------+
1 row in set (0.02 sec)

Essa consulta retorna uma única linha contendo uma única coluna: o ID
da lial Woburn. Essa tabela é juntada à coluna assigend_branch_id do
conjunto-resultado intermediário, fazendo com que todas as contas
abertas por funcionários não-alocados em Woburn sejam excluídas do
conjunto-resultado nal.

Usando a mesma tabela duas vezes


Se você estiver juntando múltiplas tabelas, você pode acabar descobrindo
que precisa juntar as mesmas tabelas mais de uma vez. No banco de
dados de exemplo, por exemplo, há chaves estrangeiras da tabela branch
tanto na tabela account (a lial em que a conta foi aberta) quanto na
tabela employee (a lial em que o funcionário trabalha). Se desejar incluir
ambas as chaves de liais em seu conjunto resultado, você pode incluir a
tabela branch duas vezes na cláusula from, juntada uma vez com a tabela
employee e outra vez com a tabela account. Para isso funcionar, você
precisará dar a cada instância da tabela branch um alias diferente para que
o servidor saiba a quem você está se referindo nas várias cláusulas, como
em:

Essa consulta mostra quem abriu cada conta corrente, em qual lial ela
foi aberta e em qual lial o funcionário que abriu a conta está
trabalhando atualmente. A tabela branch foi incluída duas vezes, com os
aliases b_a e b_e. Ao atribuir diferentes aliases para cada instância da
tabela branch, o servidor é capaz de entender a qual instância você está se
referindo: a que foi juntada à tabela account ou a que foi juntada à tabela
employee. Portanto, este é um exemplo de uma consulta que requer o uso
de aliases de tabelas.

Autojunções
Além de poder incluir a mesma tabela mais de uma vez na mesma
consulta, você também pode juntar uma tabela a ela mesma. Isso pode
parecer um pouco estranho de se fazer na primeira vez, mas existem boas
razões para fazê-lo. A tabela employee, por exemplo, inclui uma chave
estrangeira autorreferenciadora, o que signi ca que ela inclui uma coluna
(superior_emp_id) que aponta para a chave primária dentro da mesma
tabela. Essa coluna aponta para o gerente do funcionário (a menos que o
funcionário seja o chefão: nesse caso, a coluna seria null). Usando uma
autojunção, você pode escrever uma consulta que lista o nome de cada
funcionário junto com o nome de seu gerente:

Essa consulta inclui duas instâncias da tabela employee: uma para


fornecer os nomes dos funcionários (com o alias de tabela e) e a outra
para fornecer nomes de gerentes (com o alias de tabela e_mgr). A
subcláusula on usa esses aliases para juntar a tabela employee a si mesma
por meio da chave estrangeira superior_emp_id. Este é outro exemplo de
uma consulta que requer aliases de tabela – de outra forma, o servidor
não saberia se você está se referindo a um funcionário ou ao gerente de
um funcionário.
Apesar de haver 18 linhas na tabela employee, a consulta retornou apenas
17: o presidente do banco, Michael Smith, não tem superior (sua coluna
superior_emp_id é null), então a junção falha na linha dele. Para incluir
Michael Smith no conjunto-resultado, você precisaria usar uma junção
externa, que veremos no capítulo 10.

Junções equivalentes versus não-equivalentes


Todas as consultas de múltiplas tabelas mostradas até agora empregaram
junções equivalentes, o que signi ca que os valores das duas tabelas devem
corresponder para que a junção seja bem-sucedida. Uma junção
equivalente sempre emprega o sinal de igual, como em:
ON e.assigned_branch_id = b.branch_id

Enquanto a maioria de suas consultas empregará junções equivalentes,


você também pode juntar suas tabelas por meio de intervalos de valores,
que são referidos como junções não-equivalentes. Con ra um exemplo de
uma consulta que une por meio de um intervalo de valores:
SELECT e.emp_id, e.fname, e.lname, e.start_date
FROM employee e INNER JOIN product p
ON e.start_date >= p.date_offered
AND e.start_date <= p.date_retired
WHERE p.name = 'no-fee checking';

Essa consulta une duas tabelas que não têm relacionamentos de chave
estrangeira. A intenção é encontrar todos os funcionários que começaram
a trabalhar no banco enquanto o produto No-Fee Checking estava sendo
oferecido. Portanto, a data de início de um funcionário deve estar entre a
data em que o produto começou a ser oferecido e a data em que deixou
de ser oferecido.
Você também pode acabar se deparando com a necessidade de criar uma
autojunção não-equivalente. Por exemplo, digamos que o gerente de
operações tenha decidido organizar um torneio de xadrez entre todos os
caixas. Você pode tentar juntar a tabela employee a si própria para todos
os caixas (title = ‘Teller’) e retornar todas as linhas em que os emp_ids
não batam (já que uma pessoa não pode jogar xadrez contra ela mesma):

Você está no caminho certo, mas o problema aqui é que, para cada
pareamento (por exemplo, Sarah Parker versus Chris Tucker) há também
um pareamento inverso (por exemplo, Chris Tucker versus Sarah Parker).
Uma forma de obter os resultados esperados é usar a condição de junção
e1.emp_id < e2.emp_id para que cada caixa seja pareado apenas com os
caixas que tenham um ID de funcionário mais alto do que ele (você
também pode usar e1.emp_id > e2.emp_id se quiser):

Agora você tem uma lista de 36 pareamentos, que é o número correto


quando se quer os pares únicos de nove coisas distintas.

Condições de junção versus condições de ltro


Agora você está familiarizado com o fato de que condições de junção
pertencem à subcláusula on, enquanto condições de ltro pertencem à
cláusula where. No entanto, a SQL é bem exível em relação ao local em
que suas condições são colocadas – por isso, é preciso tomar cuidado ao
construir suas consultas. Por exemplo, a seguinte consulta junta duas
tabelas usando uma única condição de junção e também inclui uma
única condição de ltro na cláusula where:
Essa foi bem simples, mas o que aconteceria se você, por engano,
colocasse a condição de ltro na subcláusula on em vez de colocá-lo na
cláusula where?

Como você pode ver, a segunda versão, que coloca ambas as condições na
subcláusula on e que não tem cláusula where, gera os mesmos resultados
que a primeira versão. E se ambas as condições forem colocadas na
cláusula where, com a cláusula from ainda utilizando a sintaxe ANSI de
junção?

Mais uma vez, o servidor MySQL gerou o mesmo conjunto-resultado.


Cabe a você colocar suas condições nos lugares corretos para que suas
consultas sejam fáceis de entender e manter.

Teste seu conhecimento


Os seguintes exercícios foram projetados para testar sua compreensão de
junções internas. Por favor, consulte o apêndice C para ver as soluções dos
exercícios.

Exercício 5.1
Preencha as lacunas (denotadas por <#>) da seguinte consulta para obter
os resultados mostrados em seguida:

Exercício 5.2
Escreva uma consulta que retorne o ID de conta de cada cliente não-
corporativo (customer.cust_type_cd = ‘I’) com o ID federal do cliente
(customer.fed_id) e o nome do produto no qual a conta é baseada
(product.name).

Exercício 5.3
Construa uma consulta que encontre todos os funcionários cujo
supervisor esteja alocado em um departamento diferente. Recupere o ID,
o nome e o sobrenome do funcionário.
CAPÍTULO 6
Trabalhando com conjuntos

Apesar de você poder interagir com os dados em um banco de dados uma


linha por vez, a verdadeira razão de ser dos bancos de dados relacionais
consiste nos conjuntos. Você já viu como criar tabelas por meio de
consultas ou subconsultas, torná-las persistentes por meio de instruções
insert e juntá-las por meio de uniões – este capítulo explora como você
pode combinar múltiplas tabelas usando vários operadores de conjunto.

Introdução à teoria dos conjuntos


Em muitas partes do mundo, o básico da teoria de conjuntos é incluído
no currículo dos níveis elementares de matemática. Talvez você se lembre
de ter visto algo parecido com o que é mostrado na gura 6.1.

Figura 6.1 – Operação de união.


A área sombreada na gura 6.1 representa a união dos conjuntos A e B,
que é a combinação dos dois conjuntos (com quaisquer regiões
sobrepostas sendo incluídas apenas uma vez). Isso está começando a soar
familiar? Se estiver, então você nalmente terá uma chance de colocar esse
conhecimento em prática – se não estiver, não se preocupe, porque isso é
fácil de visualizar usando alguns diagramas.
Usando círculos para representar dois conjuntos de dados (A e B),
imagine um subconjunto de dados que seja comum a ambos os
conjuntos: esses dados comuns são representados pela área sobreposta
mostrada na gura 6.1. Já que a teoria dos conjuntos ca extremamente
desinteressante sem uma sobreposição entre conjuntos de dados, eu uso o
mesmo diagrama para ilustrar cada operação de conjunto. Existe outra
operação de conjunto voltada apenas para a sobreposição entre dois
conjuntos de dados: essa operação é conhecida como intersecção e é
demonstrada na gura 6.2.

Figura 6.2 – Operação de intersecção.


O conjunto de dados gerado pela intersecção dos conjuntos A e B é a
mesma área de sobreposição entre os dois conjuntos. Se os dois conjuntos
não se sobrepuserem, a operação de intersecção resulta em um conjunto
vazio.
A terceira e última operação de conjunto, que é demonstrada na gura 6.3,
é conhecida como a operação de diferença.
Figura 6.3 – Operação de diferença.
A gura 6.3 mostra o resultado de A diferença B, que é o total do
conjunto A menos qualquer sobreposição com o conjunto B. Se os dois
conjuntos não se sobrepuserem, a operação A diferença B resulta no
conjunto A completo.
Usando essas três operações, ou combinando diferentes operações, você
pode gerar qualquer resultado que precisar. Por exemplo, imagine que
você queira construir um conjunto demonstrado pela gura 6.4.

Figura 6.4 – Conjunto de dados misterioso.


O conjunto de dados que você está procurando inclui tudo dos conjuntos
A e B sem a região sobreposta. Você não conseguirá esse resultado com
apenas uma das três operações mostradas anteriormente – em vez disso,
precisará primeiro construir um conjunto de dados que englobe tudo dos
conjuntos A e B, e então utilizar uma segunda operação para remover a
região sobreposta. Se o conjunto combinado for descrito como A união B,
e a região sobreposta for descrita como A intersecção B, a operação
necessária para gerar o conjunto de dados representado pela gura 6.4 se
parecerá com o seguinte:
(A união B) diferença (A intersecção B)

Claro, há várias maneiras de se obter os mesmos resultados – você


poderia conseguir um resultado semelhante usando a seguinte operação:
(A diferença B) união (B diferença A)

Apesar de esses conceitos serem relativamente fáceis de serem entendidos


com o uso de diagramas, as seções seguintes mostrarão como esses
conceitos são aplicados em um banco de dados relacional usando os
operadores de conjunto da SQL.

Teoria dos conjuntos na prática


Os círculos usados nos diagramas da seção anterior representam
conjuntos de dados que não informam nada a respeito dos dados que os
compõem. No entanto, ao lidar com dados reais, existe uma necessidade
de descrever a composição dos conjuntos de dados envolvidos caso sejam
combinados. Imagine, por exemplo, o que aconteceria se você tentasse
gerar a das tabelas product e customer, cujas de nições de tabelas são as
seguintes:
Quando combinadas, a primeira coluna da tabela resultante seria a
combinação das colunas product.product_cd e customer.cust_id, a
segunda seria a combinação das colunas product.name e customer.fed_id
e assim por diante. Apesar de alguns pares serem fáceis de combinar (por
exemplo, duas colunas numéricas), não ca claro como outros pares de
colunas deveriam ser combinados, tais como uma coluna numérica com
uma coluna de string ou uma de string com uma de data. Além disso, a
sexta e a sétima colunas das tabelas combinadas incluem apenas os dados
da sexta e da sétima colunas da tabela customer, já que a tabela product
contém apenas cinco colunas. Está claro que deve haver algum tipo de
associação entre as duas tabelas que você deseja combinar.
Portanto, ao realizar operações de conjunto sobre dois conjuntos de
dados, as seguintes linhas gerais devem ser seguidas:
• Ambos os conjuntos de dados devem ter o mesmo número de colunas.
• Os tipos de dados de cada coluna ao longo dos dois conjuntos de
dados devem ser os mesmos (ou o servidor deve ser capaz de converter
um no outro).
Com essas regras postas, ca mais fácil visualizar o que “sobreposição de
dados” signi ca na prática: todas as colunas pareadas dos dois conjuntos
que estão sendo combinados devem conter o mesmo número, data ou
string para que as linhas nas duas tabelas sejam consideradas as mesmas.
Você realiza uma operação de conjunto colocando um operador de
conjunto entre duas instruções select, como demonstrado a seguir:
mysql> SELECT 1 num, 'abc' str
-> UNION
-> SELECT 9 num, 'xyz' str;
+-----+-----+
| num | str |
+-----+-----+
| 1 | abc |
| 9 | xyz |
+-----+-----+
2 rows in set (0.02 sec)

Cada uma das consultas individuais resulta em um conjunto de dados


consistindo em uma única linha que contém uma coluna numérica e uma
de string. O operador de conjunto, que nesse caso é union, diz ao servidor
de banco de dados para combinar todas as linhas dos dois conjuntos.
Essa consulta é conhecida como uma consulta composta porque abrange
consultas múltiplas que, de outra forma, seriam independentes. Como
você verá mais adiante, consultas compostas podem incluir mais de duas
consultas se várias operações de conjunto forem necessárias para obter o
resultado nal.

Operadores de conjunto
A linguagem SQL inclui três operadores de conjunto que permitem
realizar cada uma das operações de conjunto descritas anteriormente
neste capítulo. Além disso, cada operador de conjunto vem em dois
sabores, um que inclui duplicatas e outro que as remove (mas não
necessariamente todas). As próximas subseções de nem cada operador e
demonstram como eles são usados.

Operador union
Os operadores union e union all permitem combinar vários conjuntos de
dados. A diferença entre os dois é que union ordena o conjunto
combinado e remove duplicatas, enquanto union all não o faz. Com
union all, o número de linhas no conjunto de dados nal sempre será
igual à soma do número de linhas dos conjuntos que estão sendo
combinados. Essa operação é a mais simples de ser realizada (do ponto de
vista do servidor), pois não há necessidade de o servidor veri car a
sobreposição de dados. O exemplo a seguir demonstra como você pode
usar o operador union all para gerar um conjunto completo de dados de
clientes a partir das duas tabelas de subtipos de clientes:
mysql> SELECT 'IND' type_cd, cust_id, lname name
-> FROM individual
-> UNION ALL
-> SELECT 'BUS' type_cd, cust_id, name
-> FROM business;
+---------+---------+------------------------+
| type_cd | cust_id | name |
+---------+---------+------------------------+
| IND | 1 | Hadley |
| IND | 2 | Tingley |
| IND | 3 | Tucker |
| IND | 4 | Hayward |
| IND | 5 | Frasier |
| IND | 6 | Spencer |
| IND | 7 | Young |
| IND | 8 | Blake |
| IND | 9 | Farley |
| BUS | 10 | Chilton Engineering |
| BUS | 11 | Northeast Cooling Inc. |
| BUS | 12 | Superior Auto Body |
| BUS | 13 | AAA Insurance Inc. |
+---------+---------+------------------------+
13 rows in set (0.04 sec)

A consulta retorna os 13 clientes, com nove linhas vindo da tabela


individual e as outras quatro da tabela business. Enquanto a tabela
business inclui uma única coluna para armazenar o nome da companhia,
a tabela individual inclui duas colunas de nome, uma para o nome e
outra para o sobrenome da pessoa. Nesse caso, optei por incluir apenas o
sobrenome da tabela individual.
Apenas para encerrar a discussão sobre o fato de que o operador union
all não remove duplicatas, veja a mesma consulta feita no exemplo
anterior, mas com uma consulta adicional à tabela business:
mysql> SELECT 'IND' type_cd, cust_id, lname name
-> FROM individual
-> UNION ALL
-> SELECT 'BUS' type_cd, cust_id, name
-> FROM business
-> UNION ALL
-> SELECT 'BUS' type_cd, cust_id, name
-> FROM business;
+---------+---------+------------------------+
| type_cd | cust_id | name |
+---------+---------+------------------------+
| IND | 1 | Hadley |
| IND | 2 | Tingley |
| IND | 3 | Tucker |
| IND | 4 | Hayward |
| IND | 5 | Frasier |
| IND | 6 | Spencer |
| IND | 7 | Young |
| IND | 8 | Blake |
| IND | 9 | Farley |
| BUS | 10 | Chilton Engineering |
| BUS | 11 | Northeast Cooling Inc. |
| BUS | 12 | Superior Auto Body |
| BUS | 13 | AAA Insurance Inc. |
| BUS | 10 | Chilton Engineering |
| BUS | 11 | Northeast Cooling Inc. |
| BUS | 12 | Superior Auto Body |
| BUS | 13 | AAA Insurance Inc. |
+---------+---------+------------------------+
17 rows in set (0.01 sec)

Essa consulta composta inclui três instruções select, duas das quais são
idênticas. Como você pode ver pelos resultados, as quatro linhas da tabela
business são incluídas duas vezes (IDs de cliente 10, 11, 12 e 13).

Apesar de ser improvável que repita a mesma consulta duas vezes em uma
consulta composta, con ra outra consulta composta que retorna dados
duplicados:
mysql> SELECT emp_id
-> FROM employee
-> WHERE assigned_branch_id = 2
-> AND (title = 'Teller' OR title = 'Head Teller')
-> UNION ALL
-> SELECT DISTINCT open_emp_id
-> FROM account
-> WHERE open_branch_id = 2;
+--------+
| emp_id |
+--------+
| 10 |
| 11 |
| 12 |
| 10 |
+--------+
4 rows in set (0.01 sec)

A primeira consulta da instrução composta recupera todos os caixas


alocados na lial Woburn, enquanto a segunda retorna o conjunto
distinto de caixas que abriram contas na lial Woburn. Das quatro linhas
no conjunto-resultado, uma delas é uma duplicata (o empregado com ID
10). Se você quiser que sua tabela combinada exclua linhas duplicadas,
você precisará usar o operador union em vez de union all:
mysql> SELECT emp_id
-> FROM employee
-> WHERE assigned_branch_id = 2
-> AND (title = 'Teller' OR title = 'Head Teller')
-> UNION
-> SELECT DISTINCT open_emp_id
-> FROM account
-> WHERE open_branch_id = 2;
+--------+
| emp_id |
+--------+
| 10 |
| 11 |
| 12 |
+--------+
3 rows in set (0.01 sec)

Para essa versão da consulta, apenas as três linhas distintas são incluídas
no conjunto-resultado, em vez das quatro colunas (três distintas, uma
duplicata) retornadas ao usar union all.

Operador intersect
A especi cação ANSI SQL inclui o operador intersect para realizar
intersecções. Infelizmente, a versão 5.1 do MySQL não implementa o
operador intersect. Se estiver usando o Oracle ou o SQL Server 2008,
você conseguirá utilizar o intersect – no entanto, como estou usando o
MySQL em todos os exemplos deste livro, os conjuntos-resultados das
consultas de exemplo dessa seção são inventados e não podem ser
executados em nenhuma versão do MySQL igual ou inferior à 5.1.
Também me abstenho de mostrar o prompt do MySQL (mysql>), pois as
instruções não estão sendo executadas pelo servidor MySQL.
Se as duas consultas de uma consulta composta retornarem conjuntos de
dados não-sobrepostos, a intersecção será um conjunto vazio. Considere a
seguinte consulta:
SELECT emp_id, fname, lname
FROM employee
INTERSECT
SELECT cust_id, fname, lname
FROM individual;
Empty set (0.04 sec)

A primeira consulta retorna o ID e o nome de cada funcionário, enquanto


a segunda retorna o ID e o nome de cada cliente. Esses conjuntos estão
completamente não-sobrepostos, de modo que a intersecção dos dois
conjuntos resulta no conjunto vazio.
O próximo passo é identi car duas consultas que, de fato, tenham dados
sobrepostos e, então, aplicar o operador intersect. Para tanto, uso a
mesma consulta que foi utilizada para demonstrar a diferença entre union
e union all, mas dessa vez aplicando o operador intersect:
SELECT emp_id
FROM employee
WHERE assigned_branch_id = 2
AND (title = 'Teller' OR title = 'Head Teller')
INTERSECT
SELECT DISTINCT open_emp_id
FROM account
WHERE open_branch_id = 2;
+--------+
| emp_id |
+--------+
| 10 |
+--------+
1 row in set (0.01 sec)
A intersecção dessas duas consultas resulta no ID de funcionário 10, que é
o único valor encontrado em ambos os conjuntos-resultados.
Junto com o operador intersect, que remove quaisquer linhas duplicadas
na região sobreposta, a especi cação ANSI SQL também cita o operador
intersect all, que não remove duplicatas. O único servidor de banco de
dados que atualmente implementa o operador intersect all é o DB2
Universal Server da IBM.

Operador except
A especi cação ANSI SQL inclui o operador except para realizar
operações de diferença. Mais uma vez, infelizmente, a versão 5.1 do
MySQL não implementa o operador except, então as mesmas regras da
seção anterior se aplicam a esta seção.
Se estiver utilizando o Oracle Database, você precisará usar o
operador não-ANSI minus no lugar de except.
O operador except retorna a primeira tabela menos qualquer
sobreposição com a segunda tabela. Aqui está o exemplo da seção
anterior, mas usando except em vez de intersect:
SELECT emp_id
FROM employee
WHERE assigned_branch_id = 2
AND (title = 'Teller' OR title = 'Head Teller')
EXCEPT
SELECT DISTINCT open_emp_id
FROM account
WHERE open_branch_id = 2;
+--------+
| emp_id |
+--------+
| 11 |
| 12 |
+--------+
2 rows in set (0.01 sec)

Nessa versão da consulta, o conjunto-resultado consiste nas três linhas da


primeira consulta menos o ID de funcionário 10, que existe em ambos os
conjuntos-resultados. Também existe um operador except all na
especi cação ANSI SQL, mas, novamente, apenas o DB2 Universal Server
da IBM o implementa.
O operador except all é um pouco traiçoeiro, então aqui vai um exemplo
que demonstra como os dados duplicados são tratados. Digamos que
você tenha dois conjuntos de dados parecidos com os seguintes:
Conjunto A
+--------+
| emp_id |
+--------+
| 10 |
| 11 |
| 12 |
| 10 |
| 10 |
+--------+

Conjunto B
+--------+
| emp_id |
+--------+
| 10 |
| 10 |
+--------+

A operação A except B resulta no seguinte:


+--------+
| emp_id |
+--------+
| 11 |
| 12 |
+--------+

Se você mudar a operação para A except all B, você verá o seguinte:


+--------+
| emp_id |
+--------+
| 10 |
| 11 |
| 12 |
+--------+

Portanto, a diferença entre as duas operações é que except remove todas


as ocorrências de dados duplicados do conjunto A, enquanto except all
remove apenas uma ocorrência de dado duplicado para cada ocorrência
no conjunto B.

Regras das operações de conjunto


As seguintes seções esboçam algumas regras que você deve seguir ao
trabalhar com consultas compostas.

Ordenando resultados de consultas compostas


Se quiser que os resultados de sua consulta composta sejam ordenados,
você pode adicionar uma cláusula order by após a última consulta. Ao
especi car nomes de colunas na cláusula order by, você deverá escolhê-
los na primeira consulta da consulta composta. Frequentemente, os
nomes das colunas são os mesmos em ambas as consultas de uma
consulta composta, mas isso não precisa ser necessariamente verdade,
como é demonstrado a seguir:
mysql> SELECT emp_id, assigned_branch_id
-> FROM employee
-> WHERE title = 'Teller'
-> UNION
-> SELECT open_emp_id, open_branch_id
-> FROM account
-> WHERE product_cd = 'SAV'
-> ORDER BY emp_id;
+--------+--------------------+
| emp_id | assigned_branch_id |
+--------+--------------------+
| 1 | 1 |
| 7 | 1 |
| 8 | 1 |
| 9 | 1 |
| 10 | 2 |
| 11 | 2 |
| 12 | 2 |
| 14 | 3 |
| 15 | 3 |
| 16 | 4 |
| 17 | 4 |
| 18 | 4 |
+--------+--------------------+
12 rows in set (0.04 sec)

Os nomes de colunas especi cados nas duas consultas são diferentes


nesse exemplo. Se você especi car um nome de coluna da segunda
consulta em sua cláusula order by, você verá o seguinte erro:
mysql> SELECT emp_id, assigned_branch_id
-> FROM employee
-> WHERE title = 'Teller'
-> UNION
-> SELECT open_emp_id, open_branch_id
-> FROM account
-> WHERE product_cd = 'SAV'
-> ORDER BY open_emp_id;
ERROR 1054 (42S22): Unknown column 'open_emp_id' in 'order clause'

Recomendo dar aliases idênticos às colunas de ambas as consultas para


evitar esse problema.

Precedência das operações de conjunto


Se a sua consulta composta tiver mais de duas consultas usando
diferentes operadores de conjunto, você precisará pensar a respeito da
ordem em que as consultas serão colocadas em sua instrução composta
para obter os resultados desejados. Considere a seguinte instrução
composta de três consultas:
mysql> SELECT cust_id
-> FROM account
-> WHERE product_cd IN ('SAV', 'MM')
-> UNION ALL
-> SELECT a.cust_id
-> FROM account a INNER JOIN branch b
-> ON a.open_branch_id = b.branch_id
-> WHERE b.name = 'Woburn Branch'
-> UNION
-> SELECT cust_id
-> FROM account
-> WHERE avail_balance BETWEEN 500 AND 2500;
+---------+
| cust_id |
+---------+
| 1 |
| 2 |
| 3 |
| 4 |
| 8 |
| 9 |
| 7 |
| 11 |
| 5 |
+---------+
9 rows in set (0.00 sec)

Essa consulta composta inclui três consultas que retornam conjuntos de


IDs não-únicos de clientes; a primeira e a segunda são separadas pelo
operador union all, enquanto a segunda e a terceira consultas são
separadas pelo operador union. Apesar de não parecer fazer muita
diferença onde os operadores union e union all são colocados, isso, de
fato, faz a diferença. Aqui está a mesma consulta composta com os
operadores invertidos:
mysql> SELECT cust_id
-> FROM account
-> WHERE product_cd IN ('SAV', 'MM')
-> UNION
-> SELECT a.cust_id
-> FROM account a INNER JOIN branch b
-> ON a.open_branch_id = b.branch_id
-> WHERE b.name = 'Woburn Branch'
-> UNION ALL
-> SELECT cust_id
-> FROM account
-> WHERE avail_balance BETWEEN 500 AND 2500;
+---------+
| cust_id |
+---------+
| 1 |
| 2 |
| 3 |
| 4 |
| 8 |
| 9 |
| 7 |
| 11 |
| 1 |
| 1 |
| 2 |
| 3 |
| 3 |
| 4 |
| 4 |
| 5 |
| 9 |
+---------+
17 rows in set (0.00 sec)

Observando os resultados, ca óbvio que faz diferença como a consulta


composta é organizada ao usar diferentes operadores de conjunto. Em
geral, consultas compostas contendo três ou mais consultas são avaliadas
em ordem, de cima para baixo, mas com as seguintes advertências:
• A especi cação ANSI SQL diz que o operador intersect deve ter
precedência sobre os outros operadores de conjunto.
• Você pode ditar a ordem em que as consultas são combinadas
colocando consultas múltiplas entre parênteses.
No entanto, como o MySQL ainda não implementa o operador intersect
nem permite parênteses em consultas compostas, você precisará dispor
cuidadosamente as consultas de sua consulta composta para conseguir os
resultados esperados. Se estiver usando um banco de dados diferente, você
pode colocar consultas adjacentes entre parênteses para substituir o
processamento padrão cima-para-baixo de consultas compostas, como
em:
(SELECT cust_id
FROM account
WHERE product_cd IN ('SAV', 'MM')
UNION ALL
SELECT a.cust_id
FROM account a INNER JOIN branch b
ON a.open_branch_id = b.branch_id
WHERE b.name = 'Woburn Branch')
INTERSECT
(SELECT cust_id
FROM account
WHERE avail_balance BETWEEN 500 AND 2500
EXCEPT
SELECT cust_id
FROM account
WHERE product_cd = 'CD'
AND avail_balance < 1000);

Nessa consulta composta, a primeira e a segunda consultas seriam


combinadas usando o operador union all, então a terceira e a quarta
consultas seriam combinadas usando o operador except e, nalmente, os
resultados dessas duas operações seriam combinados usando o operador
intersect para gerar o conjunto-resultado nal.

Teste seu conhecimento


Os exercícios a seguir foram projetados para testar sua compreensão de
operações de conjunto. Consulte o apêndice C para veri car as respostas
desses exercícios.

Exercício 6.1
Se o conjunto A = {L M N O P} e o conjunto B = {P Q R S T}, quais
conjuntos são gerados pelas seguintes operações?
• A union B
• A union all B
• A intersect B
• A except B

Exercício 6.2
Escreva uma consulta composta que encontre o nome e o sobrenome de
todos os clientes, junto com o nome e o sobrenome de todos os
funcionários.

Exercício 6.3
Ordene os resultados do exercício 6.2 pela coluna lname.
CAPÍTULO 7
Geração, conversão e manipulação de dados

Como mencionei no prefácio, este livro pretende ensinar técnicas gerais de


SQL que possam ser aplicadas em múltiplos servidores de banco de
dados. Este capítulo, no entanto, trata da geração, conversão e
manipulação de dados dos tipos string, numérico e temporal, mas a
linguagem SQL não inclui comandos que cubram essa funcionalidade.
Em vez disso, funções nativas são usadas para facilitar a geração,
conversão e manipulação de dados, e apesar de o padrão SQL especi car
algumas funções, os fabricantes de banco de dados não obedecem às
especi cações de função.
Portanto, minha abordagem para este capítulo será mostrar algumas das
maneiras mais comuns de manipular dados dentro de instruções SQL e,
então, demonstrarei algumas das funções nativas implementadas pelo
Microsoft SQL Server, pelo Oracle Database e pelo MySQL. Em paralelo
à leitura deste capítulo, recomendo que você adquira um guia de
referência que cubra todas as funções implementadas por seu servidor. Se
você trabalha com mais de um servidor, existem vários guias de referência
que tratam de múltiplos servidores, como SQL in a Nutshell, de Kevin
Kline e colaboradores (http://oreilly.com/catalog/9780596518844/), e SQL
Pocket Guide, de Jonathan Gennick
(http://oreilly.com/catalog/9780596526887/), ambos da O’Reilly.

Trabalhando com strings


Ao trabalhar com dados do tipo string, você estará usando um dos
seguintes tipos de dados de caracteres:
CHAR
Armazena strings de tamanho xo e alinhadas internamente
adicionando-se espaços em branco. O MySQL permite valores CHAR
com extensão de até 255 caracteres, o Oracle Database permite até
2.000, e o SQL Server permite até 8.000 caracteres.
varchar
Armazena strings de tamanho variável. O MySQL permite até 65.535
caracteres em um coluna varchar, o Oracle Database (por meio do tipo
varchar2) permite até 4.000, e o SQL Server permite até 8.000
caracteres.
text (MySQL e SQL Server) ou CLOB (Character Large Object; Oracle
Database)
Armazena strings muito grandes de tamanho variável (geralmente
referenciados como documentos nesse contexto). O MySQL tem
múltiplos tipos de textos (tinytext, text, mediumtext e long text) para
documentos com tamanhos de até 4 GB. O SQL Server tem um único
tipo de texto para documentos com tamanhos de até 2 GB, e o Oracle
Database inclui o tipo de dados CLOB, que pode armazenar
documentos com tamanhos gigantescos de até 128 TB. O SQL Server
2005 também inclui o tipo de dados varchar(max) e recomenda seu
uso em vez do tipo text, que será removido do servidor em alguma
versão futura.
Para demonstrar como você pode usar esses vários tipos, uso a seguinte
tabela para alguns exemplos desta seção:
CREATE TABLE string_tbl
(char_fld CHAR(30),
vchar_fld VARCHAR(30),
text_fld TEXT
);

As próximas duas seções mostram como você pode gerar e manipular


dados de string.

Geração de strings
A maneira mais simples de se povoar uma coluna de caracteres é
colocando uma string entre aspas, como em:
mysql> INSERT INTO string_tbl (char_fld, vchar_fld, text_fld)
-> VALUES ('This is char data',
-> 'This is varchar data',
-> 'This is text data');
Query OK, 1 row affected (0.00 sec)
Ao inserir dados de string em uma tabela, lembre-se de que, caso a
largura exceda o tamanho máximo da coluna de caracteres (o máximo
designado ou o máximo permitido para o tipo de dados), o servidor
lançará uma exceção. Apesar de isso ser o comportamento padrão para os
três servidores, você pode con gurar o MySQL e o SQL Server para
truncar silenciosamente a string, em vez de lançar uma exceção. Para
demonstrar como o MySQL lida com essa situação, a instrução update a
seguir tenta modi car a coluna vchar_fld column, cujo tamanho máximo
está de nido como 30, com uma string que contém 46 caracteres:
mysql> UPDATE string_tbl
-> SET vchar_fld = 'This is a piece of extremely long varchar
data';
ERROR 1406 (22001): Data too long for column 'vchar_fld' at row 1

No MySQL 5.1, o comportamento padrão agora é o modo “strict” (estrito),


o que signi ca que exceções serão lançadas quando surgirem problemas,
ao passo que em versões anteriores do servidor a string seria truncada e
um aviso seria emitido. Caso você queira que o mecanismo do servidor
trunque a string e emita um aviso, em vez de lançar uma exceção, você
pode optar por se colocar em modo ANSI. O exemplo a seguir mostra
como veri car em qual modo você está e, então, mostra como alterar o
modo com o comando SET:

Se você executar novamente a instrução UPDATE anterior, descobrirá que a


coluna foi modi cada, mas que o seguinte aviso foi gerado:
Se você recuperar a coluna vchar_fld, verá que a string foi, de fato,
truncada:
mysql> SELECT vchar_fld
-> FROM string_tbl;
+--------------------------------+
| vchar_fld |
+--------------------------------+
| This is a piece of extremely l |
+--------------------------------+
1 row in set (0.05 sec)

Como você pode ver, apenas os primeiros 30 caracteres da string de 46


caracteres foram inseridos na coluna vchar_fld. A melhor maneira de
evitar a truncagem de strings (ou as exceções, no caso do Oracle Database
ou do MySQL em modo strict) ao trabalhar com colunas varchar seria
con gurando o limite máximo de uma coluna com um valor alto o
su ciente que consiga tratar as strings mais extensas que possam vir a ser
armazenadas na coluna (lembrando que o servidor aloca apenas espaço
su ciente para armazenar a string, então não é um desperdício con gurar
um limite superior alto para uma coluna varchar).

Incluindo aspas simples


Como as strings são demarcadas por aspas simples, você precisará car
alerta para strings que incluam aspas simples ou apóstrofos. Por exemplo,
você não conseguirá inserir a seguinte string porque o servidor pensará
que o apóstrofo na palavra “doesn’t” marca o m da string:
UPDATE string_tbl
SET text_fld = 'This string doesn't work';

Para comandar o servidor a ignorar o apóstrofo na palavra doesn’t, você


precisará adicionar um escape à string, de forma que o servidor trate o
apóstrofo como qualquer outro caractere da string. Os três servidores
permitem que você escape uma aspa simples adicionando outra aspa
simples imediatamente antes, como em:
mysql> UPDATE string_tbl
-> SET text_fld = 'This string didn''t work, but it does now';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0

Os usuários do Oracle Database e do MySQL também podem


optar por escapar uma aspa simples adicionando um caractere
de barra invertida imediatamente antes, como em:
UPDATE string_tbl SET text_fld = 'This string didn\'t
work, but it does now'

Se você recuperar uma string para uso em uma tela ou em um campo de


relatório, não precisará fazer nada especial para tratar as aspas embutidas:
mysql> SELECT text_fld
-> FROM string_tbl;
+------------------------------------------+
| text_fld |
+------------------------------------------+
| This string didn't work, but it does now |
+------------------------------------------+
1 row in set (0.00 sec)

No entanto, se você estiver recuperando a string para adicioná-la a um


arquivo que outro programa lerá, talvez você precise incluir o escape
como parte da string recuperada. Se você estiver usando o MySQL, você
pode usar a função nativa quote(), que coloca a string inteira entre aspas
e adiciona escapes para quaisquer aspas/apóstrofes dentro da string. Aqui
está como caria nossa string ao ser recuperada por meio da função
quote():

Ao recuperar dados para exportação de dados, você pode querer usar a


função quote() para todas as colunas de caracteres não geradas pelo
sistema, como em uma coluna customer_notes.
Incluindo caracteres especiais
Se a sua aplicação tem abrangência internacional, você pode acabar
trabalhando com strings que incluam caracteres que não aparecem em seu
teclado. Ao trabalhar com as línguas francesa e alemã, por exemplo, você
pode precisar incluir caracteres acentuados como é e ö.1 O SQL Server e o
MySQL incluem a função nativa char(), que permite construir strings a
partir de qualquer um dos 255 caracteres do conjuntos de caracteres
ASCII (os usuários do Oracle Database podem usar a função chr()). Para
demonstrá-la, o próximo exemplo recupera uma string digitada e sua
equivalente construída por meio de caracteres individuais:
mysql> SELECT 'abcdefg', CHAR(97,98,99,100,101,102,103);
+---------+--------------------------------+
| abcdefg | CHAR(97,98,99,100,101,102,103) |
+---------+--------------------------------+
| abcdefg | abcdefg |
+---------+--------------------------------+
1 row in set (0.01 sec)

Portanto, o 97º caractere do conjunto de caracteres ASCII é a letra a.


Apesar de os caracteres mostrados nesse exemplo não serem especiais, os
seguintes exemplos mostram a localização dos caracteres acentuados
junto com outros caracteres especiais, como símbolos de moedas:
Estou usando o conjunto de caracteres latin1 nos exemplos
desta seção. Se a sua sessão estiver con gurada para um
conjunto de caracteres diferente, você verá um conjunto de
caracteres diferente do que é mostrado aqui. Os mesmos
conceitos são aplicáveis, mas você precisará se familiarizar com
o layout de seu conjunto de caracteres para localizar caracteres
especí cos.
Construir strings caractere por caractere pode ser bem chato,
especialmente quando apenas alguns dos caracteres da string são
especiais. Felizmente, você pode usar a função concat() para concatenar
strings individuais, algumas das quais você pode digitar normalmente,
enquanto as demais você pode gerar por meio da função char(). Por
exemplo, o comando a seguir cria a frase danke schön usando as funções
concat() e char():
mysql> SELECT CONCAT('danke sch', CHAR(148), 'n');
+-------------------------------------+
| CONCAT('danke sch', CHAR(148), 'n') |
+-------------------------------------+
| danke schön |
+-------------------------------------+
1 row in set (0.00 sec)

Os usuários do Oracle Database podem usar o operador de


concatenação ( || ) em vez da função concat(), como em:
SELECT 'danke sch' || CHR(148) || 'n' FROM dual;

O SQL Server não inclui uma função concat(), então você


precisará usar o operador de concatenação ( + ), como em:
SELECT 'danke sch' + CHAR(148) + 'n'

Se você tiver um caractere e precisar encontrar seu equivalente ASCII, você


pode usar a função ascii(), que pega o caractere mais à esquerda da
string e retorna um número:
mysql> SELECT ASCII('ö');
+------------+
| ASCII('ö') |
+------------+
| 148 |
+------------+
1 row in set (0.00 sec)

Com o uso das funções char(), ascii() e concat() (ou dos operadores de
concatenação), você conseguirá trabalhar com qualquer língua de origem
latina, mesmo se estiver usando um teclado que não inclua caracteres
acentuados ou especiais.

Manipulação de strings
Todos os servidores de banco de dados incluem várias funções de
manipulação de strings. Esta seção explora dois tipos de funções de
string: aquelas que retornam números e aquelas que retornam strings. No
entanto, antes de começar, vamos recon gurar os dados da tabela
string_tbl para o seguinte:
mysql> DELETE FROM string_tbl;
Query OK, 1 row affected (0.02 sec)
mysql> INSERT INTO string_tbl (char_fld, vchar_fld, text_fld)
-> VALUES ('This string is 28 characters',
-> 'This string is 28 characters',
-> 'This string is 28 characters');
Query OK, 1 row affected (0.00 sec)

Funções de string que retornam números


Das funções de string que retornam números, uma das mais comumente
usadas é a função length(), que retorna o número de caracteres da string
(usuários do SQL Server deverão usar a função len()). A seguinte
consulta aplica a função length() em cada coluna da tabela string_tbl:

Embora o tamanho das colunas de tipo varchar e text seja o esperado,


você poderia estar esperando um tamanho de 30 para a coluna char, pois
eu disse anteriormente que strings armazenadas em colunas de tipo char
são preenchidas à direita com espaços. No entanto, o servidor MySQL
remove os espaços extras dos dados de tipo char que são recuperados,
então você verá o mesmo resultado em todas as funções de string,
independentemente do tipo de coluna em que uma string é armazenada.
Além de encontrar o tamanho de uma string, você pode precisar
encontrar a localização de uma substring dentro de uma string. Por
exemplo, se você quiser encontrar a posição em que a string 'characters'
aparece na coluna vchar_fld, você pode usar a função position(), como
demonstrado a seguir:
mysql> SELECT POSITION('characters' IN vchar_fld)
-> FROM string_tbl;
+-------------------------------------+
| POSITION('characters' IN vchar_fld) |
+-------------------------------------+
| 19 |
+-------------------------------------+
1 row in set (0.12 sec)

Se a substring não for encontrada, a função position() retorna 0.


Para aqueles que programam em linguagens como C ou C++,
em que o primeiro elemento de um array aparece na posição 0,
lembre-se de que, ao trabalhar com bancos de dados, o
primeiro caractere de uma string aparece na posição 1. Um
valor de retorno 0 da função position() indica que a substring
não foi encontrada, e não que a substring foi encontrada na
primeira posição da string.
Se quiser começar sua pesquisa a partir de um caractere que não seja o
primeiro de sua string-alvo, você precisará da função locate(), que é
similar à função position(), exceto pelo fato de que ela tem um terceiro
parâmetro opcional, usado para de nir a posição inicial da pesquisa. A
função locate() é proprietária, enquanto a função position() faz parte
do padrão SQL:2003. Aqui está um exemplo que requisita a posição da
string 'is', iniciando a pesquisa no quinto caractere da coluna vchar_fld:
mysql> SELECT LOCATE('is', vchar_fld, 5)
-> FROM string_tbl;
+----------------------------+
| LOCATE('is', vchar_fld, 5) |
+----------------------------+
| 13 |
+----------------------------+
1 row in set (0.02 sec)

O Oracle Database não inclui as funções position() e locate(),


mas inclui a função instr(), que imita a função position()
quando recebe dois argumentos e imita a função locate()
quando recebe três. O SQL Server também não tem as funções
position() e locate(), mas inclui a função charindx(), que
também aceita dois ou três argumentos, de forma similar à
função instr() do Oracle.
Outra função que recebe strings como argumentos e que retorna números
é a função de comparação de strings strcmp(). Strcmp(), que é
implementada apenas pelo MySQL e não tem análogas no Oracle
Database e no SQL Server, recebe duas strings como argumentos e retorna
um dos seguintes valores:
• -1, caso a primeira string apareça antes da segunda em ordem de
classi cação.
• 0, caso as strings sejam idênticas.
• 1, caso a primeira string apareça após a segunda em ordem de
classi cação.
Para ilustrar como ela funciona, primeiro mostrarei a ordem de
classi cação de cinco strings usando uma consulta e, então, mostrarei
como as strings comparam umas com as outras usando strcmp(). Aqui
estão as cinco strings que inseri na tabela string_tbl:
mysql> DELETE FROM string_tbl;
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO string_tbl(vchar_fld) VALUES ('abcd');
Query OK, 1 row affected (0.03 sec)
mysql> INSERT INTO string_tbl(vchar_fld) VALUES ('xyz');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO string_tbl(vchar_fld) VALUES ('QRSTUV');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO string_tbl(vchar_fld) VALUES ('qrstuv');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO string_tbl(vchar_fld) VALUES ('12345');
Query OK, 1 row affected (0.00 sec)

Aqui estão as cinco strings em ordem de classi cação:


mysql> SELECT vchar_fld
-> FROM string_tbl
-> ORDER BY vchar_fld;
+-----------+
| vchar_fld |
+-----------+
| 12345 |
| abcd |
| QRSTUV |
| qrstuv |
| xyz |
+-----------+
5 rows in set (0.00 sec)

A próxima consulta faz seis comparações entre as cinco strings:

A primeira comparação retorna 0, que é o esperado, já que comparei a


string com ela mesma. A quarta comparação também retorna 0, o que
pode ser um pouco surpreendente, pois as strings são compostas pelas
mesmas letras, mas uma está toda em maiúsculas e a outra está em
minúsculas. A razão desse resultado é que a função strcmp() do MySQL
não distingue maiúsculas de minúsculas, fato este que deve ser lembrado
ao se usar a função. As outras comparações retornam -1 ou 1,
dependendo de a primeira string aparecer antes ou depois da segunda
string na ordem de classi cação. Por exemplo, strcmp('abcd','xyz')
retorna -1, pois a string 'abcd' vem antes de 'xyz'.
Junto com a função strcmp(), o MySQL também permite a você usar os
operadores like e regexp para comparar strings na cláusula select. Tais
comparações retornam 1 (para true) e 0 (para false). Portanto, esses
operadores permitem construir expressões que retornam um número, de
forma semelhante às funções descritas nesta seção. Aqui está um exemplo
que usa o operador like:
mysql> SELECT name, name LIKE '%ns' ends_in_ns
-> FROM department;
+----------------+------------+
| name | ends_in_ns |
+----------------+------------+
| Operations | 1 |
| Loans | 1 |
| Administration | 0 |
+----------------+------------+
3 rows in set (0.25 sec)
Esse exemplo recuperou todos os nomes de departamentos, junto com
uma expressão que retorna 1 caso o nome do departamento termine em
“ns” ou 0 em caso contrário. Se quiser realizar comparações de padrões
mais complexos, você pode usar o operador regexp, como demonstrado a
seguir:

A quarta coluna retorna 1 caso o valor armazenado na coluna fed_id


corresponda ao formato do número de documento de identidade.
Usuários do SQL Server e do Oracle Database podem obter
resultados semelhantes construindo expressões case, as quais
descrevo de forma detalhada no capítulo 11.

Funções de string que retornam strings


Em alguns casos, você precisará modi car strings existentes, seja
extraindo parte dela ou adicionando mais texto à string. Todos os
servidores de banco de dados incluem várias funções que auxiliam nessas
tarefas. Antes de iniciar, vou recon gurar mais uma vez os dados da tabela
string_tbl:
mysql> DELETE FROM string_tbl;
Query OK, 5 rows affected (0.00 sec)
mysql> INSERT INTO string_tbl (text_fld)
-> VALUES ('This string was 29 characters');
Query OK, 1 row affected (0.01 sec)

Anteriormente no capítulo, eu demonstrei o uso da função concat() para


auxiliar na construção de palavras que incluam caracteres especiais. A
função concat() é útil em muitas outras situações, incluindo quando você
precisa anexar caracteres adicionais em uma string armazenada. Por
exemplo, o seguinte exemplo modi ca a string armazenada na coluna
text_fld anexando uma frase adicional no nal:
mysql> UPDATE string_tbl
-> SET text_fld = CONCAT(text_fld, ', but now it is longer');
Query OK, 1 row affected (0.03 sec)
Rows matched: 1 Changed: 1 Warnings: 0

O conteúdo da coluna text_fld agora cou assim:

Então, como todas as funções que retornam uma string, você pode usar
concat() para substituir dados armazenados em uma coluna de
caracteres.
Outro uso comum da função concat() é construir uma string a partir de
fragmentos individuais de dados. Por exemplo, a seguinte consulta gera
uma string narrativa para cada caixa do banco:
A função concat() pode manipular qualquer expressão que retorne uma
string, e até mesmo converterá números e datas em formato string, como
sugere a coluna de tipo date (start_date) usada como argumento. Apesar
de o Oracle incluir a função concat(), ela aceita apenas dois argumentos
do tipo string, de modo que a consulta anterior não funcionará no
Oracle. Em vez disso, você precisaria usar o operador de concatenação ( ||
) no lugar da função concat(), como em:
SELECT fname || ' ' || lname || ' has been a ' ||
title || ' since ' || start_date emp_narrative
FROM employee
WHERE title = 'Teller' OR title = 'Head Teller';

O SQL Server não inclui uma função concat(), portanto, você precisará
usar a mesma abordagem da consulta anterior, exceto pelo fato de que
você usaria o operador de concatenação do SQL Server ( + ) no lugar de
||.

Apesar de concat() ser útil para adicionar caracteres no início ou no nal


de strings, você também pode precisar adicionar ou substituir caracteres
no meio de uma string. Os três servidores de banco de dados fornecem
funções para esse propósito, mas são todas diferentes entre si, então
demonstrarei a função do MySQL e, em seguida, mostrarei as funções dos
outros dois servidores.
O MySQL inclui a função insert(), que recebe quatro argumentos: a
string original, a posição de início da inserção, o número de caracteres
que serão substituídos e a string de substituição. Dependendo do valor
passado ao terceiro argumento, a função pode ser usada para inserir ou
substituir caracteres de uma string. Com um valor 0 no terceiro
argumento, a string de substituição é inserida e qualquer caractere
subsequente é empurrado para a direita, como em:
mysql> SELECT INSERT('goodbye world', 9, 0, 'cruel ') string;
+---------------------+
| string |
+---------------------+
| goodbye cruel world |
+---------------------+
1 row in set (0.00 sec)

Nesse exemplo, todos os caracteres a partir da posição 9 são empurrados


para a direita, e a string 'cruel' é inserida. Se o terceiro argumento for
maior que zero, o número de caracteres especi cado nele será substituído
pela string de substituição, como em:
mysql> SELECT INSERT('goodbye world', 1, 7, 'hello') string;
+-------------+
| string |
+-------------+
| hello world |
+-------------+
1 row in set (0.00 sec)

Nesse exemplo, os primeiros sete caracteres são substituídos pela string


'hello'. O Oracle Database não fornece uma única função que tenha a
exibilidade da função insert() do MySQL, mas o Oracle fornece a
função replace(), que é útil para substituir uma substring por outra.
Aqui está o exemplo anterior refeito para usar replace():
SELECT REPLACE('goodbye world', 'goodbye', 'hello')
FROM dual;

Todas as instâncias da string 'goodbye' serão substituídas pela string


'hello', resultando na string ‘hello world’. A função replace() substituirá
cada instância da string de pesquisa pela string de substituição, então
você precisará tomar cuidado para não terminar com mais substituições
do que o esperado.
O SQL Server também tem uma função replace() com as mesmas
funcionalidades da função do Oracle, mas o SQL Server também inclui
uma função chamada stuff() com funcionalidade similar à função
insert() do MySQL. Aqui está um exemplo:
SELECT STUFF('hello world', 1, 5, 'goodbye cruel')

Quando executada, cinco caracteres são removidos a partir da posição 1 e,


então, a string 'goodbye cruel' é inserida na posição inicial, resultando na
string 'goodbye cruel world'.
Além de inserir caracteres em uma string, você pode precisar extrair uma
substring de uma string. Para isso, os três servidores incluem a função
substring() (apesar de a versão do Oracle se chamar substr()), que extrai
um número especí co de caracteres a partir de uma posição especí ca. O
exemplo a seguir extrai cinco caracteres de uma string, iniciando na nona
posição:
mysql> SELECT SUBSTRING('goodbye cruel world', 9, 5);
+----------------------------------------+
| SUBSTRING('goodbye cruel world', 9, 5) |
+----------------------------------------+
| cruel |
+----------------------------------------+
1 row in set (0.00 sec)

Além das funções demonstradas aqui, os três servidores incluem muitas


outras funções nativas para manipular dados de string. Apesar de muitas
delas serem projetadas para propósitos especí cos, como gerar uma string
equivalente a números octais ou hexadecimais, há também muitas outras
funções de propósito geral, como as funções que adicionam ou removem
espaços adicionais. Para obter mais informações, consulte o guia de
referência de seu servidor SQL ou um guia de referência genérico de SQL,
como SQL in a Nutshell (O’Reilly).

Trabalhando com dados numéricos


Diferentemente dos dados de string (e dos dados temporais, como você
verá em breve), a geração de dados numéricos é relativamente simples.
Você pode digitar um número, recuperá-lo de outra coluna ou gerá-lo por
meio de cálculos. Todas as operações aritméticas básicas (+, -, *, /)
estão disponíveis para realizar cálculos, e parênteses podem ser usados
para ditar precedências, como em:
mysql> SELECT (37 * 59) / (78 - (8 * 6));
+----------------------------+
| (37 * 59) / (78 - (8 * 6)) |
+----------------------------+
| 72.77 |
+----------------------------+
1 row in set (0.00 sec)

Como mencionei no capítulo 2, a principal preocupação ao armazenar


dados numéricos é que os números podem ser arredondados caso sejam
maiores do que o tamanho especi cado em uma coluna numérica. Por
exemplo, o número 9.96 pode ser arredondado para 10.0 se for
armazenado em uma coluna de nida como float (3,1).

Realizando funções aritméticas


A maioria das funções numéricas nativas é usada para propósitos
aritméticos especí cos, como determinar a raiz quadrada de um número.
A tabela 7.1 lista algumas das funções numéricas que recebem um único
argumento numérico e que retornam um número.
Tabela 7.1 – Funções numéricas com um argumento
Nome da função Descrição

Acos(x) Calcula o arco-cosseno de x

Asin(x) Calcula o arco-seno de x

Atan(x) Calcula o arco-tangente de x

Cos(x) Calcula o cosseno de x

Cot(x) Calcula a cotangente de x

Exp(x) Calcula ex

Ln(x) Calcula o logaritmo natural de x


Sin(x) Calcula o seno de x

Sqrt(x) Calcula a raiz quadrada de x

Tan(x) Calcula a tangente de x

Essas funções realizam tarefas bem especí cas, e vou me abster de


mostrar exemplos delas (se você não reconhecer uma função pelo nome
ou pela descrição, provavelmente não precisará dela). Outras funções
numéricas usadas em cálculos, no entanto, são um pouco mais exíveis e
merecem alguma explicação.
Por exemplo, o operador de módulo, que calcula o resto de um número
dividido por outro número, é implementado no MySQL e, no Oracle
Database, por meio da função mod(). O exemplo a seguir calcula o resto
de 10 dividido por 4:
mysql> SELECT MOD(10,4);
+-----------+
| MOD(10,4) |
+-----------+
| 2 |
+-----------+
1 row in set (0.02 sec)

Apesar de a função mod() ser usada tipicamente com argumentos inteiros,


no MySQL você também pode usar números reais, como em:
mysql> SELECT MOD(22.75, 5);
+---------------+
| MOD(22.75, 5) |
+---------------+
| 2.75 |
+---------------+
1 row in set (0.02 sec)

O SQL Server não tem uma função mod. Em vez disso, o


operador % é usado para encontrar o resto de uma divisão. A
expressão 10 % 4, portanto, resulta no valor 2.
Outra função numérica que recebe dois argumentos é a função pow() (ou
power(), caso você esteja usando o Oracle Database ou o SQL Server), que
retorna um número elevado à potência de um segundo número, como em:
mysql> SELECT POW(2,8);
+----------+
| POW(2,8) |
+----------+
| 256 |
+----------+
1 row in set (0.03 sec)

Então, pow(2,8) é o equivalente em MySQL a especi car 28. Como a


memória do computador é alocada em blocos de 2x bytes, a função pow()
pode ser uma maneira útil de determinar o número exato de bytes em
uma certa quantidade de memória:

Não sei quanto a você, mas acho mais fácil lembrar que um gigabyte é
230 bytes do que me lembrar do número 1.073.741.824.

Controlando a precisão numérica


Ao trabalhar com números de ponto utuante, você talvez não queira
interagir com ou exibir um número com a precisão total. Por exemplo,
você pode armazenar dados de transações monetárias com uma precisão
de seis casas decimais, mas pode querer arredondar para o centésimo
mais próximo por uma questão de formato de apresentação. Quatro
funções são úteis na limitação da precisão de números de ponto
utuante: ceil(), floor(), round() e truncate(). Os três servidores
incluem essas funções, apesar de o Oracle Database incluir trunc() no
lugar de truncate() e do SQL Server incluir ceiling() no lugar de ceil().
As funções ceil() e floor() são usadas para arredondar um número para
cima ou para baixo, respectivamente, como demonstrado a seguir:
mysql> SELECT CEIL(72.445), FLOOR(72.445);
+--------------+---------------+
| CEIL(72.445) | FLOOR(72.445) |
+--------------+---------------+
| 73 | 72 |
+--------------+---------------+
1 row in set (0.06 sec)

Portanto, qualquer número entre 72 e 73 seria avaliado como 73 pela


função ceil() e como 72 pela função floor(). Lembre-se que ceil()
arredonda para cima, mesmo que a parte decimal seja bem pequena, e
que floor() arredonda para baixo, mesma que a parte decimal seja
signi cativa, como em:
mysql> SELECT CEIL(72.000000001), FLOOR(72.999999999);
+--------------------+---------------------+
| CEIL(72.000000001) | FLOOR(72.999999999) |
+--------------------+---------------------+
| 73 | 72 |
+--------------------+---------------------+
1 row in set (0.00 sec)

Se isso for rígido demais para sua aplicação, você pode usar a função
round() para arredondar para cima ou para baixo a partir do ponto médio
entre dois números inteiros, como em:

Ao usar round(), qualquer número cuja parte decimal esteja bem no meio
de dois inteiros ou acima disso será arredondado para cima, enquanto o
número será arredondado para baixo caso a parte decimal seja menor do
que o valor médio entre dois inteiros.
Na maior parte do tempo, você desejará manter pelo menos uma parte da
porção decimal de um número, em vez de arredondá-lo para o inteiro
mais próximo. A função round() permite um segundo argumento
opcional que especi ca em qual casa após o ponto decimal será feito o
arredondamento. O próximo exemplo mostra como você pode usar o
segundo argumento para arredondar o número 72,0909 usando uma,
duas e três casas decimais:
Tal como a função round(), a função truncate() permite um segundo
argumento opcional que especi ca o número de dígitos à direita do
decimal, mas truncate() simplesmente descarta os dígitos indesejados
sem arredondá-los.

O SQL Server não inclui uma função truncate(). Em vez disso,


a função round() permite um terceiro argumento opcional que,
se estiver presente e for diferente de zero, trunca o número em
vez de arredondá-lo.
Tanto truncate() quanto round() também permitem um valor negativo no
segundo argumento, signi cando que números à esquerda do ponto
decimal serão truncados ou arredondados. A princípio isso pode parecer
uma coisa estranha, mas existem aplicações válidas para essa
funcionalidade. Por exemplo, talvez você venda um produto que só pode
ser vendido em unidades de 10. Se um cliente encomendar 17 unidades,
você pode optar por um dos seguintes métodos para modi car a
quantidade do pedido do cliente:
mysql> SELECT ROUND(17, -1), TRUNCATE(17, -1);
+---------------+------------------+
| ROUND(17, -1) | TRUNCATE(17, -1) |
+---------------+------------------+
| 20 | 10 |
+---------------+------------------+
1 row in set (0.00 sec)

Se o produto em questão for percevejos, pode não fazer muita diferença


no resultado nal dizer que você vendeu 10 ou 20 percevejos quando, na
verdade, foram requisitados 17. No entanto, se você estiver vendendo
relógios Rolex, seu negócio renderá mais com o arredondamento do que
com a truncagem.

Tratando dados sinalizados


Se você estiver trabalhando com colunas numéricas que permitam valores
negativos (no capítulo 2, mostrei como uma coluna numérica pode ser
rotulada como não-sinalizada, signi cando que apenas números positivos
são permitidos), várias funções numéricas poderão ser úteis. Digamos,
por exemplo, que você tenha que gerar um relatório mostrando a situação
atual de cada conta bancária. A consulta a seguir retorna três colunas
úteis para gerar o relatório:

A segunda coluna usa a função sign() para retornar -1 caso o saldo da


conta seja negativo, 0 se o saldo for zero ou 1 se o saldo da conta for
positivo. A terceira coluna retorna o valor absoluto do saldo da conta por
meio da função abs().

Trabalhando com dados temporais


Dos três tipos de dados discutidos neste capítulo (caractere, numérico e
temporal), os dados temporais são os mais complexos quando se fala em
geração e manipulação de dados. Parte da complexidade dos dados
temporais é causada pela miríade de formas pelas quais uma única data e
hora pode ser descrita. Por exemplo, a data em que escrevi este parágrafo
pode ser descrita em todas as formas mostradas a seguir:
• Quarta-feira, 17 de setembro de 2008
• 9/17/2008 2:14:56 P.M. EST
• 9/17/2008 19:14:56 GMT
• 2612008 (formato juliano)
• Data estelar [−4] 85712.03 14:14:56 (formato Jornada nas Estrelas)
Enquanto algumas dessas diferenças são meramente uma questão de
formatação, a maior parte da complexidade tem a ver com seu referencial,
assunto que exploraremos na próxima seção.

Trabalhando com fusos horários


Como as pessoas em todo o mundo preferem que o meio-dia coincida
aproximadamente com o sol a pino, nunca houve uma tentativa séria de
forçar todo mundo a usar um relógio universal. Em vez disso, o mundo foi
fatiado em 24 seções imaginárias, chamadas fusos horários (ou zonas
horárias): dentro de um fuso horário especí co, todos concordam com o
horário atual, enquanto pessoas em fusos horários diferentes não
concordarão. Apesar de isso parecer bem simples, algumas regiões
geográ cas adiantam seus horários em uma hora durante uma parte do
ano (implementando o que é conhecido com horário de verão), enquanto
outras não o fazem. Assim, a diferença de horário entre dois pontos da
Terra pode ser de quatro horas durante uma parte do ano e de cinco em
outra parte do ano. Mesmo com um único fuso horário, diferentes regiões
podem ou não aderir ao horário de verão, fazendo com que relógios
diferentes em um mesmo fuso horário coincidam durante uma parte do
ano, mas apresentem diferença de uma hora no resto do ano.
Apesar de a era do computador ter exacerbado essa questão, as pessoas
vêm lidando com as diferenças de fuso horário desde os primórdios da
exploração naval. Para garantir um ponto de referência comum no registro
de horários, os navegadores do século XV acertaram seus relógios com o
horário de Greenwich, na Inglaterra. Isso cou conhecido como
Greenwich Mean Time (Hora Média de Greenwich), ou GMT. Todas as
outras zonas horárias podem ser descritas pela diferença no número de
horas relativa ao GMT – por exemplo, a zona horária da costa leste dos
Estados Unidos, conhecida como Eastern Standard Time, pode ser descrita
como GMT -5:00, ou cinco horas mais cedo que o GMT.
Atualmente, usamos uma variação do GMT chamada Coordinated
Universal Time (Horário Universal Coordenado) ou UTC, que é baseada
em um relógio atômico (ou, para ser mais preciso, o tempo médio de 200
relógios atômicos em 50 locais diferentes do mundo, o que é conhecido
como Horário Universal). Tanto o SQL Server quanto o MySQL fornecem
funções que retornarão o timestamp UTC atual (getutcdate() no SQL
Server e utc_timestamp() no MySQL).
A maioria dos servidores de banco de dados usa como padrão a
con guração de zona horária dos servidores em que eles residem e
fornecem ferramentas para modi car a zona horária, caso seja necessário.
Por exemplo, um banco de dados usado para armazenar transações das
bolsas de valores de todo o mundo geralmente seria con gurado para
usar o horário UTC, enquanto um banco de dados usado para armazenar
transações de uma loja de varejo em particular poderia usar a zona
horária do servidor.
O MySQL mantém duas con gurações de zonas horárias diferentes: uma
zona horária global e uma zona horária de sessão, que pode ser diferente
para cada usuário logado em um banco de dados. Você pode ver ambas as
con gurações por meio da seguinte consulta:
mysql> SELECT @@global.time_zone, @@session.time_zone;
+--------------------+---------------------+
| @@global.time_zone | @@session.time_zone |
+--------------------+---------------------+
| SYSTEM | SYSTEM |
+--------------------+---------------------+
1 row in set (0.00 sec)

O valor system indica que o servidor está usando a con guração de zona
horária do computador-servidor em que o banco de dados reside.
Se você estiver sentado na frente de um computador em Zurique, na
Suíça, e abrir uma sessão pela rede em um servidor MySQL situado em
Nova Iorque, você pode querer mudar a con guração de fuso horário de
sua sessão, por meio do seguinte comando:
mysql> SET time_zone = 'Europe/Zurich';
Query OK, 0 rows affected (0.18 sec)

Se veri car as con gurações de fuso horário novamente, você verá o


seguinte:
mysql> SELECT @@global.time_zone, @@session.time_zone;
+--------------------+---------------------+
| @@global.time_zone | @@session.time_zone |
+--------------------+---------------------+
| SYSTEM | Europe/Zurich |
+--------------------+---------------------+
1 row in set (0.00 sec)

Todas as datas exibidas em sua sessão agora se conformarão ao horário de


Zurique.
Usuários do Oracle Database podem alterar a con guração de
zona horária por meio do seguinte comando:
ALTER SESSION TIMEZONE = 'Europe/Zurich'

Gerando dados temporais


Você pode gerar dados temporais por meio de qualquer uma dessas
maneiras:
• Copiando dados de uma coluna do tipo date, datetime ou time.
• Executando uma função nativa que retorne dados do tipo date,
datetime ou time.

• Construindo uma representação em string do dado temporal que será


avaliado pelo servidor.
Para usar o último método, você precisará entender os vários
componentes usados na formatação de datas.

Representações em string de dados temporais


A tabela 2.5 no capítulo 2 apresentou os componentes de data mais
populares. Para refrescar sua memória, a tabela 7.2 mostra esses mesmos
componentes.

Carregando dados de zonas horárias no MySQL


Se estiver usando o servidor MySQL em uma plataforma Windows,
você precisará carregar manualmente os dados das zonas horárias
antes de poder con gurar zonas horárias globais ou de sessão. Para
tanto, você deve seguir estes passos:
1. Baixe os dados das zonas horárias no endereço
http://dev.mysql.com/downloads/timezones.html.
2. Desative seu servidor MySQL.
3. Extraia os arquivos do arquivo ZIP baixado (em meu caso, o
arquivo chamava-se timezone-2006p.zip) e coloque-os no diretório
de instalação de seu MySQL, dentro de /data/mysql (o caminho
completo de minha instalação cou /Program
Files/MySQL/MySQL Server 5.1/data/mysql).
4. Reinicie seu servidor MySQL.
Para consultar os dados de zonas horárias, mude para o banco de
dados mysql por meio do comando use mysql e execute a seguinte
consulta:
mysql> SELECT name FROM time_zone_name;
+----------------------------------+
| name |
+----------------------------------+
| Africa/Abidjan |
| Africa/Accra |
| Africa/Addis_Ababa |
| Africa/Algiers |
| Africa/Asmera |
| Africa/Bamako |
| Africa/Bangui |
| Africa/Banjul |
| Africa/Bissau |
| Africa/Blantyre |
| Africa/Brazzaville |
| Africa/Bujumbura |
...
| US/Alaska |
| US/Aleutian |
| US/Arizona |
| US/Central |
| US/East-Indiana |
| US/Eastern |
| US/Hawaii |
| US/Indiana-Starke |
| US/Michigan |
| US/Mountain |
| US/Pacific |
| US/Pacific-New |
| US/Samoa |
| UTC |
| W-SU |
| WET |
| Zulu |
+----------------------------------+
546 rows in set (0.01 sec)

Para mudar sua con guração de zona horária, escolha um dos nomes
da consulta anterior que melhor corresponda à sua localização.
Tabela 7.2 – Componentes de formato de data
Componente De nição Escopo

AAAA Ano, incluindo século 1000 a 9999

MM Mês 01 (janeiro) a 12 (dezembro)

DD Dia 01 a 31

HH Hora 00 a 23

HHH Horas (transcorridas) −838 a 838

MI Minuto 00 a 59

SS Segundo 00 a 59

Para construir uma string que o servidor possa interpretar como um tipo
date, datetime ou time, você precisa agrupar vários componentes na
ordem mostrada na tabela 7.3.
Tabela 7.3 – Componentes de data necessários
Tipo Formato default
Date AAAA-MM-DD

Datetime AAAA-MM-DD HH:MI:SS

Timestamp AAAA-MM-DD HH:MI:SS

Time HHH:MI:SS

Então, para popular uma coluna datetime com 17 de setembro de 2008, às


15:30 horas, você precisará construir a seguinte string:
'2008-09-17 15:30:00'

Se o servidor estiver esperando um valor datetime (por exemplo, ao


atualizar uma coluna datetime ou ao chamar uma função nativa que
aceite um argumento datetime), você pode fornecer uma string
apropriadamente formatada com os componentes de data necessários, e o
servidor fará a conversão para você. Por exemplo, aqui está uma instrução
usada para modi car a data de uma transação de banco:
UPDATE transaction
SET txn_date = '2008-09-17 15:30:00'
WHERE txn_id = 99999;

O servidor determina que a string fornecida na cláusula set deve ser um


valor datetime, já que a string está sendo usada para popular uma coluna
datetime. Portanto, o servidor tentará converter a string para você,
analisando e quebrando a string nos seis componentes (ano, mês, dia,
hora, minuto, segundo) incluídos no formato datetime padrão.

Conversões de string para data


Se o servidor não estiver esperando um valor datetime ou se você quiser
representar o valor datetime usando um formato fora do padrão, então
você precisará dizer ao servidor para converter a string em um datetime.
Por exemplo, aqui está uma consulta simples que retorna um valor
datetime usando a função cast():
mysql> SELECT CAST('2008-09-17 15:30:00' AS DATETIME);
+-----------------------------------------+
| CAST('2008-09-17 15:30:00' AS DATETIME) |
+-----------------------------------------+
| 2008-09-17 15:30:00 |
+-----------------------------------------+
1 row in set (0.00 sec)

Falaremos sobre a função cast() no nal deste capítulo. Apesar de esse


exemplo demonstrar como construir valores datetime, a mesma lógica se
aplica aos tipos date e time também. A seguinte consulta usa a função
cast() para gerar um valor date e um valor time:
mysql> SELECT CAST('2008-09-17' AS DATE) date_field,
-> CAST('108:17:57' AS TIME) time_field;
+------------+------------+
| date_field | time_field |
+------------+------------+
| 2008-09-17 | 108:17:57 |
+------------+------------+
1 row in set (0.00 sec)

Você pode, é claro, converter explicitamente suas strings mesmo quando o


servidor estiver esperando um valor date, datetime ou time, em vez de
deixar que o servidor faça uma conversão implícita.
Ao converter strings em valores temporais – seja explícita ou
implicitamente – você deve fornecer todos os componentes de data na
ordem correta. Apesar de alguns servidores serem bem rígidos quanto ao
formato de data, o servidor MySQL é bem tolerante em relação aos
separadores usados entre os componentes. Por exemplo, o MySQL
aceitará todas as strings a seguir como representações válidas de 17 de
setembro de 2008, às 15:30:
'2008-09-17 15:30:00'
'2008/09/17 15:30:00'
'2008,09,17,15,30,00'
'20080917153000'

Apesar de isso oferecer um pouco mais de exibilidade, você pode acabar


se pegando em uma tentativa de gerar um valor temporal sem os
componentes de data padrão – a próxima seção mostra uma função
nativa que é bem mais exível do que a função cast().

Funções de geração de datas


Se você precisa gerar dados temporais a partir de uma string, e a string
não estiver no formato correto para usar a função cast(), você pode usar
uma função nativa que permitirá fornecer uma string de formato junto
com a string de data. O MySQL inclui a função str_to_date() para esse
propósito. Digamos, por exemplo, que você recupere a string ‘September
17, 2008’ de um arquivo e precise usá-la para atualizar uma coluna de
data. Como a string não está no formato AAAA-MM-DD necessário, você
pode usar str_to_date() em vez de reformatar a string como precisaria ser
feito com cast(), assim:
UPDATE individual
SET birth_date = STR_TO_DATE('September 17, 2008', '%M %d, %Y')
WHERE cust_id = 9999;

O segundo argumento na chamada a str_to_date() de ne o formato da


string de data, que, nesse caso, tem o nome do mês (%M), um dia numérico
(%d) e um ano numérico com quatro dígitos (%Y). Apesar de existirem
cerca de 30 componentes de formato reconhecíveis, a tabela 7.4 de ne
cerca de uma dúzia daqueles componentes mais utilizados.
Tabela 7.4 – Componentes de formato de data
Componente de formato Descrição

%M Nome do mês (de janeiro a dezembro)

%m Mês numérico (de 01 a 12)

%d Dia numérico (de 01 a 31)

%j Dia do ano (de 001 a 366)

%W Nome do dia da semana (de domingo a sábado)

%Y Ano, numérico com quatro dígitos

%y Ano, numérico com dois dígitos

%H Hora (de 00 a 23)

%h Hora (de 01 a 12)

%i Minutos (de 00 a 59)

%s Segundos (de 00 a 59)

%f Microssegundos (de 000000 a 999999)


%p A.M. ou P.M.

A função str_to_date() retorna um valor datetime, date ou time,


dependendo do conteúdo da string de formato. Por exemplo, se a string de
formato incluir apenas %H, %i e %s, um valor time será retornado.
Usuários do Oracle Database podem usar a função to_date()
da mesma forma que a função str_to_date do MySQL. O SQL
Server inclui uma função convert() que não é tão exível
quanto o MySQL e o Oracle Database: em vez de fornecer um
formato de string personalizado, sua string de data deve se
conformar a um entre 21 formatos prede nidos.
Se estiver tentando gerar a data/hora atual, você não precisará construir
uma string, pois as seguintes funções nativas acessam o relógio do sistema
e retornam a data e/ou o horário atuais como uma string:

Os valores retornados por essas funções estão no formato padrão do tipo


temporal retornado. O Oracle Database inclui current_date() e
current_timestamp(), mas não current_time(), e o SQL Server inclui
apenas a função current_timestamp().

Manipulando dados temporais


Esta seção explora as funções nativas que aceitam datas como argumento
e que retornam datas, strings ou números.

Funções temporais que retornam datas


Muitas das funções temporais recebem uma data como argumento e
retornam outra. A função date_add() do MySQL, por exemplo, permite
adicionar qualquer tipo de intervalo (por exemplo, dias, meses, anos) a
uma data especí ca para gerar outra data. Con ra um exemplo que
demonstra como adicionar cinco dias à data atual:
mysql> SELECT DATE_ADD(CURRENT_DATE(), INTERVAL 5 DAY);
+------------------------------------------+
| DATE_ADD(CURRENT_DATE(), INTERVAL 5 DAY) |
+------------------------------------------+
| 2008-09-22 |
+------------------------------------------+
1 row in set (0.06 sec)

O segundo argumento é composto de três elementos: a palavra-chave


interval, a quantidade desejada e o tipo de intervalo. A tabela 7.5 mostra
alguns dos tipos de intervalos mais usados.
Tabela 7.5 – Tipos de intervalos mais comuns
Nome do intervalo Descrição

Second Número de segundos

Minute Número de minutos

Hour Número de horas

Day Número de dias

Month Número de meses

Year Número de anos

Minute_second Número de minutos e segundos, separados por “:”

Hour_second Número de horas, minutos e segundos, separados por “:”

Year_month Número de anos e meses, separados por “-”

Enquanto os primeiros seis tipos listados na tabela 7.5 são simples, os três
últimos requerem um pouco mais de explicação, já que têm mais de um
elemento. Por exemplo, se lhe disserem que a transação de ID 9999
ocorreu, na verdade, 3 horas, 27 minutos e 11 segundos antes do que foi
armazenado na tabela transaction, você poderia corrigi-la assim:
UPDATE transaction
SET txn_date = DATE_ADD(txn_date, INTERVAL '3:27:11' HOUR_SECOND)
WHERE txn_id = 9999;

Nesse exemplo, a função date_add() pega o valor da coluna txn_date,


adiciona 3 horas, 27 minutos e 11 segundos a ela e usa o valor resultante
para modi car a coluna txn_date.
Ou, se você trabalha no setor de recursos humanos e descobre que o
funcionário de ID 4789 a rmou ser mais jovem do que realmente é, você
poderia adicionar 9 anos e 11 meses à sua data de nascimento assim:
UPDATE employee
SET birth_date = DATE_ADD(birth_date, INTERVAL '9-11' YEAR_MONTH)
WHERE emp_id = 4789;

Usuários do SQL Server podem realizar o exemplo anterior


usando a função dateadd():
UPDATE employee
SET birth_date =
DATEADD(MONTH, 119, birth_date)
WHERE emp_id = 4789

O SQL Server não tem intervalos combinados (ou seja,


year_month), então eu converti 9 anos e 11 meses em 119 meses.

Os usuários do Oracle Database podem usar a função


add_months() para esse exemplo, como em:
UPDATE employee
SET birth_date = ADD_MONTHS(birth_date, 119)
WHERE emp_id = 4789;

Há alguns casos em que é desejável adicionar um intervalo a uma data, e


você sabe em que data quer chegar, mas não sabe quantos dias existem
entre as duas datas. Por exemplo, digamos que um cliente do banco se
conecte ao sistema de on-line banking e agende uma transferência para o
m do mês. Em vez de escrever algum código que descubra em qual mês
você está atualmente e que procure pelo número de dias daquele mês,
você pode chamar a função last_day(), que faz esse trabalho para você
(tanto o MySQL quanto o Oracle Database incluem a função last_day();
o SQL Server não tem uma função desse tipo). Se o cliente solicitou a
transferência no dia 17 de setembro de 2008, você poderia encontrar o
último dia de setembro desta maneira:
mysql> SELECT LAST_DAY('2008-09-17');
+------------------------+
| LAST_DAY('2008-09-17') |
+------------------------+
| 2008-09-30 |
+------------------------+
1 row in set (0.10 sec)

Independentemente de fornecer um valor do tipo date ou datetime, a


função last_day() sempre retorna um valor do tipo date. Apesar de essa
função não parecer economizar muito tempo, a lógica por trás dela pode
car complicada caso você esteja tentando encontrar o último dia de
fevereiro e precise descobrir se o ano atual é bissexto.
Outra função temporal que retorna uma data é a que converte um valor
datetime de uma zona horária em outra. Para esse propósito, o MySQL
inclui a função convert_tz(), e o Oracle Database inclui a função
new_time(). Se eu quisesse converter meu horário local atual para UTC,
por exemplo, eu poderia fazer o seguinte:
mysql> SELECT CURRENT_TIMESTAMP() current_est,
-> CONVERT_TZ(CURRENT_TIMESTAMP(), 'US/Eastern', 'UTC')
current_utc;
+---------------------+---------------------+
| current_est | current_utc |
+---------------------+---------------------+
| 2008-09-18 20:01:25 | 2008-09-19 00:01:25 |
+---------------------+---------------------+
1 row in set (0.76 sec)

Essa função é bem útil nos casos em que são recebidas datas em zonas
horárias diferentes daquela que é armazenada em seu banco de dados.

Funções temporais que retornam strings


A maioria das funções temporais que retornam valores em string é usada
para extrair uma porção de uma data ou de um horário. Por exemplo, o
MySQL inclui a função dayname() para determinar em qual dia da
semana uma certa data cai, como em:
mysql> SELECT DAYNAME('2008-09-18');
+-----------------------+
| DAYNAME('2008-09-18') |
+-----------------------+
| Thursday |
+-----------------------+
1 row in set (0.08 sec)
Muitas dessas funções estão incluídas no MySQL para extrair
informações de valores de data, mas recomendo que você use a função
extract() no lugar delas, pois é mais fácil lembrar-se de umas poucas
variações de uma função do que de uma dúzia de funções diferentes.
Além disso, a função extract() é parte do padrão SQL:2003 e foi
implementada tanto no Oracle Database quanto no MySQL.
A função extract() usa os mesmos tipos de intervalos da função
date_add() (veja a tabela 7.5) para de nir qual elemento da data lhe
interessa. Por exemplo, se quiser extrair apenas o ano de um valor
datetime, você pode fazer o seguinte:
mysql> SELECT EXTRACT(YEAR FROM '2008-09-18 22:19:05');
+------------------------------------------+
| EXTRACT(YEAR FROM '2008-09-18 22:19:05') |
+------------------------------------------+
| 2008 |
+------------------------------------------+
1 row in set (0.00 sec)

O SQL Server não inclui uma implementação de extract(),


mas inclui a função datepart(). Veja como você extrairia o ano
de um valor datetime usando datepart();
SELECT DATEPART(YEAR, GETDATE())

Funções temporais que retornam números


Anteriormente no capítulo, mostrei uma função usada para adicionar
certo intervalo a um valor de data, gerando, assim, outro valor de data.
Outra atividade comum quando se trabalha com datas é pegar dois
valores de data e determinar o número de intervalos (dias, semanas, anos)
entre as duas datas. Para esse propósito, o MySQL inclui a função
datediff(), que retorna o número de dias completos entre duas datas. Por
exemplo, se eu quiser saber o número de dias que meus lhos carão de
férias nesse verão, posso fazer o seguinte:
mysql> SELECT DATEDIFF('2009-09-03', '2009-06-24');
+--------------------------------------+
| DATEDIFF('2009-09-03', '2009-06-24') |
+--------------------------------------+
| 71 |
+--------------------------------------+
1 row in set (0.05 sec)

Portanto, terei que aguentar 71 dias de heras venenosas, picadas de


mosquitos e joelhos arranhados antes de as crianças retornarem com
segurança à escola. A função datediff() ignora o horário do dia em seus
argumentos. Mesmo que eu inclua um horário, con gurando-o para um
segundo antes da meia-noite na primeira data e para um segundo após a
meia-noite na segunda data, esses horários não terão efeito sobre o
cálculo:

Se eu inverter os argumentos e colocar a data mais antiga primeiro,


datediff() retornará um número negativo, como em:
mysql> SELECT DATEDIFF('2009-06-24', '2009-09-03');
+--------------------------------------+
| DATEDIFF('2009-06-24', '2009-09-03') |
+--------------------------------------+
| -71 |
+--------------------------------------+
1 row in set (0.01 sec)

O SQL Server também inclui a função datediff(), mas é mais


exível do que a implementação do MySQL, pois você pode
especi car o tipo de intervalo (ou seja, ano, mês, dia, hora) em
vez de contar apenas o número de dias entre duas datas. Veja
como o SQL Server realizaria o exemplo anterior:
SELECT DATEDIFF(DAY, ‘2009-06-24’, ‘2009-09-03’)

O Oracle Database permite determinar o número de dias entre


duas datas simplesmente subtraindo uma da outra.

Funções de conversão
Anteriormente neste capítulo, mostrei como usar a função cast() para
converter uma string em um valor datetime. Apesar de cada servidor de
banco de dados incluir um número de funções proprietárias usadas para
converter dados de um tipo em outro, recomendo o uso da função cast(),
que está incluída no padrão SQL:2003 e que foi implementada no
MySQL, no Oracle Database e no SQL Server.
Para usar cast(), você fornece um valor ou expressão, a palavra-chave as e
o tipo para o qual você quer que o valor seja convertido. Aqui está um
exemplo que converte uma string em um número inteiro:
mysql> SELECT CAST('1456328' AS SIGNED INTEGER);
+-----------------------------------+
| CAST('1456328' AS SIGNED INTEGER) |
+-----------------------------------+
| 1456328 |
+-----------------------------------+
1 row in set (0.01 sec)

Ao converter uma string em um número, a função cast() tentará


converter toda a string, da esquerda para a direita; se quaisquer caracteres
não-numéricos forem encontrados na string, a conversão para sem erros.
Considere o seguinte exemplo:

Nesse caso, os primeiros três dígitos da string são convertidos, enquanto o


resto da string é descartado, resultando no valor 999. No entanto, o
servidor emite um aviso para que você saiba que nem toda a string foi
convertida.
Se estiver convertendo uma string em um valor date, time ou datetime,
você precisará se conformar aos formatos-padrão de cada tipo, já que você
não pode fornecer uma string de formato à função cast(). Se a sua string
de data não estiver no formato padrão (ou seja, AAAA-MM-DD
HH:MI:SS para tipos datetime), você terá que fazer uso de outra função,
como a str_to_date() do MySQL, descrita anteriormente no capítulo.

Teste seu conhecimento


Estes exercícios foram desenvolvidos para testar sua compreensão de
algumas das funções nativas mostradas neste capítulo. Consulte o
apêndice C para veri car as respostas.

Exercício 7.1
Escreva uma consulta que retorne do 17º ao 25º caractere da string 'Please
find the substring in this string'.

Exercício 7.2
Escreva uma consulta que retorne o valor absoluto e o sinal (-1, 0 ou 1)
do número -25,76823. Também retorne o número arredondado à centena
mais próxima.

Exercício 7.3
Escreva uma consulta que retorne apenas o mês da data atual.

1 N. de T.: os caracteres é e ö estão disponíveis no padrão português de teclado, mas isso não
invalida a explicação para a língua portuguesa, pois existem outros caracteres (ß, por exemplo,
usado no alemão) que nem sempre estão disponíveis para nós.
CAPÍTULO 8
Agrupamentos e agregações

Os dados geralmente são armazenados no nível mais baixo de


granularidade que qualquer usuário do banco de dados possa vir a
necessitar. Se Chuck, da contabilidade, precisa enxergar transações de
clientes individuais, há necessidade de se ter uma tabela no banco de
dados que armazene transações individuais. No entanto, isso não signi ca
que todos os usuários precisem lidar com os dados tais como estão
armazenados no banco de dados. O foco deste capítulo está em como os
dados podem ser agrupados e agregados para permitir que os usuários
interajam com eles em algum nível mais alto de granularidade do que
aquele que está armazenado no banco de dados.

Conceitos de agrupamento
Por vezes, você desejará extrair certas características de seus dados que
exigirão que o servidor de banco de dados os cozinhe um pouco antes de
poder gerar os resultados que você está procurando. Por exemplo,
digamos que você seja o responsável pelas operações do banco e gostaria
de descobrir quantas contas foram abertas por cada caixa. Você poderia
realizar uma consulta simples para observar os dados brutos:
mysql> SELECT open_emp_id
-> FROM account;
+-------------+
| open_emp_id |
+-------------+
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 10 |
| 10 |
| 10 |
| 10 |
| 10 |
| 10 |
| 10 |
| 13 |
| 13 |
| 16 |
| 16 |
| 16 |
| 16 |
| 16 |
| 16 |
+-------------+
24 rows in set (0.01 sec)

Com apenas 24 linhas na tabela account, ca relativamente fácil ver que


quatro funcionários diferentes abriram contas e que o funcionário cujo
ID é 16 abriu seis contas. No entanto, se o banco tiver dúzias de
funcionários e milhares de contas, essa abordagem se torna cansativa e
passível de erros.
Em vez disso, você pode pedir ao servidor de banco de dados que agrupe
os dados para você usando a cláusula group by. Aqui está a mesma
consulta, mas empregando uma cláusula group by para agrupar os dados
de conta por ID de funcionário:
mysql> SELECT open_emp_id
-> FROM account
-> GROUP BY open_emp_id;
+-------------+
| open_emp_id |
+-------------+
| 1 |
| 10 |
| 13 |
| 16 |
+-------------+
4 rows in set (0.00 sec)

O conjunto-resultado contém uma linha para cada valor distinto da


coluna open_emp_id, totalizando quatro linhas, em vez do resultado
completo de 24 linhas. A razão de o conjunto-resultado ser menor é que
cada um dos quatro funcionários abriu mais de uma conta. Para ver
quantas contas cada caixa abriu, você pode usar uma função de agregação
na cláusula select para contar o número de linhas em cada grupo:
mysql> SELECT open_emp_id, COUNT(*) how_many
-> FROM account
-> GROUP BY open_emp_id;
+-------------+----------+
| open_emp_id | how_many |
+-------------+----------+
| 1 | 8 |
| 10 | 7 |
| 13 | 3 |
| 16 | 6 |
+-------------+----------+
4 rows in set (0.00 sec)

A função de agregação count() conta o número de linhas em cada grupo,


e o asterisco diz ao servidor para contar tudo no grupo. Usando a
combinação de uma cláusula group by com a função de agregação
count(), você consegue gerar exatamente os dados necessários para
responder às questões orientadas ao negócio sem ter que olhar os dados
brutos.
Ao agrupar dados, você pode precisar descartar dados indesejados de seu
conjunto-resultado baseado em grupos de dados, em vez de remover
baseado nos dados brutos. Como a cláusula group by é executada após a
avaliação da cláusula where, você não pode adicionar condições de ltro à
sua cláusula where para esse propósito. Por exemplo, aqui está uma
tentativa de descartar quaisquer casos em que um funcionário tenha
aberto menos de cinco contas:
mysql> SELECT open_emp_id, COUNT(*) how_many
-> FROM account
-> WHERE COUNT(*) > 4
-> GROUP BY open_emp_id;
ERROR 1111 (HY000): Invalid use of group function

Você não pode fazer referência à função de agregação count(*) na cláusula


where, pois os grupos ainda não foram gerados no momento em que a
cláusula where é avaliada. Em vez disso, você pode colocar suas condições
de ltro de grupo na cláusula having. Veja como a consulta caria usando
having:
mysql> SELECT open_emp_id, COUNT(*) how_many
-> FROM account
-> GROUP BY open_emp_id
-> HAVING COUNT(*) > 4;
+-------------+----------+
| open_emp_id | how_many |
+-------------+----------+
| 1 | 8 |
| 10 | 7 |
| 16 | 6 |
+-------------+----------+
3 rows in set (0.00 sec)

Como os grupos que contêm menos de cinco membros foram


descartados por meio da cláusula having, o conjunto-resultado agora
contém apenas os funcionários que abriram cinco ou mais contas, assim
eliminando o funcionário de ID 13 dos resultados.

Funções de agregação
As funções de agregação realizam uma operação especí ca em todas as
linhas de um grupo. Apesar de cada servidor de banco de dados ter seu
próprio conjunto especializado de funções de agregação, as funções
comuns de agregação implementadas por todos os principais servidores
incluem:
Max()
Retorna o valor máximo dentro de um conjunto.
Min()
Retorna o valor mínimo dentro de um conjunto.
Avg()
Retorna o valor médio de um conjunto.
Sum()
Retorna a soma dos valores de um conjunto.
Count()
Retorna a quantidade de valores em um conjunto.
Veja uma consulta que usa todas as funções comuns de agregação para
analisar os saldos disponíveis de todas as contas correntes:
Os resultados dessa consulta informam que, entre as dez contas correntes
da tabela account, há um saldo máximo de U$ 38.552,05, um mínimo de
U$ 122,37, um saldo médio de U$ 7.300,80 e um saldo total, ao longo de
todas as dez contas, de U$ 73.008,01. Felizmente, isso proporciona uma
visão do papel dessas funções de agregação – as próximas subseções
esclarecerão como você pode utilizar essas funções.

Grupos implícitos versus grupos explícitos


No exemplo anterior, cada valor retornado pela consulta foi gerado por
uma função de agregação, e as funções de agregação são aplicadas ao
longo do grupo de linhas especi cado pela condição de ltro product_cd
= 'CHK'. Como não há uma cláusula group by, há apenas um grupo
implícito (todas as linhas retornadas pela consulta).
No entanto, na maioria dos casos você vai querer recuperar colunas
adicionais junto com as colunas geradas pelas funções de agregação. E se,
por exemplo, você quisesse estender a consulta anterior para que
executasse as mesmas cinco funções de agregação em cada tipo de
produto, em vez de apenas nas contas correntes? Nessa consulta, você
poderia querer recuperar a coluna product_cd junto com as cinco funções
de agregação, como em:
SELECT product_cd,
MAX(avail_balance) max_balance,
MIN(avail_balance) min_balance,
AVG(avail_balance) avg_balance,
SUM(avail_balance) tot_balance,
COUNT(*) num_accounts
FROM account;

No entanto, se tentar executar a consulta, você receberá o seguinte erro:


ERROR 1140 (42000): Mixing of GROUP columns
(MIN(),MAX(),COUNT(),...) with no GROUP
columns is illegal if there is no GROUP BY clause

Apesar de poder parecer óbvio que você queira aplicar as funções de


agregação em cada conjunto de produtos encontrado na tabela account,
essa consulta falha porque você não especi cou explicitamente como os
dados devem ser agrupados. Portanto, você precisará adicionar uma
cláusula group by para especi car em quais grupos de linhas as funções
de agregação deverão ser aplicadas:

Com a inclusão da cláusula group by, o servidor sabe que deve primeiro
agrupar as linhas que contenham o mesmo valor na coluna product_cd
para, em seguida, aplicar as cinco funções de agregação em cada um dos
seis grupos.

Contando valores distintos


Ao usar a função count() para determinar o número de membros de cada
grupo, você tem a opção de contar todos os membros do grupo ou contar
apenas os valores distintos inseridos em uma coluna ao longo de todos os
membros do grupo. Por exemplo, considere os seguintes dados, que
mostram cada funcionário responsável pela abertura de cada conta:
mysql> SELECT account_id, open_emp_id
-> FROM account
-> ORDER BY open_emp_id;
+------------+-------------+
| account_id | open_emp_id |
+------------+-------------+
| 8 | 1 |
| 9 | 1 |
| 10 | 1 |
| 12 | 1 |
| 13 | 1 |
| 17 | 1 |
| 18 | 1 |
| 19 | 1 |
| 1 | 10 |
| 2 | 10 |
| 3 | 10 |
| 4 | 10 |
| 5 | 10 |
| 14 | 10 |
| 22 | 10 |
| 6 | 13 |
| 7 | 13 |
| 24 | 13 |
| 11 | 16 |
| 15 | 16 |
| 16 | 16 |
| 20 | 16 |
| 21 | 16 |
| 23 | 16 |
+------------+-------------+
24 rows in set (0.00 sec)

Como você pode ver, várias contas foram abertas por quatro funcionários
diferentes (cujos IDs são 1, 10, 13 e 16). Digamos que, em vez de realizar
uma contagem manual, você queira criar uma consulta que conte o
número de funcionários que abriram contas. Se aplicar a função count()
à coluna open_emp_id, você verá o seguinte resultado:
mysql> SELECT COUNT(open_emp_id)
-> FROM account;
+--------------------+
| COUNT(open_emp_id) |
+--------------------+
| 24 |
+--------------------+
1 row in set (0.00 sec)

Nesse caso, especi car a coluna open_emp_id como a coluna a ser contada
gera os mesmos resultados da especi cação count(*). Se desejar contar
valores distintos no grupo, em vez de apenas contar o número de linhas
do grupo, você precisará especi car a palavra-chave distinct, como em:
mysql> SELECT COUNT(DISTINCT open_emp_id)
-> FROM account;
+-----------------------------+
| COUNT(DISTINCT open_emp_id) |
+-----------------------------+
| 4 |
+-----------------------------+
1 row in set (0.00 sec)

Portanto, ao especi car distinct, a função count() examina os valores de


uma coluna para cada membro do grupo para encontrar e remover
duplicatas, em vez de simplesmente contar o número de valores do grupo.

Usando expressões
Além de usar colunas como argumentos de funções de agregação, você
pode construir expressões para usar como argumentos. Por exemplo, você
pode querer encontrar o valor máximo de depósito pendente ao longo de
todas as contas, que é calculado subtraindo-se o saldo disponível do saldo
pendente. Isso é possível por meio da seguinte consulta:
mysql> SELECT MAX(pending_balance - avail_balance) max_uncleared
-> FROM account;
+---------------+
| max_uncleared |
+---------------+
| 660.00 |
+---------------+
1 row in set (0.00 sec)

Apesar de esse exemplo usar uma expressão relativamente simples, as


expressões usadas como argumentos de funções de agregação podem ser
tão complexas quanto forem necessárias, desde que retornem um número,
uma string ou uma data. No capítulo 11, mostrarei como você pode usar
expressões case junto com as funções de agregação para determinar se
uma linha especí ca deve ou não ser incluída em uma agregação.
Como nulls são tratados
Ao realizar agregações, ou melhor, ao realizar qualquer tipo de cálculo
numérico, você deve sempre considerar de que forma os valores null
afetarão o resultado de seu cálculo. Para ilustrar isso, vou construir uma
tabela simples que armazena dados numéricos e populá-la com o
conjunto {1, 3, 5}:
mysql> CREATE TABLE number_tbl
-> (val SMALLINT);
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT INTO number_tbl VALUES (1);
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO number_tbl VALUES (3);
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO number_tbl VALUES (5);
Query OK, 1 row affected (0.00 sec)

Considere a seguinte consulta, que realiza cinco funções de agregação


nesse conjunto de valores:
mysql> SELECT COUNT(*) num_rows,
-> COUNT(val) num_vals,
-> SUM(val) total,
-> MAX(val) max_val,
-> AVG(val) avg_val
-> FROM number_tbl;
+----------+----------+-------+---------+---------+
| num_rows | num_vals | total | max_val | avg_val |
+----------+----------+-------+---------+---------+
| 3 | 3 | 9 | 5 | 3.0000 |
+----------+----------+-------+---------+---------+
1 row in set (0.08 sec)

Os resultados são os esperados: tanto count(*) quanto count(val)


retornam o valor 3, sum(val) retorna o valor 9, max(val) retorna 5 e
avg(val) retorna 3. Em seguida, adicionarei um valor null à tabela
number_tbl e executarei novamente a consulta:
mysql> INSERT INTO number_tbl VALUES (NULL);
Query OK, 1 row affected (0.01 sec)
mysql> SELECT COUNT(*) num_rows,
-> COUNT(val) num_vals,
-> SUM(val) total,
-> MAX(val) max_val,
-> AVG(val) avg_val
-> FROM number_tbl;
+----------+----------+-------+---------+---------+
| num_rows | num_vals | total | max_val | avg_val |
+----------+----------+-------+---------+---------+
| 4 | 3 | 9 | 5 | 3.0000 |
+----------+----------+-------+---------+---------+
1 row in set (0.00 sec)

Mesmo com a adição do valor null à tabela, as funções sum(), max() e


avg() retornam os mesmos valores, indicando que eles ignoram os valores
null encontrados. A função count(*) agora retorna o valor 4, que é válido,
pois a tabela number_tbl contém quatro linhas, enquanto a função
count(val) ainda retorna o valor 3. A diferença é que count(*) conta o
número de linhas, enquanto count(val) conta o número de valores
contidos na coluna val e ignora qualquer valor null encontrado.

Gerando grupos
As pessoas raramente estão interessadas em observar dados brutos: em
vez disso, os envolvidos em análise de dados desejarão manipular os
dados brutos para melhor se adequarem às suas necessidades. Exemplos
comuns de manipulação de dados incluem:
• Gerar os totais de uma região geográ ca, como o total de vendas na
Europa.
• Encontrar os destaques, como os maiores vendedores de 2005.
• Determinar frequências, como o número de novas contas abertas em
cada lial.
Para responder a esses tipos de indagações, você precisará solicitar ao
servidor de banco de dados que agrupe as linhas por uma ou mais
colunas ou expressões. Como você já viu em vários exemplos, a cláusula
group by é o mecanismo de agrupamento de dados dentro de uma
consulta. Nesta seção, você verá como agrupar dados por uma ou mais
colunas, como agrupar dados usando expressões e como gerar resumos
(rollups) dentro de grupos.
Agrupamento por uma coluna
Grupos de uma coluna formam o tipo mais simples e mais utilizado de
agrupamento. Se você quiser encontrar os balanços totais de cada
produto, por exemplo, você precisa apenas agrupar pela coluna
account.product_cd, como em:
mysql> SELECT product_cd, SUM(avail_balance) prod_balance
-> FROM account
-> GROUP BY product_cd;
+------------+--------------+
| product_cd | prod_balance |
+------------+--------------+
| BUS | 9345.55 |
| CD | 19500.00 |
| CHK | 73008.01 |
| MM | 17045.14 |
| SAV | 1855.76 |
| SBL | 50000.00 |
+------------+--------------+
6 rows in set (0.00 sec)

Essa consulta gera seis grupos, um para cada produto, e então soma os
saldos disponíveis para cada membro do grupo.

Agrupamento por múltiplas colunas


Em alguns casos, você pode querer gerar grupos que envolvam mais de
uma coluna. Expandindo o exemplo anterior, imagine que você queira
encontrar os balanços totais não apenas de cada produto, mas de cada
produto em cada lial (por exemplo, qual é o balanço total de todas as
contas correntes abertas na lial de Woburn?). O exemplo a seguir mostra
como você poderia realizar isso:
mysql> SELECT product_cd, open_branch_id,
-> SUM(avail_balance) tot_balance
-> FROM account
-> GROUP BY product_cd, open_branch_id;
+------------+----------------+-------------+
| product_cd | open_branch_id | tot_balance |
+------------+----------------+-------------+
| BUS | 2 | 9345.55 |
| BUS | 4 | 0.00 |
| CD | 1 | 11500.00 |
| CD | 2 | 8000.00 |
| CHK | 1 | 782.16 |
| CHK | 2 | 3315.77 |
| CHK | 3 | 1057.75 |
| CHK | 4 | 67852.33 |
| MM | 1 | 14832.64 |
| MM | 3 | 2212.50 |
| SAV | 1 | 767.77 |
| SAV | 2 | 700.00 |
| SAV | 4 | 387.99 |
| SBL | 3 | 50000.00 |
+------------+----------------+-------------+
14 rows in set (0.00 sec)

Essa versão da consulta gera 14 grupos, um para cada combinação de


produto e lial encontrada na tabela account. Além de adicionar a coluna
open_branch_id à cláusula select, também a adicionei à cláusula group
by, já que open_branch_id é recuperado de uma tabela e não é gerado por
meio de uma função de agregação.

Agrupamento por meio de expressões


Além de usar colunas para agrupar dados, você pode construir grupos
baseados nos valores gerados por expressões. Considere a seguinte
consulta, que agrupa os funcionários pelo ano em que começaram a
trabalhar no banco:
mysql> SELECT EXTRACT(YEAR FROM start_date) year,
-> COUNT(*) how_many
-> FROM employee
-> GROUP BY EXTRACT(YEAR FROM start_date);
+------+----------+
| year | how_many |
+------+----------+
| 2004 | 2 |
| 2005 | 3 |
| 2006 | 8 |
| 2007 | 3 |
| 2008 | 2 |
+------+----------+
5 rows in set (0.15 sec)

Essa consulta emprega uma expressão relativamente simples, que usa a


função extract() para retornar apenas a porção do ano de uma data, para
agrupar as linhas da tabela employee.
Gerando resumos (rollups)
Em “Agrupamento por múltiplas colunas”, eu mostrei um exemplo que
gerou balanços totais de contas agrupadas por produto e lial. Digamos,
no entanto, que junto com os balanços totais de cada combinação
produto/ lial, você também queira os balanços de cada produto em
particular. Você poderia executar uma consulta adicional e mesclá-la aos
resultados, construir um script em Perl, um programa em Java ou algum
outro mecanismo que pegue os dados e realize os cálculos adicionais.
Melhor ainda, você poderia usar a opção with rollup para que o servidor
faça o serviço por você. Con ra a consulta modi cada, que usa with
rollup na cláusula group by:
mysql> SELECT product_cd, open_branch_id,
-> SUM(avail_balance) tot_balance
-> FROM account
-> GROUP BY product_cd, open_branch_id WITH ROLLUP;
+------------+----------------+-------------+
| product_cd | open_branch_id | tot_balance |
+------------+----------------+-------------+
| BUS | 2 | 9345.55 |
| BUS | 4 | 0.00 |
| BUS | NULL | 9345.55 |
| CD | 1 | 11500.00 |
| CD | 2 | 8000.00 |
| CD | NULL | 19500.00 |
| CHK | 1 | 782.16 |
| CHK | 2 | 3315.77 |
| CHK | 3 | 1057.75 |
| CHK | 4 | 67852.33 |
| CHK | NULL | 73008.01 |
| MM | 1 | 14832.64 |
| MM | 3 | 2212.50 |
| MM | NULL | 17045.14 |
| SAV | 1 | 767.77 |
| SAV | 2 | 700.00 |
| SAV | 4 | 387.99 |
| SAV | NULL | 1855.76 |
| SBL | 3 | 50000.00 |
| SBL | NULL | 50000.00 |
| NULL | NULL | 170754.46 |
+------------+----------------+-------------+
21 rows in set (0.02 sec)
Agora existem sete linhas adicionais no conjunto-resultado, uma para
cada um dos seis produtos distintos e uma para o total geral (todos os
produtos combinados). Nos seis resumos de produtos, um valor null é
fornecido para a coluna open_branch_id, já que o resumo está sendo
realizado ao longo de todas as liais. Olhando na terceira linha da saída,
por exemplo, você verá que um total de U$ 9.345,55 foi depositado em
contas BUS ao longo de todas as liais. Para a linha do total geral, um
valor null é fornecido para as colunas product_cd e open_branch_id. A
última linha da saída mostra um total de U$ 170.754,46 ao longo de todos
os produtos e liais.
Se estiver usando o Oracle Database, você precisará empregar
uma sintaxe ligeiramente diferente para indicar que deseja que
um resumo seja realizado. A cláusula group by da consulta
anterior caria assim no Oracle:
GROUP BY ROLLUP(product_cd, open_branch_id)

A vantagem dessa sintaxe é que ela permite realizar resumos


em um subconjunto das colunas da cláusula group by. Se
estiver agrupando pelas colunas a, b e c, por exemplo, você
poderia indicar que o servidor deve realizar resumos em apenas
b e c assim:
GROUP BY a, ROLLUP(b, c)

Se, junto aos totais por produto, você também quiser calcular totais por
lial, você pode usar a opção with cube, que gera linhas de resumo para
todas as combinações possíveis das colunas de agrupamento. Infelizmente,
with cube não está disponível na versão 5.1 do MySQL, mas está
disponível no SQL Server e no Oracle Database. Aqui está um exemplo
que usa with cube, mas eu removi o prompt mysql> para indicar que a
consulta ainda não pode ser realizada no MySQL:
SELECT product_cd, open_branch_id,
SUM(avail_balance) tot_balance
FROM account
GROUP BY product_cd, open_branch_id WITH CUBE;
+------------+----------------+-------------+
| product_cd | open_branch_id | tot_balance |
+------------+----------------+-------------+
| NULL | NULL | 170754.46 |
| NULL | 1 | 27882.57 |
| NULL | 2 | 21361.32 |
| NULL | 3 | 53270.25 |
| NULL | 4 | 68240.32 |
| BUS | 2 | 9345.55 |
| BUS | 4 | 0.00 |
| BUS | NULL | 9345.55 |
| CD | 1 | 11500.00 |
| CD | 2 | 8000.00 |
| CD | NULL | 19500.00 |
| CHK | 1 | 782.16 |
| CHK | 2 | 3315.77 |
| CHK | 3 | 1057.75 |
| CHK | 4 | 67852.33 |
| CHK | NULL | 73008.01 |
| MM | 1 | 14832.64 |
| MM | 3 | 2212.50 |
| MM | NULL | 17045.14 |
| SAV | 1 | 767.77 |
| SAV | 2 | 700.00 |
| SAV | 4 | 387.99 |
| SAV | NULL | 1855.76 |
| SBL | 3 | 50000.00 |
| SBL | NULL | 50000.00 |
+------------+----------------+-------------+
25 rows in set (0.02 sec)

O uso de with cube gera quatro linhas a mais do que a versão da consulta
que usa with rollup, uma para cada um dos quatro IDs de lial. De
forma semelhante ao with rollup, valores null são colocados na coluna
product_cd para indicar que um resumo de lial está sendo realizado.

Mais uma vez, se estiver usando o Oracle Database, você


precisará usar uma sintaxe ligeiramente diferente para indicar
que você quer que uma operação de cubo seja realizada. A
cláusula group by da consulta anterior caria assim usando o
Oracle:
GROUP BY CUBE(product_cd, open_branch_id)

Condições de ltro de grupo


No capítulo 4, apresentei vários tipos de condições de ltro e mostrei
como você pode usá-las na cláusula where. Ao agrupar dados, você
também pode aplicar condições de ltro a eles após a geração dos grupos.
A cláusula having é o lugar em que você deve colocar esses tipos de
condições de ltro. Considere o seguinte exemplo:
mysql> SELECT product_cd, SUM(avail_balance) prod_balance
-> FROM account
-> WHERE status = 'ACTIVE'
-> GROUP BY product_cd
-> HAVING SUM(avail_balance) >= 10000;
+------------+--------------+
| product_cd | prod_balance |
+------------+--------------+
| CD | 19500.00 |
| CHK | 73008.01 |
| MM | 17045.14 |
| SBL | 50000.00 |
+------------+--------------+
4 rows in set (0.00 sec)

Essa consulta tem duas condições de ltro: uma na cláusula where, que
descarta contas inativas, e a outra na cláusula having, que descarta
qualquer produto cujo balanço total disponível seja inferior a $ 10.000.
Então, um dos ltros age sobre os dados antes de eles serem agrupados, e
o outro ltro age sobre os dados após a criação dos grupos. Se, por
engano, você colocar ambos os ltros na cláusula where, você verá o
seguinte erro:
mysql> SELECT product_cd, SUM(avail_balance) prod_balance
-> FROM account
-> WHERE status = 'ACTIVE'
-> AND SUM(avail_balance) > 10000
-> GROUP BY product_cd;
ERROR 1111 (HY000): Invalid use of group function

Essa consulta falha porque você não pode incluir uma função de
agregação na cláusula where de uma consulta. Isso ocorre porque os ltros
na cláusula where são avaliados antes que os agrupamentos ocorram,
então o servidor ainda não pode realizar quaisquer funções sobre grupos.
Ao adicionar ltros em uma consulta que inclua uma cláusula
group by, preste atenção para ver se o ltro age sobre dados
brutos, nesse caso pertencendo à cláusula where, ou se age sobre
dados agrupados, nesse caso pertencendo à cláusula having.
No entanto, você pode incluir funções de agregação à cláusula having que
não apareçam na cláusula select, como demonstrado a seguir:
mysql> SELECT product_cd, SUM(avail_balance) prod_balance
-> FROM account
-> WHERE status = 'ACTIVE'
-> GROUP BY product_cd
-> HAVING MIN(avail_balance) >= 1000
-> AND MAX(avail_balance) <= 10000;
+------------+--------------+
| product_cd | prod_balance |
+------------+--------------+
| CD | 19500.00 |
| MM | 17045.14 |
+------------+--------------+
2 rows in set (0.00 sec)

Essa consulta gera os balanços totais de cada produto ativo, mas então a
condição de ltro na cláusula having exclui todos os produtos cujo
balanço mínimo seja menor do que $ 1.000 ou cujo balanço máximo seja
maior do que $ 10.000.

Teste seu conhecimento


Resolva os seguintes exercícios para testar sua compreensão das
características de agrupamento e agregação do SQL. Compare suas
respostas às fornecidas no apêndice C.

Exercício 8.1
Construa uma consulta que conte o número de linhas da tabela account.

Exercício 8.2
Modi que sua consulta do exercício 8.1 para contar o número de contas
abertas por cada cliente. Mostre o ID de cliente e o número de contas de
cada cliente.

Exercício 8.3
Modi que sua consulta do exercício 8.2 para incluir apenas os clientes
que tenham pelo menos duas contas.

Exercício 8.4 (crédito extra)


Encontre o balanço total disponível por produto e lial em que haja mais
de uma conta por produto e lial. Ordene os resultados pelo balanço total
(do maior para o menor).
CAPÍTULO 9
Subconsultas

Subconsultas são ferramentas poderosas que você pode usar nas quatro
instruções de dados SQL. Este capítulo explora com mais detalhes as
várias utilidades da subconsulta.

O que é uma subconsulta?


Uma subconsulta é uma consulta contida dentro de outra instrução SQL
(à qual me re ro como a instrução-contêiner ao longo desta discussão). Ela
é colocada sempre entre parênteses e normalmente é executada antes da
instrução-contêiner. Como qualquer consulta, uma subconsulta retorna
um conjunto-resultado que pode consistir em:
• Uma única linha com uma única coluna.
• Múltiplas linhas com uma única coluna.
• Múltiplas linhas e colunas.
O tipo de conjunto-resultado que a subconsulta retorna determina como
ela será usada e quais operadores a instrução-contêiner deve usar para
interagir com os dados retornados pela subconsulta. Quando a instrução-
contêiner termina sua execução, os dados retornados por quaisquer
subconsultas são descartados, fazendo com que uma subconsulta aja
como uma tabela temporária com escopo de instrução (signi cando que o
servidor libera a memória alocada aos resultados da subconsulta após o
término da execução da instrução SQL).
Você já viu vários exemplos de subconsultas em capítulos anteriores, mas
aqui está um exemplo simples para começarmos:
Nesse exemplo, a subconsulta retorna o valor máximo encontrado na
coluna account_id da tabela account e, então, a instrução-contêiner
retorna os dados relacionados àquela conta. Se você sempre ca confuso a
respeito do que uma subconsulta está fazendo, você pode executar a
subconsulta de forma independente (sem os parênteses) para ver o que ela
retorna. Aqui está a subconsulta do exemplo anterior:
mysql> SELECT MAX(account_id) FROM account;
+-----------------+
| MAX(account_id) |
+-----------------+
| 29 |
+-----------------+
1 row in set (0.00 sec)

Assim, a subconsulta retorna uma única linha com uma única coluna, o
que permite que ela seja usada como uma das expressões em uma
condição de igualdade (se a subconsulta retornasse duas ou mais linhas,
ela poderia ser comparada, mas não igualada a algo, mas falaremos sobre
isso mais tarde). Nesse caso, você pode pegar o valor retornado pela
subconsulta e colocá-lo no lado direito da expressão da condição de ltro
usada na instrução-contêiner, como em:

A subconsulta é útil nesse caso porque permite recuperar informações


sobre a conta com a numeração mais alta em uma única consulta, em vez
de recuperar o account_id máximo em uma consulta para, em seguida,
escrever uma segunda consulta que recupere os dados desejados da tabela
account. Como você verá, as subconsultas também são úteis em muitas
outras situações, e podem vir a se tornar uma das ferramentas mais
poderosas de seu kit de ferramentas SQL.

Tipos de subconsultas
Além das diferenças citadas anteriormente a respeito do tipo de conjunto-
resultado que uma subconsulta retorna (linha/coluna únicas, linha
única/múltiplas colunas, linhas/colunas múltiplas), você pode usar outro
fator para diferenciar subconsultas; algumas subconsultas são
completamente autocontidas (denominadas subconsultas não-correlatas),
enquanto outras referenciam colunas da instrução-contêiner
(denominadas subconsultas correlatas). As próximas seções exploram esses
dois tipos de subconsultas e mostram os diferentes operadores que você
pode empregar para interagir com elas.

Subconsultas não-correlatas
O exemplo mostrado anteriormente no capítulo era uma subconsulta
não-correlata: ela pode ser executada de forma independente e não
referencia qualquer coisa da instrução-contêiner. A maioria das
subconsultas que você encontrará pelo caminho é desse tipo, a não ser
que você esteja escrevendo instruções update ou delete, que
frequentemente utilizam subconsultas correlatas (falaremos sobre isso
adiante). Além de ser não-correlato, o exemplo mostrado anteriormente
neste capítulo é conhecido como uma subconsulta escalar e pode aparecer
em ambos os lados de uma condição que utilize os operadores comuns
(=, <>, <, >, <=, >=). O próximo exemplo mostra como você pode usar
uma subconsulta escalar em uma condição de desigualdade:
Essa consulta retorna dados relativos a todas as consultas que não foram
abertas pelo gerente de caixa na lial de Woburn (a subconsulta é escrita
partindo do pressuposto de que há apenas um gerente de caixa em cada
lial). A subconsulta nesse exemplo é um pouco mais complexa do que
no exemplo anterior, pois junta duas tabelas e inclui duas condições de
ltro. As subconsultas podem ser tão simples ou tão complexas quanto
você precise que elas sejam, e podem utilizar quaisquer cláusulas de
consulta disponíveis (select, from, where, group by, having e order by).
Se usar uma subconsulta em uma condição de desigualdade que retorne
mais de uma linha, você receberá uma mensagem de erro. Por exemplo, se
modi car o exemplo anterior de maneira tal que a subconsulta retorne
todos os caixas da lial de Woburn, em vez de retornar apenas o único
gerente de caixa, você receberá o seguinte erro:
mysql> SELECT account_id, product_cd, cust_id, avail_balance
-> FROM account
-> WHERE open_emp_id <> (SELECT e.emp_id
-> FROM employee e INNER JOIN branch b
-> ON e.assigned_branch_id = b.branch_id
-> WHERE e.title = 'Teller' AND b.city = 'Woburn');
ERROR 1242 (21000): Subquery returns more than 1 row

Se executar a subconsulta de forma independente, verá os seguintes


resultados:
mysql> SELECT e.emp_id
-> FROM employee e INNER JOIN branch b
-> ON e.assigned_branch_id = b.branch_id
-> WHERE e.title = 'Teller' AND b.city = 'Woburn';
+--------+
| emp_id |
+--------+
| 11 |
| 12 |
+--------+
2 rows in set (0.02 sec)

A consulta-contêiner falha porque uma expressão (open_emp_id) não pode


ser igualada a um conjunto de expressões (emp_ids 11 e 12). Em outras
palavras, um elemento único não pode ser igualado a um conjunto de
elementos. Na próxima seção, veremos como consertar esse problema
usando um operador diferente.

Subconsultas de linhas múltiplas e coluna única


Se a sua subconsulta retornar mais de uma linha, você não conseguirá
usá-la em um dos lados de uma condição de igualdade, como foi
demonstrado pelo exemplo anterior. No entanto, existem quatro
operadores adicionais que você pode usar para construir condições com
esses tipos de subconsultas.

Operadores in e not in
Apesar de não ser possível igualar um valor único a um conjunto de
valores, você pode veri car se um valor único pode ser encontrado dentro
de um conjunto de valores. O próximo exemplo, apesar de não usar uma
subconsulta, demonstra como construir uma condição que usa o
operador in para pesquisar um valor dentro de um conjunto de valores:
mysql> SELECT branch_id, name, city
-> FROM branch
-> WHERE name IN ('Headquarters', 'Quincy Branch');
+-----------+---------------+---------+
| branch_id | name | city |
+-----------+---------------+---------+
| 1 | Headquarters | Waltham |
| 3 | Quincy Branch | Quincy |
+-----------+---------------+---------+
2 rows in set (0.03 sec)

A expressão no lado esquerdo da condição é a coluna name, enquanto o


lado direito da condição é um conjunto de strings. O operador in veri ca
se uma das strings pode ser encontrada na coluna name; em caso positivo,
a condição é preenchida e a linha é adicionada ao conjunto-resultado.
Você pode obter os mesmos resultados usando duas condições de
igualdade, como em:
mysql> SELECT branch_id, name, city
-> FROM branch
-> WHERE name = 'Headquarters' OR name = 'Quincy Branch';
+-----------+---------------+---------+
| branch_id | name | city |
+-----------+---------------+---------+
| 1 | Headquarters | Waltham |
| 3 | Quincy Branch | Quincy |
+-----------+---------------+---------+
2 rows in set (0.01 sec)

Apesar de essa abordagem parecer razoável quando o conjunto contém


apenas duas expressões, é fácil enxergar o porquê de uma única condição
que use o operador in ser preferível no caso do conjunto conter dúzias
(ou centenas, milhares etc.) de valores.
Embora você crie ocasionalmente conjuntos de strings, datas ou números
para usar em um dos lados de uma condição, é mais provável que você
gere o conjunto por meio de uma subconsulta que retorne uma ou mais
linhas. A consulta a seguir usa o operador in com uma subconsulta no
lado direito da condição de ltro para descobrir quais funcionários
supervisionam outros funcionários:
A subconsulta retorna os IDs de todos os funcionários que
supervisionam outros funcionários, e a consulta-contêiner recupera
quatro colunas da tabela employee para esses funcionários. Veja os
resultados da subconsulta:
mysql> SELECT superior_emp_id
-> FROM employee;
+-----------------+
| superior_emp_id |
+-----------------+
| NULL |
| 1 |
| 1 |
| 3 |
| 4 |
| 4 |
| 4 |
| 4 |
| 4 |
| 6 |
| 6 |
| 6 |
| 10 |
| 10 |
| 13 |
| 13 |
| 16 |
| 16 |
+-----------------+
18 rows in set (0.00 sec)
Como você pode ver, alguns IDs de funcionário são listados mais de uma
vez, já que alguns funcionários supervisionam várias pessoas. Isso não
afeta negativamente os resultados da consulta-contêiner, pois não importa
se um ID de funcionário pode ser encontrado uma vez ou mais de uma
vez no conjunto-resultado da subconsulta. É claro que você poderia
adicionar a palavra-chave distinct à cláusula select da subconsulta se
lhe incomoda ter duplicatas na tabela retornada pela subconsulta, mas
isso não mudará o conjunto-resultado da consulta-contêiner.
Além de enxergar se um valor existe dentro de um conjunto de valores,
você pode veri car o contrário usando o operador not in. Aqui está outra
versão da consulta anterior que usa not in no lugar de in:

Essa consulta encontra todos os funcionários que não supervisionam


outras pessoas. Para essa consulta, precisei adicionar uma condição de
ltro à subconsulta para garantir que valores null não aparecessem na
tabela retornada pela subconsulta – consulte a próxima seção para ver
uma explicação do porquê de esse ltro ser necessário neste caso.

Operador all
Enquanto o operador in é usado para veri car se uma expressão pode ser
encontrada dentro de um conjunto de valores, o operador all permite
fazer comparações entre um único valor e cada valor de um conjunto. Para
construir esse tipo de condição, você precisará usar um dos operadores de
comparação (=, <>, <, > etc.) em conjunto com o operador all. Por
exemplo, a próxima consulta encontra todos os funcionários cujos IDs de
funcionário não sejam iguais a qualquer um dos IDs de funcionário dos
supervisores:

Mais uma vez, a subconsulta retorna o conjunto de IDs daqueles


funcionários que supervisionam outras pessoas, e a consulta-contêiner
retorna dados de todos os funcionários cujo ID de funcionário não seja
igual a qualquer um dos IDs retornados pela subconsulta. Em outras
palavras, a consulta encontra todos os funcionários que não sejam
supervisores. Se essa abordagem parecer um pouco desajeitada, você não
está sozinho: a maioria das pessoas prefere formular a consulta de
maneira diferente, evitando o uso do operador all. Por exemplo, essa
consulta gera os mesmos resultados que o último exemplo da seção
anterior, que usava o operador not in. É uma questão de preferência, mas
creio que a maioria das pessoas acharia que a versão que usa not in é
mais fácil de entender.
Ao usar not in ou <> all para comparar um valor a um
conjunto de valores, certi que-se de que o conjunto de valores
não contenha valores null, pois o servidor iguala o valor no
lado esquerdo da expressão a cada membro do conjunto, e
qualquer tentativa de igualar um valor a null resulta em
unknown (desconhecido). Portanto, a consulta a seguir retorna
um conjunto vazio:
mysql> Select emp_id, fname, lname, title
-> from employee
-> WHERE emp_id NOT IN (1, 2, Null);
Empty set (0.00 sec)

Em alguns casos, o operador all se torna um pouco mais natural. O


próximo exemplo usa all para encontrar as contas que tenham um saldo
disponível menor do que todas as contas de Frank Tucker:

Con ra os dados retornados pela subconsulta, que consistem no saldo


disponível em cada conta de Frank:
mysql> SELECT a.avail_balance
-> FROM account a INNER JOIN individual i
-> ON a.cust_id = i.cust_id
-> WHERE i.fname = 'Frank' AND i.lname = 'Tucker';
+---------------+
| avail_balance |
+---------------+
| 1057.75 |
| 2212.50 |
+---------------+
2 rows in set (0.01 sec)

Frank tem duas contas, com o saldo menor sendo U$ 1.057,75. A consulta-
contêiner encontra todas as contas que tenham um saldo inferior a todas
as contas de Frank, então o conjunto-resultado inclui todas as contas que
tenham um saldo inferior a U$ 1.057,75.

Operador any
Tal como o operador all, o operador any permite que um valor seja
comparado aos membros de um conjunto de valores. No entanto,
diferentemente de all, uma condição que use o operador any é avaliada
como verdadeira tão logo uma única comparação seja favorável. Isso é
diferente do exemplo anterior, que usava o operador all, que é avaliado
como verdadeiro apenas quando as comparações feitas a todos os
membros do conjunto são favoráveis. Por exemplo, você pode querer
encontrar todas as contas que tenham um saldo disponível superior a
qualquer conta de Frank Tucker:
Frank tem duas contas com saldos de U$ 1.057,75 e U$ 2.212,50; para ter
um saldo superior a qualquer uma dessas duas contas, uma delas deve ter
um saldo de pelo menos U$ 1.057,75.
Apesar da maioria das pessoas preferir usar in, usar = any é
equivalente a usar o operador in.

Subconsultas de múltiplas colunas


Até agora, todos os exemplos de subconsulta deste capítulo retornaram
uma única coluna e uma ou mais linhas. No entanto, em determinadas
situações, você pode usar subconsultas que retornem duas ou mais
colunas. Para mostrar a utilidade de subconsultas de múltiplas colunas,
pode ser interessante observar primeiro um exemplo que use múltiplas
subconsultas de uma única coluna:
mysql> SELECT account_id, product_cd, cust_id
-> FROM account
-> WHERE open_branch_id = (SELECT branch_id
-> FROM branch
-> WHERE name = 'Woburn Branch')
-> AND open_emp_id IN (SELECT emp_id
-> FROM employee
-> WHERE title = 'Teller' OR title = 'Head Teller');
+------------+------------+---------+
| account_id | product_cd | cust_id |
+------------+------------+---------+
| 1 | CHK | 1 |
| 2 | SAV | 1 |
| 3 | CD | 1 |
| 4 | CHK | 2 |
| 5 | SAV | 2 |
| 17 | CD | 7 |
| 27 | BUS | 11 |
+------------+------------+---------+
7 rows in set (0.09 sec)

Essa consulta usa duas subconsultas para identi car o ID da lial de


Woburn e os IDs de todos os caixas do banco e, em seguida, a consulta-
contêiner usa essas informações para recuperar todas as contas correntes
abertas por um caixa da lial Woburn. No entanto, como a tabela
employee inclui informações a respeito de qual lial cada funcionário está
alocado, você pode conseguir o mesmo resultado comparando as colunas
account.open_branch_id e account.open_emp_id em uma única
subconsulta às tabelas employee e branch. Para tanto, sua condição de
ltro deve nomear ambas as colunas da tabela account entre parênteses e
na mesma ordem em que são retornadas pela subconsulta, como em:
mysql> SELECT account_id, product_cd, cust_id
-> FROM account
-> WHERE (open_branch_id, open_emp_id) IN
-> (SELECT b.branch_id, e.emp_id
-> FROM branch b INNER JOIN employee e
-> ON b.branch_id = e.assigned_branch_id
-> WHERE b.name = 'Woburn Branch'
-> AND (e.title = 'Teller' OR e.title = 'Head Teller'));
+------------+------------+---------+
| account_id | product_cd | cust_id |
+------------+------------+---------+
| 1 | CHK | 1 |
| 2 | SAV | 1 |
| 3 | CD | 1 |
| 4 | CHK | 2 |
| 5 | SAV | 2 |
| 17 | CD | 7 |
| 27 | BUS | 11 |
+------------+------------+---------+
7 rows in set (0.00 sec)

Essa versão da consulta realiza a mesma função do exemplo anterior, mas


com uma única subconsulta que retorna duas colunas em vez das duas
subconsultas que retornavam uma única coluna cada.
É claro que você poderia reescrever o exemplo anterior simplesmente
juntando as três tabelas em vez de usar uma subconsulta, mas é muito
útil durante o processo de aprendizado de SQL ver múltiplas maneiras de
se alcançar os mesmos resultados. No entanto, aqui vai um exemplo que
necessita de uma subconsulta. Seu trabalho é encontrar todas as contas
cujos saldos não correspondam à soma dos valores das transações
daquela conta.
Veja uma solução parcial para o problema:
SELECT 'ALERT! : Account #1 Has Incorrect Balance!'
FROM account
WHERE (avail_balance, pending_balance) <>
(SELECT SUM(<expressão para gerar o saldo disponível>),
SUM(<expressão para gerar o saldo pendente>)
FROM transaction
WHERE account_id = 1)
AND account_id = 1;

Como você pode ver, não preenchi as expressões usadas para somar os
valores das transações dos saldos atual e pendente, mas prometo terminar
o serviço no capítulo 11, depois que você aprender a construir expressões
case. Mesmo assim, a consulta está su cientemente completa para
mostrar que a subconsulta está gerando duas somas a partir da tabela
transaction que são, no nal, comparadas às colunas avail_balance e
pending_balance da tabela account. Tanto a subconsulta quanto a
consulta-contêiner incluem a condição de ltro account_id = 1, então a
consulta em sua forma atual veri ca apenas uma conta por vez. Na
próxima seção, você aprenderá a escrever uma forma mais geral dessa
consulta que veri cará todas as contas em uma única execução.

Subconsultas correlatas
Todas as subconsultas mostradas até agora foram independentes de suas
instruções-contêineres, o que signi ca que você pode executá-las de forma
isolada e inspecionar os resultados. Uma subconsulta correlata, no
entanto, é dependente de sua instrução-contêiner, da qual ela referencia
uma ou mais colunas. Diferentemente de uma subconsulta não-correlata,
uma subconsulta correlata não é executada uma vez, anteriormente à
execução da instrução-contêiner: em vez disso, a subconsulta correlata é
executada uma vez para cada linha-candidata (linhas que podem ser
incluídas no resultado nal). Por exemplo, a seguinte consulta usa uma
subconsulta correlata para contar o número de contas de cada cliente, e
então a consulta-contêiner recupera aqueles clientes que têm exatamente
duas contas:
mysql> SELECT c.cust_id, c.cust_type_cd, c.city
-> FROM customer c
-> WHERE 2 = (SELECT COUNT(*)
-> FROM account a
-> WHERE a.cust_id = c.cust_id);
+---------+--------------+---------+
| cust_id | cust_type_cd | city |
+---------+--------------+---------+
| 2 | I | Woburn |
| 3 | I | Quincy |
| 6 | I | Waltham |
| 8 | I | Salem |
| 10 | B | Salem |
+---------+--------------+---------+
5 rows in set (0.01 sec)

A referência a c.cust_id bem no nal da subconsulta é o que torna a


subconsulta correlata: a consulta-contêiner deve fornecer valores de
c.cust_id para que a subconsulta possa ser executada. Nesse caso, a
consulta-contêiner recupera as 13 linhas da tabela customer e executa a
subconsulta uma vez para cada cliente, passando o ID de cliente
apropriado a cada execução. Se a subconsulta retornar o valor 2, a
condição de ltro é preenchida e a linha é adicionada ao conjunto-
resultado.
Além das condições de igualdade, você pode usar as subconsultas
correlatas em outros tipos de condições, como na condição de intervalo
ilustrada aqui:
mysql> SELECT c.cust_id, c.cust_type_cd, c.city
-> FROM customer c
-> WHERE (SELECT SUM(a.avail_balance)
-> FROM account a
-> WHERE a.cust_id = c.cust_id)
-> BETWEEN 5000 AND 10000;
+---------+--------------+------------+
| cust_id | cust_type_cd | city |
+---------+--------------+------------+
| 4 | I | Waltham |
| 7 | I | Wilmington |
| 11 | B | Wilmington |
+---------+--------------+------------+
3 rows in set (0.02 sec)

Essa variante da consulta anterior encontra todos os clientes cujo saldo


total disponível ao longo de todas as contas ca entre $ 5.000 e $ 10.000.
Mais uma vez, a subconsulta correlata é executada 13 vezes (uma vez para
cada linha de cliente) e cada execução da subconsulta retorna o saldo em
conta total de um determinado cliente.
Outra diferença sutil na consulta anterior é que a subconsulta
está no lado esquerdo da condição, o que pode parecer
estranho, mas é perfeitamente válido.
No nal da seção anterior, demonstrei como se veri cam os saldos
disponível e pendente de uma conta relativos às transações efetuadas
nessa mesma conta, e prometi mostrar a você como modi car o exemplo
para rodar todas as contas em uma única execução. Con ra o exemplo
novamente:
SELECT 'ALERT! : Account #1 Has Incorrect Balance!'
FROM account
WHERE (avail_balance, pending_balance) <>
(SELECT SUM(<expressão para gerar o saldo disponível>),
SUM(<expressão para gerar o saldo pendente>)
FROM transaction
WHERE account_id = 1)
AND account_id = 1;

Usando uma subconsulta correlata em vez de uma subconsulta não-


correlata, você pode executar a consulta-contêiner uma vez, e a
subconsulta será executada uma vez para cada conta. Con ra a versão
atualizada:
SELECT CONCAT('ALERT! : Account #', a.account_id,
' Has Incorrect Balance!')
FROM account a
WHERE (a.avail_balance, a.pending_balance) <>
(SELECT SUM(<expressão para gerar o saldo disponível>),
SUM(<expressão para gerar o saldo pendente>)
FROM transaction t
WHERE t.account_id = a.account_id);

A subconsulta agora inclui uma condição de ltro vinculando o ID de


conta da transação ao ID de conta da consulta-contêiner. A cláusula
select também foi modi cada para concatenar uma mensagem de alerta
que inclui o ID de conta em vez do valor literal 1.

Operador exists
Apesar de ser comum encontrar subconsultas correlatas sendo usadas em
condições de igualdade e de intervalo, o operador mais comumente usado
para construir condições que utilizem subconsultas correlatas é o
operador exists. Ele é usado quando se quer identi car que um
relacionamento existe, independentemente da quantidade. Por exemplo, a
consulta a seguir encontra todas as contas nas quais uma transação foi
efetuada em um dia em particular, independentemente de quantas
transações foram efetuadas:
SELECT a.account_id, a.product_cd, a.cust_id, a.avail_balance
FROM account a
WHERE EXISTS (SELECT 1
FROM transaction t
WHERE t.account_id = a.account_id
AND t.txn_date = '2008-09-22');

Ao usar o operador exists, sua subconsulta pode retornar zero, uma ou


várias linhas, e a condição simplesmente veri ca se a subconsulta
retornou alguma linha. Se olhar para a cláusula select da subconsulta,
verá que ela consiste em um valor literal (1): já que a condição da
consulta-contêiner precisa saber apenas quantas linhas foram retornadas,
os dados reais da subconsulta retornada são irrelevantes. Sua subconsulta
pode retornar o que você quiser, como demonstrado a seguir:
SELECT a.account_id, a.product_cd, a.cust_id, a.avail_balance
FROM account a
WHERE EXISTS (SELECT t.txn_id, 'hello', 3.1415927
FROM transaction t
WHERE t.account_id = a.account_id
AND t.txn_date = '2008-09-22');

No entanto, a convenção é especi car select 1 ou select * quando se


estiver usando exists. Você também pode usar not exists para veri car
se as subconsultas não retornam linhas, como demonstrado a seguir:
mysql> SELECT a.account_id, a.product_cd, a.cust_id
-> FROM account a
-> WHERE NOT EXISTS (SELECT 1
-> FROM business b
-> WHERE b.cust_id = a.cust_id);
+------------+------------+---------+
| account_id | product_cd | cust_id |
+------------+------------+---------+
| 1 | CHK | 1 |
| 2 | SAV | 1 |
| 3 | CD | 1 |
| 4 | CHK | 2 |
| 5 | SAV | 2 |
| 7 | CHK | 3 |
| 8 | MM | 3 |
| 10 | CHK | 4 |
| 11 | SAV | 4 |
| 12 | MM | 4 |
| 13 | CHK | 5 |
| 14 | CHK | 6 |
| 15 | CD | 6 |
| 17 | CD | 7 |
| 18 | CHK | 8 |
| 19 | SAV | 8 |
| 21 | CHK | 9 |
| 22 | MM | 9 |
| 23 | CD | 9 |
+------------+------------+---------+
19 rows in set (0.99 sec)

Essa consulta encontra todos os clientes cujo ID de cliente não apareça na


tabela business, que é uma maneira indireta de encontrar todos os
clientes não-corporativos.

Manipulação de dados usando subconsultas correlatas


Todos os exemplos mostrados até agora no capítulo foram instruções
select, mas não pense que isso signi ca que as subconsultas não sejam
úteis em outras instruções SQL. Subconsultas também são muito usadas
em instruções update, delete e insert, com as subconsultas correlatas
aparecendo frequentemente nas instruções update e delete. Aqui está um
exemplo de uma subconsulta correlata usada para modi car a coluna
last_activity_date da tabela account:
UPDATE account a
SET a.last_activity_date =
(SELECT MAX(t.txn_date)
FROM transaction t
WHERE t.account_id = a.account_id);

Essa instrução modi ca cada linha da tabela account (pois não há uma
cláusula where) encontrando a data da última transação feita em cada
conta. Apesar de parecer razoável esperar que cada conta tenha pelo
menos uma transação vinculada a ela, seria melhor veri car se uma conta
tem alguma transação antes de tentar atualizar a coluna
last_activity_data; senão, a coluna será con gurada como null, já que a
subconsulta não retornaria nenhuma linha. Aqui está outra versão da
instrução update, dessa vez empregando uma cláusula where com uma
segunda subconsulta correlata:
UPDATE account a
SET a.last_activity_date =
(SELECT MAX(t.txn_date)
FROM transaction t
WHERE t.account_id = a.account_id)
WHERE EXISTS (SELECT 1
FROM transaction t
WHERE t.account_id = a.account_id);

As duas subconsultas correlatas são idênticas, exceto pelas cláusulas


select. No entanto, a subconsulta na cláusula set é executada apenas se a
condição na cláusula where da instrução update for avaliada como true
(signi cando que pelo menos uma transação foi encontrada para a conta),
protegendo, assim, os dados da coluna last_activity_date de serem
sobrescritos com um null.
Subconsultas correlatas também são comuns em instruções delete. Por
exemplo, você pode executar um script de manutenção de dados no nal
de cada mês que remova dados desnecessários. O script pode incluir a
seguinte instrução, que remove os dados da tabela department que não
tenham linhas- lhas na tabela employee:
DELETE FROM department
WHERE NOT EXISTS (SELECT 1
FROM employee
WHERE employee.dept_id = department.dept_id);

Ao usar subconsultas correlatas com instruções delete no MySQl, lembre-


se de que aliases de tabela não são permitidos por nenhum motivo, e foi
por isso que tive que usar o nome completo da tabela na subconsulta.
Com a maioria dos outros servidores de banco de dados, você poderia
fornecer aliases para as tabelas department e employee, como em:
DELETE FROM department d
WHERE NOT EXISTS (SELECT 1
FROM employee e
WHERE e.dept_id = d.dept_id);

Quando usar subconsultas


Agora que você aprendeu sobre os diferentes tipos de subconsultas e os
diferentes operadores que você pode empregar para interagir com os
dados retornados pelas subconsultas, chegou a hora de explorar as várias
maneiras pelas quais você pode usar subconsultas para construir
instruções SQL poderosas. As próximas três seções demonstram como
você pode usar subconsultas para construir tabelas personalizadas, para
construir condições e para gerar valores de colunas em conjuntos-
resultado.

Subconsultas como fontes de dados


Lá no capítulo 3, eu a rmei que a cláusula from de uma instrução select
nomeia as tabelas a serem usadas pela consulta. Já que uma subconsulta
gera um conjunto-resultado contendo linhas e colunas de dados, é
perfeitamente válido incluir subconsultas em sua cláusula from junto com
tabelas. Apesar de isso parecer, à primeira vista, ser uma funcionalidade
interessante sem muitos méritos práticos, o uso de subconsultas junto
com tabelas é uma das ferramentas mais poderosas disponíveis ao se
escrever consultas. Veja um exemplo simples:
mysql> SELECT d.dept_id, d.name, e_cnt.how_many num_employees
-> FROM department d INNER JOIN
-> (SELECT dept_id, COUNT(*) how_many
-> FROM employee
-> GROUP BY dept_id) e_cnt
-> ON d.dept_id = e_cnt.dept_id;
+---------+----------------+---------------+
| dept_id | name | num_employees |
+---------+----------------+---------------+
| 1 | Operations | 14 |
| 2 | Loans | 1 |
| 3 | Administration | 3 |
+---------+----------------+---------------+
3 rows in set (0.04 sec)

Nesse exemplo, uma subconsulta gerou uma lista de IDs departamento


junto com o número de funcionários alocados em cada departamento.
Con ra o conjunto-resultado gerado pela subconsulta:
mysql> SELECT dept_id, COUNT(*) how_many
-> FROM employee
-> GROUP BY dept_id;
+---------+----------+
| dept_id | how_many |
+---------+----------+
| 1 | 14 |
| 2 | 1 |
| 3 | 3 |
+---------+----------+
3 rows in set (0.00 sec)

A subconsulta recebe o nome e_cnt e é juntada à tabela department por


meio da coluna dept_id. A consulta-contêiner, então, recupera o ID e o
nome de departamento da tabela department, junto com a contagem de
funcionários da subconsulta e_cnt.
Subconsultas usadas na cláusula from devem ser não-correlatas: elas são
executadas primeiro, e os dados são mantidos em memória até a consulta-
contêiner terminar sua execução. As subconsultas oferecem uma
exibilidade enorme ao se escrever consultas, porque você pode ir muito
além do conjunto de tabelas disponíveis para criar praticamente qualquer
view dos dados que você escolher e, então, juntar os resultados com
outras tabelas ou subconsultas. Se estiver gerando relatórios ou
mecanismos de alimentação de dados para sistemas externos, você
conseguirá fazer coisas com uma única consulta que demandariam
múltiplas consultas ou o uso de uma linguagem procedural para alcançar
o mesmo resultado.

Fabricação de dados
Além de usar subconsultas para resumir dados existentes, você pode usá-
las para gerar dados que não existam em qualquer forma dentro de seu
banco de dados. Por exemplo, você pode querer agrupar seus clientes pela
quantia de dinheiro existente em suas contas de depósito, mas usando
de nições de grupo que não estejam armazenadas em seu banco de
dados. Digamos, por exemplo, que você queira ordenar seus clientes pelos
grupos mostrados na tabela 9.1.
Table 9.1. Grupos de clientes por saldo1
Nome do grupo Limite inferior Limite superior

Small Fry 0 $4.999,99


Average Joes $5.000 $9.999,99

Heavy Hitters $10.000 $9.999.999,99

Para gerar esses grupos dentro de uma única consulta, você precisaria de
uma maneira de de nir esses três grupos. O primeiro passo é de nir uma
consulta que gere as de nições de grupo:

Eu usei o operador de conjunto union all para mesclar os resultados das


três consultas separadas em um único conjunto-resultado. Cada consulta
retorna três literais, e os resultados das três consultas são agrupados para
gerar um conjunto-resultado com três linhas e três colunas. Agora você
tem uma consulta que gera os grupos desejados e pode colocá-la dentro
da cláusula from de outra consulta para gerar seus grupos de clientes:
mysql> SELECT groups.name, COUNT(*) num_customers
-> FROM
-> (SELECT SUM(a.avail_balance) cust_balance
-> FROM account a INNER JOIN product p
-> ON a.product_cd = p.product_cd
-> WHERE p.product_type_cd = 'ACCOUNT'
-> GROUP BY a.cust_id) cust_rollup
-> INNER JOIN
-> (SELECT 'Small Fry' name, 0 low_limit, 4999.99 high_limit
-> UNION ALL
-> SELECT 'Average Joes' name, 5000 low_limit,
-> 9999.99 high_limit
-> UNION ALL
-> SELECT 'Heavy Hitters' name, 10000 low_limit,
-> 9999999.99 high_limit) groups
-> ON cust_rollup.cust_balance
-> BETWEEN groups.low_limit AND groups.high_limit
-> GROUP BY groups.name;
+---------------+---------------+
| name | num_customers |
+---------------+---------------+
| Average Joes | 2 |
| Heavy Hitters | 4 |
| Small Fry | 5 |
+---------------+---------------+
3 rows in set (0.01 sec)

A cláusula from contém duas subconsultas: a primeira, nomeada


cust_rollup, retorna os saldos de depósito atuais de cada cliente,
enquanto a segunda, nomeada groups, gera os três agrupamentos de
clientes. Veja os dados gerados por cust_rollup:
mysql> SELECT SUM(a.avail_balance) cust_balance
-> FROM account a INNER JOIN product p
-> ON a.product_cd = p.product_cd
-> WHERE p.product_type_cd = 'ACCOUNT'
-> GROUP BY a.cust_id;
+--------------+
| cust_balance |
+--------------+
| 4557.75 |
| 2458.02 |
| 3270.25 |
| 6788.98 |
| 2237.97 |
| 10122.37 |
| 5000.00 |
| 3875.18 |
| 10971.22 |
| 23575.12 |
| 38552.05 |
+--------------+
11 rows in set (0.05 sec)

Os dados gerados por cust_rollup são, então, juntados à tabela groups


por meio de uma condição de intervalo (cust_rollup.cust_balance
Between groups.low_limit and groups.high_limit). Por m, os dados
juntados são agrupados e o número de clientes em cada grupo é contado
para gerar o conjunto-resultado nal.
É claro que você poderia simplesmente decidir construir uma tabela
permanente para armazenar as de nições de grupo em vez de usar uma
subconsulta. Usando essa abordagem, e após um certo tempo, você
acabaria com um banco de dados abarrotado de pequenas tabelas com
propósitos especí cos, e não se lembraria da razão pela qual a maioria
delas foi criada. Já trabalhei em ambientes nos quais os usuários de banco
de dados tinham permissão para criar suas próprias tabelas com
propósitos especiais, e os resultados foram desastrosos (tabelas não
incluídas em backups, tabelas perdidas durante atualizações do servidor,
servidor ocioso devido a problemas de alocação de espaço etc.). Armado
com as subconsultas, você conseguirá aderir a uma política em que
tabelas são adicionadas a um banco de dados apenas quando há uma
necessidade de negócio clara para se armazenar novos dados.

Subconsultas orientadas a tarefas


Em sistemas usados em relatórios ou na geração de alimentação de dados,
você normalmente encontrará o seguinte:
Essa consulta soma os saldos de todas as contas de depósito por tipo de
conta, mostra o funcionário que abriu as contas e as liais onde as contas
foram abertas. Se observar atentamente a consulta, verá que as tabelas
product, branch e employee são necessárias apenas por questões de
exibição, e que a tabela account tem todos os dados necessários para gerar
os agrupamentos (product_cd, open_branch_id, open_emp_id e
avail_balance). Portanto, você pode separar a tarefa de gerar os grupos
em uma subconsulta e, então, juntar as outras três tabelas geradas pela
subconsulta para obter o resultado nal desejado. Aqui está a subconsulta
de agrupamento:

Isso é o coração da consulta: as outras tabelas são necessárias apenas para


fornecer strings signi cativas no lugar das colunas de chave estrangeira
product_cd, open_branch_id e open_emp_id. A próxima consulta engloba a
consulta à tabela account em uma subconsulta e junta a tabela resultante
às outras três tabelas:

Eu sei que a beleza está nos olhos de quem a vê, mas acho que essa versão
da consulta é muito mais satisfatória do que a versão anterior, que era
grande e plani cada. Essa versão também pode acabar executando mais
rápido, porque os agrupamentos são feitos sobre colunas de chave
estrangeira pequenas e numéricas (product_cd, open_branch_id,
open_emp_id), em vez das colunas de string potencialmente grandes
(branch.name, product.name, employee.fname, employee.lname).

Subconsultas em condições de ltro


Muitos dos exemplos deste capítulo usaram subconsultas como
expressões em condições de ltro, então não deve lhe surpreender o fato
de que esse é um dos principais usos das subconsultas. No entanto,
condições de ltro que usam subconsultas não são encontradas apenas na
cláusula where. Por exemplo, a consulta a seguir usa uma subconsulta na
cláusula having para encontrar o funcionário responsável pela abertura da
maioria das contas:
mysql> SELECT open_emp_id, COUNT(*) how_many
-> FROM account
-> GROUP BY open_emp_id
-> HAVING COUNT(*) = (SELECT MAX(emp_cnt.how_many)
-> FROM (SELECT COUNT(*) how_many
-> FROM account
-> GROUP BY open_emp_id) emp_cnt);
+-------------+----------+
| open_emp_id | how_many |
+-------------+----------+
| 1 | 8 |
+-------------+----------+
1 row in set (0.01 sec)

A subconsulta na cláusula having encontra o número máximo de contas


abertas por um funcionário, e a consulta-contêiner encontra o
funcionário que abriu aquele número de contas. Se vários funcionários
empatarem com o número máximo de contas abertas, a consulta
retornará múltiplas linhas.

Subconsultas como geradoras de expressões


Nesta última seção do capítulo, termino onde comecei: com subconsultas
escalares de coluna e linha únicas. Além de serem usadas em condições de
ltro, subconsultas escalares podem ser usadas em qualquer lugar que
possa aparecer uma expressão, incluindo as cláusulas select e order by de
uma consulta e a cláusula values de uma instrução insert.
Em “Subconsultas orientadas a tarefas”, mostrei como você usaria uma
subconsulta para separar o mecanismo de agrupamento do resto da
consulta. Aqui está outra versão da mesma consulta, que usa as
subconsultas com o mesmo propósito, mas de forma diferente:

Existem duas diferenças principais entre essa consulta e a versão anterior


que usava uma subconsulta na cláusula from:
• Em vez de juntar as tabelas product, branch e employee aos dados de
conta, subconsultas escalares correlatas são usadas na cláusula select
para procurar os nomes de produto, lial e funcionário.
• O conjunto-resultado tem 14 linhas em vez de 11, e três dos nomes de
produtos são null.
A razão das três linhas extras no conjunto-resultado é que a versão
anterior da consulta incluía a condição de ltro p.product_type_cd =
'Account'. Esse ltro eliminava as linhas com tipo de produto INSURANCE e
LOAN, como no caso de empréstimos (loans) a pequenas empresas. Como
essa versão da consulta não inclui uma junção à tabela product, não há
como incluir a condição de ltro na consulta principal. A subconsulta
correlata feita à tabela product inclui esse ltro, mas o único efeito acaba
sendo deixar o nome de produto como null. Se quiser se livrar das três
linhas extras, você poderia juntar a tabela product à tabela account e
incluir a condição de ltro, ou poderia simplesmente fazer o seguinte:

Simplesmente envolvendo a consulta anterior em uma subconsulta


(chamada all_prods) e adicionando uma condição de ltro para excluir
os valores null da coluna product, a consulta agora retorna as 11 linhas
desejadas. O resultado nal é uma consulta que realiza todos os
agrupamentos relativos aos dados brutos da tabelas account e, então,
embeleza a saída usando dados das outras três tabelas – isso sem usar
nenhuma junção.
Como citado anteriormente, as subconsultas escalares também podem
aparecer na cláusula order by. A consulta a seguir recupera os dados dos
funcionários ordenados pelo sobrenome do chefe de cada funcionário e,
em seguida, pelo sobrenome do funcionário:

A consulta usa duas subconsultas escalares correlatas: uma na cláusula


select para recuperar o nome completo do chefe de cada funcionário e
outra na cláusula order by para retornar apenas o sobrenome do chefe de
cada funcionário por questões de ordenação.
Além de usar subconsultas escalares correlatas em instruções select, você
pode usar subconsultas escalares não-correlatas para gerar valores para
uma instrução insert. Por exemplo, digamos que você vai gerar uma nova
linha de conta e que lhe passaram os seguintes dados:
• O nome do produto (“savings account”).
• O ID federal do cliente (“555-55-5555”).
• O nome da lial onde a conta foi aberta (“Quincy Branch”).
• O nome e o sobrenome do caixa que abriu a conta (“Frank Portman”).
Antes de criar uma linha na tabela account, você precisará procurar os
valores-chave de todos esses fragmentos de dados para poder popular as
colunas de chave estrangeira da tabela account. Você tem duas escolhas de
como fazê-lo: executar quatro consultas para recuperar os valores de
chave primária e colocar esses valores em uma instrução insert, ou usar
subconsultas para recuperar os quatro valores-chave dentro de uma
instrução insert. Veja um exemplo da segunda abordagem:
INSERT INTO account
(account_id, product_cd, cust_id, open_date, last_activity_date,
status, open_branch_id, open_emp_id, avail_balance,
pending_balance)
VALUES (NULL,
(SELECT product_cd FROM product WHERE name = 'savings account'),
(SELECT cust_id FROM customer WHERE fed_id = '555-55-5555'),
'2008-09-25', '2008-09-25', 'ACTIVE',
(SELECT branch_id FROM branch WHERE name = 'Quincy Branch'),
(SELECT emp_id FROM employee WHERE lname = 'Portman' AND fname =
'Frank'),
0, 0);

Usando uma única instrução SQL, você pode criar uma linha na tabela
account e pesquisar quatro valores de chave estrangeira ao mesmo tempo.
No entanto, há um problema nessa abordagem. Quando você usa
subconsultas para gerar dados de colunas que permitam valores null, sua
instrução insert pode ser bem-sucedida mesmo se uma das subconsultas
falhar em retornar um valor. Por exemplo, se você errar a digitação do
nome Frank Portman na quarta subconsulta, ainda será criada uma linha
na tabela account, mas a coluna open_emp_id será con gurada como null.

Resumo das subconsultas


Vimos muita coisa neste capítulo, então é uma boa ideia revisá-lo. Os
exemplos que usei neste capítulo demonstraram subconsultas que:
• Retornam uma única coluna e linha, uma única coluna com múltiplas
linhas, e múltiplas colunas e linhas.
• São independentes da instrução-contêiner (subconsultas não-
correlatas).
• Referenciam uma ou mais colunas da instrução-contêiner
(subconsultas correlatas).
• São usadas em condições que utilizam operadores de comparação,
bem como os operadores de propósito mais especí co in, not in,
exists e not exists.

• Podem ser encontradas em instruções select, update, delete e insert.


• Geram conjuntos-resultados que podem ser juntados a outras tabelas
(ou subconsultas) em uma consulta.
• Podem ser usadas na geração de valores para popular uma tabela ou
para popular colunas do conjunto-resultado de uma consulta.
• São usadas nas cláusulas select, from, where e order by das consultas.
Obviamente, as subconsultas são uma ferramenta versátil. Portanto, não
se sinta mal se todos esses conceitos não foram assimilados após a
primeira leitura deste capítulo. Continue testando os vários usos das
subconsultas, e logo você se verá pensando em como utilizar uma
subconsulta toda vez que precisar escrever uma instrução SQL incomum.

Teste seu conhecimento


Estes exercícios foram desenvolvidos para testar sua compreensão de
subconsultas. Por favor, consulte o apêndice C para conferir as soluções.

Exercício 9.1
Construa uma consulta à tabela account que use uma condição de ltro
com uma subconsulta não-correlata à tabela product para encontrar todas
as contas de empréstimo (product.product_type_cd = 'LOAN'). Recupere o
ID de conta, o código de produto, o ID de cliente e o saldo disponível.

Exercício 9.2
Refaça a consulta do exercício 9.1 usando uma subconsulta correlata à
tabela product para obter os mesmos resultados.

Exercício 9.3
Junte a consulta a seguir à tabela employee para mostrar o nível de
experiência de cada funcionário:
SELECT 'trainee' name, '2004-01-01' start_dt, '2005-12-31' end_dt
UNION ALL
SELECT 'worker' name, '2002-01-01' start_dt, '2003-12-31' end_dt
UNION ALL
SELECT 'mentor' name, '2000-01-01' start_dt, '2001-12-31' end_dt

Atribua à subconsulta o alias levels e inclua o ID de funcionário, nome,


sobrenome e nível de experiência (levels.name). (Dica: construa uma
condição de junção usando uma condição de desigualdade para
determinar em qual nível a coluna employee.start_date se enquadra).

Exercício 9.4
Construa uma consulta à tabela employee que recupere o ID de
funcionário, o nome e o sobrenome, junto com os nomes do
departamento e da lial aos quais o funcionário foi alocado. Não junte
nenhuma tabela.

1 N. de T.: os três grupos poderiam ser traduzidos como “Peixe pequeno”, “Sujeitos comuns” e
“Pesos-pesados”.
CAPÍTULO 10
Junções revisitadas

A esta altura, você deve estar confortável com o conceito de junção


interna (inner join), que foi apresentado no capítulo 5. Este capítulo se
concentra nas outras maneiras de juntar tabelas, incluindo a junção
externa (outer join) e a junção cruzada (cross join).

Junções externas
Em todos os exemplos mostrados até agora que incluíam múltiplas
tabelas, não nos preocupamos com o fato de que as condições de junção
poderiam falhar em encontrar correspondências para todas as linhas das
tabelas. Por exemplo, ao juntar a tabela account à tabela customer, não
mencionei a possibilidade de um valor na coluna cust_id da tabela
account poder não corresponder a um valor da coluna cust_id na tabela
customer. Se esse fosse o caso, algumas das linhas em uma tabela ou outra
cariam de fora do conjunto-resultado.
Apenas para ter certeza, vamos veri car os dados nas tabelas. Con ra as
colunas account_id e cust_id da tabela account:
mysql> SELECT account_id, cust_id
-> FROM account;
+------------+---------+
| account_id | cust_id |
+------------+---------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
| 5 | 2 |
| 7 | 3 |
| 8 | 3 |
| 10 | 4 |
| 11 | 4 |
| 12 | 4 |
| 13 | 5 |
| 14 | 6 |
| 15 | 6 |
| 17 | 7 |
| 18 | 8 |
| 19 | 8 |
| 21 | 9 |
| 22 | 9 |
| 23 | 9 |
| 24 | 10 |
| 25 | 10 |
| 27 | 11 |
| 28 | 12 |
| 29 | 13 |
+------------+---------+
24 rows in set (1.50 sec)

Existem 24 contas pertencentes a 13 clientes diferentes, com os IDs de


cliente de 1 a 13 tendo pelo menos uma conta. Veja o conjunto de IDs de
cliente da tabela customer:
mysql> SELECT cust_id
-> FROM customer;
+---------+
| cust_id |
+---------+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
| 11 |
| 12 |
| 13 |
+---------+
13 rows in set (0.02 sec)

Há 13 linhas na tabela customer com IDs de 1 a 13, então cada ID de


cliente está incluído pelo menos uma vez na tabela account. Portanto,
quando as duas tabelas são juntadas pela coluna cust_id, você esperaria
que todas as 24 linhas fossem incluídas no conjunto-resultado
(excetuando qualquer outra condição de ltro):
mysql> SELECT a.account_id, c.cust_id
-> FROM account a INNER JOIN customer c
-> ON a.cust_id = c.cust_id;
+------------+---------+
| account_id | cust_id |
+------------+---------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
| 5 | 2 |
| 7 | 3 |
| 8 | 3 |
| 10 | 4 |
| 11 | 4 |
| 12 | 4 |
| 13 | 5 |
| 14 | 6 |
| 15 | 6 |
| 17 | 7 |
| 18 | 8 |
| 19 | 8 |
| 21 | 9 |
| 22 | 9 |
| 23 | 9 |
| 24 | 10 |
| 25 | 10 |
| 27 | 11 |
| 28 | 12 |
| 29 | 13 |
+------------+---------+
24 rows in set (0.06 sec)

Como esperado, as 24 linhas estão presentes no conjunto-resultado. Mas o


que acontece se você juntar a tabela account a uma das tabelas de clientes
especializadas, como a tabela business?
mysql> SELECT a.account_id, b.cust_id, b.name
-> FROM account a INNER JOIN business b
-> ON a.cust_id = b.cust_id;
+------------+---------+------------------------+
| account_id | cust_id | name |
+------------+---------+------------------------+
| 24 | 10 | Chilton Engineering |
| 25 | 10 | Chilton Engineering |
| 27 | 11 | Northeast Cooling Inc. |
| 28 | 12 | Superior Auto Body |
| 29 | 13 | AAA Insurance Inc. |
+------------+---------+------------------------+
5 rows in set (0.10 sec)

Em vez de 24 linhas no conjunto-resultado, agora existem apenas cinco.


Vamos olhar a tabela business para ver o porquê disso:
mysql> SELECT cust_id, name
-> FROM business;
+---------+------------------------+
| cust_id | name |
+---------+------------------------+
| 10 | Chilton Engineering |
| 11 | Northeast Cooling Inc. |
| 12 | Superior Auto Body |
| 13 | AAA Insurance Inc. |
+---------+------------------------+
4 rows in set (0.01 sec)

Das 13 linhas na tabela customer, apenas quatro representam clientes


corporativos, e como um desses clientes corporativos tem duas contas, um
total de cinco linhas da tabela account é vinculado aos clientes
corporativos.
Mas e se você quiser que sua consulta retorne todas as contas, incluindo o
nome da empresa, apenas se a conta estiver vinculada a um cliente
corporativo? Este é um exemplo em que você precisaria de uma junção
externa entre as tabelas account e business, como em:
mysql> SELECT a.account_id, a.cust_id, b.name
-> FROM account a LEFT OUTER JOIN business b
-> ON a.cust_id = b.cust_id;
+------------+---------+------------------------+
| account_id | cust_id | name |
+------------+---------+------------------------+
| 1 | 1 | NULL |
| 2 | 1 | NULL |
| 3 | 1 | NULL |
| 4 | 2 | NULL |
| 5 | 2 | NULL |
| 7 | 3 | NULL |
| 8 | 3 | NULL |
| 10 | 4 | NULL |
| 11 | 4 | NULL |
| 12 | 4 | NULL |
| 13 | 5 | NULL |
| 14 | 6 | NULL |
| 15 | 6 | NULL |
| 17 | 7 | NULL |
| 18 | 8 | NULL |
| 19 | 8 | NULL |
| 21 | 9 | NULL |
| 22 | 9 | NULL |
| 23 | 9 | NULL |
| 24 | 10 | Chilton Engineering |
| 25 | 10 | Chilton Engineering |
| 27 | 11 | Northeast Cooling Inc. |
| 28 | 12 | Superior Auto Body |
| 29 | 13 | AAA Insurance Inc. |
+------------+---------+------------------------+
24 rows in set (0.04 sec)

Uma junção externa inclui todas as linhas de uma das tabelas e inclui os
dados da segunda tabela apenas quando linhas correspondentes são
encontradas. Nesse caso, todas as linhas da tabela account são incluídas,
já que especi quei left outer join e a tabela account está no lado
esquerdo da de nição de junção. A coluna name recebe null em todas as
linhas, exceto no caso das linhas dos quatro clientes corporativos
(cust_ids 10, 11, 12 e 13). Veja uma consulta semelhante com uma junção
externa à tabela individual em vez da tabela business:
Essa consulta é, essencialmente, o inverso da anterior: o nome e o
sobrenome são fornecidos para clientes pessoa física, enquanto essas
colunas recebem null no caso de clientes corporativos.

Junção externa esquerda versus junção externa direita


Em cada um dos exemplos de junção externa da seção anterior,
especi quei left outer join. A palavra-chave left (esquerda) indica que
a tabela no lado esquerdo da junção é responsável por determinar o
número de linhas do conjunto-resultado, enquanto a tabela no lado
direito é usada para fornecer valores de coluna sempre que uma
correspondência é encontrada. Considere a seguinte consulta:
mysql> SELECT c.cust_id, b.name
-> FROM customer c LEFT OUTER JOIN business b
-> ON c.cust_id = b.cust_id;
+---------+------------------------+
| cust_id | name |
+---------+------------------------+
| 1 | NULL |
| 2 | NULL |
| 3 | NULL |
| 4 | NULL |
| 5 | NULL |
| 6 | NULL |
| 7 | NULL |
| 8 | NULL |
| 9 | NULL |
| 10 | Chilton Engineering |
| 11 | Northeast Cooling Inc. |
| 12 | Superior Auto Body |
| 13 | AAA Insurance Inc. |
+---------+------------------------+
13 rows in set (0.00 sec)

A cláusula from especi ca uma junção externa à esquerda, então todas as


13 linhas da tabela customer são incluídas no conjunto-resultado, com a
tabela business contribuindo com valores para a segunda coluna do
conjunto-resultado no caso dos quatro clientes corporativos. Se você
executar a mesma consulta, mas indicando right outer join, você veria
os seguintes resultados:
mysql> SELECT c.cust_id, b.name
-> FROM customer c RIGHT OUTER JOIN business b
-> ON c.cust_id = b.cust_id;
+---------+------------------------+
| cust_id | name |
+---------+------------------------+
| 10 | Chilton Engineering |
| 11 | Northeast Cooling Inc. |
| 12 | Superior Auto Body |
| 13 | AAA Insurance Inc. |
+---------+------------------------+
4 rows in set (0.00 sec)

O número de linhas do conjunto-resultado agora é determinado pelo


número de linhas da tabela business, o que explica o fato de o conjunto-
resultado ter apenas quatro linhas.
Tenha em mente que ambas as consultas estão realizando junções
externas: as palavras-chave left (esquerda) e right (direita) estão ali
apenas para dizer ao servidor qual tabela tem permissão para deixar
lacunas nos dados. Se você quiser juntar externamente as tabelas A e B e
quiser todas as linhas de A com colunas adicionais de B sempre que
houver dados correspondentes, você pode especi car A left outer join B
ou B right outer join A.

Junções externas de três tabelas


Em alguns casos, você pode querer juntar externamente uma tabela com
duas outras tabelas. Por exemplo, você pode querer uma lista de todas as
contas mostrando o nome e o sobrenome no caso de clientes pessoa
física, ou o nome da empresa no caso de clientes corporativos, como em:
Os resultados incluem todas as 24 linhas da tabela account, junto com o
nome da pessoa ou o nome da empresa vindo das duas outras tabelas
juntadas externamente.
Não conheço nenhuma restrição do MySQL com relação ao número de
tabelas que podem ser juntadas externamente em uma mesma tabela,
mas você sempre pode usar subconsultas para limitar o número de
junções em sua consulta. Por exemplo, você poderia rescrever o exemplo
anterior da seguinte maneira:
mysql> SELECT account_ind.account_id, account_ind.product_cd,
-> account_ind.person_name,
-> b.name business_name
-> FROM
-> (SELECT a.account_id, a.product_cd, a.cust_id,
-> CONCAT(i.fname, ' ', i.lname) person_name
-> FROM account a LEFT OUTER JOIN individual i
-> ON a.cust_id = i.cust_id) account_ind
-> LEFT OUTER JOIN business b
-> ON account_ind.cust_id = b.cust_id;
+------------+------------+-----------------+---------------------
---+
| account_id | product_cd | person_name | business_name |
+------------+------------+-----------------+---------------------
---+
| 1 | CHK | James Hadley | NULL |
| 2 | SAV | James Hadley | NULL |
| 3 | CD | James Hadley | NULL |
| 4 | CHK | Susan Tingley | NULL |
| 5 | SAV | Susan Tingley | NULL |
| 7 | CHK | Frank Tucker | NULL |
| 8 | MM | Frank Tucker | NULL |
| 10 | CHK | John Hayward | NULL |
| 11 | SAV | John Hayward | NULL |
| 12 | MM | John Hayward | NULL |
| 13 | CHK | Charles Frasier | NULL |
| 14 | CHK | John Spencer | NULL |
| 15 | CD | John Spencer | NULL |
| 17 | CD | Margaret Young | NULL |
| 18 | CHK | George Blake | NULL |
| 19 | SAV | George Blake | NULL |
| 21 | CHK | Richard Farley | NULL |
| 22 | MM | Richard Farley | NULL |
| 23 | CD | Richard Farley | NULL |
| 24 | CHK | NULL | Chilton Engineering |
| 25 | BUS | NULL | Chilton Engineering |
| 27 | BUS | NULL | Northeast Cooling Inc. |
| 28 | CHK | NULL | Superior Auto Body |
| 29 | SBL | NULL | AAA Insurance Inc. |
+------------+------------+-----------------+---------------------
---+
24 rows in set (0.08 sec)

Nessa versão da consulta, a tabela individual é juntada externamente à


tabela account dentro de uma subconsulta nomeada account_ind, cujos
resultados são, então, juntados externamente à tabela business. Portanto,
cada consulta (a subconsulta e a consulta-contêiner) usa apenas uma
única junção externa. Se estiver usando um banco de dados que não seja
o MySQL, você pode precisar utilizar essa estratégia caso queira juntar
externamente mais de duas tabelas.

Autojunções externas
No capítulo 5, apresentei o conceito de autojunção, em que uma tabela é
da a si mesma. Aqui está um exemplo de autojunção do capítulo 5, que
junta a tabela employee a si mesma para gerar uma lista de funcionários e
seus supervisores:

Essa consulta funciona bem, exceto por um pequeno detalhe:


funcionários que não tenham um supervisor são descartados do
conjunto-resultado. No entanto, mudando a junção interna para uma
junção externa, o conjunto-resultado incluirá todos os funcionários,
inclusive aqueles sem supervisores:
O conjunto-resultado agora inclui Michael Smith, que é o presidente do
banco e que, portanto, não tem um supervisor. A consulta utiliza uma
junção externa à esquerda para gerar uma lista de todos os funcionários e,
se aplicável, seu supervisor. Se mudar a junção para uma junção externa à
direita, você verá os seguintes resultados:
Essa consulta mostra cada supervisor (ainda a terceira e a quarta colunas)
junto com o conjunto de funcionários que ele (ou ela) supervisiona.
Portanto, Michael Smith aparece duas vezes, como supervisor de Susan
Barker e Robert Tyler; Susan Barker aparece uma vez, como supervisora
de ninguém (valores null na primeira e na segunda colunas). Todos os 18
funcionários aparecem pelo menos uma vez na terceira e quarta colunas,
com alguns deles aparecendo mais de uma vez, caso supervisionem mais
de um funcionário, formando um total de 28 linhas no conjunto-
resultado. Esse resultado é diferente da consulta anterior, e isso aconteceu
mudando apenas uma palavra-chave (de left para right). Portanto, ao
usar junções externas, certi que-se de que especi cou corretamente o uso
de junções externas à esquerda ou à direita.

Junções cruzadas
No capítulo 5, apresentei o conceito de produto cartesiano, que é
essencialmente o resultado da junção de múltiplas tabelas sem especi car
quaisquer condições de junção. Produtos cartesianos são usados muito
frequentemente por acidente (por exemplo, esquecendo de adicionar a
condição de união à cláusula from) e, fora isso, não são muito comuns. Se,
no entanto, você realmente pretende gerar um produto cartesiano de duas
tabelas, é necessário especi car uma junção cruzada, como em:

Essa consulta gera o produto cartesiano das tabelas product e


product_type, resultando em 24 linhas (8 linhas da tabela product x 3
linhas da tabela product_type). Mas agora que você sabe o que é uma
junção cruzada e como especi cá-la, para que ela serve? A maioria dos
livros de SQL descreverá o que é uma junção cruzada e, então, mostrará
que ela é raramente usada, mas eu gostaria de compartilhar com você
uma situação na qual creio que a junção cruzada seja bem útil.
No capítulo 9, discuti sobre como usar subconsultas para produzir
tabelas. O exemplo que usei mostrava como construir uma tabela de três
linhas que poderia ser juntada a outras tabelas. Aqui está a tabela
fabricada daquele exemplo:
mysql> SELECT 'Small Fry' name, 0 low_limit, 4999.99 high_limit
-> UNION ALL
-> SELECT 'Average Joes' name, 5000 low_limit, 9999.99
high_limit
-> UNION ALL
-> SELECT 'Heavy Hitters' name, 10000 low_limit, 9999999.99
high_limit;
+---------------+-----------+------------+
| name | low_limit | high_limit |
+---------------+-----------+------------+
| Small Fry | 0 | 4999.99 |
| Average Joes | 5000 | 9999.99 |
| Heavy Hitters | 10000 | 9999999.99 |
+---------------+-----------+------------+
3 rows in set (0.00 sec)

Apesar de essa tabela ter sido exatamente o necessário para distribuir os


clientes em três grupos baseados em seus saldos agregados de conta, essa
estratégia de mesclar tabelas de linha única usando o operador de
conjunto union all não funciona muito bem caso você precise fabricar
tabelas grandes.
Digamos, por exemplo, que você queira criar uma consulta que gere uma
linha para cada dia do ano de 2008, mas você não tem uma tabela em seu
banco de dados que contenha uma linha para cada dia. Usando a
estratégia do exemplo no capítulo 9, você poderia fazer algo parecido com
o seguinte:
SELECT '2008-01-01' dt
UNION ALL
SELECT '2008-01-02' dt
UNION ALL
SELECT '2008-01-03' dt
UNION ALL
...
...
...
SELECT '2008-12-29' dt
UNION ALL
SELECT '2008-12-30' dt
UNION ALL
SELECT '2008-12-31' dt

Construir uma consulta que mescle os resultados de 366 consultas é um


tanto cansativo, então talvez seja necessária uma estratégia diferente. E se
você gerasse uma tabela com 366 linhas (2008 foi um ano bissexto), uma
única coluna contendo um número entre 0 e 366 e, então, adicionasse
aquele número de dias a 1o de janeiro de 2008? Con ra um dos possíveis
métodos de geração dessa tabela:
mysql> SELECT ones.num + tens.num + hundreds.num
-> FROM
-> (SELECT 0 num UNION ALL
-> SELECT 1 num UNION ALL
-> SELECT 2 num UNION ALL
-> SELECT 3 num UNION ALL
-> SELECT 4 num UNION ALL
-> SELECT 5 num UNION ALL
-> SELECT 6 num UNION ALL
-> SELECT 7 num UNION ALL
-> SELECT 8 num UNION ALL
-> SELECT 9 num) ones
-> CROSS JOIN
-> (SELECT 0 num UNION ALL
-> SELECT 10 num UNION ALL
-> SELECT 20 num UNION ALL
-> SELECT 30 num UNION ALL
-> SELECT 40 num UNION ALL
-> SELECT 50 num UNION ALL
-> SELECT 60 num UNION ALL
-> SELECT 70 num UNION ALL
-> SELECT 80 num UNION ALL
-> SELECT 90 num) tens
-> CROSS JOIN
-> (SELECT 0 num UNION ALL
-> SELECT 100 num UNION ALL
-> SELECT 200 num UNION ALL
-> SELECT 300 num) hundreds;
+------------------------------------+
| ones.num + tens.num + hundreds.num |
+------------------------------------+
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
| 11 |
| 12 |
...
...
...
| 391 |
| 392 |
| 393 |
| 394 |
| 395 |
| 396 |
| 397 |
| 398 |
| 399 |
+------------------------------------+
400 rows in set (0.00 sec)

Se você pegar o produto cartesiano dos três conjuntos {0, 1, 2, 3, 4, 5, 6, 7,


8, 9}, {0, 10, 20, 30, 40, 50, 60, 70, 80, 90} e {0, 100, 200, 300} e adicionar
os valores das três colunas, você obtém um conjunto-resultado de 400
linhas contendo todos os números entre 0 e 399. Apesar de isso ser mais
do que as 366 linhas necessárias para gerar o conjunto de dias de 2008, é
muito fácil se livrar das linhas excessivas, e mostrarei a você como fazê-lo
em breve.
O próximo passo é converter o conjunto de números em um conjunto de
datas. Para fazê-lo, usarei a função date_add() para adicionar cada um
dos números do conjunto-resultado a 1o de janeiro de 2008. Então,
adicionarei uma condição de ltro para descartar quaisquer datas que
adentrem 2009:
mysql> SELECT DATE_ADD('2008-01-01',
-> INTERVAL (ones.num + tens.num + hundreds.num) DAY) dt
-> FROM
-> (SELECT 0 num UNION ALL
-> SELECT 1 num UNION ALL
-> SELECT 2 num UNION ALL
-> SELECT 3 num UNION ALL
-> SELECT 4 num UNION ALL
-> SELECT 5 num UNION ALL
-> SELECT 6 num UNION ALL
-> SELECT 7 num UNION ALL
-> SELECT 8 num UNION ALL
-> SELECT 9 num) ones
-> CROSS JOIN
-> (SELECT 0 num UNION ALL
-> SELECT 10 num UNION ALL
-> SELECT 20 num UNION ALL
-> SELECT 30 num UNION ALL
-> SELECT 40 num UNION ALL
-> SELECT 50 num UNION ALL
-> SELECT 60 num UNION ALL
-> SELECT 70 num UNION ALL
-> SELECT 80 num UNION ALL
-> SELECT 90 num) tens
-> CROSS JOIN
-> (SELECT 0 num UNION ALL
-> SELECT 100 num UNION ALL
-> SELECT 200 num UNION ALL
-> SELECT 300 num) hundreds
-> WHERE DATE_ADD('2008-01-01',
-> INTERVAL (ones.num + tens.num + hundreds.num) DAY) < '2009-
01-01'
-> ORDER BY 1;
+------------+
| dt |
+------------+
| 2008-01-01 |
| 2008-01-02 |
| 2008-01-03 |
| 2008-01-04 |
| 2008-01-05 |
| 2008-01-06 |
| 2008-01-07 |
| 2008-01-08 |
| 2008-01-09 |
| 2008-01-10 |
...
...
...
| 2008-02-20 |
| 2008-02-21 |
| 2008-02-22 |
| 2008-02-23 |
| 2008-02-24 |
| 2008-02-25 |
| 2008-02-26 |
| 2008-02-27 |
| 2008-02-28 |
| 2008-02-29 |
| 2008-03-01 |
...
...
...
| 2008-12-20 |
| 2008-12-21 |
| 2008-12-22 |
| 2008-12-23 |
| 2008-12-24 |
| 2008-12-25 |
| 2008-12-26 |
| 2008-12-27 |
| 2008-12-28 |
| 2008-12-29 |
| 2008-12-30 |
| 2008-12-31 |
+------------+
366 rows in set (0.01 sec)

A parte legal dessa abordagem é que o conjunto-resultado


automaticamente inclui o dia bissexto extra (29 de fevereiro) sem a sua
intervenção, já que o servidor de banco de dados percebe esse fato ao
adicionar 59 dias a 1o de janeiro de 2008.
Agora que você tem um mecanismo para fabricar todos os dias de 2008, o
que você faria com isso? Bem, podem pedir a você que gere uma consulta
mostrando cada dia de 2008 junto com o número de transações bancárias
conduzidas naquele dia, o número de contas abertas naquele dia e assim
por diante. Veja um exemplo que responde à primeira questão:
mysql> SELECT days.dt, COUNT(t.txn_id)
-> FROM transaction t RIGHT OUTER JOIN
-> (SELECT DATE_ADD('2008-01-01',
-> INTERVAL (ones.num + tens.num + hundreds.num) DAY) dt
-> FROM
-> (SELECT 0 num UNION ALL
-> SELECT 1 num UNION ALL
-> SELECT 2 num UNION ALL
-> SELECT 3 num UNION ALL
-> SELECT 4 num UNION ALL
-> SELECT 5 num UNION ALL
-> SELECT 6 num UNION ALL
-> SELECT 7 num UNION ALL
-> SELECT 8 num UNION ALL
-> SELECT 9 num) ones
-> CROSS JOIN
-> (SELECT 0 num UNION ALL
-> SELECT 10 num UNION ALL
-> SELECT 20 num UNION ALL
-> SELECT 30 num UNION ALL
-> SELECT 40 num UNION ALL
-> SELECT 50 num UNION ALL
-> SELECT 60 num UNION ALL
-> SELECT 70 num UNION ALL
-> SELECT 80 num UNION ALL
-> SELECT 90 num) tens
-> CROSS JOIN
-> (SELECT 0 num UNION ALL
-> SELECT 100 num UNION ALL
-> SELECT 200 num UNION ALL
-> SELECT 300 num) hundreds
-> WHERE DATE_ADD('2008-01-01',
-> INTERVAL (ones.num + tens.num + hundreds.num) DAY) <
-> '2009-01-01') days
-> ON days.dt = t.txn_date
-> GROUP BY days.dt
-> ORDER BY 1;
+------------+-----------------+
| dt | COUNT(t.txn_id) |
+------------+-----------------+
| 2008-01-01 | 0 |
| 2008-01-03 | 0 |
| 2008-01-04 | 0 |
| 2008-01-05 | 21 |
| 2008-01-06 | 0 |
| 2008-01-07 | 0 |
| 2008-01-08 | 0 |
| 2008-01-09 | 0 |
| 2008-01-10 | 0 |
| 2008-01-11 | 0 |
| 2008-01-12 | 0 |
| 2008-01-13 | 0 |
| 2008-01-14 | 0 |
| 2008-01-15 | 0 |
...
| 2008-12-31 | 0 |
+------------+-----------------+
366 rows in set (0.03 sec)

Essa é uma das consultas mais interessantes feitas até agora neste livro,
pois inclui junções cruzadas, junções externas, uma função de data,
agrupamentos, operações de conjunto (union all) e uma função de
agregação (count()). Ela também não é uma das soluções mais elegantes
para o problema dado, mas serve de exemplo para mostrar como, com um
pouco de criatividade e um conhecimento sólido da linguagem, você pode
tornar uma funcionalidade pouco utilizada, como as junções cruzadas,
em uma ferramenta poderosa de seu kit de ferramentas SQL.

Junções naturais
Se você for preguiçoso (e não somos todos?), você pode escolher um tipo
de junção que permita nomear as tabelas a serem juntadas, mas que deixe
a cabo do servidor determinar quais serão as condições de junção.
Conhecida como junção natural, esse tipo de junção depende da
existência de nomes de coluna idênticos ao longo de múltiplas tabelas
para inferir as condições de junção apropriadas. Por exemplo, a tabela
account inclui uma coluna nomeada cust_id, que é a chave estrangeira da
tabela customer, cuja chave primária também é nomeada cust_id. Então,
você pode escrever uma consulta que use junções naturais para juntar as
duas tabelas:
Como você especi cou uma junção natural, o servidor inspecionou as
de nições das tabelas e adicionou a condição de junção a.cust_id =
c.cust_id para juntar as duas tabelas.

Tudo está indo muito bem, mas e se as colunas não tiverem o mesmo
nome ao longo das tabelas? Por exemplo, a tabela account também tem
uma chave estrangeira da tabela branch, mas a coluna na tabela account é
nomeada open_branch_id em vez de apenas branch_id. Vejamos o que
acontece se eu usar natural join entre as tabelas account e branch:
Parece que algo deu errado: a consulta deveria retornar não mais do que
24 linhas, já que existem 24 linhas na tabela account. O que aconteceu foi
que, como o servidor não pôde encontrar duas colunas nomeadas de
forma idêntica nas duas tabelas, nenhuma condição de união foi gerada e,
com isso, foi realizada uma junção cruzada entre elas, resultando em 96
linhas (24 contas x 4 liais).
Então, no nal das contas, poupar os pobres dedos cansados do esforço
de digitar a condição de junção valeu a pena? Absolutamente não: você
deve evitar esse tipo de junção e, em seu lugar, usar junções internas com
condições explícitas de junção.

Teste seu conhecimento


Os seguintes exercícios testam seu conhecimento de junções externas e
cruzadas. Por favor, consulte o apêndice C para veri car as soluções.

Exercício 10.1
Escreva uma consulta que retorne todos os nomes de produtos junto com
as contas baseadas naquele produto (use a coluna product_cd da tabela
account para vinculá-la à tabela product). Inclua todos os produtos,
mesmo que nenhuma conta tenha sido aberta para aquele produto.

Exercício 10.2
Reformule a sua consulta do exercício 10.1 para usar o outro tipo de
junção externa (por exemplo, se você usou uma junção externa à esquerda
no exercício 10.1, use uma junção externa à direita desta vez), de forma
que os resultados sejam idênticos aos do exercício 10.1.

Exercício 10.3
Junte externamente a tabela account às tabelas individual e business (por
meio da coluna account.cust_id) de tal forma que o conjunto-resultado
contenha uma linha por conta. As colunas a serem incluídas são
account.account_id, account.product_cd, individual.fname,
individual.lname e business.name.
Exercício 10.4 (Crédito extra)
Invente uma consulta que gere o conjunto {1, 2, 3,..., 99, 100}. (Dica: use
uma junção cruzada com pelo menos duas subconsultas na cláusula
from).
CAPÍTULO 11
Lógica condicional

Em certas situações, você pode querer que a sua lógica SQL siga em uma
direção ou outra, dependendo dos valores de certas colunas ou
expressões. Este capítulo focaliza em como escrever instruções que podem
se comportar diferentemente dependendo dos dados encontrados durante
a execução da instrução.

O que é lógica condicional?


Lógica condicional é simplesmente a habilidade de seguir um entre vários
caminhos durante a execução do programa. Por exemplo, ao consultar
informações de clientes, você pode desejar recuperar as colunas
fname/lname da tabela individual ou a coluna name da tabela business,
dependendo de qual tipo de cliente é encontrado. Usando junções
externas, você poderia retornar ambas as strings e deixar que o usuário
descubra qual usar, como em:
O usuário pode observar o valor da coluna cust_type_cd e decidir se deve
usar a coluna indiv_name ou a coluna business_name. No entanto, em vez
disso, você poderia usar a lógica condicional por meio de uma expressão
case para determinar o tipo de cliente e retornar a string apropriada,
como em:
Essa versão da consulta retorna uma única coluna de nome que é gerada
pela expressão case iniciando na segunda linha da consulta, que, nesse
exemplo, veri ca o valor da coluna cust_type_cd e retorna o primeiro e o
último nome da pessoa ou o nome da empresa.

Expressão case
Todos os principais servidores de banco de dados incluem funções nativas
projetadas para imitar a instrução if-then-else encontrada na maioria
das linguagens de programação (exemplos incluem a função decode() do
Oracle, a função if() do MySQL e a função coalesce() do SQL Server).
Expressões case também são projetadas para facilitar a lógica if-then-
else, mas gozam de duas vantagens sobre as funções nativas:
• A expressão case é parte do padrão SQL (versão SQL92) e foi
implementada pelo Oracle Database, MySQL, Sybase, PostgreSQL,
IBM UDB e por outros.
• Expressões case fazem parte da sintaxe SQL e podem ser incluídas em
instruções select, insert, update e delete.
As próximas duas subseções apresentam os dois diferentes tipos de
expressões case e, no nal, eu mostro alguns exemplos de expressões case
em ação.

Expressões case pesquisadas


A expressão case demonstrada anteriormente é um exemplo de uma
expressão case pesquisada, que tem a seguinte sintaxe:
CASE
WHEN C1 THEN E1
WHEN C2 THEN E2
...
WHEN CN THEN EN
[ELSE EP]
END

Na de nição anterior, os símbolos C1, C2, ..., CN representam condições, e


os símbolos E1, E2, ..., EN representam expressões que serão retornadas
pela expressão case. Se a condição de uma cláusula when retorna true, a
expressão case retorna a expressão correspondente. Além disso, o símbolo
EP representa a expressão padrão, retornada pela expressão case se
nenhuma das condições C1, C2, ..., CN retornar true (a cláusula else é
opcional, por isso está entre colchetes). Todas as expressões retornadas
pelas várias cláusulas when devem retornar o mesmo tipo de dados (por
exemplo, date, number, varchar).
Con ra um exemplo de uma expressão case pesquisada:
CASE
WHEN employee.title = 'Head Teller'
THEN 'Head Teller'
WHEN employee.title = 'Teller'
AND YEAR(employee.start_date) > 2007
THEN 'Teller Trainee'
WHEN employee.title = 'Teller'
AND YEAR(employee.start_date) < 2006
THEN 'Experienced Teller'
WHEN employee.title = 'Teller'
THEN 'Teller'
ELSE 'Non-Teller'
END

Essa expressão case retorna uma string que pode ser usada para
determinar escalas de valor-hora, crachás com nomes impressos e assim
por diante. Quando a expressão case é avaliada, as cláusulas when são
avaliadas em ordem, de cima para baixo. Assim que uma das condições
em uma cláusula when é avaliada como true, a expressão correspondente é
retornada e quaisquer cláusulas when restantes são ignoradas. Se nenhuma
das condições das cláusulas when for avaliada como true, a expressão na
cláusula else é retornada.
Apesar de os exemplos anteriores retornarem expressões string, tenha em
mente que expressões case podem retornar qualquer tipo de expressão,
incluindo subconsultas. Veja outra versão da consulta de nomes de
pessoas/empresas realizada anteriormente no capítulo que usa
subconsultas em vez de junções externas para recuperar dados das tabelas
individual e business:
Essa versão da consulta inclui apenas a tabela customer na cláusula from e
usa subconsultas correlatas para recuperar o nome apropriado para cada
cliente. Pre ro essa versão em vez da versão de junção externa usada
anteriormente no capítulo, pois o servidor acessa as tabelas individual e
business apenas quando necessário, em vez de sempre juntar as três
tabelas.

Expressões case simples


A expressão case simples é muito semelhante à expressão case pesquisada,
mas é um pouco menos exível. Veja a sintaxe:
CASE V0
WHEN V1 THEN E1
WHEN V2 THEN E2
...
WHEN VN THEN EN
[ELSE EP]
END

Na de nição anterior, V0 representa uma valor, e os símbolos V1, V2, ..., VN


representam valores que serão comparados com V0. Os símbolos E1, E2, ...,
EN representam expressões retornadas pela expressão case, e EP representa
a expressão a ser retornada caso nenhum dos valores do conjunto V1, V2,
..., VN corresponda ao valor V0.
Con ra um exemplo de uma expressão case simples:
CASE customer.cust_type_cd
WHEN 'I' THEN
(SELECT CONCAT(i.fname, ' ', i.lname)
FROM individual I
WHERE i.cust_id = customer.cust_id)
WHEN 'B' THEN
(SELECT b.name
FROM business b
WHERE b.cust_id = customer.cust_id)
ELSE 'Unknown Customer Type'
END

Expressões case simples são menos poderosas que expressões case


pesquisadas porque você não pode especi car suas próprias condições:
em vez disso, condições de igualdade são construídas para você. Para
entender o que quero dizer, observe uma expressão case pesquisada que
tem a mesma lógica da expressão case simples anterior:
CASE
WHEN customer.cust_type_cd = 'I' THEN
(SELECT CONCAT(i.fname, ' ', i.lname)
FROM individual I
WHERE i.cust_id = customer.cust_id)
WHEN customer.cust_type_cd = 'B' THEN
(SELECT b.name
FROM business b
WHERE b.cust_id = customer.cust_id)
ELSE 'Unknown Customer Type'
END

Com expressões case pesquisadas, você pode construir condições de


intervalo, condições de desigualdade e condições multipartes que usam
and/or/not, razão pela qual recomendo o uso de expressões case
pesquisadas para tudo, exceto a mais simples das lógicas.

Exemplos de expressões case


As seções a seguir apresentam uma variedade de exemplos que ilustram a
utilidade da lógica condicional em instruções case.

Transformações de conjuntos-resultados
Já deve ter ocorrido com você uma situação em que você está realizando
agregações em um conjunto nito de valores, como os dias da semana,
mas deseja que o conjunto-resultado contenha uma única linha com uma
coluna por valor em vez de uma linha por valor. Como exemplo, digamos
que você deva escrever uma consulta que mostre o número de contas
abertas entre os anos de 2000 e 2005:
mysql> SELECT YEAR(open_date) year, COUNT(*) how_many
-> FROM account
-> WHERE open_date > '1999-12-31'
-> AND open_date < '2006-01-01'
-> GROUP BY YEAR(open_date);
+------+----------+
| year | how_many |
+------+----------+
| 2000 | 3 |
| 2001 | 4 |
| 2002 | 5 |
| 2003 | 3 |
| 2004 | 9 |
+------+----------+
5 rows in set (0.00 sec)

No entanto, você foi instruído a retornar uma única linha de dados com
seis colunas (uma para cada ano do intervalo solicitado). Para transformar
esse conjunto-resultado em uma única linha, você precisará criar seis
colunas e, dentro de cada coluna, somar apenas as linhas pertencentes ao
ano em questão:
As seis colunas da consulta anterior são idênticas, exceto pelo valor do
ano. Quando a função extract() retorna o ano desejado para aquela
coluna, a expressão case retorna o valor 1: caso contrário, ela retorna 0.
Ao somar todas as contas abertas desde 2000, cada coluna retorna o
número de contas abertas para aquele ano. Obviamente, tais
transformações são práticas apenas quando se tem uma quantidade
pequena de valores: gerar uma coluna para cada ano desde 1905 logo se
transformaria em uma tarefa cansativa.
Apesar de ser um pouco avançado para este livro, vale notar
que tanto o SQL Server quanto o Oracle Database 11g incluem
cláusulas PIVOT especi camente para esses tipos de consultas.

Agregação seletiva
No capítulo 9, mostrei uma solução parcial para um exemplo que
demonstrava como encontrar contas cujos saldos não correspondessem
aos dados brutos da tabela transaction. A razão da solução parcial era
que uma solução completa necessitaria do uso de lógica condicional, mas
agora temos todas as peças no lugar para terminar o serviço. Parei neste
ponto no capítulo 9:
SELECT CONCAT('ALERT! : Account #', a.account_id,
' Has Incorrect Balance!')
FROM account a
WHERE (a.avail_balance, a.pending_balance) <>
(SELECT SUM(<expressão para gerar o saldo disponível>),
SUM(<expressão para gerar o saldo pendente>)
FROM transaction t
WHERE t.account_id = a.account_id);

A consulta usa uma subconsulta correlata na tabela transaction para


somar as transações individuais de uma determinada conta. Ao somar as
transações, você deve considerar as seguintes questões:
• As quantidades das transações são sempre positivas, então você
deverrá observar o tipo de transação para ver se ela é um débito ou um
crédito e inverter o sinal (multiplicar por -1) no caso de transações de
débito.
• Se a data da coluna funds_avail_date for maior do que o dia atual, a
transação deve ser adicionada ao saldo total pendente, mas não ao
saldo total disponível.
Enquanto algumas transações precisam ser excluídas do saldo disponível,
todas as transações são incluídas no saldo pendente, tornando-o o mais
simples dos dois cálculos. Veja a expressão case usada para calcular o
saldo pendente:
CASE
WHEN transaction.txn_type_cd = 'DBT'
THEN transaction.amount * -1
ELSE transaction.amount
END

Assim, todas as quantias das transações são multiplicadas por -1 no caso


de transações de débito e cam como estão no caso de transações de
crédito. Essa mesma lógica também se aplica ao cálculo de saldo
disponível, mas apenas as transações que já se tornaram disponíveis
devem ser incluídas. Portanto, a expressão case usada para calcular o
saldo disponível inclui mais uma cláusula when:
CASE
WHEN transaction.funds_avail_date > CURRENT_TIMESTAMP()
THEN 0
WHEN transaction.txn_type_cd = 'DBT'
THEN transaction.amount * -1
ELSE transaction.amount
END

Com a primeira cláusula when a postos, fundos indisponíveis, como os


cheques que não foram compensados, contribuirão com $0 à soma.
Con ra a consulta nal com as duas expressões case devidamente
posicionadas:
SELECT CONCAT('ALERT! : Account #', a.account_id,
' Has Incorrect Balance!')
FROM account a
WHERE (a.avail_balance, a.pending_balance) <>
(SELECT
SUM(CASE
WHEN t.funds_avail_date > CURRENT_TIMESTAMP()
THEN 0
WHEN t.txn_type_cd = 'DBT'
THEN t.amount * -1
ELSE t.amount
END),
SUM(CASE
WHEN t.txn_type_cd = 'DBT'
THEN t.amount * -1
ELSE t.amount
END)
FROM transaction t
WHERE t.account_id = a.account_id);

Usando lógica condicional, as funções de agregação sum() são


alimentadas com dados manipulados pelas duas expressões case,
permitindo que as quantias apropriadas sejam somadas.

Veri cando a existência


Algumas vezes será desejável determinar se existe um relacionamento
entre duas entidades sem se importar com quantidade. Por exemplo, você
pode querer saber se um cliente tem alguma conta corrente ou conta
poupança, mas não é relevante saber se um cliente tem mais de uma
conta de cada tipo. Veja uma consulta que usa múltiplas expressões case
para gerar duas colunas de saída, uma para mostrar se o cliente tem
alguma conta corrente e outra para mostrar se o cliente tem alguma conta
poupança:
Cada expressão case inclui uma subconsulta correlata à tabela account:
uma pesquisa por contas correntes, a outra por contas poupança. Como
todas as cláusulas when usam o operador exists, as condições são
avaliadas como true sempre que o cliente possuir pelo menos uma das
contas pesquisadas.
Em outros casos, você pode querer saber quantas linhas foram
encontradas, mas só até um certo ponto. Por exemplo, a próxima consulta
usa uma expressão case simples para contar o número de contas de cada
cliente e, então, retornar 'None', '1', '2' ou '3+':

Para essa consulta, eu não quis diferenciar os clientes que tenham mais de
duas contas, então a expressão case simplesmente cria uma categoria '3+'.
Esse tipo de consulta pode ser útil se você estiver procurando clientes que
devem ser contatados a respeito de abertura de uma nova conta no banco.

Erros de divisão por zero


Ao realizar cálculos que incluam divisão, você deve sempre tomar o
cuidado de veri car que os denominadores não sejam iguais a zero.
Apesar de alguns servidores de banco de dados, como o Oracle Database,
lançarem um erro quando um denominador zero é encontrado, o MySQL
simplesmente con gura o resultado do cálculo para null, como
demonstrado a seguir:
mysql> SELECT 100 / 0;
+---------+
| 100 / 0 |
+---------+
| NULL |
+---------+
1 row in set (0.00 sec)

Para evitar que seus cálculos encontrem erros ou, ainda pior, que sejam
misteriosamente con gurados como null, você deve englobar todos os
denominadores dentro de uma lógica condicional, como mostrado a
seguir:
mysql> SELECT a.cust_id, a.product_cd, a.avail_balance /
-> CASE
-> WHEN prod_tots.tot_balance = 0 THEN 1
-> ELSE prod_tots.tot_balance
-> END percent_of_total
-> FROM account a INNER JOIN
-> (SELECT a.product_cd, SUM(a.avail_balance) tot_balance
-> FROM account a
-> GROUP BY a.product_cd) prod_tots
-> ON a.product_cd = prod_tots.product_cd;
+---------+------------+------------------+
| cust_id | product_cd | percent_of_total |
+---------+------------+------------------+
| 10 | BUS | 0.000000 |
| 11 | BUS | 1.000000 |
| 1 | CD | 0.153846 |
| 6 | CD | 0.512821 |
| 7 | CD | 0.256410 |
| 9 | CD | 0.076923 |
| 1 | CHK | 0.014488 |
| 2 | CHK | 0.030928 |
| 3 | CHK | 0.014488 |
| 4 | CHK | 0.007316 |
| 5 | CHK | 0.030654 |
| 6 | CHK | 0.001676 |
| 8 | CHK | 0.047764 |
| 9 | CHK | 0.001721 |
| 10 | CHK | 0.322911 |
| 12 | CHK | 0.528052 |
| 3 | MM | 0.129802 |
| 4 | MM | 0.321915 |
| 9 | MM | 0.548282 |
| 1 | SAV | 0.269431 |
| 2 | SAV | 0.107773 |
| 4 | SAV | 0.413723 |
| 8 | SAV | 0.209073 |
| 13 | SBL | 1.000000 |
+---------+------------+------------------+
24 rows in set (0.13 sec)

Essa consulta calcula a proporção entre o saldo de cada conta e o saldo


total de todas as contas do mesmo tipo de produto. Já que alguns tipos de
produtos, como os empréstimos para empresas, poderiam ter um saldo de
zero no caso de todos os empréstimos terem sido completamente pagos, é
melhor incluir a expressão case para garantir que o denominador nunca
seja zero.

Atualizações condicionais
Ao atualizar linhas em uma tabela, às vezes você precisa decidir quais
valores serão alocados em quais colunas. Por exemplo, depois de inserir
uma nova transação, você precisa modi car as colunas avail_balance,
pending_balance e last_activity da tabela account. Apesar de as duas
últimas colunas serem facilmente atualizáveis, para modi car
corretamente a coluna avail_balance você precisa saber se os fundos da
transação estão imediatamente disponíveis veri cando a coluna
funds_avail_date da tabela transaction. Supondo que a transação de ID
999 tenha acabado de ser inserida, você pode usar a seguinte instrução
update para modi car as três colunas da tabela account:
1 UPDATE account
2 SET last_activity_date = CURRENT_TIMESTAMP(),
3 pending_balance = pending_balance +
4 (SELECT t.amount *
5 CASE t.txn_type_cd WHEN 'DBT' THEN -1 ELSE 1 END
6 FROM transaction t
7 WHERE t.txn_id = 999),
8 avail_balance = avail_balance +
9 (SELECT
10 CASE
11 WHEN t.funds_avail_date > CURRENT_TIMESTAMP() THEN 0
12 ELSE t.amount *
13 CASE t.txn_type_cd WHEN 'DBT' THEN -1 ELSE 1 END
14 END
15 FROM transaction t
16 WHERE t.txn_id = 999)
17 WHERE account.account_id =
18 (SELECT t.account_id
19 FROM transaction t
20 WHERE t.txn_id = 999);

Essa instrução contém um total de três expressões case: duas delas (linhas
5 e 13) são usadas para inverter o sinal do total da transação em
transações de débito, e a terceira expressão case (linha 10) é usada para
veri car a data de disponibilidade dos fundos. Se a data estiver no futuro,
um zero é adicionado ao saldo disponível: caso contrário, a quantia da
transação é adicionada.

Tratando valores null


Apesar de valores null serem a coisa certa a ser armazenada em uma
tabela se o valor de uma coluna for desconhecido, nem sempre é
apropriado recuperar valores null para ns de exibição ou para tomar
parte em expressões. Por exemplo, você pode querer exibir a palavra
unknown (desconhecido) em um campo de dados na tela em vez de deixar
o campo em branco. Ao recuperar os dados, você pode usar uma
expressão case para substituir a string se o valor for null, como em:
SELECT emp_id, fname, lname,
CASE
WHEN title IS NULL THEN 'Unknown'
ELSE title
END
FROM employee;

Em cálculos, valores null costumam provocar um retorno null, como


demonstrado a seguir:
mysql> SELECT (7 * 5) / ((3 + 14) * null);
+-----------------------------+
| (7 * 5) / ((3 + 14) * null) |
+-----------------------------+
| NULL |
+-----------------------------+
1 row in set (0.08 sec)

Ao realizar cálculos, expressões case são úteis para converter um valor


null em um número (geralmente 0 ou 1) que permitirá ao cálculo
retornar um valor não-nulo. Se estiver realizando um cálculo que inclua a
coluna account.avail_balance, por exemplo, você poderia substituir o
valor null por 0 (se estiver somando ou subtraindo) ou por 1 (se estiver
multiplicando ou dividindo) no caso das contas que foram estabelecidas,
mas que ainda não foram movimentadas:
SELECT <some calculation> +
CASE
WHEN avail_balance IS NULL THEN 0
ELSE avail_balance
END
+ <rest of calculation>
...

Se uma coluna numérica permitir valores null, geralmente é uma boa


ideia usar lógica condicional em quaisquer cálculos que incluam a
coluna, para que os resultados sejam utilizáveis.

Teste seu conhecimento


Desa e sua habilidade de resolver problemas de lógica condicional com
os exemplos a seguir. Ao terminar, compare suas soluções com as
apresentadas no apêndice C.

Exercício 11.1
Rescreva a seguinte consulta, que usa uma expressão case simples, de
forma que os mesmos resultados sejam alcançados utilizando-se uma
expressão case. Tente usar o menor número possível de cláusulas when.
SELECT emp_id,
CASE title
WHEN 'President' THEN 'Management'
WHEN 'Vice President' THEN 'Management'
WHEN 'Treasurer' THEN 'Management'
WHEN 'Loan Manager' THEN 'Management'
WHEN 'Operations Manager' THEN 'Operations'
WHEN 'Head Teller' THEN 'Operations'
WHEN 'Teller' THEN 'Operations'
ELSE 'Unknown'
END
FROM employee;

Exercício 11.2
Reescreva a consulta a seguir de forma que o conjunto-resultado contenha
uma única linha com quatro colunas (uma para cada lial). Nomeie as
quatro colunas de branch_1 a branch_4.
mysql> SELECT open_branch_id, COUNT(*)
-> FROM account
-> GROUP BY open_branch_id;
+----------------+----------+
| open_branch_id | COUNT(*) |
+----------------+----------+
| 1 | 8 |
| 2 | 7 |
| 3 | 3 |
| 4 | 6 |
+----------------+----------+
4 rows in set (0.00 sec)
CAPÍTULO 12
Transações

Todos os exemplos mostrados até agora neste livro foram instruções SQL
individuais e independentes. Embora isso seja a norma no caso de
relatórios sob demanda ou de scripts de manutenção de dados, a lógica
de aplicação frequentemente incluirá várias instruções SQL que precisam
ser executadas em conjunto como uma unidade de trabalho lógica. Este
capítulo explora as necessidades e a infraestrutura necessária para a
execução de várias instruções SQL de forma concorrente.

Bancos de dados multiusuários


Sistemas de gerenciamento de banco de dados permitem não apenas que
um único usuário consulte e modi que dados, mas também que
múltiplos usuários o façam simultaneamente. Se todos os usuários
estiverem executando consultas, como pode ser o caso de um data
warehouse durante o período normal de trabalho, o servidor terá pouco
para se preocupar. No entanto, se alguns dos usuários estiverem
adicionando e/ou modi cando dados, o servidor terá um pouco mais de
trabalho pela frente.
Digamos, por exemplo, que você está executando um relatório que mostra
o saldo disponível de todas as contas correntes abertas em sua lial. No
entanto, ao mesmo tempo em que você está executando o relatório, as
seguintes atividades estão acontecendo:
• Um caixa em sua lial está realizando um depósito para um de seus
clientes.
• Um cliente está nalizando um saque no caixa eletrônico do salão de
entrada.
• A aplicação de m de mês do banco está aplicando os juros às contas.
Portanto, enquanto seu relatório está em execução, vários usuários estão
modi cando os dados subjacentes – então quais números deverão
aparecer no relatório? A resposta depende, de certa forma, de como o
servidor trata o locking, que é descrito na próxima seção.

Locking
Os bloqueios (locks) são o mecanismo que o servidor de banco de dados
usa para controlar o uso simultâneo de recursos de dados. Quando
alguma parte do banco de dados é bloqueada, qualquer outro usuário
que deseje modi car (ou possivelmente ler) aqueles dados deve esperar
até que o bloqueio seja liberado. A maioria dos servidores de banco de
dados usa uma dessas duas estratégias de locking:
• Usuários que queiram escrever no banco de dados devem requisitar e
receber do servidor um bloqueio de escrita (write lock) para modi car
dados, e os usuários que queiram ler o banco de dados devem
requisitar e receber do servidor um bloqueio de leitura (read lock) para
consultar dados. Enquanto múltiplos usuários podem ler dados
simultaneamente, apenas um bloqueio de escrita é realizado por vez
para cada tabela (ou porções dela), e requisições de leitura são
bloqueadas até que o bloqueio de escrita seja liberado.
• Usuários que queiram escrever no banco de dados devem requisitar e
receber do servidor um bloqueio de escrita (write lock) para modi car
dados, mas os usuários que queiram ler do banco de dados não
precisam de um bloqueio para consultar dados. Em vez disso, o
servidor se garante que um usuário leitor veja uma exibição
consistente dos dados (os dados aparentam estar iguais, mesmo que
outros usuários estejam realizando modi cações) do momento em que
sua consulta se inicia até o término da consulta. Essa abordagem é
conhecida como versioning (algo como “geração de versão”).
Existem prós e contras em ambas as abordagens. A primeira abordagem
pode levar a longas esperas se houver muitas leituras concorrentes e
requisições de escrita, e a segunda abordagem pode ser problemática se
houver consultas longas sendo executadas enquanto os dados são
modi cados. Dos três servidores discutidos neste livro, o Microsoft SQL
Server usa a primeira abordagem, o Oracle Database usa a segunda e o
MySQL usa ambas (dependendo de sua escolha de mecanismo de
armazenamento, que discutiremos mais tarde no capítulo).

Granularidade dos bloqueios


Existem várias estratégias diferentes que você pode empregar ao decidir
como bloquear um recurso. O servidor pode aplicar um bloqueio em três
níveis diferentes, ou granularidades:
Bloqueios de tabela (table locks)
Impedem que múltiplos usuários modi quem dados na mesma tabela
simultaneamente.
Bloqueios de página (page locks)
Impedem que múltiplos usuários modi quem dados na mesma
página (uma página é um segmento de memória, geralmente variando
entre 2 e 16 KB) de uma tabela simultaneamente.
Bloqueios de linha (row locks)
Impedem que múltiplos usuários modi quem a mesma linha de uma
tabela simultaneamente.
Mais uma vez, há prós e contras em cada uma dessas abordagens. Poucos
recursos são gastos para bloquear tabelas inteiras, mas essa abordagem
rapidamente resulta em tempos de espera inaceitáveis conforme o número
de usuários aumenta. Por outro lado, o bloqueio de linhas requer um
pouco mais de recursos, mas permite que muitos usuários modi quem a
mesma tabela, desde que estejam interessados em linhas diferentes. Dos
três servidores discutidos neste livro, o Microsoft SQL Server usa
bloqueios de página, linha e tabela, o Oracle Database usa apenas
bloqueios de linha, e o MySQL usa bloqueios de tabela, página ou linha
(dependendo, novamente, de sua escolha de mecanismo de
armazenamento). O SQL Server fará, sob certas circunstâncias, o
escalonamento de linha para página, e de página para tabela, enquanto o
Oracle Database nunca escalona os bloqueios.
Voltando a seu relatório, os dados que aparecerem em suas páginas
re etirão o estado do banco de dados no momento em que ele foi iniciado
(se o seu servidor usar uma abordagem de versioning) ou o estado do
banco de dados no momento em que o servidor concede um bloqueio de
leitura à aplicação de relatório (se o servidor usar tanto bloqueios de
leitura quanto de escrita).

O que é uma transação?


Se os bancos de dados gozassem de 100% de operacionalidade, se os
usuários sempre permitissem que os programas terminassem de ser
executados e se as aplicações sempre fossem concluídas sem encontrar
erros fatais que interrompessem sua execução, não haveria mais o que
discutir a respeito de acesso concorrente a banco de dados. No entanto,
não podemos contar com essas suposições, então mais um elemento é
necessário para permitir que múltiplos usuários acessem os mesmos
dados.
Essa peça extra do quebra-cabeça da concorrência é a transação, que é um
dispositivo que agrupa várias instruções SQL de forma que todas ou
nenhuma das instruções seja bem-sucedida (uma propriedade conhecida
como atomicidade). Se você tentar transferir U$ 500 de sua conta
poupança para sua conta corrente, você caria um tanto bravo se o
dinheiro fosse sacado com sucesso de sua conta poupança, mas nunca
chegasse à sua conta corrente. Independentemente da razão da falha (o
servidor foi desligado para manutenção, a requisição de um bloqueio de
página expirou etc.), você vai querer seus U$ 500 de volta.
Para proteger o usuário desse tipo de erro, o programa que trataria sua
requisição de transferência primeiro iniciaria uma transação, para em
seguida invocar as instruções SQL necessárias à movimentação do
dinheiro da conta poupança para a conta corrente e, se tudo corresse
bem, nalizaria a transação invocando o comando commit. No entanto, se
algo inesperado acontecesse, o programa invocaria o comando rollback,
que instrui o servidor a desfazer todas as alterações feitas desde o início
da transação. O processo completo se pareceria com o seguinte:
START TRANSACTION;
/* saca dinheiro da primeira conta, certificando-se que o saldo é
suficiente */
UPDATE account SET avail_balance = avail_balance - 500
WHERE account_id = 9988
AND avail_balance > 500;
IF <exatamente uma linha foi atualizada pela instrução anterior>
THEN
/* deposita o dinheiro na segunda conta */
UPDATE account SET avail_balance = avail_balance + 500
WHERE account_id = 9989;
IF <exatamente uma linha foi atualizada pela instrução anterior>
THEN
/* tudo correu bem, torne as modificações permanentes */
COMMIT;
ELSE
/* algo deu errado, desfaça todas as mudanças dessa transação */
ROLLBACK;
END IF;
ELSE
/* fundos insuficientes, ou erro encontrado durante a atualização
*/
ROLLBACK;
END IF;

Apesar de o bloco de código anterior se parecer com algumas


das linguagens procedurais fornecidas pelas principais
fabricantes de bancos de dados, como a PL/SQL da Oracle ou a
Transact-SQL da Microsoft, ele foi escrito em pseudocódigo e
não tenta imitar qualquer linguagem em particular.
O bloco de código anterior começa por iniciar uma transação e, então,
tenta remover U$ 500 da conta poupança e adicionar o valor à conta
corrente. Se tudo correr bem, a transação é cometida (committed). No
entanto, se algo der errado, a transação é retrocedida (rolled back),
signi cando que todas as modi cações nos dados ocorridas desde o
início da transação serão desfeitas.
Usando uma transação, o programa garante que seus U$ 500 carão na
conta poupança ou serão movidos para sua conta corrente, sem a
possibilidade de o dinheiro cair no limbo. Independentemente de a
transação ser executada ou desfeita, todos os recursos adquiridos durante
a execução da transação (por exemplo, bloqueios de escrita) são liberados
quando a transação é concluída.
É claro que, caso o programa consiga completar ambas as instruções
insert, mas o servidor seja desligado antes de um commit ou rollback ser
executado, a transação será desfeita no momento em que o servidor voltar
a car on-line. (Uma das tarefas que um servidor de banco de dados deve
concluir antes de car on-line é encontrar quaisquer transações
incompletas que estavam sendo executadas no momento em que o
servidor caiu e desfazê-las.) Além disso, se o seu programa termina uma
transação e invoca um commit, mas o servidor é desligado antes que as
mudanças sejam aplicadas ao armazenamento permanente (ou seja, os
dados modi cados estão armazenados em memória, mas não foram
descarregados para o disco), o servidor de banco de dados deverá
reaplicar as modi cações de sua transação no momento em que o
servidor é reiniciado (uma propriedade conhecida como durabilidade).

Iniciando uma transação


Os servidores de banco de dados podem lidar com a criação de transações
de duas maneiras diferentes:
• Uma transação ativa é sempre associada a uma sessão de banco de
dados, então não há um método (nem há necessidade) de se iniciar
uma transação explicitamente. Quando a transação atual termina, o
servidor automaticamente inicia uma nova transação para sua sessão.
• A menos que você inicie uma transação explicitamente, instruções
SQL individuais são automaticamente cometidas, independentes umas
das outras. Para iniciar uma transação, você deve primeiro invocar um
comando.
Dos três servidores, o Oracle Database usa a primeira abordagem,
enquanto o Microsoft SQL Server e o MySQL usam a segunda. Uma das
vantagens da abordagem de transações do Oracle é que, mesmo que você
esteja invocando apenas um comando SQL, você consegue desfazer as
mudanças se não gostar do resultado ou se mudar de ideia. Então, se você
esquecer-se de adicionar uma cláusula where à sua instrução delete, você
terá a oportunidade de desfazer os danos (presumindo que você acabou
de tomar seu café matinal e percebeu que não tinha a intenção de apagar
todas as 125.000 linhas de sua tabela). No entanto, com o SQL Server e o
MySQL, uma vez pressionada a tecla Enter, as mudanças ocasionadas por
sua instrução SQL serão permanentes (a menos que seu DBA possa
recuperar os dados originais de um backup ou por qualquer outro meio).
O padrão SQL:2003 inclui uma comando start transaction que deve ser
usado quando você deseja iniciar uma transação explicitamente.
Enquanto o MySQL se conforma ao padrão, usuários do SQL Server
devem, em vez disso, invocar o comando begin transaction. Em ambos os
servidores, enquanto não iniciar uma transação explicitamente, você
estará no que se chama de modo de autocomissão (auto-commit), o que
signi ca que instruções individuais têm um commit realizado
automaticamente pelo servidor. Portanto, você pode decidir se quer
invocar um comando de início de transação ou se simplesmente deixará a
cargo do servidor realizar um commit das instruções individuais.
Tanto o MySQL quanto o SQL Server permitirão desativar o modo de
autocomissão em sessões individuais – nesse caso, os servidores agirão
como o Oracle Database no que diz respeito a transações. No SQL Server,
você deve invocar o seguinte comando para desativar o modo de
autocomissão:
SET IMPLICIT_TRANSACTIONS ON

O MySQL permite desativar o modo de autocomissão da seguinte


maneira:
SET AUTOCOMMIT=0

Uma vez que você tenha saído do modo de autocomissão, todos os


comandos SQL serão realizados dentro do escopo de uma transação e
devem ter um commit realizado ou devem ser desfeitos explicitamente.
Um conselho: desative o modo de autocomissão sempre que
zer o login e habitue-se a executar todas as suas instruções
dentro de uma transação. No mínimo, isso vai evitar a situação
embaraçosa de ter que pedir ao DBA para reconstruir os dados
que você apagou acidentalmente.

Finalizando uma transação


Uma vez que uma transação tenha iniciado, seja explicitamente por meio
do comando start transaction ou de forma implícita pelo servidor de
banco de dados, você deverá explicitamente nalizar a transação para que
as mudanças sejam permanentes. Isso é possível por meio do comando
commit, que instrui o servidor a marcar as mudanças como permanentes e
a liberar quaisquer recursos (ou seja, bloqueios de página ou de linha)
usados durante a transação.
Se decidir que deseja desfazer todas as mudanças realizadas desde o início
da transação, você pode invocar o command rollback, que instrui o
servidor a retornar os dados a seu estado de pré-transação. Após o
término do retrocesso, qualquer recurso usado por sua sessão é liberado.
Além de invocar o comando commit ou rollback, existem vários outros
cenários de término da transação, seja como o resultado indireto de suas
ações ou como o resultado de algo fora de seu controle:
• O servidor é desligado – nesse caso, sua transação será retrocedida
automaticamente quando o servidor for reiniciado.
• Você invoca uma instrução de esquema SQL, como alter table,
causando a comissão da transação atual e o início de uma nova
transação.
• Você invoca outro comando start transaction, provocando a
comissão da transação anterior.
• O servidor termina sua transação de forma prematura porque
detectou um deadlock (impasse) e decidiu que a sua transação é a
responsável por isso. Nesse caso, a transação será retrocedida e você
receberá uma mensagem de erro.
Desses quatro cenários, o primeiro e o terceiro são simples, mas os outros
dois merecem ser discutidos. Em relação ao segundo cenário, alterações
feitas em um banco de dados, seja pela adição de uma nova tabela ou
índice, ou a remoção de uma coluna de uma tabela, não podem ser
retrocedidas, então os comandos que alteram o seu esquema deverão ser
colocados fora de uma transação. Portanto, se uma transação estiver
acontecendo, o servidor realizará um commit da sua transação atual,
executará o(s) comando(s) de instrução de esquema SQL e, então,
automaticamente iniciará uma nova transação para sua sessão. O servidor
não informará a você o que aconteceu, então você deve cuidar para que as
instruções que formam uma unidade de trabalho não sejam divididas,
acidentalmente, em múltiplas transações pelo servidor.
O quarto cenário lida com a detecção de deadlocks. Um deadlock ocorre
quando duas transações diferentes estão esperando por recursos que a
outra transação esteja usando. Por exemplo, a transação A pode ter
acabado de atualizar a tabela account e ca esperando uma trava de
escrita para a tabela transaction, enquanto a transação B acabou de
inserir uma linha na tabela transaction e ca esperando por uma trava de
escrita na tabela account. Se acontecer de ambas as transações estarem
modi cando a mesma página ou linha (dependendo da granularidade de
bloqueio em uso pelo servidor de banco de dados), elas esperarão para
sempre pelo término da outra transação e pela liberação dos recursos
necessários. Servidores de banco de dados devem estar sempre alertas
para esse tipo de situação, para que seu rendimento não despenque:
quando um deadlock é detectado, uma das transações é escolhida
(arbitrariamente ou por meio de algum critério) para ser retrocedida a m
de que a outra transação possa prosseguir. Na maior parte do tempo, a
transação nalizada poderá ser reiniciada e será bem-sucedida, sem
encontrar outra situação de deadlock.
Diferentemente do segundo cenário discutido anteriormente, o servidor
de banco de dados sinalizará um erro para informar que sua transação foi
retrocedida devido à detecção de um deadlock. No MySQL, por exemplo,
você receberá o erro #1213, que apresenta a seguinte mensagem:
Message: Deadlock found when trying to get lock; try restarting
transaction

Como a mensagem de erro sugere, é uma prática razoável reexecutar uma


transação que tenha sido retrocedida devido à detecção de um deadlock.
No entanto, se os deadlocks começarem a se tornar comuns, você
precisará modi car as aplicações que acessam o banco de dados com o
objetivo de diminuir a probabilidade de ocorrer deadlocks (uma
estratégia comum é garantir que os recursos de dados sejam sempre
acessados na mesma ordem, como, por exemplo, sempre modi car os
dados das contas bancárias antes de inserir dados de transação).

Pontos de gravação de transações


Em alguns casos, você pode encontrar um problema em uma transação
que necessitará de um retrocesso, mas você não quer desfazer todo o seu
suado trabalho. Nessas situações, você pode estabelecer um ou mais
pontos de gravação (savepoints) dentro de uma transação e usá-los para
retroceder a um local especí co dentro da transação, em vez de retroceder
até o início dela.

Escolhendo um mecanismo de armazenamento


Ao usar o Oracle Database ou o Microsoft SQL Server, um único
conjunto de código ca responsável pelas operações de banco de
dados de baixo nível, como a recuperação de uma linha particular de
uma tabela baseada no valor da chave primária. No entanto, o MySQL
foi projetado de forma a permitir que múltiplos mecanismos de
armazenamento possam ser utilizados para fornecer funcionalidades
de banco de dados de baixo nível, incluindo o bloqueio de recursos e
o gerenciamento de transações. Na versão 5.1, o MySQL inclui os
seguintes mecanismos de armazenamento:
MyISAM
Um mecanismo não-transacional que emprega bloqueio de tabelas.
MEMORY
Um mecanismo não-transacional usado para alocar tabelas em
memória volátil.
BDB
Um mecanismo transacional que emprega bloqueio de página.
InnoDB
Um mecanismo transacional que emprega bloqueio de linha.
Merge
Um mecanismo especial, usado para fazer com que múltiplas tabelas
MyISAM idênticas aparentem ser uma única tabela (também
conhecido como particionamento de tabela).
Maria
Um substituto do MyISAM incluído na versão 6.0.6 que adiciona
recuperação completa.
Falcon
Um mecanismo transacional novo (incluído na versão 6.0.4) de alto
desempenho que emprega bloqueio de linha.
Archive
Um mecanismo especial, usado para armazenar grandes quantidades
de dados não-indexados, principalmente com propósitos de
arquivamento.
Apesar de você poder ter pensado que seria forçado a escolher um
único mecanismo de armazenamento para seu banco de dados, o
MySQL é exível o su ciente para permitir a seleção de um
mecanismo de armazenamento para cada tabela. No entanto, no caso
de tabelas que farão parte de transações, você deverá escolher o
mecanismo de armazenamento InnoDB ou Falcon, que usa bloqueio
de linha e versioning para fornecer o nível mais alto de concorrência
entre todos os mecanismos de armazenamento.
Você pode especi car explicitamente um mecanismo de
armazenamento ao criar uma tabela ou pode alterar uma tabela
existente para que use um mecanismo diferente. Se você não sabe qual
mecanismo está associado a qual tabela, você pode usar o comando
show table, demonstrado a seguir:
mysql> SHOW TABLE STATUS LIKE ‘transaction’ \G
*************************** 1. row ***************************
Name: transaction
Engine: InnoDB
Version: 10
Row_format: Compact
Rows: 21
Avg_row_length: 780
Data_length: 16384
Max_data_length: 0
Index_length: 49152
Data_free: 0
Auto_increment: 22
Create_time: 2008-02-19 23:24:36
Update_time: NULL
Check_time: NULL
Collation: latin1_swedish_ci
Checksum: NULL
Create_options:
Comment:
1 row in set (1.46 sec)

Observando o segundo item, você pode ver que a tabela transaction já


está usando o mecanismo InnoDB. Se não estivesse, você poderia
atribuir o mecanismo InnoDB à tabela transaction por meio do
seguinte comando:
ALTER TABLE transaction ENGINE = INNODB;

Todos os pontos de gravação devem ser nomeados, o que permite ter


vários pontos de gravação dentro de uma única transação. Para criar um
ponto de gravação nomeado my_savepoint, você pode fazer o seguinte:
SAVEPOINT my_savepoint;

Para retroceder até um ponto de gravação especí co, você simplesmente


invoca o comando rollback, seguido das palavras-chave to savepoint e do
nome do ponto de gravação, como em:
ROLLBACK TO SAVEPOINT my_savepoint;

Veja um exemplo de como os pontos de gravação podem ser usados:


START TRANSACTION;
UPDATE product
SET date_retired = CURRENT_TIMESTAMP()
WHERE product_cd = 'XYZ';
SAVEPOINT before_close_accounts;
UPDATE account
SET status = 'CLOSED', close_date = CURRENT_TIMESTAMP(),
last_activity_date = CURRENT_TIMESTAMP()
WHERE product_cd = 'XYZ';
ROLLBACK TO SAVEPOINT before_close_accounts;
COMMIT;

O resultado nal dessa transação é que o produto ctício XYZ é


aposentado, mas nenhuma das contas é fechada.
Ao usar pontos de gravação, lembre-se do seguinte:
• Apesar de seu nome, nada é gravado quando você cria um ponto de
gravação. Eventualmente, você precisará invocar um commit se quiser
que a transação torne-se permanente.
• Se invocar um rollback sem nomear um ponto de gravação, todos os
pontos de gravação dentro da transação serão ignorados, e a transação
inteira será desfeita.
Se estiver usando o SQL Server, você precisará usar o comando
proprietário save transaction para criar um ponto de gravação e rollback
transaction para retroceder a um ponto de gravação, com cada um desses
comandos sendo seguido do nome do ponto de gravação.

Teste seu conhecimento


Teste sua compreensão de transações resolvendo o exercício a seguir.
Quando tiver terminado, compare sua solução à apresentada no apêndice
C.

Exercício 12.1
Gere uma transação que trans ra U$ 50 da conta de aplicação (money
market) de Frank Tucker para a conta corrente do cliente. Você precisará
inserir duas linhas na tabela transaction e atualizar duas linhas na tabela
account.
CAPÍTULO 13
Índices e restrições

Como o foco deste livro consiste nas técnicas de programação, os


primeiros 12 capítulos se concentraram nos elementos da linguagem SQL
que você pode usar para construir instruções select, insert, update e
delete poderosas. No entanto, outras características dos bancos de dados
afetam indiretamente o código que você escreve. Este capítulo concentra-
se em duas dessas características: os índices e as restrições.

Índices
Quando você insere uma linha em uma tabela, o servidor de banco de
dados não tenta colocar os dados em algum local especí co dentro da
tabela. Por exemplo, se você adicionar uma linha à tabela department, o
servidor não posiciona a linha em ordem numérica por meio da coluna
dept_id, nem em ordem alfabética por meio da coluna name. Em vez disso,
o servidor simplesmente coloca os dados na próxima localização
disponível dentro do arquivo (o servidor mantém uma lista de espaços
livres para cada tabela). Portanto, ao consultar a tabela department, o
servidor precisará inspecionar cada linha da tabela para responder à
consulta. Por exemplo, digamos que você faça a seguinte consulta:
mysql> SELECT dept_id, name
-> FROM department
-> WHERE name LIKE 'A%';
+---------+----------------+
| dept_id | name |
+---------+----------------+
| 3 | Administration |
+---------+----------------+
1 row in set (0.03 sec)

Para encontrar todos os departamentos cujos nomes iniciem com A, o


servidor precisa visitar cada linha da tabela department e inspecionar o
conteúdo da coluna name – se o departamento iniciar com A, a linha é
adicionada ao conjunto-resultado. Esse tipo de acesso é conhecido como
uma varredura de tabela (table scan).
Apesar de esse método funcionar bem com uma tabela de apenas três
linhas, imagine quanto tempo pode levar para responder à consulta se a
tabela tiver três milhões de linhas. Para qualquer quantidade de linhas
maior que três e menor que três milhões, vai chegar um ponto em que o
servidor não conseguirá responder à consulta em uma quantidade de
tempo razoável sem ajuda adicional. Essa ajuda vem na forma de um ou
mais índices na tabela department.
Mesmo que nunca tenha ouvido falar de um índice de banco de dados,
provavelmente você sabe o que é um índice (por exemplo, este livro tem
um). Um índice é nada mais do que um mecanismo para encontrar um
item especí co dentro de um recurso. Todos os livros técnicos, por
exemplo, incluem um índice no nal que permite localizar uma palavra
ou frase especí ca dentro da obra. O índice lista essas palavras e frases
em uma ordem alfabética, permitindo que o leitor mova-se rapidamente
para uma letra especí ca no índice, encontre a entrada desejada e, então, a
página ou páginas nas quais a palavra ou frase pode ser encontrada.
Da mesma forma que uma pessoa usa um índice para encontrar palavras
dentro de uma publicação, um servidor de banco de dados usa índices
para localizar linhas em uma tabela. Índices são tabelas especiais que,
diferentemente das tabelas normais, são mantidas em uma ordem
especí ca. No entanto, em vez de conter todos os dados de uma entidade,
um índice contém apenas a coluna (ou colunas) usada para localizar as
linhas da tabela de dados, junto com informações que descrevem onde as
linhas estão sicamente localizadas. Portanto, o papel dos índices é
facilitar a recuperação de um subconjunto de linhas e colunas de uma
tabela sem a necessidade de inspecionar cada linha da tabela.

Criação de índices
Voltando à tabela department, você pode decidir adicionar um índice na
coluna name para acelerar qualquer consulta que especi que um nome
de departamento completo ou parcial, bem como qualquer operação
update ou delete que especi que um nome de departamento. Veja como
você pode adicionar tal índice em um banco de dados MySQL:
mysql> ALTER TABLE department
-> ADD INDEX dept_name_idx (name);
Query OK, 3 rows affected (0.08 sec)
Records: 3 Duplicates: 0 Warnings: 0

Essa instrução cria um índice (um índice de árvore balanceada ou índice


B-Tree, para ser mais preciso, mas falaremos sobre isso mais adiante) à
coluna department.name; além disso, o índice recebe o nome de
dept_name_idx. Com o índice posicionado, o otimizador de consultas
(discutido no capítulo 3) pode escolher usar esse índice, caso julgue que
isso bene cie o processo (com apenas três linhas na tabela department,
por exemplo, o otimizador poderá muito bem escolher ignorar o índice e
ler a tabela completa). Se houver mais de um índice em uma tabela, o
otimizador deve decidir qual índice será o mais bené co para uma
instrução SQL em particular.
O MySQL trata os índices como componentes opcionais de uma
tabela, e é por isso que você deve usar o comando alter table
para adicionar ou remover um índice. Outros bancos de dados,
incluindo o SQL Server e o Oracle Database, tratam os índices
como objetos de esquema independentes. Portanto, tanto para o
SQL Server como para o Oracle, você geraria um índice usando o
comando create index, como em:
CREATE INDEX dept_name_idx
ON department (name);

A partir da versão 5.0 do MySQL, um comando create index está


disponível, apesar de ser mapeado para o comando alter table.
Todos os servidores de banco de dados permitem observar os índices
disponíveis. Usuários do MySQL podem usar o comando show para ver
todos os índices de uma tabela especí ca, como em:
mysql> SHOW INDEX FROM department \G ***************************
1. row
*************************** 1. row ***************************
Table: department
Non_unique: 0
Key_name: PRIMARY
Seq_in_index: 1
Column_name: dept_id
Collation: A
Cardinality: 3
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_Comment:
*************************** 2. row ***************************
Table: department
Non_unique: 1
Key_name: dept_name_idx
Seq_in_index: 1
Column_name: name
Collation: A
Cardinality: 3
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_Comment:
2 rows in set (0.01 sec)

A saída indica que há dois índices da tabela department: um na coluna


dept_id, chamado PRIMARY, e o outro na coluna name, chamado
dept_name_idx. Como criei apenas um índice até agora (dept_name_idx),
você pode estar se perguntando de onde surgiu o outro: quando a tabela
department foi criada, a instrução create table incluiu uma restrição que
nomeava a coluna dept_id como a chave primária da tabela. Veja a
instrução usada para criar a tabela:
CREATE TABLE department
(dept_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(20) NOT NULL,
CONSTRAINT pk_department PRIMARY KEY (dept_id) );

Quando a tabela foi criada, o servidor MySQL automaticamente gerou


um índice na coluna de chave primária, que, nesse caso, é dept_id, e dei o
nome PRIMARY ao índice. Falarei sobre as restrições mais tarde neste
capítulo.
Se, depois de criar um índice, você chegar à conclusão de que ele não está
sendo útil, você pode removê-lo da seguinte maneira:
mysql> ALTER TABLE department
-> DROP INDEX dept_name_idx;
Query OK, 3 rows affected (0.02 sec)
Records: 3 Duplicates: 0 Warnings: 0

Os usuários do SQL Server e do Oracle Database podem usar o


comando drop index para remover um índice, como em:
DROP INDEX dept_name_idx; (Oracle)
DROP INDEX dept_name_idx ON department (SQL Server)

O MySQL agora também suporta o comando drop index.

Índices exclusivos
Ao projetar um banco de dados, é importante considerar quais colunas
têm permissão para conter dados duplicados e quais não. Por exemplo, é
permitido ter dois clientes com o nome John Smith na tabela individual,
já que cada linha terá um identifcador (cust_id), uma data de nascimento
e um número federal (customer.fed_id) diferentes para diferenciar um do
outro. No entanto, você não iria querer ter dois departamentos com o
mesmo nome na tabela department. Você pode forçar uma regra contra
nomes de departamento duplicados criando um índice exclusivo na coluna
department.name.

Um índice exclusivo exerce vários papéis, pois, além de fornecer todos os


benefícios de um índice comum, ele também serve como um mecanismo
de desativação de valores duplicados na coluna indexada. Sempre que
uma linha é inserida ou quando a coluna indexada é modi cada, o
servidor de banco de dados veri ca o índice exclusivo para ver se o valor
já existe em alguma outra linha da tabela. Veja como você criaria um
índice exclusivo na coluna department.name:
mysql> ALTER TABLE department
-> ADD UNIQUE dept_name_idx (name);
Query OK, 3 rows affected (0.04 sec)
Records: 3 Duplicates: 0 Warnings: 0

Os usuários do SQL Server e do Oracle Database precisam


apenas adicionar a palavra-chave unique ao criar um índice, como
em:
CREATE UNIQUE INDEX dept_name_idx
ON department (name);

Com o índice no lugar, você receberá um erro se tentar adicionar outro


departamento com o nome 'Operations':
mysql> INSERT INTO department (dept_id, name)
-> VALUES (999, 'Operations');
ERROR 1062 (23000): Duplicate entry 'Operations' for key
'dept_name_idx'

Você não deve construir índices exclusivos na(s) coluna(s) de sua chave
primária, pois o servidor já veri ca a exclusividade dos valores da chave.
No entanto, você pode criar mais de um índice exclusivo na mesma tabela
se sentir que isso é justi cável.

Índices de múltiplas colunas


Além dos índices de coluna única demonstrados até agora, você pode
construir índices que se estendam por múltiplas colunas. Se, por exemplo,
você tiver que pesquisar funcionários pelo nome e sobrenome, você pode
construir um único índice para ambas as colunas, como em:
mysql> ALTER TABLE employee
-> ADD INDEX emp_names_idx (lname, fname);
Query OK, 18 rows affected (0.10 sec)
Records: 18 Duplicates: 0 Warnings: 0

Esse índice será útil em consultas que especi quem o nome e o


sobrenome ou apenas o sobrenome, mas você não poderá usá-lo em
consultas que especi quem apenas o nome do funcionário. Para entender
por quê, considere como você encontraria o número de telefone de uma
pessoa: se souber o nome e o sobrenome, poderia usar uma lista
telefônica para encontrar o número rapidamente, já que uma lista é
organizada pelo sobrenome e, então, pelo nome. Se soubesse apenas o
nome da pessoa, você precisaria varrer cada entrada da lista telefônica
para encontrar todas as que contenham o nome especi cado.
Portanto, ao construir índices de múltiplas colunas, você deve pensar bem
em qual coluna listar primeiro, qual coluna listar em segundo lugar e
assim por diante, de forma que o índice seja o mais útil possível. No
entanto, tenha em mente que nada lhe impede de construir índices
múltiplos usando o mesmo conjunto de colunas, mas em uma ordem
diferente, caso você sinta que isso seja necessário para garantir um tempo
de resposta adequado.

Tipos de índices
A indexação é uma ferramenta poderosa, mas como existem vários tipos
de dados diferentes, uma única estratégia de indexação nem sempre
realiza a tarefa esperada. As seções a seguir ilustram os diferentes tipos de
indexação disponíveis nos vários servidores.

Índices B-tree
Todos os índices mostrados até agora são índices de árvore balanceada,
que são mais conhecidos como índices B-tree. O MySQL, o SQL Server e
o Oracle Database usam a indexação B-tree por padrão, então você terá
um índice B-tree a não ser que solicite explicitamente outro tipo. Como
era de se esperar, índices B-tree são organizados como árvores, com um
ou mais níveis de nós-galhos levando a um único nível de nós-folhas. Nós-
galhos são usados para navegar pela árvore, enquanto nós-folhas
armazenam os valores reais e as informações de localização. Por exemplo,
um índice B-tree construído na coluna employee.lname poderia parecer
com o conteúdo da gura 13.1.
Se você fosse realizar uma consulta que recuperasse todos os funcionários
cujo sobrenome inicie com G, o servidor olharia no nó-galho do topo
(chamado de nó-raiz) e seguiria o vínculo ao nó-galho referente a
sobrenomes iniciados de A a M. Esse nó-galho, por sua vez, direcionaria o
servidor a um nó-folha contendo sobrenomes iniciados de G a I. O
servidor, por m, iniciaria a leitura dos valores no nó-folha até encontrar
um valor que não iniciasse com G (que, nesse caso, seria “Hawthorne”).
Figura 13.1 – Exemplo de B-tree.
Conforme linhas são inseridas, atualizadas e apagadas da tabela employee,
o servidor tentará manter a árvore balanceada para que não haja mais
galhos/folhas de um lado da raiz do que no outro. O servidor pode
adicionar ou remover nós-galhos para redistribuir os valores de maneira
mais uniforme e pode até adicionar ou remover um nível inteiro de nós-
galhos. Ao manter a árvore balanceada, o servidor é capaz de percorrê-la
rapidamente até os nós-folhas para encontrar os valores desejados sem ter
que navegar por vários níveis de nós-galhos.

Índices de mapa de bits


Apesar de os índices B-tree serem ótimos para lidar com colunas que
contenham muitos valores diferentes, como o nome e sobrenome de um
cliente, eles podem se tornar pesados quando construídos em uma coluna
que permita um pequeno número de valores diferentes. Por exemplo, você
pode decidir gerar um índice na coluna account.product_cd para
recuperar rapidamente todas as contas de um tipo especí co (por
exemplo, contas correntes, contas poupanças). No entanto, como só há
oito produtos diferentes, e como alguns produtos são muito mais
populares que outros, pode ser difícil manter um índice B-tree balanceado
conforme o número de contas cresce.
Para colunas que contenham apenas uma pequena quantidade de valores
ao longo de um grande número de linhas (conhecidos como dados de
baixa cardinalidade), torna-se necessária uma estratégia diferente de
indexação. Para lidar com essa situação de forma mais e ciente, o Oracle
Database inclui os índices de mapa de bits, que geram um mapa de bits
para cada valor armazenado na coluna. A gura 13.2 mostra a aparência
de um mapa de bits para os dados da coluna account.product_cd.

Figura 13.2 – Exemplo de mapa de bits.


O índice contém seis mapas de bits, um para cada valor da coluna
product_cd (dois dos oito produtos disponíveis não estão sendo usados), e
cada mapa de bits inclui um valor 0/1 para cada uma das 24 linhas da
tabela account. Assim, se você solicitar que o servidor recupere todas as
contas de aplicação e contas poupança (product_cd = 'MM'), o servidor
simplesmente encontra todos os valores 1 no mapa de bits MM e retorna
as linhas 7, 10 e 18. O servidor também pode combinar mapas de bits se
estiver procurando por múltiplos valores; por exemplo, se quiser recuperar
todas as contas de aplicação e contas poupança (product_cd = 'MM' ou
product_cd = 'SAV'), o servidor poderá realizar uma operação OR nos
mapas de bits MM e SAV e retornará as linhas 2, 5, 7, 9, 10, 16, e 18.
Índices de mapa de bits representam uma solução de indexação elegante e
compacta para dados de baixa cardinalidade, mas essa estratégia de
indexação entra em colapso se o número de valores diferentes
armazenados na coluna crescer demais em relação ao número de linhas
(conhecidos como dados de alta cardinalidade), já que o servidor
precisaria manter muitos mapas de bits. Por exemplo, você nunca deveria
construir um índice de mapa de bits em sua coluna de chave primária,
pois ela representa a maior cardinalidade possível (um valor diferente
para cada linha).
Os usuários do Oracle podem gerar índices de mapa de bits simplesmente
adicionando a palavra-chave bitmap à instrução create index, como em:
CREATE BITMAP INDEX acc_prod_idx ON account (product_cd);

Índices de mapa de bits são muito usados em ambientes de data


warehousing, em que grandes quantidades de dados costumam ser
indexados em colunas que contêm relativamente poucos valores (por
exemplo, vendas trimestrais, regiões geográ cas, produtos, vendedores).

Índices de texto
Se o seu banco de dados armazena documentos, você pode precisar
disponibilizar aos usuários a pesquisa de palavras ou frases dentro de
documentos. Certamente, você não vai querer que o seu servidor abra
cada documento, varrendo-o à procura do texto desejado cada vez que
uma pesquisa for solicitada – no entanto, as estratégias tradicionais de
indexação não funcionam nessa situação. Para lidar com esse tipo de
situação, o MySQL, o SQL Server e o Oracle Database incluem
indexações especializadas e mecanismos de pesquisa em documentos.
Tanto o SQL Server quanto o MySQL incluem o que eles chamam de
índices de texto completo (full-text; no MySQL, os índices de texto
completo estão disponíveis apenas com o mecanismo de armazenamento
MyISAM), e o Oracle Database inclui um conjunto poderoso de
ferramentas conhecido como Oracle Text. Pesquisas em documentos são
especializadas a ponto de eu me abster de mostrar um exemplo delas, mas
eu quis, pelo menos, que você soubesse quais opções estão disponíveis.

Como os índices são usados


Os índices costumam ser usados pelo servidor para rapidamente localizar
linhas em uma tabela especí ca – depois disso, o servidor visita a tabela
associada para extrair as informações adicionais solicitadas pelo usuário.
Considere a seguinte consulta:
mysql> SELECT emp_id, fname, lname
-> FROM employee
-> WHERE emp_id IN (1, 3, 9, 15);
+--------+---------+----------+
| emp_id | fname | lname |
+--------+---------+----------+
| 1 | Michael | Smith |
| 3 | Robert | Tyler |
| 9 | Jane | Grossman |
| 15 | Frank | Portman |
+--------+---------+----------+
4 rows in set (0.00 sec)

Para essa consulta, o servidor pode usar o índice de chave primária na


coluna emp_id para localizar os funcionários de ID 1, 3, 9 e 15 na tabela
employee e, então, visitar cada uma das quatro linhas para recuperar as
colunas de nome e sobrenome.
No entanto, se o índice contiver tudo que for necessário para atender à
consulta, o servidor não precisará visitar a tabela associada. Para ilustrar
isso, vejamos como o otimizador de consultas aborda uma mesma
consulta, mas com índices diferentes
A consulta, que agrega saldos de contas de clientes especí cos, é
mostrada a seguir:
mysql> SELECT cust_id, SUM(avail_balance) tot_bal
-> FROM account
-> WHERE cust_id IN (1, 5, 9, 11)
-> GROUP BY cust_id;
+---------+----------+
| cust_id | tot_bal |
+---------+----------+
| 1 | 4557.75 |
| 5 | 2237.97 |
| 9 | 10971.22 |
| 11 | 9345.55 |
+---------+----------+
4 rows in set (0.00 sec)

Para ver como o otimizador de consultas do MySQL decide executar a


consulta, uso a instrução explain para solicitar ao servidor que mostre
seu plano de execução da consulta, em vez de executá-la efetivamente:
mysql> EXPLAIN SELECT cust_id, SUM(avail_balance) tot_bal
-> FROM account
-> WHERE cust_id IN (1, 5, 9, 11)
-> GROUP BY cust_id \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: account
type: index
possible_keys: fk_a_cust_id
key: fk_a_cust_id
key_len: 4
ref: NULL
rows: 24
Extra: Using where
1 row in set (0.00 sec)

Cada servidor de banco de dados inclui suas próprias


ferramentas que permitem ver como o otimizador de consultas
lida com sua instrução SQL. O SQL Server permite ver um plano
de execução invocando a instrução set showplan_text on antes
da execução de sua instrução SQL. O Oracle Database inclui a
instrução explain plan, que escreve o plano de execução em uma
tabela especial chamada plan_table.
Sem entrar muito nos detalhes, aqui está o que o plano de execução
informa:
• O índice fk_a_cust_id é usado para encontrar as linhas da tabela
account que atendam à cláusula where.

• Após a leitura do índice, o servidor pretende ler todas as 24 linhas da


tabela account para reunir os dados de saldo disponíveis, pois ele não
sabe que podem existir outros clientes além dos IDs 1, 5, 9 e 11.
O índice fk_a_cust_id é outro índice gerado automaticamente pelo
servidor, mas dessa vez ele é gerado por causa de uma restrição de chave
estrangeira, em vez de uma restrição de chave primária (falaremos mais
tarde sobre isso). O índice fk_a_cust_id é construído sobre a coluna
account.cust_id, então o servidor está usando o índice para localizar os
clientes de ID 1, 5, 9 e 11 na tabela account e, então, está visitando aquelas
linhas para recuperar e agregar os dados de saldo disponível.
Em seguida, vou adicionar um novo índice chamado acc_bal_idx em
ambas as colunas cust_id e avail_balance:
mysql> ALTER TABLE account
-> ADD INDEX acc_bal_idx (cust_id, avail_balance);
Query OK, 24 rows affected (0.03 sec)
Records: 24 Duplicates: 0 Warnings: 0

Com esse índice no lugar, vejamos como o otimizador de consultas


aborda a mesma consulta:
mysql> EXPLAIN SELECT cust_id, SUM(avail_balance) tot_bal
-> FROM account
-> WHERE cust_id IN (1, 5, 9, 11)
-> GROUP BY cust_id \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: account
type: range
possible_keys: acc_bal_idx
key: acc_bal_idx
key_len: 4
ref: NULL
rows: 8
Extra: Using where; Using index
1 row in set (0.01 sec)

Uma comparação dos dois planos de execução resulta nas seguintes


diferenças:
• O otimizador está usando o novo índice acc_bal_idx em vez do índice
fk_a_cust_id.
• O otimizador prevê que precisa de apenas oito linhas em vez de 24.
• A tabela account não é necessária (designado por Using index na
coluna Extra) para satisfazer os resultados da consulta.
Portanto, o servidor pode usar índices para auxiliar na localização de
linhas na tabela associada ou pode usar um índice como se fosse uma
tabela desde que o índice contenha todas as colunas necessárias à
consulta.
O processo descrito é um exemplo de otimização (tuning) de
consulta. A otimização envolve observar uma instrução SQL e
determinar os recursos disponíveis ao servidor para executar a
instrução. Você pode decidir modi car a instrução SQL, ajustar
os recursos do banco de dados ou ambos para fazer com que uma
instrução seja executada de forma mais e ciente. Otimização é
um tópico detalhado, então encorajo você a ler o guia de
otimização de seu servidor ou a ler um bom livro sobre
otimização, de forma que você possa ver todas as diferentes
abordagens disponíveis em seu servidor.

O lado negativo dos índices


Se os índices são tão bons, por que não indexar tudo? Bem, o ponto
principal para entender o porquê de mais chaves não signi car
necessariamente uma vantagem é ter em mente que todo índice é uma
tabela (um tipo especial de tabela, mas ainda assim uma tabela). Portanto,
cada vez que uma linha é adicionada ou removida de uma tabela, todos
os índices naquela tabela devem ser modi cados. Quando uma linha é
atualizada, quaisquer índices na coluna ou colunas que forem afetados
também precisam ser modi cados. Portanto, quanto mais índices você
tem, mais trabalho o servidor precisa realizar para manter todos os
objetos de esquema atualizados, o que tende a prejudicar o desempenho.
Índices também necessitam de espaço em disco, bem como de certo
cuidado por parte de seus administradores, então a melhor estratégia é
adicionar um índice quando surge uma necessidade clara. Se precisar de
um índice apenas para ns especiais, como uma rotina de manutenção
mensal, você sempre pode adicionar o índice, executar a rotina e, então,
descartá-lo até precisar dele novamente. No caso de data warehouses, em
que índices são cruciais durante o horário comercial e durante as
consultas ad hoc, mas são problemáticos quando os dados estão sendo
carregados no warehouse durante a noite, torna-se uma prática comum
descartar os índices antes de os dados serem carregados e, então, recriá-
los antes que o warehouse abra para negócio.
Em geral, você deve se esforçar para ter nem índices de mais nem índices
de menos. Se não tiver certeza sobre quantos índices deve ter, pode usar a
seguinte estratégia como um padrão:
• Certi que-se de que todas as colunas de chave primária estejam
indexadas (a maioria dos servidores cria índices exclusivos
automaticamente quando você cria restrições de chave primária). No
caso de chaves primárias de múltiplas colunas, considere construir
índices adicionais em um subconjunto das colunas da chave primária,
ou em todas as colunas da chave primária, mas em uma ordem
diferente da de nição de restrição da chave primária.
• Construa índices em todas as colunas que sejam referenciadas nas
restrições de chave estrangeira. Tenha em mente que o servidor veri ca
se não existem linhas- lhas quando uma linha-mãe é apagada e,
portanto, ele faz uma consulta para pesquisar um valor especí co na
coluna. Se não houver um índice na coluna, a tabela inteira será
varrida.
• Indexe quaisquer colunas que serão frequentemente usadas para
recuperar dados. A maioria das colunas de data é boa candidata, além
das colunas de string curtas (de 3 a 50 caracteres).
Depois de ter construído seu conjunto inicial de índices, tente capturar
consultas reais às suas tabelas e modi que sua estratégia de indexação
para se ajustar aos caminhos de acesso mais comuns.

Restrições
Uma restrição é simplesmente uma limitação colocada em uma ou mais
colunas de uma tabela. Existem vários tipos diferentes de restrições,
incluindo:
Restrições de chave primária
Identi cam a coluna ou colunas que garantam exclusividade dentro de
uma tabela.
Restrições de chave estrangeira
Restringem uma ou mais colunas que contenham apenas valores
encontrados nas colunas de chave primária de outra tabela, e também
podem restringir os valores permitidos em outras tabelas se regras de
atualização em cascata ou exclusão em cascata forem estabelecidas.
Restrições exclusivas
Restringem uma ou mais colunas para conter valores exclusivos dentro
de uma tabela (restrições de chave primária representam um tipo
especial de restrição exclusiva).
Restrições de veri cação
Restringem os valores permitidos em uma coluna.
Sem restrições, a consistência de um banco de dados torna-se suspeita.
Por exemplo, se o servidor permitir mudar um ID de cliente na tabela
customer sem mudar o mesmo ID de cliente na tabela account, você
acabará com contas bancárias que não mais apontam para registros
válidos de clientes (conhecidas como linhas órfãs). No entanto, com as
restrições de chaves primária e estrangeira no lugar, o servidor levantará
um erro se for feita uma tentativa de modi car ou apagar dados que sejam
referenciados por outras tabelas, ou propagará as mudanças às outras
tabelas para você (leia mais sobre isso em breve).
Se quiser usar chaves estrangeiras no servidor MySQL, você
deve usar o mecanismo de armazenamento InnoDB em suas
tabelas. Restrições de chave estrangeira não são suportadas no
mecanismo Falcon na versão 6.0.4, mas serão suportadas em
versões futuras.

Criação de restrições
As restrições costumam ser criadas junto com a tabela associada por meio
da instrução create table. Para ilustrar isso, aqui está um exemplo do
script de geração de esquema do banco de dados de exemplo deste livro:
CREATE TABLE product
(product_cd VARCHAR(10) NOT NULL,
name VARCHAR(50) NOT NULL,
product_type_cd VARCHAR (10) NOT NULL,
date_offered DATE,
date_retired DATE,
CONSTRAINT fk_product_type_cd FOREIGN KEY (product_type_cd)
REFERENCES product_type (product_type_cd),
CONSTRAINT pk_product PRIMARY KEY (product_cd)
);

A tabela resultante inclui duas restrições: uma para especi car que a
coluna product_cd é a chave primária da tabela e outra para especi car
que a coluna product_type_cd é uma chave estrangeira da tabela
product_type. Como alternativa, você pode criar a tabela product sem as
restrições e adicionar as restrições de chave primária e chave estrangeira
mais tarde por meio de instruções alter table:
ALTER TABLE product
ADD CONSTRAINT pk_product PRIMARY KEY (product_cd);
ALTER TABLE product
ADD CONSTRAINT fk_product_type_cd FOREIGN KEY (product_type_cd)
REFERENCES product_type (product_type_cd);

Se quiser remover uma restrição de chave primária ou chave estrangeira,


você pode usar a instrução alter table novamente, exceto que você deve
especi car drop em vez de add, como em:
ALTER TABLE product
DROP PRIMARY KEY;
ALTER TABLE product
DROP FOREIGN KEY fk_product_type_cd;

Embora seja bem incomum descartar uma restrição de chave primária,


restrições de chave estrangeira por vezes são descartadas durante certas
operações de manutenção e, posteriormente, são restabelecidas.

Restrições e índices
Como você viu anteriormente no capítulo, a criação de restrições às vezes
envolve a geração automática de um índice. No entanto, os servidores de
banco de dados se comportam de maneira diferente, independentemente
do relacionamento entre restrições e índices. A tabela 13.1 mostra como o
MySQL, o SQL Server e o Oracle Database lidam com o relacionamento
entre restrições e índices.
Tabela 13.1 – Geração de restrição
Tipo de restrição MySQL SQL Server Oracle Database

Gera índice Gera índice Usa índice existente ou cria novo


Restrições de chave primária
exclusivo exclusivo índice

Restrições de chave
Gera índice Não gera índice Não gera índice
estrangeira

Gera índice Gera índice Usa índice existente ou cria novo


Restrições exclusivas
exclusivo exclusivo índice

O MySQL, portanto, gera um novo índice para reforçar chaves primárias,


chaves estrangeiras e restrições exclusivas; o SQL Server gera um novo
índice para chaves primárias e restrições exclusivas, mas não para chaves
estrangeiras; e o Oracle Database usa a mesma abordagem do SQL Server,
exceto que usará um índice existente (se existir um índice apropriado)
para reforçar chaves primárias e restrições exclusivas. Apesar de nem o
SQL Server nem o Oracle Database gerarem um índice para uma restrição
de chave estrangeira, a documentação de ambos os servidores aconselha
que índices sejam criados para todas as chaves estrangeiras.

Restrições em cascata
Com as restrições de chave estrangeira a postos, se um usuário tentar
inserir uma nova linha ou alterar uma linha existente, de forma que uma
chave estrangeira não tenha um valor correspondente na tabela-mãe, o
servidor sinalizará um erro. Para ilustrar esse fato, vamos olhar para os
dados das tabelas product e product_type:

Há três valores diferentes para a coluna product_type_cd da tabela


product_type (ACCOUNT, INSURANCE e LOAN). Desses três valores, dois deles
(ACCOUNT e LOAN) são referenciados na coluna product_type_cd da tabela
product.

A instrução a seguir tenta mudar a coluna product_type_cd da tabela


product para um valor que não existe na tabela product_type:
mysql> UPDATE product
-> SET product_type_cd = 'XYZ'
-> WHERE product_type_cd = 'LOAN';
ERROR 1452 (23000): Cannot add or update a child row: a foreign
key constraint
fails ('bank'.'product', CONSTRAINT 'fk_product_type_cd' FOREIGN
KEY
('product_type_cd') REFERENCES 'product_type' ('product_type_cd'))

Devido à restrição de chave estrangeira na coluna


product.product_type_cd, o servidor não permite que a atualização
aconteça, pois não há uma linha na tabela product_type com um valor de
XYZ na coluna product_type_cd. Portanto, a restrição de chave estrangeira
não permite que você altere uma linha- lha se não houver um valor
correspondente na linha-mãe.
No entanto, o que aconteceria se você tentasse mudar a linha-mãe na
tabela product_type para XYZ? Aqui vai uma instrução update que tenta
alterar o tipo de produto LOAN para XYZ:
mysql> UPDATE product_type
-> SET product_type_cd = 'XYZ'
-> WHERE product_type_cd = 'LOAN';
ERROR 1451 (23000): Cannot delete or update a parent row: a
foreign key constraint fails ('bank'.'product', CONSTRAINT
'fk_product_type_cd' FOREIGN KEY ('product_type_cd') REFERENCES
'product_type' ('product_type_cd'))

Mais uma vez, um erro é sinalizado, dessa vez porque existem linhas-
lhas na tabela product cuja coluna product_type_cd contém o valor LOAN.
Esse é o comportamento padrão para restrições de chave estrangeira, mas
não é o único comportamento possível: em vez disso, você pode instruir o
servidor a propagar por você a mudança a todas as linhas- lhas,
preservando, então, a integridade dos dados. Conhecida como uma
atualização em cascata, essa variação da restrição de chave estrangeira
pode ser instalada removendo-se a chave estrangeira existente e
adicionando-se uma nova chave que inclua a cláusula on update cascade:
mysql> ALTER TABLE product
-> DROP FOREIGN KEY fk_product_type_cd;
Query OK, 8 rows affected (0.02 sec)
Records: 8 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE product
-> ADD CONSTRAINT fk_product_type_cd FOREIGN KEY
(product_type_cd)
-> REFERENCES product_type (product_type_cd)
-> ON UPDATE CASCADE;
Query OK, 8 rows affected (0.03 sec)
Records: 8 Duplicates: 0 Warnings: 0

Com essa restrição modi cada devidamente posicionada, vejamos o que


acontece quando a instrução update anterior é tentada mais uma vez:
mysql> UPDATE product_type
-> SET product_type_cd = 'XYZ'
-> WHERE product_type_cd = 'LOAN';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0

Dessa vez, a instrução foi bem-sucedida. Para veri car se a mudança foi
propagada à tabela product, vamos olhar novamente os dados em ambas
as tabelas:
mysql> SELECT product_type_cd, name
-> FROM product_type;
+-----------------+-------------------------------+
| product_type_cd | name |
+-----------------+-------------------------------+
| ACCOUNT | Customer Accounts |
| INSURANCE | Insurance Offerings |
| XYZ | Individual and Business Loans |
+-----------------+-------------------------------+
3 rows in set (0.02 sec)
mysql> SELECT product_type_cd, product_cd, name
-> FROM product
-> ORDER BY product_type_cd;
+-----------------+------------+-------------------------+
| product_type_cd | product_cd | name |
+-----------------+------------+-------------------------+
| ACCOUNT | CD | certificate of deposit |
| ACCOUNT | CHK | checking account |
| ACCOUNT | MM | money market account |
| ACCOUNT | SAV | savings account |
| XYZ | AUT | auto loan |
| XYZ | BUS | business line of credit |
| XYZ | MRT | home mortgage |
| XYZ | SBL | small business loan |
+-----------------+------------+-------------------------+
8 rows in set (0.01 sec)

Como você pode ver, a mudança na tabela product_type também foi


propagada à tabela product. Além das atualizações em cascata, você
também pode especi car exclusões em cascata. Uma exclusão em cascata
remove linhas da tabela- lha quando uma linha é excluída da tabela-mãe.
Para especi car exclusões em cascata, use a cláusula on delete cascade,
como em:
ALTER TABLE product
ADD CONSTRAINT fk_product_type_cd FOREIGN KEY (product_type_cd)
REFERENCES product_type (product_type_cd)
ON UPDATE CASCADE
ON DELETE CASCADE;

Com essa versão da restrição, o servidor agora atualizará as linhas- lhas


na tabela product quando uma linha da tabela product_type é atualizada,
bem como excluirá linhas- lhas na tabela product quando uma linha na
tabela product_type é excluída.
Restrições em cascata formam um dos casos nos quais as restrições afetam
diretamente o código que você escreve. Você precisa saber quais restrições
em seu banco de dados especi cam atualizações e/ou exclusões em
cascata, para que você tenha consciência do real efeito de suas instruções
update e delete.

Teste seu conhecimento


Resolva os exercícios a seguir para testar seu conhecimento de índices e
restrições. Ao terminar, compare suas soluções com as mostradas no
apêndice C.

Exercício 13.1
Modi que a tabela account de forma que os clientes não possam ter mais
de uma conta para cada produto.
Exercício 13.2
Gere um índice de múltiplas colunas na tabela transaction que possa ser
usado em ambas as consultas mostradas a seguir:
SELECT txn_date, account_id, txn_type_cd, amount
FROM transaction
WHERE txn_date > cast('2008-12-31 23:59:59' as datetime);
SELECT txn_date, account_id, txn_type_cd, amount
FROM transaction
WHERE txn_date > cast('2008-12-31 23:59:59' as datetime)
AND amount < 1000;
CAPÍTULO 14
Views

Aplicações bem projetadas geralmente expõem uma interface pública


enquanto mantêm privados os detalhes de implementação, permitindo,
assim, mudanças futuras de projeto sem impacto nos usuários nais. Ao
projetar seu banco de dados, você pode obter um resultado semelhante
mantendo suas tabelas privadas e permitindo que seus usuários acessem
dados apenas por meio de um conjunto de views. Este capítulo procura
de nir o que são views, como são criadas e quando e como você pode
querer usá-las.

O que são views?


Uma view é simplesmente um mecanismo de consulta de dados.
Diferentemente das tabelas, as views não envolvem armazenamento de
dados: você não precisa se preocupar com as views ocupando seu espaço
em disco. Você cria uma view atribuindo um nome a uma instrução
select e, em seguida, armazenando a consulta para que outros a usem.
Outros usuários podem, então, usar sua view para acessar dados como se
estivessem consultando tabelas diretamente (na verdade, eles podem nem
saber que estão usando views).
Como um exemplo simples, digamos que você queira ocultar
parcialmente os IDs federais (números de Seguro Social e identi cadores
corporativos) da tabela customer. O departamento de serviço ao cliente,
por exemplo, pode precisar ter acesso apenas à última parte do ID federal
para veri car a identidade de um cliente que está ligando, mas a
exposição do número inteiro violaria a política de privacidade da
empresa. Portanto, em vez de permitir o acesso direto à tabela customer,
você de ne uma view chamada customer_vw e determina que todos os
funcionários do banco a usem para acessar os dados de cliente. Veja a
de nição de view:
CREATE VIEW customer_vw
(cust_id,
fed_id,
cust_type_cd,
address,
city,
state,
zipcode
)
AS
SELECT cust_id,
concat('ends in ', substr(fed_id, 8, 4)) fed_id,
cust_type_cd,
address,
city,
state,
postal_code
FROM customer;

A primeira parte da instrução lista os nomes das colunas das views, que
podem ser diferentes dos nomes das colunas da tabela subjacente (por
exemplo, a view customer_vw tem uma coluna nomeada zipcode que é
uma referência à coluna customer.postal_code). A segunda parte da
instrução é uma instrução select, que precisa conter uma expressão para
cada coluna da view.
Quando a instrução create view é executada, o servidor de banco de
dados simplesmente armazena a de nição da view para uso futuro: a
consulta não é executada, e nenhum dado é recuperado ou armazenado.
Uma vez que a view tenha sido criada, os usuários podem consultá-la
como fariam com uma tabela, como em:
mysql> SELECT cust_id, fed_id, cust_type_cd
-> FROM customer_vw;
+---------+--------------+--------------+
| cust_id | fed_id | cust_type_cd |
+---------+--------------+--------------+
| 1 | ends in 1111 | I |
| 2 | ends in 2222 | I |
| 3 | ends in 3333 | I |
| 4 | ends in 4444 | I |
| 5 | ends in 5555 | I |
| 6 | ends in 6666 | I |
| 7 | ends in 7777 | I |
| 8 | ends in 8888 | I |
| 9 | ends in 9999 | I |
| 10 | ends in 111 | B |
| 11 | ends in 222 | B |
| 12 | ends in 333 | B |
| 13 | ends in 444 | B |
+---------+--------------+--------------+
13 rows in set (0.02 sec)

A consulta real que o servidor executa não é nem a consulta enviada pelo
usuário nem a anexada à de nição da view. Em vez disso, o servidor
mescla essas duas consultas para criar outra instrução, que, no caso,
caria assim:
SELECT cust_id,
concat('ends in ', substr(fed_id, 8, 4)) fed_id,
cust_type_cd
FROM customer;

Mesmo que a de nição da view customer_vw inclua sete colunas da tabela


customer, a consulta executada pelo servidor recupera apenas três delas.
Como você verá mais tarde neste capítulo, isso é uma distinção
importante, caso algumas das colunas em sua view estejam anexadas a
funções ou subconsultas.
Do ponto de vista do usuário, uma view se parece exatamente com uma
tabela. Se quiser saber quais colunas estão disponíveis em uma view, você
pode usar o comando describe do MySQL (ou do Oracle) para examiná-
la:
mysql> describe customer_vw;
+--------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+-------+
| cust_id | int(10) unsigned | NO | | 0 | |
| fed_id | varchar(12) | YES | | NULL | |
| cust_type_cd | enum('I','B') | NO | | NULL | |
| address | varchar(30) | YES | | NULL | |
| city | varchar(20) | YES | | NULL | |
| state | varchar(20) | YES | | NULL | |
| postal_code | varchar(10) | YES | | NULL | |
+--------------+------------------+------+-----+---------+-------+
7 rows in set (1.40 sec)

Você pode usar qualquer cláusula da instrução select quando estiver


consultando por meio de uma view, incluindo group by, having e order
by. Con ra um exemplo:
mysql> SELECT cust_type_cd, count(*)
-> FROM customer_vw
-> WHERE state = 'MA'
-> GROUP BY cust_type_cd
-> ORDER BY 1;
+--------------+----------+
| cust_type_cd | count(*) |
+--------------+----------+
| I | 7 |
| B | 2 |
+--------------+----------+
2 rows in set (0.22 sec)

Além disso, você pode juntar views com outras tabelas (ou mesmo com
outras views) dentro de uma consulta, como em:
mysql> SELECT cst.cust_id, cst.fed_id, bus.name
-> FROM customer_vw cst INNER JOIN business bus
-> ON cst.cust_id = bus.cust_id;
+---------+-------------+------------------------+
| cust_id | fed_id | name |
+---------+-------------+------------------------+
| 10 | ends in 111 | Chilton Engineering |
| 11 | ends in 222 | Northeast Cooling Inc. |
| 12 | ends in 333 | Superior Auto Body |
| 13 | ends in 444 | AAA Insurance Inc. |
+---------+-------------+------------------------+
4 rows in set (0.24 sec)

Essa consulta junta a view customer_vw à tabela business para recuperar


apenas os clientes corporativos.

Por que usar as views?


Na seção anterior, mostrei uma view simples cujo único propósito era
mascarar os conteúdos da coluna customer.fed_id. Apesar de as views
serem normalmente empregadas com esse propósito, existem várias razões
para se utilizar view, como demonstro nas subseções a seguir.

Segurança de dados
Se você criar uma tabela e permitir que usuários a consultem, eles serão
capazes de acessar todas as colunas e linhas presentes. No entanto, como
já indiquei anteriormente, sua tabela pode incluir algumas colunas que
contenham dados sigilosos, como números de identi cação ou números
de cartão de crédito. Não apenas é uma péssima ideia expor tais dados a
todos os usuários como isso também pode violar as políticas de
privacidade de sua empresa, ou mesmo leis estaduais e federais, e assim
por diante.
A melhor abordagem para essas situações é manter a tabela privada (ou
seja, não conferir permissão de consulta a qualquer usuário) e, então,
criar uma ou mais views que omitam ou obscureçam (como a abordagem
'ends in ####' usada na coluna customer_vw.fed_id) as colunas sigilosas.
Você também pode restringir quais linhas um conjunto de usuários pode
acessar, adicionando uma cláusula where à sua de nição de view. Por
exemplo, a próxima de nição de view permite que apenas clientes
corporativos sejam consultados:
CREATE VIEW business_customer_vw
(cust_id,
fed_id,
cust_type_cd,
address,
city,
state,
zipcode
)
AS
SELECT cust_id,
concat('ends in ', substr(fed_id, 8, 4)) fed_id,
cust_type_cd,
address,
city,
state,
postal_code
FROM customer
WHERE cust_type_cd = 'B'

Se você fornecer essa view ao departamento bancário corporativo, as


pessoas do departamento conseguirão acessar apenas contas corporativas,
pois a condição na cláusula where será sempre incluída em suas consultas.
Os usuários do Oracle Database têm outra opção para garantir
a segurança tanto das linhas quanto das colunas de uma
tabela: o Virtual Private Database (VPD). O VPD permite
anexar políticas a suas tabelas, após as quais o servidor
modi cará as consultas de um usuário para impor as políticas.
Por exemplo, se você promulgar uma política de que membros
do departamento bancário corporativo podem ver apenas
contas corporativas, a condição cust_type_cd = 'B' será
adicionada a todas as consultas que eles zerem à tabela
customer.

Agregação de dados
Aplicações de relatório geralmente requerem dados agregados, e as views
são uma maneira excelente de fazer parecer que dados estão sendo pré-
agregados e armazenados no banco de dados. Como exemplo, digamos
que uma aplicação gere um relatório mensal mostrando o número de
contas e o total dos depósitos de cada cliente. Em vez de permitir que os
desenvolvedores da aplicação escrevam consultas às tabelas-base, você
poderia fornecê-las com a seguinte view:
CREATE VIEW customer_totals_vw
(cust_id,
cust_type_cd,
cust_name,
num_accounts,
tot_deposits
)
AS
SELECT cst.cust_id, cst.cust_type_cd,
CASE
WHEN cst.cust_type_cd = 'B' THEN
(SELECT bus.name FROM business bus WHERE bus.cust_id =
cst.cust_id)
ELSE
(SELECT concat(ind.fname, ' ', ind.lname)
FROM individual ind
WHERE ind.cust_id = cst.cust_id)
END cust_name,
sum(CASE WHEN act.status = 'ACTIVE' THEN 1 ELSE 0 END)
tot_active_accounts,
sum(CASE WHEN act.status = 'ACTIVE' THEN act.avail_balance ELSE
0 END) tot_balance
FROM customer cst INNER JOIN account act
ON act.cust_id = cst.cust_id
GROUP BY cst.cust_id, cst.cust_type_cd;

O uso dessa abordagem oferece uma grande exibilidade como projetista


de banco de dados. Se você decidir em algum momento que o
desempenho das consultas melhoraria drasticamente se os dados fosse
pré-agregados em uma tabela, em vez de serem somados a uma view, você
pode criar uma tabela customer_totals e modi car a de nição da view
customer_vw para que recupere os dados dessa tabela. Antes de modi car
a de nição da view, você pode usá-la para popular a tabela nova. Aqui
estão as instruções SQL necessárias para realizar esse cenário:
mysql> CREATE TABLE customer_totals
-> AS
-> SELECT * FROM customer_totals_vw;
Query OK, 13 rows affected (3.33 sec)
Records: 13 Duplicates: 0 Warnings: 0
mysql> CREATE OR REPLACE VIEW customer_totals_vw
-> (cust_id,
-> cust_type_cd,
-> cust_name,
-> num_accounts,
-> tot_deposits
-> )
-> AS
-> SELECT cust_id, cust_type_cd, cust_name, num_accounts,
tot_deposits
-> FROM customer_totals;
Query OK, 0 rows affected (0.02 sec)

De agora em diante, todas as consultas que usarem a view


customer_totals_vw extrairão dados da nova tabela customer_totals, o
que signi ca que os usuários verão uma melhoria no desempenho sem
precisar modi car suas consultas.

Escondendo a complexidade
Uma das razões mais comuns para se empregar views é proteger os
usuários nais da complexidade. Por exemplo, digamos que um relatório
seja criado mensalmente, mostrando o número de funcionários, o
número total de contas ativas e o número total de transações de cada
lial. Em vez de esperar que o projetista de relatórios navegue por quatro
tabelas diferentes para reunir os dados necessários, você poderia fornecer
uma view parecida com a seguinte:
CREATE VIEW branch_activity_vw
(branch_name,
city,
state,
num_employees,
num_active_accounts,
tot_transactions
)
AS
SELECT br.name, br.city, br.state,
(SELECT count(*)
FROM employee emp
WHERE emp.assigned_branch_id = br.branch_id) num_emps,
(SELECT count(*)
FROM account acnt
WHERE acnt.status = 'ACTIVE' AND acnt.open_branch_id =
br.branch_id) num_accounts,
(SELECT count(*)
FROM transaction txn
WHERE txn.execution_branch_id = br.branch_id) num_txns
FROM branch br;

Essa de nição de view é interessante porque três dos seis valores de


colunas são gerados usando subconsultas escalares. Se alguém usar essa
view, mas não referenciar as colunas num_employees, num_active_accounts
ou tot_transactions, nenhuma das subconsultas será executada.

Juntando dados particionados


Alguns projetos de banco de dados quebram tabelas grandes em vários
pedaços para melhorar o desempenho. Por exemplo, se a tabela
transaction car grande, os projetistas podem decidir parti-la em duas:
transaction_current, que armazena os últimos seis meses de dados, e
transaction_history, que armazena todos os dados de até seis meses
atrás. Se um cliente quiser ver todas as transações de uma conta
especí ca, você precisaria consultar ambas as tabelas. No entanto, ao criar
uma view que consulte as duas tabelas e combine os resultados, você
pode fazê-la parecer uma única tabela que armazena todos os dados de
transação. Con ra a de nição da view:
CREATE VIEW transaction_vw
(txn_date,
account_id,
txn_type_cd,
amount,
teller_emp_id,
execution_branch_id,
funds_avail_date
)
AS
SELECT txn_date, account_id, txn_type_cd, amount, teller_emp_id,
execution_branch_id, funds_avail_date
FROM transaction_historic
UNION ALL
SELECT txn_date, account_id, txn_type_cd, amount, teller_emp_id,
execution_branch_id, funds_avail_date
FROM transaction_current;

Usar uma view nesse caso é uma boa ideia porque ela permite que os
projetistas modi quem a estrutura dos dados subjacentes sem precisar
obrigar todos os usuários do banco de dados a modi carem suas
consultas.

Views atualizáveis
Se você fornece aos usuários um conjunto de views usado para recuperar
dados, o que deveria fazer no caso de os usuários também precisarem
modi car esses mesmos dados? Seria estranho, por exemplo, obrigar os
usuários a recuperarem dados por meio de uma view, mas permitir que
eles modi cassem diretamente a tabela subjacente usando instruções
update ou insert. Com isso em mente, o MySQL, o Oracle Database e o
SQL Server permitem modi car dados por meio de uma view, desde que
você obedeça a certas restrições. No caso do MySQL, uma view é
atualizável se as seguintes condições forem seguidas:
• Não são usadas funções de agregação (max(), min(), avg() etc.).
• A view não emprega cláusulas group by ou having.
• Não existem subconsultas na cláusula select ou from, e nenhuma
subconsulta na cláusula where não se refere a tabelas na cláusula from.
• A view não utiliza union, union all ou distinct.
• A cláusula from inclui pelo menos uma tabela ou view atualizável.
• A cláusula from usa junções internas se houver mais de uma tabela ou
view.
Para demonstrar a utilidade das views atualizáveis, seria melhor
começarmos com uma de nição de view simples e, então, seguirmos para
uma view mais complexa.

Atualizando views simples


A view mostrada no início do capítulo é bem simples, então vamos
começar por ela:
CREATE VIEW customer_vw
(cust_id,
fed_id,
cust_type_cd,
address,
city,
state,
zipcode
)
AS
SELECT cust_id,
concat('ends in ', substr(fed_id, 8, 4)) fed_id,
cust_type_cd,
address,
city,
state,
postal_code
FROM customer;

A view customer_vw consulta uma única tabela, e apenas uma das sete
colunas é derivada por meio de uma expressão. Essa de nição de view
não viola nenhuma das restrições listadas anteriormente, então você pode
usá-la para modi car os dados da tabela customer, como em:
mysql> UPDATE customer_vw
-> SET city = 'Woooburn'
-> WHERE city = 'Woburn';
Query OK, 1 row affected (0.34 sec)
Rows matched: 1 Changed: 1 Warnings: 0

Como você pode ver, a instrução a rma ter modi cado uma linha, mas
vamos veri car a tabela customer subjacente apenas para ter certeza:
mysql> SELECT DISTINCT city FROM customer;
+------------+
| city |
+------------+
| Lynnfield |
| Woooburn |
| Quincy |
| Waltham |
| Salem |
| Wilmington |
| Newton |
+------------+
7 rows in set (0.12 sec)

Apesar de você poder modi car a maioria das colunas da view dessa
maneira, você não conseguirá modi car a coluna fed_id, já que ela é
derivada de uma expressão:
mysql> UPDATE customer_vw
-> SET city = 'Woburn', fed_id = '999999999'
-> WHERE city = 'Woooburn';
ERROR 1348 (HY000): Column 'fed_id' is not updatable

Nesse caso isso não seria uma coisa ruim, pois o principal objetivo da
view é ocultar os identi cadores federais.
Se quiser inserir dados usando a view customer_vw, você está sem sorte:
views que contenham colunas derivadas não podem ser usada para inserir
dados, mesmo se as colunas derivadas não forem incluídas na instrução.
Por exemplo, a instrução a seguir tenta popular apenas as colunas
cust_id, cust_type_cd e city usando a view customer_view:
mysql> INSERT INTO customer_vw(cust_id, cust_type_cd, city)
-> VALUES (9999, 'I', 'Worcester');
ERROR 1471 (HY000): The target table customer_vw of the INSERT is
not insertable
-into

Agora que você já viu as limitações das views simples, a próxima seção
demonstrará o uso de uma view que junta múltiplas tabelas.

Atualizando views complexas


Apesar de as views de tabela única certamente serem comuns, muitas das
views que você encontrará incluem múltiplas tabelas na cláusula from da
consulta subjacente. A próxima view, por exemplo, junta as tabelas
business e customer de forma que todos os dados dos clientes
corporativos possam ser facilmente consultados:
CREATE VIEW business_customer_vw
(cust_id,
fed_id,
address,
city,
state,
postal_code,
business_name,
state_id,
incorp_date
)
AS
SELECT cst.cust_id,
cst.fed_id,
cst.address,
cst.city,
cst.state,
cst.postal_code,
bsn.name,
bsn.state_id,
bsn.incorp_date
FROM customer cst INNER JOIN business bsn
ON cst.cust_id = bsn.cust_id
WHERE cust_type_cd = 'B';

Você pode usar essa view para atualizar a tabela customer ou a tabela
business, como demonstram as instruções a seguir:
mysql> UPDATE business_customer_vw
-> SET postal_code = '99999'
-> WHERE cust_id = 10;
Query OK, 1 row affected (0.09 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql> UPDATE business_customer_vw


-> SET incorp_date = '2008-11-17'
-> WHERE cust_id = 10;
Query OK, 1 row affected (0.11 sec)
Rows matched: 1 Changed: 1 Warnings: 0
A primeira instrução modi ca a coluna customer.postal_code, enquanto a
segunda modi ca a coluna business.incorp_date. Você pode estar se
perguntando o que aconteceria se você tentasse atualizar as colunas de
ambas as tabelas em uma única instrução, então, vamos descobrir:
mysql> UPDATE business_customer_vw
-> SET postal_code = '88888', incorp_date = '2008-10-31'
-> WHERE cust_id = 10;
ERROR 1393 (HY000): Can not modify more than one base table
through a join view
'bank.business_customer_vw'

Como você pode ver, é permitido modi car as duas tabelas subjacentes,
contanto que você não tente fazê-lo em uma única instrução. Agora,
vamos tentar inserir dados de um cliente novo (cust_id = 99) em ambas
as tabelas:
mysql> INSERT INTO business_customer_vw
-> (cust_id, fed_id, address, city, state, postal_code)
-> VALUES (99, '04-9999999', '99 Main St.', 'Peabody', 'MA',
'01975');
Query OK, 1 row affected (0.07 sec)

mysql> INSERT INTO business_customer_vw


-> (cust_id, business_name, state_id, incorp_date)
-> VALUES (99, 'Ninety-Nine Restaurant', '99-999-999', '1999-
01-01');
ERROR 1393 (HY000): Can not modify more than one base table
through a join view
'bank.business_customer_vw'

A primeira instrução, que tenta inserir dados na tabela customer,


funciona bem, mas a segunda, que tenta inserir uma linha na tabela
business, gera uma exceção. A segunda instrução falha porque ambas as
tabelas incluem uma coluna cust_id, mas a coluna cust_id da de nição
da view referencia a coluna customer.cust_id. Portanto, não é possível
inserir dados na tabela business usando a de nição de view anterior.
O Oracle Database e o SQL Server também permitem que
dados sejam inseridos e atualizados por meio de views, mas,
como no MySQL, há várias restrições. No entanto, se estiver
pensando em escrever código em PL/SQL ou Transanct-SQL,
você pode usar uma funcionalidade chamada instead-of triggers
(gatilhos em-vez-de), que, essencialmente, permitem interceptar
instruções insert, update e delete feitas em uma view e
escrever código personalizado para incorporar as mudanças.
Sem esse tipo de funcionalidade, existiriam restrições demais
para tornar a atualização por views uma estratégia praticável no
caso de aplicações não-triviais.

Teste seu conhecimento


Teste sua compreensão das views resolvendo os seguintes exercícios.
Quando tiver terminado, compare suas soluções com as mostradas no
apêndice C.

Exercício 14.1
Crie uma view que consulte a tabela employee e gere a seguinte saída
quando consultada sem uma cláusula where:
+-----------------+------------------+
| supervisor_name | employee_name |
+-----------------+------------------+
| NULL | Michael Smith |
| Michael Smith | Susan Barker |
| Michael Smith | Robert Tyler |
| Robert Tyler | Susan Hawthorne |
| Susan Hawthorne | John Gooding |
| Susan Hawthorne | Helen Fleming |
| Helen Fleming | Chris Tucker |
| Helen Fleming | Sarah Parker |
| Helen Fleming | Jane Grossman |
| Susan Hawthorne | Paula Roberts |
| Paula Roberts | Thomas Ziegler |
| Paula Roberts | Samantha Jameson |
| Susan Hawthorne | John Blake |
| John Blake | Cindy Mason |
| John Blake | Frank Portman |
| Susan Hawthorne | Theresa Markham |
| Theresa Markham | Beth Fowler |
| Theresa Markham | Rick Tulman |
+-----------------+------------------+
18 rows in set (1.47 sec)

Exercício 14.2
O presidente do banco gostaria de ter um relatório mostrando o nome e a
cidade de cada lial, junto com o saldo total de todas as contas abertas na
lial. Crie uma view para gerar os dados.
CAPÍTULO 15
Metadados

Além de armazenar todos os dados que os usuários inserem em um banco


de dados, um servidor de banco de dados também precisa armazenar
informações sobre todos os objetos de banco de dados (tabelas, views,
índices etc.) que foram criados para armazenar esses dados. O servidor de
banco de dados armazena essas informações, como era de se esperar, em
um banco de dados. Este capítulo discute como e onde essas informações,
conhecidas como metadados, são armazenadas, como você pode acessá-las
e como pode usá-las para construir sistemas exíveis.

Dados sobre dados


Metadados são essencialmente dados a respeito de dados. Cada vez que
você cria um objeto de banco de dados, o servidor de banco de dados
precisa registrar vários fragmentos de informação. Por exemplo, se você
fosse criar uma tabela com múltiplas colunas, uma restrição de chave
primária, três índices e uma restrição de chave estrangeira, o servidor de
banco de dados precisaria armazenar as seguintes informações:
• Nome da tabela
• Informação de armazenamento da tabela (espaço da tabela, tamanho
inicial etc.)
• Mecanismo de armazenamento
• Nomes das colunas
• Tipos de dados das colunas
• Valores-padrão das colunas
• Restrições de colunas NOT Null
• Colunas de chave primária
• Nome da chave primária
• Nome do índice de chave primária
• Nomes dos índices
• Tipos dos índices (B-tree, mapa de bits)
• Colunas indexadas
• Ordenação das colunas de índice (ascendente ou descendente)
• Informação de armazenamento dos índices
• Nome da chave estrangeira
• Colunas da chave estrangeira
• Tabela/colunas associadas à chave estrangeira
Esses dados são conhecidos coletivamente como dicionário de dados ou
catálogo de sistema. O servidor de banco de dados precisa armazenar esses
dados de forma persistente e precisa ser capaz de recuperar rapidamente
esses dados para veri car e executar instruções SQL. Além disso, o
servidor de banco de dados deve proteger esses dados, de forma que sejam
modi cados apenas por meio de um mecanismo apropriado, como uma
instrução alter table.
Apesar de existirem padrões de troca de metadados entre diferentes
servidores, cada servidor de banco de dados usa um mecanismo diferente
para publicar metadados, tais como:
• Um conjunto de views, como as views user_tables e all_constraints
do Oracle Database.
• Um conjunto de procedimentos armazenados de sistema (system
stored procedures), como o procedimento sp_tables do SQL Server ou
o pacote dbms_metadata do Oracle Database.
• Um banco de dados especial, como o banco de dados
information_schema do MySQL.

Além dos procedimentos armazenados de sistema, que são um vestígio da


linhagem do Sybase, o SQL Server também inclui um esquema especial
chamado information_schema que é fornecido automaticamente dentro de
cada banco de dados. Tanto o MySQL quanto o SQL Server fornecem essa
interface para se conformarem ao padrão ANSI SQL:2003. O restante
deste capítulo discute os objetos information_schema que estão
disponíveis no MySQL e no SQL Server.
Information_schema
Todos os objetos disponíveis dentro do banco de dados
information_schema (ou schema, no caso do SQL Server) são views.
Diferentemente do utilitário describe, que usei em vários capítulos como
uma forma de mostrar a estrutura de várias tabelas e views, as views
dentro de information_schema podem ser consultadas e, portanto, usadas
programaticamente (falaremos sobre isso mais tarde). Veja um exemplo
que demonstra como recuperar os nomes de todas as tabelas do banco de
dados bank:
mysql> SELECT table_name, table_type
-> FROM information_schema.tables
-> WHERE table_schema = 'bank'
-> ORDER BY 1;
+----------------------+------------+
| table_name | table_type |
+----------------------+------------+
| account | BASE TABLE |
| branch | BASE TABLE |
| branch_activity_vw | VIEW |
| business | BASE TABLE |
| business_customer_vw | VIEW |
| customer | BASE TABLE |
| customer_vw | VIEW |
| department | BASE TABLE |
| employee | BASE TABLE |
| employee_vw | VIEW |
| individual | BASE TABLE |
| nh_customer_vw | VIEW |
| officer | BASE TABLE |
| product | BASE TABLE |
| product_type | BASE TABLE |
| transaction | BASE TABLE |
+----------------------+------------+
16 rows in set (0.02 sec)

Além das inúmeras tabelas que criamos no capítulo 2, os resultados


mostram as views que foram criadas no capítulo 14. Se quiser excluir as
views dos resultados, basta adicionar outra condição à cláusula where:
mysql> SELECT table_name, table_type
-> FROM information_schema.tables
-> WHERE table_schema = 'bank' AND table_type = 'BASE TABLE'
-> ORDER BY 1;
+--------------+------------+
| table_name | table_type |
+--------------+------------+
| account | BASE TABLE |
| branch | BASE TABLE |
| business | BASE TABLE |
| customer | BASE TABLE |
| department | BASE TABLE |
| employee | BASE TABLE |
| individual | BASE TABLE |
| officer | BASE TABLE |
| product | BASE TABLE |
| product_type | BASE TABLE |
| transaction | BASE TABLE |
+--------------+------------+
11 rows in set (0.01 sec)

Se estiver interessado apenas nas informações a respeito das views, você


pode consultar information_schema.views. Além dos nomes das views,
você pode recuperar informações adicionais, como uma ag (sinalização)
que mostra se uma view é atualizável:
mysql> SELECT table_name, is_updatable
-> FROM information_schema.views
-> WHERE table_schema = 'bank'
-> ORDER BY 1;
+----------------------+--------------+
| table_name | is_updatable |
+----------------------+--------------+
| branch_activity_vw | NO |
| business_customer_vw | YES |
| customer_vw | YES |
| employee_vw | YES |
| nh_customer_vw | YES |
+----------------------+--------------+
5 rows in set (1.83 sec)

Além disso, você pode recuperar a consulta subjacente à view usando a


coluna view_definition, contanto que a consulta seja su cientemente
pequena (4.000 caracteres ou menos no MySQL).
Informações de coluna tanto para as tabelas quanto para as views estão
disponíveis por meio da view columns. A consulta a seguir mostra as
informações de coluna da tabela account:
A coluna ordinal_position é incluída apenas como um meio de recuperar
as colunas na ordem em que foram adicionadas à tabela.
Você pode recuperar informações sobre os índices de uma tabela por meio
da view information_schema.statistics, como demonstra a consulta a
seguir, que recupera as informações dos índices construídos na tabela
account:

A tabela account tem um total de cinco índices, um com duas colunas


(acc_bal_idx) e outro sendo um índice exclusivo (PRIMARY).
Você pode recuperar os diferentes tipos de restrições (chave estrangeira,
chave primária, exclusiva) que foram criadas por meio da view
information_schema.table_constraints. Aqui está uma consulta que
recupera todas as restrições do esquema bank:

A tabela 15.1
mostra o conjunto completo das views de
information_schema que estão disponíveis na versão 5.1 do MySQL.

Tabela 15.1 – Views de information_schema


Nome da view Fornece informações sobre...

Schemata Bancos de dados


Nome da view Fornece informações sobre...

Tables Tabelas e views

Columns Colunas de tabelas e views

Statistics Índices

Quem tem privilégios sobre quais objetos de


User_Privileges
esquema

Quem tem privilégios sobre quais bancos de


Schema_Privileges
dados

Table_Privileges Quem tem privilégios sobre quais tabelas

Quem tem privilégios sobre quais colunas de


Column_Privileges
quais tabelas

Quais conjuntos de caracteres estão


Character_Sets
disponíveis

Quais collations estão disponíveis para quais


Collations
conjuntos de caracteres

Quais conjuntos de caracteres estão


Collation_Character_Set_Applicability
disponíveis para quais collations

As restrições exclusivas, de chave estrangeira


Table_Constraints
e de chave primária

As restrições associadas a cada uma das


Key_Column_Usage
colunas de chave

Rotinas armazenadas (procedimentos e


Routines
funções)

Views Views

Triggers Gatilhos de tabelas

Plugins Plug-ins do servidor

Engines Mecanismos de armazenamento disponíveis

Partitions Partições de tabela


Nome da view Fornece informações sobre...

Events Eventos agendados

Process_List Processos em execução

Referential_Constraints Chaves estrangeiras

Global_Status O estado do servidor

Session_Status O estado da sessão

Global_Variables Variáveis de estado do servidor

Session_Variables Variáveis de estado da sessão

Parâmetros dos procedimentos armazenados e


Parameters
das funções

Profiling O perfil dos usuários

Apesar de algumas dessas views, como engines, events e plugins, serem


especí cas do MySQL, muitas delas também estão disponíveis no SQL
Server. Se estiver usando o Oracle Database, consulte o guia online Oracle
Database Reference Guide
(http://www.oracle.com/pls/db111/portal.all_books) para obter informações
sobre user_, all_ e dba_views.

Trabalhando com metadados


Como já havia mencionado, a capacidade de recuperar informações sobre
seus objetos de esquema por meio de consultas SQL abre algumas
possibilidades interessantes. Esta seção mostra várias maneiras pelas
quais você pode usar os metadados em suas aplicações.

Scripts de geração de esquemas


Apesar de algumas equipes de projeto incluírem um projetista de bancos
de dados em tempo integral que supervisiona o projeto e a
implementação do banco de dados, muitos projetos adotam a abordagem
“projeto-por-comitê”, permitindo que várias pessoas criem os objetos do
banco de dados. Após várias semanas ou meses de desenvolvimento, você
pode precisar gerar um script que criará as várias tabelas, índices, views e
outros objetos que a equipe tenha implantado. Embora haja uma grande
variedade de ferramentas e utilitários que gerem esses scripts, você
também pode consultar as views de information_schema e gerar o script
você mesmo.
Como exemplo, vamos construir um script que criará a tabela
bank.customer. Con ra o comando usado para construir a tabela,
extraído do script usado para construir o banco de dados de exemplo:
create table customer
(cust_id integer unsigned not null auto_increment,
fed_id varchar(12) not null,
cust_type_cd enum('I','B') not null,
address varchar(30),
city varchar(20),
state varchar(20),
postal_code varchar(10),
constraint pk_customer primary key (cust_id)
);

Apesar de ser mais fácil gerar o script com o auxílio de uma linguagem
procedural (por exemplo, Transact-SQL ou Java), sendo este um livro
sobre SQL, escreverei uma única consulta para gerar a instrução create
table. O primeiro passo é consultar a tabela information_schema.columns
para recuperar as informações sobre as colunas da tabela:
mysql> SELECT 'CREATE TABLE customer (' create_table_statement
-> UNION ALL
-> SELECT cols.txt
-> FROM
-> (SELECT concat(' ',column_name, ' ', column_type,
-> CASE
-> WHEN is_nullable = 'NO' THEN ' not null'
-> ELSE ''
-> END,
-> CASE
-> WHEN extra IS NOT NULL THEN concat(' ', extra)
-> ELSE ''
-> END,
-> ',') txt
-> FROM information_schema.columns
-> WHERE table_schema = 'bank' AND table_name = 'customer'
-> ORDER BY ordinal_position
-> ) cols
-> UNION ALL
-> SELECT ')';

Bem, isso nos deixa bem próximos do objetivo. Agora, precisamos apenas
adicionar consultas às views table_constraints e key_column_usage para
recuperar as informações sobre a restrição de chave primária:
mysql> SELECT 'CREATE TABLE customer (' create_table_statement
-> UNION ALL
-> SELECT cols.txt
-> FROM
-> (SELECT concat(' ',column_name, ' ', column_type,
-> CASE
-> WHEN is_nullable = 'NO' THEN ' not null'
-> ELSE ''
-> END,
-> CASE
-> WHEN extra IS NOT NULL THEN concat(' ', extra)
-> ELSE ''
-> END,
-> ',') txt
-> FROM information_schema.columns
-> WHERE table_schema = 'bank' AND table_name = 'customer'
-> ORDER BY ordinal_position
-> ) cols
-> UNION ALL
-> SELECT concat(' constraint primary key (')
-> FROM information_schema.table_constraints
-> WHERE table_schema = 'bank' AND table_name = 'customer'
-> AND constraint_type = 'PRIMARY KEY'
-> UNION ALL
-> SELECT cols.txt
-> FROM
-> (SELECT concat(CASE WHEN ordinal_position > 1 THEN ' ,'
-> ELSE ' ' END, column_name) txt
-> FROM information_schema.key_column_usage
-> WHERE table_schema = 'bank' AND table_name = 'customer'
-> AND constraint_name = 'PRIMARY'
-> ORDER BY ordinal_position
-> ) cols
-> UNION ALL
-> SELECT ' )'
-> UNION ALL
-> SELECT ')';

Para veri car se a instrução está bem-formada, colarei a saída da consulta


na ferramenta mysql (mudei o nome da tabela para customer2 para que ela
não sobreponha nossa outra tabela):
mysql> CREATE TABLE customer2 (
-> cust_id int(10) unsigned not null auto_increment,
-> fed_id varchar(12) not null ,
-> cust_type_cd enum('I','B') not null ,
-> address varchar(30) ,
-> city varchar(20) ,
-> state varchar(20) ,
-> postal_code varchar(10) ,
-> constraint primary key (
-> cust_id
-> )
-> );
Query OK, 0 rows affected (0.14 sec)
A instrução executou sem erros, e agora há uma tabela customer2 no
banco de dados bank. Para que a consulta possa gerar uma instrução
create table que funcione para qualquer tabela, seria necessário mais
trabalho de nossa parte (como tratar índices e restrições de chave
estrangeira), mas deixarei isso como um exercício para o leitor.

Veri cação de implantação


Muitas empresas permitem uma janela para a manutenção do banco de
dados, na qual objetos existentes no banco de dados podem ser
administrados (como adicionar/excluir partições) e novos objetos e
códigos de esquema podem ser implantados. Após a execução dos scripts
de implantação, é uma boa ideia executar um script de veri cação para
garantir que os novos objetos de esquema estejam em seus devidos
lugares, com colunas, índices e chaves primárias apropriados, e assim por
diante. Aqui está uma consulta que retorna o número de colunas, o
número de índices e o número de restrições de chave primária (0 ou 1) de
cada tabela no esquema bank:
Você poderia executar essa instrução antes e depois da implantação e,
então, veri car quaisquer diferenças entre os dois conjuntos antes de
declarar que a implantação foi bem-sucedida.

Geração dinâmica de SQL


Algumas linguagens, como a PL/SQL do Oracle e a Transact-SQL da
Microsoft, são superconjuntos da linguagem SQL, o que signi ca que
elas incluem em sua sintaxe as instruções SQL junto com as construções
procedurais usuais, como “if-then-else” e “while”. Outras linguagens,
como Java, incluem a capacidade de interfacear com um banco de dados
relacional, mas não incluem instruções SQL em sua sintaxe, o que
signi ca que todas as instruções SQL devem ser contidas dentro de
strings.
Portanto, a maioria dos servidores de banco de dados relacionais,
incluindo o SQL Server, o Oracle Database e o MySQL, permite que
instruções SQL sejam enviadas ao servidor como strings. O envio de
strings para um mecanismo de banco de dados, em vez da utilização de
sua interface SQL, é geralmente conhecido como execução dinâmica de
SQL. A linguagem PL/SQL do Oracle, por exemplo, inclui um comando
execute immediate, que você pode usar para enviar uma string para ser
executada, enquanto o SQL Server inclui um procedimento armazenado
de sistema chamado sp_executesql que executa instruções SQL
dinamicamente.
O MySQL fornece as instruções prepare, execute e deallocate para
permitir a execução dinâmica de SQL. Veja um exemplo simples:
mysql> SET @qry = 'SELECT cust_id, cust_type_cd, fed_id FROM
customer';
Query OK, 0 rows affected (0.07 sec)
mysql> PREPARE dynsql1 FROM @qry;
Query OK, 0 rows affected (0.04 sec)
Statement prepared
mysql> EXECUTE dynsql1;
+---------+--------------+-------------+
| cust_id | cust_type_cd | fed_id |
+---------+--------------+-------------+
| 1 | I | 111-11-1111 |
| 2 | I | 222-22-2222 |
| 3 | I | 333-33-3333 |
| 4 | I | 444-44-4444 |
| 5 | I | 555-55-5555 |
| 6 | I | 666-66-6666 |
| 7 | I | 777-77-7777 |
| 8 | I | 888-88-8888 |
| 9 | I | 999-99-9999 |
| 10 | B | 04-1111111 |
| 11 | B | 04-2222222 |
| 12 | B | 04-3333333 |
| 13 | B | 04-4444444 |
| 99 | I | 04-9999999 |
+---------+--------------+-------------+
14 rows in set (0.27 sec)
mysql> DEALLOCATE PREPARE dynsql1;
Query OK, 0 rows affected (0.00 sec)

A instrução set simplesmente atribui uma string à variável qry, que é


enviada ao mecanismo de banco de dados (para análise, veri cação de
segurança e otimização) usando a instrução prepare. Após a execução da
instrução por meio de uma chamada a execute, a instrução deve ser
fechada usando deallocate prepare, que libera qualquer recurso de banco
de dados (cursores, por exemplo) utilizado durante a execução.
O próximo exemplo mostra como você poderia executar uma consulta
que inclui curingas, para que certas condições possam ser especi cadas
durante o tempo de execução:

Nessa sequência, a consulta contém um curinga (o ? no nal da


instrução) para que o código do produto seja enviado em tempo de
execução. A instrução é preparada uma vez e, então, executada duas vezes,
uma para o código de produto 'CHK' e outra para o código de produto 'SAV',
após as quais a instrução é fechada.
O que, você pode pensar, isso tem a ver com metadados? Bem, se você vai
usar SQL dinâmico para consultar uma tabela, por que não construir a
string de consulta usando metadados em vez de codi car na mão a
de nição da tabela? O exemplo a seguir gera a mesma string SQL
dinâmica do exemplo anterior, mas recupera os nomes das colunas a
partir da view information_schema.columns:
mysql> SELECT concat('SELECT ',
-> concat_ws(',', cols.col1, cols.col2, cols.col3, cols.col4,
-> cols.col5, cols.col6, cols.col7, cols.col8, cols.col9),
-> ' FROM product WHERE product_cd = ?')
-> INTO @qry
-> FROM
-> (SELECT
-> max(CASE WHEN ordinal_position = 1 THEN column_name
-> ELSE NULL END) col1,
-> max(CASE WHEN ordinal_position = 2 THEN column_name
-> ELSE NULL END) col2,
-> max(CASE WHEN ordinal_position = 3 THEN column_name
-> ELSE NULL END) col3,
-> max(CASE WHEN ordinal_position = 4 THEN column_name
-> ELSE NULL END) col4,
-> max(CASE WHEN ordinal_position = 5 THEN column_name
-> ELSE NULL END) col5,
-> max(CASE WHEN ordinal_position = 6 THEN column_name
-> ELSE NULL END) col6,
-> max(CASE WHEN ordinal_position = 7 THEN column_name
-> ELSE NULL END) col7,
-> max(CASE WHEN ordinal_position = 8 THEN column_name
-> ELSE NULL END) col8,
-> max(CASE WHEN ordinal_position = 9 THEN column_name
-> ELSE NULL END) col9
-> FROM information_schema.columns
-> WHERE table_schema = 'bank' AND table_name = 'product'
-> GROUP BY table_name
-> ) cols;
Query OK, 1 row affected (0.02 sec)
mysql> SELECT @qry;
+-----------------------------------------------------------------
--------------+
| @qry |
+-----------------------------------------------------------------
--------------+
| SELECT product_cd,name,product_type_cd,date_offered,date_retired
FROM product
WHERE product_cd = ? |
+-----------------------------------------------------------------
--------------+
1 row in set (0.00 sec)
mysql> PREPARE dynsql3 FROM @qry;
Query OK, 0 rows affected (0.01 sec)
Statement prepared
mysql> SET @prodcd = 'MM';
Query OK, 0 rows affected (0.00 sec)
mysql> EXECUTE dynsql3 USING @prodcd;
+------------+----------------------+-----------------+-----------
---+--------------+
| product_cd | name | product_type_cd | date_offered |
date_retired |
+------------+----------------------+-----------------+-----------
---+--------------+
| MM | money market account | ACCOUNT | 2004-01-01 | NULL |
+------------+----------------------+-----------------+-----------
---+--------------+
1 row in set (0.00 sec)
mysql> DEALLOCATE PREPARE dynsql3;
Query OK, 0 rows affected (0.00 sec)

A consulta acessa as primeiras nove colunas da tabela product, gera uma


string de consulta usando as funções concat e concat_ws e atribui a string
à variável qry. A string de consulta é, então, executada como
anteriormente.
Normalmente, seria melhor gerar uma consulta usando uma
linguagem procedural que inclua construções de loop, como
Java, PL/SQL, Transact-SQL ou a Stored Procedure Language,
do MySQL. No entanto, minha intenção foi demonstrar um
exemplo de SQL puro, então eu tive que limitar a quantidade
de colunas recuperadas a um número razoável, que, nesse
exemplo, foi de nove colunas
Teste seu conhecimento
Os exercícios a seguir foram desenvolvidos para testar sua compreensão
de metadados. Ao terminá-los, consulte o apêndice C para veri car as
soluções.

Exercício 15.1
Escreva uma consulta que liste todos os índices do esquema bank. Inclua
os nomes das tabelas.

Exercício 15.2
Escreva uma consulta que gere uma saída que possa ser usada para criar
todos os índices da tabela bank.employee. A saída deve ter o seguinte
formato:
"ALTER TABLE <nome_tabela> ADD INDEX <nome_índice>
(<lista_de_colunas>)"
APÊNDICE A
Diagrama ER do banco de dados de exemplo

A gura A-1 mostra um diagrama entidade-relacionamento (E-R) do


banco de dados de exemplo usado neste livro. Como o nome sugere, o
diagrama mostra as entidades, ou tabelas, no banco de dados junto com
os relacionamentos de chave estrangeira entre as tabelas. Aqui estão
algumas dicas para ajudá-lo a entender a notação:
• Cada retângulo representa uma tabela, com o nome da tabela acima
do canto superior esquerdo do retângulo. As colunas de chave
primária são listadas primeiro e são separadas por uma linha das
colunas não-chave. Colunas não-chave são listadas abaixo da linha, e
as colunas de chave estrangeira são marcadas com “(FK)”.
• Linhas entre as tabelas representam relacionamentos de chave
estrangeira. As marcações em cada um dos lados das linhas
representam as quantidades permitidas, que podem ser zero (0), um
(1) ou muitos ([símbolo]). Por exemplo, se observar o relacionamento
entre as tabelas account e product, você diria que uma conta deve
pertencer a exatamente um produto, mas um produto pode ter zero,
uma ou mais contas.
Para mais informações sobre a modelagem entidade-relacionamento,
acesse http://en.wikipedia.org/wiki/Entity-relationship_model.
APÊNDICE B
Extensões do MySQL para a linguagem SQL

Como este livro usa o servidor MySQL em todos os exemplos, pensei que
seria útil para os leitores que estão planejando continuar usando o
MySQL incluir um apêndice sobre as extensões do MySQL para a
linguagem SQL. Este apêndice explora algumas das extensões do MySQL
às instruções select, insert, update e delete que podem ser muito úteis
em determinadas situações.

Extensões para a instrução select


A implementação que o MySQL faz da instrução select inclui duas
cláusulas adicionais, que são discutidas nas subseções seguintes.

Cláusula limit
Em algumas situações, você pode não estar interessado em todas as linhas
retornadas por uma consulta. Por exemplo, você pode construir uma
consulta que retorne todos os caixas do banco junto com o número de
contas abertas por cada caixa. Se a sua razão para executar a consulta é
determinar os três caixas que mais abriram contas, para que eles possam
receber um prêmio do banco, você não precisa necessariamente saber
quem cou em quarto, quinto e assim por diante. Para auxiliar nesse tipo
de situação, a instrução select do MySQL inclui a cláusula limit, que
permite restringir o número de linhas retornadas por uma consulta.
Para demonstrar a utilidade da cláusula limit, começarei construindo
uma consulta para mostrar o número de contas abertas por cada caixa do
banco:
mysql> SELECT open_emp_id, COUNT(*) how_many
-> FROM account
-> GROUP BY open_emp_id;
+-------------+----------+
| open_emp_id | how_many |
+-------------+----------+
| 1 | 8 |
| 10 | 7 |
| 13 | 3 |
| 16 | 6 |
+-------------+----------+
4 rows in set (0.31 sec)

O resultado mostra que quatro caixas diferentes abriram contas. Se quiser


limitar o conjunto-resultado a apenas três registros, você pode adicionar a
cláusula limit, especi cando que apenas três registros devem ser
retornados:
mysql> SELECT open_emp_id, COUNT(*) how_many
-> FROM account
-> GROUP BY open_emp_id
-> LIMIT 3;
+-------------+----------+
| open_emp_id | how_many |
+-------------+----------+
| 1 | 8 |
| 10 | 7 |
| 13 | 3 |
+-------------+----------+
3 rows in set (0.06 sec)

Graças à cláusula limit (a quarta linha da consulta), o conjunto-resultado


agora inclui exatamente três registros, e o quarto caixa (ID de funcionário
16) foi descartado do conjunto-resultado.

Combinando a cláusula limit com a cláusula order by


Apesar de a consulta anterior retornar três registros, existe um pequeno
problema: você não disse quais são os três registros que lhe interessam. Se
estiver procurando por três registros especí cos, como os três caixas que
mais abriram contas bancárias, será preciso usar a cláusula limit junto
com a cláusula order by, como em:
mysql> SELECT open_emp_id, COUNT(*) how_many
-> FROM account
-> GROUP BY open_emp_id
-> ORDER BY how_many DESC
-> LIMIT 3;
+-------------+----------+
| open_emp_id | how_many |
+-------------+----------+
| 1 | 8 |
| 10 | 7 |
| 16 | 6 |
+-------------+----------+
3 rows in set (0.03 sec)

A diferença entre essa consulta e a consulta anterior é que a cláusula


limit agora está sendo aplicada a um conjunto ordenado, resultando nos
três caixas com mais contas abertas sendo incluídos no conjunto-
resultado nal. A menos que você esteja interessado em enxergar apenas
uma amostra arbitrária de registros, normalmente você desejará usar uma
cláusula order by junto com uma cláusula limit.
A cláusula limit é aplicada depois que a ltragem, o
agrupamento e a ordenação ocorreram, então ela nunca
mudará o resultado de sua instrução select, a não ser pelo fato
de restringir o número de registros retornados pela instrução.

O segundo parâmetro opcional da cláusula limit


Em vez de encontrar os três caixas do topo da lista, digamos que seu
objetivo seja identi car todos, exceto os dois caixas do topo da lista (em
vez de dar prêmios aos melhores, o banco enviará alguns dos caixas
menos produtivos para um treinamento de assertividade). Para esses tipos
de situação, a cláusula limit permite um segundo parâmetro opcional:
quando são usados dois parâmetros, o primeiro indica em qual registro
será iniciada a adição de registros no conjunto-resultado nal, e o
segundo indica quantos registros deverão ser incluídos. Ao especi car um
registro pelo número, lembre-se de que o MySQL reconhece o primeiro
registro como sendo o registro 0. Portanto, se o seu objetivo é encontrar o
terceiro melhor em desempenho, você pode fazer o seguinte:
mysql> SELECT open_emp_id, COUNT(*) how_many
-> FROM account
-> GROUP BY open_emp_id
-> ORDER BY how_many DESC
-> LIMIT 2, 1;
+-------------+----------+
| open_emp_id | how_many |
+-------------+----------+
| 16 | 6 |
+-------------+----------+
1 row in set (0.00 sec)

Nesse exemplo, os registros 0 e 1 são descartados, e registros são incluídos


iniciando-se no registro 2. Como o segundo parâmetro da cláusula limit é
1, apenas um registro é incluído.

Se você quiser começar a partir do registro 2 e incluir todos os registros


restantes, você pode atribuir um valor alto o su ciente ao segundo
parâmetro da cláusula limit para garantir que todos os registros serão
incluídos. Portanto, se você não sabe quantos são os caixas que abriram
contas de clientes, você poderia fazer algo assim para encontrar todos os
caixas, exceto os dois com melhor desempenho:
mysql> SELECT open_emp_id, COUNT(*) how_many
-> FROM account
-> GROUP BY open_emp_id
-> ORDER BY how_many DESC
-> LIMIT 2, 999999999;
+-------------+----------+
| open_emp_id | how_many |
+-------------+----------+
| 16 | 6 |
| 13 | 3 |
+-------------+----------+
2 rows in set (0.00 sec)

Nessa versão da consulta, os registros 0 e 1 são descartados, e cerca de


999.999.999 registros serão incluídos, iniciando no segundo registro (nesse
caso, só existem mais dois registros, mas é melhor exagerar do que correr
o risco de excluir registros válidos do conjunto-resultado nal porque
você subestimou a quantidade de registros).

Consultas de classi cação


Consultas que usam a cláusula order by em conjunto com a cláusula
limit podem ser chamadas de consultas de classi cação, pois permitem
classi car seus dados. Apesar de eu ter mostrado como classi car caixas
de banco pelo número de contas abertas, as consultas de classi cação são
usadas para responder inúmeros tipos diferentes de questões de negócio,
tais como:
• Quem foram os cinco melhores vendedores de 2005?
• Quem foi o terceiro maior realizador de home-runs na história do
baseball?
• Além da Bíblia Sagrada e de O livro vermelho, quem são os outros 98
livros best-sellers de todos os tempos?
• Quem são os dois sabores de sorvete com índices mais baixos de
vendas?
Até agora, mostrei como encontrar os três melhores caixas, o terceiro
melhor caixa e todos, exceto os dois melhores caixas. Se eu quiser fazer
algo semelhante ao quarto exemplo (ou seja, encontrar os piores caixas),
preciso apenas reverter a ordem de classi cação para que os resultados
prossigam do menor número de contas abertas para o maior número de
contas abertas, como em:
mysql> SELECT open_emp_id, COUNT(*) how_many
-> FROM account
-> GROUP BY open_emp_id
-> ORDER BY how_many ASC
-> LIMIT 2;
+-------------+----------+
| open_emp_id | how_many |
+-------------+----------+
| 13 | 3 |
| 16 | 6 |
+-------------+----------+
2 rows in set (0.24 sec)

Simplesmente mudando a ordem de classi cação (de ORDER BY how_many


DESC para ORDER BY how_many ASC), a consulta agora retorna os dois piores
caixas. Portanto, ao usar uma cláusula limit com uma ordem de
classi cação ascendente ou descendente, você pode produzir consultas de
classi cação para responder à maioria das questões de negócio.

Cláusula into out le


Se quiser que o resultado de sua consulta seja escrito em um arquivo, você
poderia selecionar os resultados da consulta, copiá-los para o bu er e
colá-los em seu editor de texto favorito. No entanto, se o conjunto-
resultado da consulta for muito grande, ou se a consulta está sendo
executada de dentro de um script, você precisará de uma maneira de
escrever os resultados em um arquivo sem sua intervenção. Para auxiliar
nessas situações, o MySQL inclui a cláusula info out le para permitir
fornecer o nome de um arquivo em que os resultados serão escritos. Veja
um exemplo que escreve os resultados da consulta em um arquivo no
diretório c:\temp:
mysql> SELECT emp_id, fname, lname, start_date
-> INTO OUTFILE 'C:\\TEMP\\emp_list.txt'
-> FROM employee;
Query OK, 18 rows affected (0.20 sec)

Se você se recorda do capítulo 7, a barra invertida é usada para


fazer o escape de outro caractere dentro de uma string.
Portanto, se você for usuário do Windows, você precisará
inserir duas barras invertidas ao criar nomes de caminho.
Em vez de exibir os resultados da consulta na tela, o conjunto-resultado
foi escrito no arquivo emp_list.txt, que se parece com o seguinte:
1 Michael Smith 2001-06-22
2 Susan Barker 2002-09-12
3 Robert Tyler 2000-02-09
4 Susan Hawthorne 2002-04-24
...
16 Theresa Markham 2001-03-15
17 Beth Fowler 2002-06-29
18 Rick Tulman 2002-12-12

A formatação padrão usa tabs (‘\t’) entre colunas e newlines (‘\n’) após
cada registro. Se quiser ter mais controle sobre a formatação dos dados,
várias subcláusulas adicionais estão disponíveis para a cláusula into
outfile. Por exemplo, se quiser que os dados estejam no que é referido
como formatação delimitada por barras, você pode usar a subcláusula
fields para solicitar que o caractere ‘|’ seja colocado entre as colunas,
como em:
mysql> SELECT emp_id, fname, lname, start_date
-> INTO OUTFILE 'C:\\TEMP\\emp_list_delim.txt'
-> FIELDS TERMINATED BY '|'
-> FROM employee;
Query OK, 18 rows affected (0.02 sec)

O MySQL não permite que um arquivo preexistente seja


sobrescrito quando se utiliza into outfile, então você precisará
primeiro remover um arquivo existente caso queira executar a
mesma consulta mais de uma vez.
O conteúdo do arquivo emp_list_delim.txt se parece com o seguinte:
1|Michael|Smith|2001-06-22
2|Susan|Barker|2002-09-12
3|Robert|Tyler|2000-02-09
4|Susan|Hawthorne|2002-04-24
...
16|Theresa|Markham|2001-03-15
17|Beth|Fowler|2002-06-29
18|Rick|Tulman|2002-12-12

Além do formato delimitado por barras, você pode precisar que seus
dados sejam colocados no formato delimitado por vírgulas – nesse caso,
você usaria fields terminated by ','. No entanto, se os dados que estão
sendo escritos no arquivo incluírem vírgulas, usar vírgulas como
separadores de campos pode ser problemático, já que as vírgulas têm uma
chance maior de aparecer dentro das strings do que o caractere de barra.
Considere a seguinte consulta, que escreve um número e duas strings
delimitadas por vírgulas para o arquivo comma1.txt:
mysql> SELECT data.num, data.str1, data.str2
-> INTO OUTFILE 'C:\\TEMP\\comma1.txt'
-> FIELDS TERMINATED BY ','
-> FROM
-> (SELECT 1 num, 'This string has no commas' str1,
-> 'This string, however, has two commas' str2) data;
Query OK, 1 row affected (0.04 sec)

Como a terceira coluna (str2) do arquivo de saída é uma string que


contém vírgulas, você pode pensar que uma aplicação que tente ler o
arquivo comma1.txt encontrará problemas ao dividir cada linha em
colunas, mas o servidor MySQL já tomou medidas nesse sentido. Aqui
está o conteúdo de comma1.txt:
1,This string has no commas,This string\, however\, has two commas

Como você pode ver, as vírgulas dentro da terceira coluna foram


escapadas colocando uma barra invertida antes das duas vírgulas
embutidas na coluna str2. Se você executar a mesma consulta, mas com a
formatação delimitada por barras, as vírgulas não serão escapadas, já que
não há a necessidade disso. Se quiser usar um caractere de escape
diferente – outra vírgula, por exemplo – você pode usar a subcláusula
fields escaped by para especi car o caractere de escape que será usado
em seu arquivo de saída.
Além de especi car os separadores de coluna, você também pode
especi car o caractere usado para separar os diferentes registros em seu
arquivo de dados. Se quiser que os registros do arquivo de saída sejam
separados por algo diferente de um caractere newline, você pode usar a
subcláusula lines, como em:
mysql> SELECT emp_id, fname, lname, start_date
-> INTO OUTFILE 'C:\\TEMP\\emp_list_atsign.txt'
-> FIELDS TERMINATED BY '|'
-> LINES TERMINATED BY '@'
-> FROM employee;
Query OK, 18 rows affected (0.03 sec)

Como não estou usando um caractere newline entre os registros, o


arquivo emp_list_atsign.txt será exibido como uma única e longa linha
de texto, com os registros separados pelo caractere ‘@’:
1|Michael|Smith|2001-06-22@2|Susan|Barker|2002-09-
12@3|Robert|Tyler|2000-02-
09@4|Susan|Hawthorne|2002-04-24@5|John|Gooding|2003-11-
14@6|Helen|Fleming|2004-03-
17@7|Chris|Tucker|2004-09-15@8|Sarah|Parker|2002-12-
02@9|Jane|Grossman|2002-05-
03@10|Paula|Roberts|2002-07-27@11|Thomas|Ziegler|2000-10-
23@12|Samantha|Jameson|2003-
01-08@13|John|Blake|2000-05-11@14|Cindy|Mason|2002-08-
09@15|Frank|Portman|2003-04-
01@16|Theresa|Markham|2001-03-15@17|Beth|Fowler|2002-06-
29@18|Rick|Tulman|2002-12-12@

Se você precisa gerar um arquivo de dados que será carregado em uma


aplicação de planilha de dados ou transportado dentro ou fora da
empresa, a cláusula into outfile fornece exibilidade su ciente para
qualquer formato de arquivo que você precisar.

Instruções insert/update combinadas


Digamos que você deva criar uma tabela que capture informações sobre
quais liais do banco foram visitadas por quais clientes. A tabela precisa
conter o ID do cliente, o ID da lial e uma coluna datetime que indique a
última vez que o cliente visitou a lial. Linhas são adicionadas à tabela
sempre que um cliente visita uma determinada lial, mas se o cliente já
visitou uma, a linha existente deve simplesmente ter sua coluna datetime
atualizada. Veja a de nição da tabela:
CREATE TABLE branch_usage
(branch_id SMALLINT UNSIGNED NOT NULL,
cust_id INTEGER UNSIGNED NOT NULL,
last_visited_on DATETIME,
CONSTRAINT pk_branch_usage PRIMARY KEY (branch_id, cust_id)
);

Além das de nições das três colunas, a tabela branch_usage de ne uma


restrição de chave primária nas colunas branch_id e cust_id. Portanto, o
servidor rejeitará qualquer linha adicionada à tabela cujo par lial/cliente
já exista na tabela.
Digamos que, depois que a tabela foi criada, o cliente de ID 5 visite a
matriz (ID de lial 1) três vezes na primeira semana. Após a primeira
visita, você pode inserir um registro na tabela branch_usage, pois ainda
não existe um registro para o cliente de ID 5 e a lial de ID 1:
mysql> INSERT INTO branch_usage (branch_id, cust_id,
last_visited_on)
-> VALUES (1, 5, CURRENT_TIMESTAMP());
Query OK, 1 row affected (0.02 sec)

No entanto, na próxima vez que o cliente visitar a matriz, você precisará


atualizar o registro existente, em vez de inserir um novo, caso contrário,
você receberá o seguinte erro:
ERROR 1062 (23000): Duplicate entry '1-5' for key 1

Para evitar esse erro, você pode consultar a tabela branch_usage para ver
se um determinado par cliente/ lial já existe e, então, inserir um registro
se um não for encontrado ou atualizar a linha existente, caso ele já exista.
No entanto, para poupar seu tempo, os projetistas do MySQL estenderam
a instrução insert para permitir especi car que uma ou mais colunas
sejam modi cadas se uma instrução insert falhar devido a uma chave
duplicada. A instrução a seguir instrui o servidor a modi car a coluna
last_visited caso o par cliente/ lial já exista na tabela branch_usage:
mysql> INSERT INTO branch_usage (branch_id, cust_id,
last_visited_on)
-> VALUES (1, 5, CURRENT_TIMESTAMP())
-> ON DUPLICATE KEY UPDATE last_visited_on =
CURRENT_TIMESTAMP();
Query OK, 2 rows affected (0.02 sec)

A cláusula on duplicate key permite que essa mesma instrução seja


executada toda vez que o cliente de ID 5 realizar negócios na lial de ID 1.
Se for executada 100 vezes, a primeira execução resultará em uma única
linha sendo adicionada à tabela, e as próximas 99 execuções resultarão na
coluna last_visited sendo alterada para a data e hora atuais. Esse tipo de
operação costuma ser referido como um upsert, por ser uma combinação
de uma instrução insert com uma instrução update.

Substituindo o comando replace


Antes da versão 4.1 do servidor MySQL, operações de upsert eram
realizadas usando o comando replace – uma instrução proprietária
que primeiro apaga uma linha existente se aquele valor de chave
primária já existir na tabela no momento em que se insere uma linha.
Se você estiver usando a versão 4.1 ou superior, você pode escolher
entre o comando replace e o comando insert...on duplicate key ao
realizar operações de upsert.
No entanto, o comando replace realiza uma operação delete quando
valores duplicados são encontrados, o que pode causar um efeito
triplo se você estiver usando o mecanismo de armazenamento InnoDB
com as restrições de chave estrangeira ativadas. Se as restrições foram
criadas com a opção delete cascade, outras linhas em outras tabelas
também serão automaticamente apagadas no momento em que um
comando replace apagar uma linha da tabela-alvo. Por esse motivo,
costuma-se considerar mais seguro usar a cláusula on duplicate key
da instrução insert em vez do comando-legado replace.

Atualizações e exclusões ordenadas


Anteriormente neste apêndice, mostrei como escrever consultas usando a
cláusula limit em conjunto com uma cláusula order by para gerar
classi cações, como os três melhores caixas do banco em termos de
abertura de contas. O MySQL também permite que as cláusula limit e
order by sejam usadas nas instruções update e delete, permitindo, assim,
que você modi que ou remova linhas especí cas de uma tabela com base
em classi cações. Por exemplo, imagine que você precise remover os
registros de uma tabela usada para rastrear o login de clientes no sistema
de on-line banking. A tabela que rastreia o ID de cliente e a data/hora do
login caria assim:
CREATE TABLE login_history
(cust_id INTEGER UNSIGNED NOT NULL,
login_date DATETIME,
CONSTRAINT pk_login_history PRIMARY KEY (cust_id, login_date)
);

A instrução a seguir popula a tabela login_history com alguns dados


gerados a partir de uma junção cruzada entre as tabelas account e
customer, usando a coluna open_date como valor-base para gerar as datas
de login:
mysql> INSERT INTO login_history (cust_id, login_date)
-> SELECT c.cust_id,
-> ADDDATE(a.open_date, INTERVAL a.account_id * c.cust_id
HOUR)
-> FROM customer c CROSS JOIN account a;
Query OK, 312 rows affected (0.03 sec)
Records: 312 Duplicates: 0 Warnings: 0

A tabela, agora, está populada com 312 linhas de dados relativamente


aleatórios. Sua tarefa é olhar esses dados na tabela login_history uma vez
por mês, gerar um relatório para seu gerente, mostrando quem está
usando o sistema de on-line banking e, então, apagar tudo, exceto os 50
registros mais recentes da tabela. Uma abordagem seria escrever uma
consulta usando order by e limit para encontrar o 50º login mais recente,
como em:
mysql> SELECT login_date
-> FROM login_history
-> ORDER BY login_date DESC
-> LIMIT 49,1;
+---------------------+
| login_date |
+---------------------+
| 2004-07-02 09:00:00 |
+---------------------+
1 row in set (0.00 sec)

Munido dessa informação, você pode, então, construir uma instrução


delete que remova todas as linhas cuja coluna login_date seja inferior à
data retornada pela consulta:
mysql> DELETE FROM login_history
-> WHERE login_date < '2004-07-02 09:00:00';
Query OK, 262 rows affected (0.02 sec)

A tabela agora contém os 50 logins mais recentes. No entanto, usando as


extensões do MySQL, você pode obter o mesmo resultado com uma única
instrução delete, usando as cláusulas limit e order by. Depois de
retornar as 312 linhas originais da tabela login_history, você pode
executar o seguinte:
mysql> DELETE FROM login_history
-> ORDER BY login_date ASC
-> LIMIT 262;
Query OK, 262 rows affected (0.05 sec)

Com essa instrução, as linhas são ordenadas pela coluna login_date em


ordem ascendente e, então, as primeiras 262 linhas são apagadas,
deixando apenas as 50 linhas mais recentes.
Nesse exemplo, eu precisava saber o número de linhas na
tabela para construir a cláusula limit (312 linhas originais – 50
linhas restantes = 262 exclusões). Seria melhor se você pudesse
ordenar as linhas em ordem descendente e orientar o servidor a
pular as primeiras 50 linhas, apagando as restantes:
DELETE from login_history
ORDER BY login_date DESC
LIMIT 49, 9999999;

No entanto, o MySQL não permite o uso do segundo


parâmetro opcional quando a cláusula limit é usada em
instruções delete ou update.
Além de excluir dados, você também pode usar as cláusulas limit e order
by ao modi car dados. Por exemplo, se o banco decidir adicionar U$ 100
a cada uma das 10 contas mais antigas para ajudar a manter os clientes
éis, você pode fazer o seguinte:
mysql> UPDATE account
-> SET avail_balance = avail_balance + 100
-> WHERE product_cd IN ('CHK', 'SAV', 'MM')
-> ORDER BY open_date ASC
-> LIMIT 10;
Query OK, 10 rows affected (0.06 sec)
Rows matched: 10 Changed: 10 Warnings: 0

Essa instrução ordena as contas pela data de abertura, em ordem


ascendente, e modi ca os primeiros 10 registros, que, nesse caso, são as 10
contas mais antigas.

Atualizações e exclusões em múltiplas tabelas


Em algumas situações, você pode precisar modi car ou excluir dados de
várias tabelas diferentes para realizar uma tarefa especí ca. Se, por
exemplo, você descobrir que o banco de dados do banco contém um
cliente de teste que sobrou da bateria de testes do sistema, você pode
precisar remover dados das tabelas account, customer e individual.
Para essa seção, criarei um conjunto de clones das tabelas
account, customer e individual chamados account2, customer2
e individual2. Isso servirá para evitar que os dados de exemplo
sejam alterados e para evitar quaisquer problemas com as
restrições de chave estrangeira das tabelas (falaremos sobre isso
mais adiante). Aqui estão as instruções create table usadas
para gerar as três tabelas-clones:
Create table individual2 AS
Select * from individual;
Create table customer2 AS
Select * from customer;
Create table account2 AS
Select * from account;

Se o ID de cliente do cliente-teste for 1, você poderia gerar três instruções


delete individuais, uma para cada tabela, como em:
DELETE FROM account2
WHERE cust_id = 1;
DELETE FROM customer2
WHERE cust_id = 1;
DELETE FROM individual2
WHERE cust_id = 1;

No entanto, em vez de escrever instruções delete individuais, o MySQL


permite escrever uma única instrução delete de múltiplas tabelas, que,
nesse caso, caria assim:
mysql> DELETE account2, customer2, individual2
-> FROM account2 INNER JOIN customer2
-> ON account2.cust_id = customer2.cust_id
-> INNER JOIN individual2
-> ON customer2.cust_id = individual2.cust_id
-> WHERE individual2.cust_id = 1;
Query OK, 5 rows affected (0.02 sec)

Essa instrução remove um total de cinco linhas, uma da tabela


individual2, uma da tabela customer2 e três da tabela account2 (o cliente
de ID 1 tem três contas). A instrução é composta de três cláusulas
separadas:
delete
Especi ca as tabelas-alvo da exclusão.
from
Especi ca as tabelas usadas para identi car as linhas a serem
excluídas. Essa cláusula é idêntica em forma e função à cláusula from
de uma instrução select, e nem todas as tabelas nomeadas aqui
precisam ser incluídas na cláusula delete.
where
Contém condições de ltro usadas para identi car as linhas a serem
excluídas.
A instrução delete de múltiplas tabelas se parece muito com uma
instrução select, exceto que uma cláusula delete é usada em vez da
cláusula select. Se você estiver excluindo linhas de uma única tabela
usando o formato delete de múltiplas tabelas, a diferença torna-se ainda
menos perceptível. Por exemplo, aqui está uma instrução select que
encontra os IDs de conta de todas as contas que pertençam a John
Hayward:
mysql> SELECT account2.account_id
-> FROM account2 INNER JOIN customer2
-> ON account2.cust_id = customer2.cust_id
-> INNER JOIN individual2
-> ON individual2.cust_id = customer2.cust_id
-> WHERE individual2.fname = 'John'
-> AND individual2.lname = 'Hayward';
+------------+
| account_id |
+------------+
| 8 |
| 9 |
| 10 |
+------------+
3 rows in set (0.01 sec)

Se, depois de visualizar os resultados, você decidir apagar todas as três


contas de John da tabela account2, será preciso apenas substituir a
cláusula select da consulta anterior por uma cláusula delete que nomeie
a tabela account2, como em:
mysql> DELETE account2
-> FROM account2 INNER JOIN customer2
-> ON account2.cust_id = customer2.cust_id
-> INNER JOIN individual2
-> ON customer2.cust_id = individual2.cust_id
-> WHERE individual2.fname = 'John'
-> AND individual2.lname = 'Hayward';
Query OK, 3 rows affected (0.01 sec)

Por sorte, isso lhe dá uma ideia melhor da função das cláusula delete e
from dentro de uma instrução delete de múltiplas tabelas. Essa instrução
é funcionalmente idêntica à instrução delete de tabela única a seguir, que
usa uma subconsulta para identi car o ID de cliente de John Hayward:
DELETE FROM account2
WHERE cust_id =
(SELECT cust_id
FROM individual2
WHERE fname = 'John' AND lname = 'Hayward';
Ao usar uma instrução delete de múltiplas tabelas para apagar linhas de
uma única tabela, você está simplesmente escolhendo usar um formato
parecido com o de uma consulta envolvendo junções de tabelas, em vez de
uma instrução delete tradicional que use subconsultas. O verdadeiro
poder das instruções delete de múltiplas tabelas está na capacidade de
apagar dados de múltiplas tabelas em uma única instrução, como
demonstrei na primeira instrução desta seção.
Além da capacidade de apagar linhas de várias tabelas, o MySQL também
oferece a capacidade de modi car linhas em várias tabelas usando uma
atualização de múltiplas tabelas. Digamos que seu banco realize uma
fusão com outro banco, e os bancos de dados de ambos os bancos tenham
IDs de clientes sobrepostos. Seu gerente decide corrigir o problema
incrementando o valor de todos os IDs de cliente de seu banco de dados
em 10.000, para que os dados do outro banco possam ser importados com
segurança. A instrução a seguir mostra como modi car o ID do cliente
cujo ID é 3 ao longo das tabelas individual2, customer2 e account2,
usando uma única instrução:
mysql> UPDATE individual2 INNER JOIN customer2
-> ON individual2.cust_id = customer2.cust_id
-> INNER JOIN account2
-> ON customer2.cust_id = account2.cust_id
-> SET individual2.cust_id = individual2.cust_id + 10000,
-> customer2.cust_id = customer2.cust_id + 10000,
-> account2.cust_id = account2.cust_id + 10000
-> WHERE individual2.cust_id = 3;
Query OK, 4 rows affected (0.01 sec)
Rows matched: 5 Changed: 4 Warnings: 0

Essa instrução modi ca quatro linhas: uma na tabela individual2, uma


na tabela customer2 e duas na tabela account2. A sintaxe da instrução
update de múltiplas tabelas é muito semelhante à da instrução update de
tabela única, exceto que a cláusula update contém múltiplas tabelas e suas
condições de junção correspondentes, em vez de apenas nomear uma
única tabela. Tal como o update de tabela única, a versão de múltiplas
tabelas inclui uma cláusula set, com a diferença de que quaisquer tabelas
referenciadas na cláusula update podem ser modi cadas por meio da
cláusula set.
Se estiver usando o mecanismo de armazenamento InnoDB,
provavelmente você não conseguirá usar instruções update e
delete de múltiplas tabelas no caso de as tabelas envolvidas
terem restrições de chave estrangeira. Isso acontece porque o
mecanismo não garante que as mudanças serão aplicadas em
uma ordem que não viole as restrições. Em vez disso, você deve
usar múltiplas instruções de tabela única na ordem correta
para que as restrições de chave estrangeira não sejam violadas.
APÊNDICE C
Soluções dos exercícios

Capítulo 3
Exercício 3.1
Recupere o ID de funcionário, o nome e o sobrenome de todos os
funcionários do banco. Ordene pelo sobrenome e, então, pelo nome.
mysql> SELECT emp_id, fname, lname
-> FROM employee
-> ORDER BY lname, fname;
+--------+----------+-----------+
| emp_id | fname | lname |
+--------+----------+-----------+
| 2 | Susan | Barker |
| 13 | John | Blake |
| 6 | Helen | Fleming |
| 17 | Beth | Fowler |
| 5 | John | Gooding |
| 9 | Jane | Grossman |
| 4 | Susan | Hawthorne |
| 12 | Samantha | Jameson |
| 16 | Theresa | Markham |
| 14 | Cindy | Mason |
| 8 | Sarah | Parker |
| 15 | Frank | Portman |
| 10 | Paula | Roberts |
| 1 | Michael | Smith |
| 7 | Chris | Tucker |
| 18 | Rick | Tulman |
| 3 | Robert | Tyler |
| 11 | Thomas | Ziegler |
+--------+----------+-----------+
18 rows in set (0.01 sec)

Exercício 3.2
Recupere o ID de conta, o ID de cliente e o saldo disponível de todas as
contas cujo status seja igual a 'ACTIVE' e cujo saldo disponível seja maior
que U$ 2.500.
mysql> SELECT account_id, cust_id, avail_balance
-> FROM account
-> WHERE status = 'ACTIVE'
-> AND avail_balance > 2500;
+------------+---------+---------------+
| account_id | cust_id | avail_balance |
+------------+---------+---------------+
| 3 | 1 | 3000.00 |
| 10 | 4 | 5487.09 |
| 13 | 6 | 10000.00 |
| 14 | 7 | 5000.00 |
| 15 | 8 | 3487.19 |
| 18 | 9 | 9345.55 |
| 20 | 10 | 23575.12 |
| 22 | 11 | 9345.55 |
| 23 | 12 | 38552.05 |
| 24 | 13 | 50000.00 |
+------------+---------+---------------+
10 rows in set (0.00 sec)

Exercício 3.3
Escreva uma consulta à tabela account que retorne os IDs dos
empregados que abriram as contas (use a coluna account.open_emp_id).
Inclua uma única linha para cada funcionário especí co.
mysql> SELECT DISTINCT open_emp_id
-> FROM account;
+-------------+
| open_emp_id |
+-------------+
| 1 |
| 10 |
| 13 |
| 16 |
+-------------+
4 rows in set (0.00 sec)

Exercício 3.4
Preencha as lacunas (denotadas por <#>) dessa consulta para obter os
resultados mostrados em seguida:
mysql> SELECT p.product_cd, a.cust_id, a.avail_balance
-> FROM product p INNER JOIN account <1>
-> ON p.product_cd = <2>
-> WHERE p.<3> = 'ACCOUNT';
+------------+---------+---------------+
| product_cd | cust_id | avail_balance |
+------------+---------+---------------+
| CD | 1 | 3000.00 |
| CD | 6 | 10000.00 |
| CD | 7 | 5000.00 |
| CD | 9 | 1500.00 |
| CHK | 1 | 1057.75 |
| CHK | 2 | 2258.02 |
| CHK | 3 | 1057.75 |
| CHK | 4 | 534.12 |
| CHK | 5 | 2237.97 |
| CHK | 6 | 122.37 |
| CHK | 8 | 3487.19 |
| CHK | 9 | 125.67 |
| CHK | 10 | 23575.12 |
| CHK | 12 | 38552.05 |
| MM | 3 | 2212.50 |
| MM | 4 | 5487.09 |
| MM | 9 | 9345.55 |
| SAV | 1 | 500.00 |
| SAV | 2 | 200.00 |
| SAV | 4 | 767.77 |
| SAV | 8 | 387.99 |
+------------+---------+---------------+
21 rows in set (0.02 sec)

Os valores corretos para <1>, <2> e <3> são:


1. a
2. a.product_cd
3. product_type_cd

Capítulo 4
Exercício 4.1
Quais IDs de transação seriam retornados pelas seguintes condições de
ltro?
txn_date < '2005-02-26' AND (txn_type_cd = 'DBT' OR amount > 100)
IDs de transação 1, 2, 3, 5, 6 e 7.

Exercício 4.2
Quais IDs de transação seriam retornados pelas seguintes condições de
ltro?
account_id IN (101,103) AND NOT (txn_type_cd = 'DBT' OR amount >
100)

IDs de transação 4 e 9.

Exercício 4.3
Construa uma consulta que recupere todas as contas abertas em 2002.
mysql> SELECT account_id, open_date
-> FROM account
-> WHERE open_date BETWEEN '2002-01-01' AND '2002-12-31';
+------------+------------+
| account_id | open_date |
+------------+------------+
| 6 | 2002-11-23 |
| 7 | 2002-12-15 |
| 12 | 2002-08-24 |
| 20 | 2002-09-30 |
| 21 | 2002-10-01 |
+------------+------------+
5 rows in set (0.01 sec)

Exercício 4.4
Construa uma consulta que encontre todos os clientes não-corporativos
cujo sobrenome contenha um a na segunda posição e um e em qualquer
lugar após a.
mysql> SELECT cust_id, lname, fname
-> FROM individual
-> WHERE lname LIKE '_a%e%';
+---------+--------+---------+
| cust_id | lname | fname |
+---------+--------+---------+
| 1 | Hadley | James |
| 9 | Farley | Richard |
+---------+--------+---------+
2 rows in set (0.02 sec)
Capítulo 5
Exercício 5.1
Preencha as lacunas (denotadas por <#>) da seguinte consulta para obter
os resultados mostrados em seguida:

Os valores corretos para <1> e <2> são:


1. branch
2. branch_id

Exercício 5.2
Escreva uma consulta que retorne o ID de conta de cada cliente não-
corporativo (customer.cust_type_cd = 'I') com o ID federal do cliente
(customer.fed_id) e o nome do produto no qual a conta é baseada
(product.name).
Exercício 5.3
Construa uma consulta que encontre todos os funcionários cujo
supervisor esteja alocado em um departamento diferente. Recupere o ID,
o nome e o sobrenome do funcionário.
mysql> SELECT e.emp_id, e.fname, e.lname
-> FROM employee e INNER JOIN employee mgr
-> ON e.superior_emp_id = mgr.emp_id
-> WHERE e.dept_id != mgr.dept_id;
+--------+-------+-----------+
| emp_id | fname | lname |
+--------+-------+-----------+
| 4 | Susan | Hawthorne |
| 5 | John | Gooding |
+--------+-------+-----------+
2 rows in set (0.00 sec)
Capítulo 6
Exercício 6.1
Se o conjunto A = {L M N O P} e o conjunto B = {P Q R S T}, quais
conjuntos são gerados pelas seguintes operações?
• A union B
• A union all B
• A intersect B
• A except B
1. A union B = {L M N O P Q R S T}
2. A union all B = {L M N O P P Q R S T}
3. A intersect B = {P}
4. A except B = {L M N O}

Exercício 6.2
Escreva uma consulta composta que encontre o nome e o sobrenome de
todos os clientes pessoa física, junto com o nome e o sobrenome de todos
os funcionários.
mysql> SELECT fname, lname
-> FROM individual
-> UNION
-> SELECT fname, lname
-> FROM employee;
+----------+-----------+
| fname | lname |
+----------+-----------+
| James | Hadley |
| Susan | Tingley |
| Frank | Tucker |
| John | Hayward |
| Charles | Frasier |
| John | Spencer |
| Margaret | Young |
| Louis | Blake |
| Richard | Farley |
| Michael | Smith |
| Susan | Barker |
| Robert | Tyler |
| Susan | Hawthorne |
| John | Gooding |
| Helen | Fleming |
| Chris | Tucker |
| Sarah | Parker |
| Jane | Grossman |
| Paula | Roberts |
| Thomas | Ziegler |
| Samantha | Jameson |
| John | Blake |
| Cindy | Mason |
| Frank | Portman |
| Theresa | Markham |
| Beth | Fowler |
| Rick | Tulman |
+----------+-----------+
27 rows in set (0.01 sec)

Exercício 6.3
Ordene os resultados do exercício 6.2 pela coluna lname.
mysql> SELECT fname, lname
-> FROM individual
-> UNION ALL
-> SELECT fname, lname
-> FROM employee
-> ORDER BY lname;
+----------+-----------+
| fname | lname |
+----------+-----------+
| Susan | Barker |
| Louis | Blake |
| John | Blake |
| Richard | Farley |
| Helen | Fleming |
| Beth | Fowler |
| Charles | Frasier |
| John | Gooding |
| Jane | Grossman |
| James | Hadley |
| Susan | Hawthorne |
| John | Hayward |
| Samantha | Jameson |
| Theresa | Markham |
| Cindy | Mason |
| Sarah | Parker |
| Frank | Portman |
| Paula | Roberts |
| Michael | Smith |
| John | Spencer |
| Susan | Tingley |
| Chris | Tucker |
| Frank | Tucker |
| Rick | Tulman |
| Robert | Tyler |
| Margaret | Young |
| Thomas | Ziegler |
+----------+-----------+
27 rows in set (0.01 sec)

Capítulo 7
Exercício 7.1
Escreva uma consulta que retorne do 17º ao 25º caractere da string
'Please find the substring in this string'.

Exercício 7.2
Escreva uma consulta que retorne o valor absoluto e o sinal (-1, 0 ou 1)
do número -25,76823. Também retorne o número arredondado à centena
mais próxima.

Exercício 7.3
Escreva uma consulta que retorne apenas o mês da data atual.
mysql> SELECT EXTRACT(MONTH FROM CURRENT_DATE());
+----------------------------------+
| EXTRACT(MONTH FROM CURRENT_DATE) |
+----------------------------------+
| 5 |
+----------------------------------+
1 row in set (0.02 sec)

(Provavelmente o seu resultado será diferente, a menos que seja maio


quando você realizar este exercício).

Capítulo 8
Exercício 8.1
Construa uma consulta que conte o número de linhas da tabela account.
mysql> SELECT COUNT(*)
-> FROM account;
+----------+
| count(*) |
+----------+
| 24 |
+----------+
1 row in set (0.32 sec)

Exercício 8.2
Modi que a sua consulta do exercício 8.1 para contar o número de contas
abertas por cada cliente. Mostre o ID de cliente e o número de contas de
cada cliente.
mysql> SELECT cust_id, COUNT(*)
-> FROM account
-> GROUP BY cust_id;
+---------+----------+
| cust_id | count(*) |
+---------+----------+
| 1 | 3 |
| 2 | 2 |
| 3 | 2 |
| 4 | 3 |
| 5 | 1 |
| 6 | 2 |
| 7 | 1 |
| 8 | 2 |
| 9 | 3 |
| 10 | 2 |
| 11 | 1 |
| 12 | 1 |
| 13 | 1 |
+---------+----------+
13 rows in set (0.00 sec)

Exercício 8.3
Modi que sua consulta do exercício 8.2 para incluir apenas os clientes
que tenham pelo menos duas contas.
mysql> SELECT cust_id, COUNT(*)
-> FROM account
-> GROUP BY cust_id
-> HAVING COUNT(*) >= 2;
+---------+----------+
| cust_id | COUNT(*) |
+---------+----------+
| 1 | 3 |
| 2 | 2 |
| 3 | 2 |
| 4 | 3 |
| 6 | 2 |
| 8 | 2 |
| 9 | 3 |
| 10 | 2 |
+---------+----------+
8 rows in set (0.04 sec)

Exercício 8.4 (crédito extra)


Encontre o balanço total disponível por produto e lial em que haja mais
de uma conta por produto e lial. Ordene os resultados pelo balanço total
(do maior para o menor).
mysql> SELECT product_cd, open_branch_id, SUM(avail_balance)
-> FROM account
-> GROUP BY product_cd, open_branch_id
-> HAVING COUNT(*) > 1
-> ORDER BY 3 DESC;
+------------+----------------+--------------------+
| product_cd | open_branch_id | SUM(avail_balance) |
+------------+----------------+--------------------+
| CHK | 4 | 67852.33 |
| MM | 1 | 14832.64 |
| CD | 1 | 11500.00 |
| CD | 2 | 8000.00 |
| CHK | 2 | 3315.77 |
| CHK | 1 | 782.16 |
| SAV | 2 | 700.00 |
+------------+----------------+--------------------+
7 rows in set (0.01 sec)

Perceba que o MySQL não aceita ORDER BY SUM(avail_balance) DESC,


então fui forçado a indicar a coluna de ordenação pela sua posição.

Capítulo 9
Exercício 9.1
Construa uma consulta à tabela account que use uma condição de ltro
com uma subconsulta não-correlata à tabela product para encontrar todas
as contas de empréstimo (product.prod uct_type_cd = 'LOAN'). Recupere
o ID de conta, o código de produto, o ID de cliente e o saldo disponível.
mysql> SELECT account_id, product_cd, cust_id, avail_balance
-> FROM account
-> WHERE product_cd IN (SELECT product_cd
-> FROM product
-> WHERE product_type_cd = 'LOAN');
+------------+------------+---------+---------------+
| account_id | product_cd | cust_id | avail_balance |
+------------+------------+---------+---------------+
| 21 | BUS | 10 | 0.00 |
| 22 | BUS | 11 | 9345.55 |
| 24 | SBL | 13 | 50000.00 |
+------------+------------+---------+---------------+
3 rows in set (0.07 sec)

Exercício 9.2
Refaça a consulta do exercício 9.1 usando uma subconsulta correlata à
tabela product para obter os mesmos resultados.
mysql> SELECT a.account_id, a.product_cd, a.cust_id,
a.avail_balance
-> FROM account a
-> WHERE EXISTS (SELECT 1
-> FROM product p
-> WHERE p.product_cd = a.product_cd
-> AND p.product_type_cd = 'LOAN');
+------------+------------+---------+---------------+
| account_id | product_cd | cust_id | avail_balance |
+------------+------------+---------+---------------+
| 21 | BUS | 10 | 0.00 |
| 22 | BUS | 11 | 9345.55 |
| 24 | SBL | 13 | 50000.00 |
+------------+------------+---------+---------------+
3 rows in set (0.01 sec)

Exercício 9.3
Junte a consulta a seguir à tabela employee para mostrar o nível de
experiência de cada funcionário:
SELECT 'trainee' name, '2004-01-01' start_dt, '2005-12-31' end_dt
UNION ALL
SELECT 'worker' name, '2002-01-01' start_dt, '2003-12-31' end_dt
UNION ALL
SELECT 'mentor' name, '2000-01-01' start_dt, '2001-12-31' end_dt

Dê à subconsulta o alias levels e inclua o ID de funcionário, nome,


sobrenome e nível de experiência (levels.name). (Dica: construa uma
condição de junção usando uma condição de desigualdade para
determinar em qual nível a coluna employee.start_date se enquadra).
Exercício 9.4
Construa uma consulta à tabela employee que recupere o ID de
funcionário, o nome e o sobrenome, junto com os nomes do
departamento e da lial aos quais o funcionário foi alocado. Não junte
nenhuma tabela.
Capítulo 10
Exercício 10.1
Escreva uma consulta que retorne todos os nomes de produtos junto com
as contas baseadas naquele produto (use a coluna product_cd da tabela
account para vinculá-la à tabela product). Inclua todos os produtos,
mesmo que nenhuma conta tenha sido aberta para o produto.
mysql> SELECT p.product_cd, a.account_id, a.cust_id,
a.avail_balance
-> FROM product p LEFT OUTER JOIN account a
-> ON p.product_cd = a.product_cd;
+------------+------------+---------+---------------+
| product_cd | account_id | cust_id | avail_balance |
+------------+------------+---------+---------------+
| AUT | NULL | NULL | NULL |
| BUS | 21 | 10 | 0.00 |
| BUS | 22 | 11 | 9345.55 |
| CD | 3 | 1 | 3000.00 |
| CD | 13 | 6 | 10000.00 |
| CD | 14 | 7 | 5000.00 |
| CD | 19 | 9 | 1500.00 |
| CHK | 1 | 1 | 1057.75 |
| CHK | 4 | 2 | 2258.02 |
| CHK | 6 | 3 | 1057.75 |
| CHK | 8 | 4 | 534.12 |
| CHK | 11 | 5 | 2237.97 |
| CHK | 12 | 6 | 122.37 |
| CHK | 15 | 8 | 3487.19 |
| CHK | 17 | 9 | 125.67 |
| CHK | 20 | 10 | 23575.12 |
| CHK | 23 | 12 | 38552.05 |
| MM | 7 | 3 | 2212.50 |
| MM | 10 | 4 | 5487.09 |
| MM | 18 | 9 | 9345.55 |
| MRT | NULL | NULL | NULL |
| SAV | 2 | 1 | 500.00 |
| SAV | 5 | 2 | 200.00 |
| SAV | 9 | 4 | 767.77 |
| SAV | 16 | 8 | 387.99 |
| SBL | 24 | 13 | 50000.00 |
+------------+------------+---------+---------------+
26 rows in set (0.01 sec)

Exercício 10.2
Reformule a sua consulta do exercício 10.1 para usar o outro tipo de
junção externa (por exemplo, se você usou uma junção externa à esquerda
no exercício 10.1, use uma junção externa à direita dessa vez) de forma que
os resultados sejam idênticos aos do exercício 10.1.
mysql> SELECT p.product_cd, a.account_id, a.cust_id,
a.avail_balance
-> FROM account a RIGHT OUTER JOIN product p
-> ON p.product_cd = a.product_cd;
+------------+------------+---------+---------------+
| product_cd | account_id | cust_id | avail_balance |
+------------+------------+---------+---------------+
| AUT | NULL | NULL | NULL |
| BUS | 21 | 10 | 0.00 |
| BUS | 22 | 11 | 9345.55 |
| CD | 3 | 1 | 3000.00 |
| CD | 13 | 6 | 10000.00 |
| CD | 14 | 7 | 5000.00 |
| CD | 19 | 9 | 1500.00 |
| CHK | 1 | 1 | 1057.75 |
| CHK | 4 | 2 | 2258.02 |
| CHK | 6 | 3 | 1057.75 |
| CHK | 8 | 4 | 534.12 |
| CHK | 11 | 5 | 2237.97 |
| CHK | 12 | 6 | 122.37 |
| CHK | 15 | 8 | 3487.19 |
| CHK | 17 | 9 | 125.67 |
| CHK | 20 | 10 | 23575.12 |
| CHK | 23 | 12 | 38552.05 |
| MM | 7 | 3 | 2212.50 |
| MM | 10 | 4 | 5487.09 |
| MM | 18 | 9 | 9345.55 |
| MRT | NULL | NULL | NULL |
| SAV | 2 | 1 | 500.00 |
| SAV | 5 | 2 | 200.00 |
| SAV | 9 | 4 | 767.77 |
| SAV | 16 | 8 | 387.99 |
| SBL | 24 | 13 | 50000.00 |
+------------+------------+---------+---------------+
26 rows in set (0.02 sec)

Exercício 10.3
Junte externamente a tabela account às tabelas individual e business (por
meio da coluna account.cust_id) de tal forma que o conjunto-resultado
contenha uma linha por conta. As colunas a serem incluídas são
account.account_id, account.product_cd, individual.fname,
individual.lname e business.name.
mysql> SELECT a.account_id, a.product_cd,
-> i.fname, i.lname, b.name
-> FROM account a LEFT OUTER JOIN business b
-> ON a.cust_id = b.cust_id
-> LEFT OUTER JOIN individual i
-> ON a.cust_id = i.cust_id;
+------------+------------+----------+---------+------------------
------+
| account_id | product_cd | fname | lname | name |
+------------+------------+----------+---------+------------------
------+
| 1 | CHK | James | Hadley | NULL |
| 2 | SAV | James | Hadley | NULL |
| 3 | CD | James | Hadley | NULL |
| 4 | CHK | Susan | Tingley | NULL |
| 5 | SAV | Susan | Tingley | NULL |
| 6 | CHK | Frank | Tucker | NULL |
| 7 | MM | Frank | Tucker | NULL |
| 8 | CHK | John | Hayward | NULL |
| 9 | SAV | John | Hayward | NULL |
| 10 | MM | John | Hayward | NULL |
| 11 | CHK | Charles | Frasier | NULL |
| 12 | CHK | John | Spencer | NULL |
| 13 | CD | John | Spencer | NULL |
| 14 | CD | Margaret | Young | NULL |
| 15 | CHK | Louis | Blake | NULL |
| 16 | SAV | Louis | Blake | NULL |
| 17 | CHK | Richard | Farley | NULL |
| 18 | MM | Richard | Farley | NULL |
| 19 | CD | Richard | Farley | NULL |
| 20 | CHK | NULL | NULL | Chilton Engineering |
| 21 | BUS | NULL | NULL | Chilton Engineering |
| 22 | BUS | NULL | NULL | Northeast Cooling Inc. |
| 23 | CHK | NULL | NULL | Superior Auto Body |
| 24 | SBL | NULL | NULL | AAA Insurance Inc. |
+------------+------------+----------+---------+------------------
------+
24 rows in set (0.05 sec)

Exercício 10.4 (Crédito extra)


Invente uma consulta que gere o conjunto {1, 2, 3,..., 99, 100}. (Dica: use
uma junção cruzada com pelo menos duas subconsultas na cláusula
from.)
SELECT ones.x + tens.x + 1
FROM
(SELECT 0 x UNION ALL
SELECT 1 x UNION ALL
SELECT 2 x UNION ALL
SELECT 3 x UNION ALL
SELECT 4 x UNION ALL
SELECT 5 x UNION ALL
SELECT 6 x UNION ALL
SELECT 7 x UNION ALL
SELECT 8 x UNION ALL
SELECT 9 x) ones
CROSS JOIN
(SELECT 0 x UNION ALL
SELECT 10 x UNION ALL
SELECT 20 x UNION ALL
SELECT 30 x UNION ALL
SELECT 40 x UNION ALL
SELECT 50 x UNION ALL
SELECT 60 x UNION ALL
SELECT 70 x UNION ALL
SELECT 80 x UNION ALL
SELECT 90 x) tens;

Capítulo 11
Exercício 11.1
Reescreva a seguinte consulta, que usa uma expressão case simples, de
forma que os mesmos resultados sejam alcançados utilizando-se uma
expressão case. Tente usar o menor número possível de cláusulas when.
SELECT emp_id,
CASE title
WHEN 'President' THEN 'Management'
WHEN 'Vice President' THEN 'Management'
WHEN 'Treasurer' THEN 'Management'
WHEN 'Loan Manager' THEN 'Management'
WHEN 'Operations Manager' THEN 'Operations'
WHEN 'Head Teller' THEN 'Operations'
WHEN 'Teller' THEN 'Operations'
ELSE 'Unknown'
END
FROM employee;

SELECT emp_id,
CASE
WHEN title LIKE '%President' OR title = 'Loan Manager'
OR title = 'Treasurer'
THEN 'Management'
WHEN title LIKE '%Teller' OR title = 'Operations Manager'
THEN 'Operations'
ELSE 'Unknown'
END
FROM employee;

Exercício 11.2
Reescreva a consulta a seguir de forma que o conjunto-resultado contenha
uma única linha com quatro colunas (uma para cada lial). Nomeie as
quatro colunas de branch_1 a branch_4.
mysql> SELECT open_branch_id, COUNT(*)
-> FROM account
-> GROUP BY open_branch_id;
+----------------+----------+
| open_branch_id | COUNT(*) |
+----------------+----------+
| 1 | 8 |
| 2 | 7 |
| 3 | 3 |
| 4 | 6 |
+----------------+----------+
4 rows in set (0.00 sec)
mysql> SELECT
-> SUM(CASE WHEN open_branch_id = 1 THEN 1 ELSE 0 END)
branch_1,
-> SUM(CASE WHEN open_branch_id = 2 THEN 1 ELSE 0 END)
branch_2,
-> SUM(CASE WHEN open_branch_id = 3 THEN 1 ELSE 0 END)
branch_3,
-> SUM(CASE WHEN open_branch_id = 4 THEN 1 ELSE 0 END)
branch_4
-> FROM account;
+----------+----------+----------+----------+
| branch_1 | branch_2 | branch_3 | branch_4 |
+----------+----------+----------+----------+
| 8 | 7 | 3 | 6 |
+----------+----------+----------+----------+
1 row in set (0.02 sec)

Capítulo 12
Exercício 12.1
Gere uma transação que trans ra U$ 50 da conta de aplicação (money
market) de Frank Tucker para a conta corrente do cliente. Você precisará
inserir duas linhas na tabela transaction e atualizar duas linhas na tabela
account.
START TRANSACTION;
SELECT i.cust_id,
(SELECT a.account_id FROM account a
WHERE a.cust_id = i.cust_id
AND a.product_cd = 'MM') mm_id,
(SELECT a.account_id FROM account a
WHERE a.cust_id = i.cust_id
AND a.product_cd = 'chk') chk_id
INTO @cst_id, @mm_id, @chk_id
FROM individual i
WHERE i.fname = 'Frank' AND i.lname = 'Tucker';
INSERT INTO transaction (txn_id, txn_date, account_id,
txn_type_cd, amount)
VALUES (NULL, now(), @mm_id, 'CDT', 50);
INSERT INTO transaction (txn_id, txn_date, account_id,
txn_type_cd, amount)
VALUES (NULL, now(), @chk_id, 'DBT', 50);
UPDATE account
SET last_activity_date = now(),
avail_balance = avail_balance - 50
WHERE account_id = @mm_id;
UPDATE account
SET last_activity_date = now(),
avail_balance = avail_balance + 50
WHERE account_id = @chk_id;
COMMIT;

Capítulo 13
Exercício 13.1
Modi que a tabela account de forma que os clientes não possam ter mais
de uma conta para cada produto.
ALTER TABLE account
ADD CONSTRAINT account_unq1 UNIQUE (cust_id, product_cd);

Exercício 13.2
Gere um índice de múltiplas colunas na tabela transaction que possa ser
usado nas duas consultas a seguir:
SELECT txn_date, account_id, txn_type_cd, amount
FROM transaction
WHERE txn_date > cast('2008-12-31 23:59:59' as datetime);
SELECT txn_date, account_id, txn_type_cd, amount
FROM transaction
WHERE txn_date > cast('2008-12-31 23:59:59' as datetime)
AND amount < 1000;
CREATE INDEX txn_idx01
ON transaction (txn_date, amount);

Capítulo 14
Exercício 14.1
Crie uma view que consulta a tabela employee e gere a seguinte saída
quando consultada sem uma cláusula where:
+-----------------+------------------+
| supervisor_name | employee_name |
+-----------------+------------------+
| NULL | Michael Smith |
| Michael Smith | Susan Barker |
| Michael Smith | Robert Tyler |
| Robert Tyler | Susan Hawthorne |
| Susan Hawthorne | John Gooding |
| Susan Hawthorne | Helen Fleming |
| Helen Fleming | Chris Tucker |
| Helen Fleming | Sarah Parker |
| Helen Fleming | Jane Grossman |
| Susan Hawthorne | Paula Roberts |
| Paula Roberts | Thomas Ziegler |
| Paula Roberts | Samantha Jameson |
| Susan Hawthorne | John Blake |
| John Blake | Cindy Mason |
| John Blake | Frank Portman |
| Susan Hawthorne | Theresa Markham |
| Theresa Markham | Beth Fowler |
| Theresa Markham | Rick Tulman |
+-----------------+------------------+
18 rows in set (1.47 sec)
mysql> CREATE VIEW supervisor_vw
-> (supervisor_name,
-> employee_name
-> )
-> AS
-> SELECT concat(spr.fname, ' ', spr.lname),
-> concat(emp.fname, ' ', emp.lname)
-> FROM employee emp LEFT OUTER JOIN employee spr
-> ON emp.superior_emp_id = spr.emp_id;
Query OK, 0 rows affected (0.12 sec)
mysql> SELECT * FROM supervisor_vw;
+-----------------+------------------+
| supervisor_name | employee_name |
+-----------------+------------------+
| NULL | Michael Smith |
| Michael Smith | Susan Barker |
| Michael Smith | Robert Tyler |
| Robert Tyler | Susan Hawthorne |
| Susan Hawthorne | John Gooding |
| Susan Hawthorne | Helen Fleming |
| Helen Fleming | Chris Tucker |
| Helen Fleming | Sarah Parker |
| Helen Fleming | Jane Grossman |
| Susan Hawthorne | Paula Roberts |
| Paula Roberts | Thomas Ziegler |
| Paula Roberts | Samantha Jameson |
| Susan Hawthorne | John Blake |
| John Blake | Cindy Mason |
| John Blake | Frank Portman |
| Susan Hawthorne | Theresa Markham |
| Theresa Markham | Beth Fowler |
| Theresa Markham | Rick Tulman |
+-----------------+------------------+
18 rows in set (0.17 sec)

Exercício 14.2
O presidente do banco gostaria de ter um relatório mostrando o nome e a
cidade de cada lial, junto com o saldo total de todas as contas abertas na
lial. Crie uma view para gerar os dados.
mysql> CREATE VIEW branch_summary_vw
-> (branch_name,
-> branch_city,
-> total_balance
-> )
-> AS
-> SELECT b.name, b.city, sum(a.avail_balance)
-> FROM branch b INNER JOIN account a
-> ON b.branch_id = a.open_branch_id
-> GROUP BY b.name, b.city;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM branch_summary_vw;


+---------------+-------------+---------------+
| branch_name | branch_city | total_balance |
+---------------+-------------+---------------+
| Headquarters | Waltham | 27882.57 |
| Quincy Branch | Quincy | 53270.25 |
| So. NH Branch | Salem | 68240.32 |
| Woburn Branch | Woburn | 21361.32 |
+---------------+-------------+---------------+
4 rows in set (0.01 sec)

Capítulo 15
Exercício 15.1
Escreva uma consulta que liste todos os índices do esquema bank. Inclua
os nomes das tabelas.
mysql> SELECT DISTINCT table_name, index_name
-> FROM information_schema.statistics
-> WHERE table_schema = 'bank';
+--------------+--------------------+
| table_name | index_name |
+--------------+--------------------+
| account | PRIMARY |
| account | account_unq1 |
| account | fk_product_cd |
| account | fk_a_branch_id |
| account | fk_a_emp_id |
| account | acc_bal_idx |
| branch | PRIMARY |
| business | PRIMARY |
| customer | PRIMARY |
| department | PRIMARY |
| department | dept_name_idx |
| employee | PRIMARY |
| employee | fk_dept_id |
| employee | fk_e_branch_id |
| employee | fk_e_emp_id |
| individual | PRIMARY |
| officer | PRIMARY |
| officer | fk_o_cust_id |
| product | PRIMARY |
| product | fk_product_type_cd |
| product_type | PRIMARY |
| transaction | PRIMARY |
| transaction | fk_t_account_id |
| transaction | fk_teller_emp_id |
| transaction | fk_exec_branch_id |
| transaction | txn_idx01 |
+--------------+--------------------+
26 rows in set (0.00 sec)

Exercício 15.2
Escreva uma consulta que gere uma saída que possa ser usada para criar
todos os índices da tabela bank.employee. A saída deve ter o seguinte
formato:
Colofão
O animal na capa do Aprendendo SQL, é uma rã marsupial arborícola
dos Andes (Gastrotheca riobambae). Como seu nome sugere, essa rã
crepuscular e noturna é nativa dos picos ocidentais das montanhas dos
Andes e está amplamente disseminada desde o vale do Riobamba até
Ibarra ao norte.
Durante o período de acasalamento, o macho emite um chamado
("wraaack-ack-ack") para atrair a fêmea. Se uma fêmea prenhe é atraída,
ele se posiciona nas costas dela e realiza um acasalamento típico de rãs
denominado amplexo nupcial. Conforme os ovos emergem da cloaca da
fêmea, o macho recolhe os ovos com seus pés e fertiliza-os enquanto os
manobra até uma bolsa nas costas da fêmea. Uma fêmea pode incubar
uma média de 130 ovos, e o desenvolvimento na bolsa dura de 60 a 120
dias. Durante a incubação, um inchaço torna-se visível, e surgem
calombos abaixo da pele nas costas da fêmea. Quando os girinos
emergem da bolsa, a rã fêmea os deposita na água. Dentro de dois ou três
meses os girinos se metamorfoseiam em sapinhos, e aos sete meses já
estão prontos para o acasalamento ("wraaack-ack-ack").
Tanto a rã macho quanto a rã fêmea possuem discos digitais expandidos
em seus dedos e dedões que os ajudam a escalar superfícies verticais
como árvores. Machos adultos alcançam 2 polegadas de comprimento,
enquanto que as fêmeas chegam a 2,5 polegadas. Algumas vezes são
verdes, outras verdes são marrons, e algumas vezes são uma combinação
de verde e marrom. A cor das rãs jovens muda de marrom para verde
conforme crescem.

Você também pode gostar