Aprendendo SQL
Aprendendo SQL
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
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
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
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.
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
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”).
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');
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)
Python Python DB
Visual
ADO.NET (Microsoft)
Basic
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';
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';
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
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:
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
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
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
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).
DD Dia 01 a 31
HH Hora 00 a 23
MI Minuto 00 a 59
SS Segundo 00 a 59
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)
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
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
Food Varchar(20)
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á.
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')),
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)
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:
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).
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’:
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
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.
department Um grupo de funcionários do banco que executa uma determinada função bancária
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
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)
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
Identifica as tabelas de onde serão retirados os dados e como as tabelas deverão ser
From
juntadas
Group by Usada para agrupar linhas por meio de valores comuns de colunas
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)
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.
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)
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.
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)
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:
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;
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):
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)
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)
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.
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'
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.
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.
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 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 <>.
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:
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');
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:
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
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)
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:
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
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)
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)
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.
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:
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.)
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.
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 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):
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?
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
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)
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)
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)
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)
Conjunto B
+--------+
| emp_id |
+--------+
| 10 |
| 10 |
+--------+
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
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
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)
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
||.
Exp(x) Calcula ex
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.
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 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)
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
DD Dia 01 a 31
HH Hora 00 a 23
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
Time HHH:MI:SS
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;
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 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)
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
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)
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.
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.
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)
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)
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.
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.
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.
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.
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.
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:
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
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)
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:
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.
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)
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');
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);
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
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 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).
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.
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
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
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)
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.
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:
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 é 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.
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.
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.
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.
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);
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.
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)
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.
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.
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).
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
Í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)
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
Í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.
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.
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 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.
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);
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
Restrições de chave
Gera índice Não gera índice Não gera índice
estrangeira
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:
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
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)
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
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;
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)
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'
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;
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;
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.
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.
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
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)
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
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.
Statistics Índices
Views Views
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 ')';
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
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.
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)
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)
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)
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)
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
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)
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:
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)
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)
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
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)
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)
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.