Esta é uma pré-visualização de arquivo. Entre para ver o arquivo original
<p>Alan Beaulieu</p><p>Novatec</p><p>Authorized translation of the English edition of Learning SQL 2nd Edition ISBN 978-0-59652083-0</p><p>© 2009, Alan Beaulieu. This translation is published and sold by permission of O'Reilly Media, Inc., the owner of</p><p>all rights to publish and sell the same.</p><p>Tradução autorizada da edição em inglês da obra Learning SQL 2nd Edition ISBN 978-0-59652083-0</p><p>© 2009, Alan Beaulieu. Esta tradução é publicada e vendida com a permissão da O'Reilly Media, Inc., detentora</p><p>de todos os direitos para publicação e venda desta obra.</p><p>© Novatec Editora Ltda. 2010.</p><p>Todos os direitos reservados e protegidos pela Lei 9.610 de 19/02/1998.</p><p>É proibida a reprodução desta obra, mesmo parcial, por</p><p>qualquer processo, sem prévia autorização, por escrito, do</p><p>autor e da Editora.</p><p>Editor: Rubens Prates</p><p>Tradução: Edgard B.Damiani</p><p>Revisão gramatical: Lia Gabriele Regius</p><p>Editoração eletrônica: Camila Kuwabata</p><p>ISBN: 978-85-7522-743-5</p><p>Histórico de edições impressas:</p><p>Novembro/2015 Terceira reimpressão</p><p>Dezembro/2013 Segunda reimpressão</p><p>Junho/2012 Primeira reimpressão</p><p>Janeiro/2010 Primeira edição</p><p>Novatec Editora Ltda.</p><p>Rua Luís Antônio dos Santos 110</p><p>02460-000 – São Paulo, SP – Brasil</p><p>Tel.: +55 11 2959-6529</p><p>E-mail: novatec@novatec.com.br</p><p>Site: www.novatec.com.br</p><p>Twitter: twitter.com/novateceditora</p><p>Facebook: facebook.com/novatec</p><p>LinkedIn: linkedin.com/in/novatec</p><p>mailto:novatec%40novatec.com.br?subject=</p><p>http://www.novatec.com.br/</p><p>http://twitter.com/novateceditora</p><p>http://facebook.com/novatec</p><p>http://linkedin.com/in/novatec</p><p>Sumário</p><p>Prefácio</p><p>CAPÍTULO 1 ■ Uma breve introdução</p><p>Introdução aos bancos de dados</p><p>Sistemas de banco de dados não-relacional</p><p>O modelo relacional</p><p>Um pouco de terminologia</p><p>O que é SQL?</p><p>Classes de instruções SQL</p><p>SQL: uma linguagem não-procedural</p><p>Exemplos em SQL</p><p>O que é MySQL?</p><p>O que vem em seguida</p><p>CAPÍTULO 2 ■ Criando e populando um banco de dados</p><p>Criando um banco de dados MySQL</p><p>Usando a ferramenta de linha de comando mysql</p><p>Tipos de dados do MySQL</p><p>Dados do tipo caractere</p><p>Dados numéricos</p><p>Dados temporais</p><p>Criação de tabelas</p><p>Passo 1: Projeto</p><p>Passo 2: Re�namento</p><p>Passo 3: Construindo instruções de esquema SQL</p><p>Povoando e modi�cando tabelas</p><p>Inserindo dados</p><p>Atualizando dados</p><p>Exclusão de dados</p><p>Quando boas instruções se dão mal</p><p>Chaves primárias não-únicas</p><p>Chave estrangeira não-existente</p><p>Violações dos valores das colunas</p><p>Conversões de data inválidas</p><p>Esquema bank (bank schema)</p><p>CAPÍTULO 3 ■ Introdução a consultas</p><p>Funcionamento das consultas</p><p>Cláusulas de consulta</p><p>Cláusula select</p><p>Aliases de colunas</p><p>Removendo linhas duplicadas</p><p>Cláusula from</p><p>Tabelas</p><p>Vínculos de tabela</p><p>De�nindo aliases de tabela</p><p>Cláusula where</p><p>Cláusulas group by e having</p><p>Cláusula order by</p><p>Ordenação ascendente versus ordenação descendente</p><p>Ordenação por meio de expressões</p><p>Ordenando por meio de referências numéricas</p><p>Teste seu conhecimento</p><p>CAPÍTULO 4 ■ Filtragem</p><p>Avaliação de condições</p><p>Usando parênteses</p><p>Usando o operador not</p><p>Construindo uma condição</p><p>Tipos de condições</p><p>Condições de igualdade</p><p>Condições de intervalo</p><p>Condições de adesão</p><p>Condições de correspondência</p><p>Null: aquela palavra de quatro letras</p><p>Teste seu conhecimento</p><p>CAPÍTULO 5 ■ Consultando múltiplas tabelas</p><p>O que é uma junção?</p><p>Produto cartesiano</p><p>Junções internas</p><p>A sintaxe ANSI de junção</p><p>Juntando três ou mais tabelas</p><p>Usando subconsultas como tabelas</p><p>Usando a mesma tabela duas vezes</p><p>Autojunções</p><p>Junções equivalentes versus não-equivalentes</p><p>Condições de junção versus condições de �ltro</p><p>Teste seu conhecimento</p><p>CAPÍTULO 6 ■ Trabalhando com conjuntos</p><p>Introdução à teoria dos conjuntos</p><p>Teoria dos conjuntos na prática</p><p>Operadores de conjunto</p><p>Operador union</p><p>Operador intersect</p><p>Operador except</p><p>Regras das operações de conjunto</p><p>Ordenando resultados de consultas compostas</p><p>Precedência das operações de conjunto</p><p>Teste seu conhecimento</p><p>CAPÍTULO 7 ■ Geração, conversão e manipulação de dados</p><p>Trabalhando com strings</p><p>Geração de strings</p><p>Manipulação de strings</p><p>Trabalhando com dados numéricos</p><p>Realizando funções aritméticas</p><p>Controlando a precisão numérica</p><p>Tratando dados sinalizados</p><p>Trabalhando com dados temporais</p><p>Trabalhando com fusos horários</p><p>Gerando dados temporais</p><p>Manipulando dados temporais</p><p>Funções de conversão</p><p>Teste seu conhecimento</p><p>CAPÍTULO 8 ■ Agrupamentos e agregações</p><p>Conceitos de agrupamento</p><p>Funções de agregação</p><p>Grupos implícitos versus grupos explícitos</p><p>Contando valores distintos</p><p>Usando expressões</p><p>Como nulls são tratados</p><p>Gerando grupos</p><p>Agrupamento por uma coluna</p><p>Agrupamento por múltiplas colunas</p><p>Agrupamento por meio de expressões</p><p>Gerando resumos (rollups)</p><p>Condições de �ltro de grupo</p><p>Teste seu conhecimento</p><p>CAPÍTULO 9 ■ Subconsultas</p><p>O que é uma subconsulta?</p><p>Tipos de subconsultas</p><p>Subconsultas não-correlatas</p><p>Subconsultas de linhas múltiplas e coluna única</p><p>Subconsultas de múltiplas colunas</p><p>Subconsultas correlatas</p><p>Operador exists</p><p>Manipulação de dados usando subconsultas correlatas</p><p>Quando usar subconsultas</p><p>Subconsultas como fontes de dados</p><p>Subconsultas em condições de �ltro</p><p>Subconsultas como geradoras de expressões</p><p>Resumo das subconsultas</p><p>Teste seu conhecimento</p><p>CAPÍTULO 10 ■ Junções revisitadas</p><p>Junções externas</p><p>Junção externa esquerda versus junção externa direita</p><p>Junções externas de três tabelas</p><p>Autojunções externas</p><p>Junções cruzadas</p><p>Junções naturais</p><p>Teste seu conhecimento</p><p>CAPÍTULO 11 ■ Lógica condicional</p><p>O que é lógica condicional?</p><p>Expressão case</p><p>Expressões case pesquisadas</p><p>Expressões case simples</p><p>Exemplos de expressões case</p><p>Transformações de conjuntos-resultados</p><p>Agregação seletiva</p><p>Veri�cando a existência</p><p>Erros de divisão por zero</p><p>Atualizações condicionais</p><p>Tratando valores null</p><p>Teste seu conhecimento</p><p>CAPÍTULO 12 ■ Transações</p><p>Bancos de dados multiusuários</p><p>Locking</p><p>Granularidade dos bloqueios</p><p>O que é uma transação?</p><p>Iniciando uma transação</p><p>Finalizando uma transação</p><p>Pontos de gravação de transações</p><p>Teste seu conhecimento</p><p>CAPÍTULO 13 ■ Índices e restrições</p><p>Índices</p><p>Criação de índices</p><p>Tipos de índices</p><p>Como os índices são usados</p><p>O lado negativo dos índices</p><p>Restrições</p><p>Criação de restrições</p><p>Restrições e índices</p><p>Restrições em cascata</p><p>Teste seu conhecimento</p><p>CAPÍTULO 14 ■ Views</p><p>O que são views?</p><p>Por que usar as views?</p><p>Segurança de dados</p><p>Agregação de dados</p><p>Escondendo a complexidade</p><p>Juntando dados particionados</p><p>Views atualizáveis</p><p>Atualizando views simples</p><p>Atualizando views complexas</p><p>Teste seu conhecimento</p><p>CAPÍTULO 15 ■ Metadados</p><p>Dados sobre dados</p><p>Information_schema</p><p>Trabalhando com metadados</p><p>Scripts de geração de esquemas</p><p>Veri�cação de implantação</p><p>Geração dinâmica de SQL</p><p>Teste seu conhecimento</p><p>APÊNDICE A ■ Diagrama ER do banco de dados de exemplo</p><p>APÊNDICE B ■ Extensões do MySQL para a linguagem SQL</p><p>Extensões para a instrução select</p><p>Cláusula limit</p><p>Cláusula into out�le</p><p>Instruções insert/update combinadas</p><p>Atualizações e exclusões ordenadas</p><p>Atualizações e exclusões em múltiplas tabelas</p><p>APÊNDICE C ■ Soluções dos exercícios</p><p>Prefácio</p><p>Linguagens de programação vêm e vão constantemente, e poucas linguagens em uso</p><p>atualmente têm raízes que remontam a mais de uma década. Alguns exemplos são o</p><p>Cobol, que ainda é amplamente usado em ambientes de mainframe, e a linguagem</p><p>C, que é muito popular no desenvolvimento de sistemas operacionais e de</p><p>servidores, além de ser usada em sistemas embarcados.</p><p>SQL é a linguagem para geração, manipulação e recuperação de dados de um banco</p><p>de dados relacional. Uma das razões da popularidade dos bancos de dados</p><p>relacionais é que bancos relacionais corretamente projetados podem tratar</p><p>quantidades enormes de dados. Ao trabalhar com grandes conjuntos de dados, a</p><p>linguagem SQL é semelhante a uma daquelas câmeras digitais incrementadas</p><p>equipadas com lentes de zoom potentes, pois você pode usar a SQL para observar</p><p>grandes conjuntos de dados, ou você pode dar um zoom em linhas individuais (ou</p><p>em qualquer nível entre esses extremos). Outros sistemas de gerenciamento de banco</p><p>de dados tendem a entregar os pontos quando colocados sob cargas pesadas, pois</p><p>seu foco é muito estreito (o zoom está travado na ampliação máxima), e é por isso</p><p>que falharam todas</p>
<p>questões em consideração, a tabela 2.8 mostra a versão</p><p>normalizada da tabela person.</p><p>Tabela 2.8 – Tabela Person, segundo passo</p><p>Coluna Tipo Valores permitidos</p><p>Person_id Smallint (unsigned)</p><p>First_name Varchar(20)</p><p>Last_name Varchar(20)</p><p>Gender Char(1) M, F</p><p>Birth_date Date</p><p>Street Varchar(30)</p><p>City Varchar(20)</p><p>State Varchar(20)</p><p>Country Varchar(20)</p><p>Postal_code Varchar(20)</p><p>Agora que a tabela person tem uma chave primária (person_id) para garantir a</p><p>unicidade, o próximo passo é construir uma tabela favorite_food que inclua uma</p><p>chave estrangeira para a tabela person. A tabela 2.9 mostra o resultado.</p><p>Tabela 2.9 – Tabela favorite_food</p><p>Coluna Tipo</p><p>Person_id Smallint (unsigned)</p><p>Food Varchar(20)</p><p>As colunas person_id e food formam a chave primária da tabela favorite_food, e a</p><p>coluna person_id também é uma chave estrangeira para a tabela person.</p><p>Quanto é su�ciente?</p><p>Retirar a coluna favorite_foods da tabela person certamente foi uma boa ideia,</p><p>mas será que não há mais nada a fazer? O que acontece, por exemplo, se uma</p><p>pessoa lista “massa” como uma comida favorita enquanto outra lista</p><p>“espaguete”? Elas são a mesma coisa? Para evitar esse problema, você pode</p><p>querer que as pessoas escolham suas comidas favoritas de uma lista de opções.</p><p>Nesse caso, você deverá criar uma tabela food com as colunas food_id e</p><p>food_name e, então, alterar a tabela favorite_food para conter uma chave</p><p>estrangeira para a tabela food. Apesar de essa abordagem ser totalmente</p><p>normalizada, você pode decidir que é melhor apenas armazenar os valores que</p><p>os usuários inserirem, deixando a tabela do jeito que está.</p><p>Passo 3: Construindo instruções de esquema SQL</p><p>Agora que o projeto está completo para as duas tabelas de informações a respeito de</p><p>pessoas e suas comidas favoritas, o próximo passo é gerar instruções SQL para criar</p><p>as tabelas no banco de dados. Aqui está a instrução que cria a tabela person:</p><p>CREATE TABLE person</p><p>(person_id SMALLINT UNSIGNED,</p><p>fname VARCHAR(20),</p><p>lname VARCHAR(20),</p><p>gender CHAR(1),</p><p>birth_date DATE,</p><p>street VARCHAR(30),</p><p>city VARCHAR(20),</p><p>state VARCHAR(20),</p><p>country VARCHAR(20),</p><p>postal_code VARCHAR(20),</p><p>CONSTRAINT pk_person PRIMARY KEY (person_id)</p><p>);</p><p>Tudo nessa instrução deve ser autoexplicativo, exceto pelo último item: ao de�nir</p><p>sua tabela, você precisa informar ao servidor de banco de dados qual ou quais</p><p>colunas servirão de chave primária para a tabela. Você faz isso criando uma restrição</p><p>(constraint) na tabela. Você pode adicionar vários tipos de restrições em uma</p><p>de�nição de tabela. Essa restrição é uma restrição de chave primária. Ela é criada</p><p>sobre a coluna person_id e recebe o nome de pk_person.</p><p>Ainda falando sobre restrições, existe outro tipo de restrição que pode ser útil para a</p><p>tabela person. Na tabela 2.7, adicionei uma terceira coluna para mostrar os valores</p><p>permitidos para certas colunas (tais como ‘M’ e ‘F’ para a coluna gender). Outro tipo</p><p>de restrição, denominado restrição de veri�cação (check constraint), restringe os</p><p>valores permitidos para uma coluna especí�ca. O MySQL permite que uma restrição</p><p>de veri�cação seja anexada a uma de�nição de coluna, como mostrado a seguir:</p><p>gender CHAR(1) CHECK (gender IN ('M','F')), Apesar de as restrições</p><p>de veri�cação operarem como esperado nos servidores de banco de dados, o</p><p>servidor MySQL permite que restrições sejam de�nidas, mas não força seu uso. No</p><p>entanto, o MySQL fornece outro tipo de dados de caracteres chamado enum, que une</p><p>a veri�cação de restrição à de�nição de dados. Con�ra como �caria a de�nição da</p><p>coluna gender: gender ENUM('M','F'),</p><p>Aqui está como a tabela person �caria com um tipo de dados enum para a coluna</p><p>gender: CREATE TABLE person</p><p>(person_id SMALLINT UNSIGNED,</p><p>fname VARCHAR(20),</p><p>lname VARCHAR(20),</p><p>gender ENUM('M','F'),</p><p>birth_date DATE,</p><p>street VARCHAR(30),</p><p>city VARCHAR(20),</p><p>state VARCHAR(20),</p><p>country VARCHAR(20),</p><p>postal_code VARCHAR(20),</p><p>CONSTRAINT pk_person PRIMARY KEY (person_id)</p><p>);</p><p>Mais tarde neste capítulo, você verá o que acontece caso tente adicionar dados em</p><p>uma coluna que violem sua restrição de veri�cação (ou, no caso, do MySQL, seus</p><p>valores de enumeração).</p><p>Agora você está pronto para executar a instrução create table usando a ferramenta</p><p>de linha de comandos mysql. Ela �caria assim: mysql> CREATE TABLE person -</p><p>> (person_id SMALLINT UNSIGNED,</p><p>-> fname VARCHAR(20),</p><p>-> lname VARCHAR(20),</p><p>-> gender ENUM('M','F'),</p><p>-> birth_date DATE,</p><p>-> street VARCHAR(30),</p><p>-> city VARCHAR(20),</p><p>-> state VARCHAR(20),</p><p>-> country VARCHAR(20),</p><p>-> postal_code VARCHAR(20),</p><p>-> CONSTRAINT pk_person PRIMARY KEY (person_id) -> );</p><p>Query OK, 0 rows affected (0.27 sec)</p><p>Depois de processar a instrução create table, o servidor MySQL retorna a</p><p>mensagem “Query OK, 0 rows a�ected”, dizendo que a instrução não tinha erros de</p><p>sintaxe. Se você quiser se certi�car de que a tabela existe de fato, você pode usar o</p><p>comando describe (ou sua forma abreviada, desc) para observar a de�nição da</p><p>tabela:</p><p>As colunas 1 e 2 da saída do comando describe são autoexplicativas. A coluna 3</p><p>mostra se uma coluna em particular pode ser omitida quando os dados são</p><p>inseridos na tabela. Deixei esse tópico de fora da discussão propositalmente (veja o</p><p>quadro “O que é Null?” abaixo para uma breve explicação), mas o exploraremos a</p><p>fundo no capítulo 4. A quarta coluna mostra se uma coluna faz parte de alguma</p><p>chave (primária ou estrangeira) – nesse caso, a coluna person_id está marcada como</p><p>chave primária. A coluna 5 mostra se uma coluna em particular será populada com</p><p>um valor padrão caso seja omitida no momento da inserção de dados na tabela. A</p><p>sexta coluna (chamada “Extra”) exibe qualquer outra informação relevante que</p><p>possa ser aplicável a uma coluna.</p><p>O que é Null?</p><p>Em alguns casos, não é possível ou aplicável fornecer um valor para uma coluna</p><p>especí�ca em uma tabela. Por exemplo, ao adicionar dados de um novo pedido</p><p>de cliente, a coluna ship_date ainda não pode ser determinada. Nesse caso, diz-</p><p>se que a coluna é nula (perceba que eu não disse que seu valor é nulo), o que</p><p>indica a ausência de um valor. Null é usado em inúmeros casos em que um valor</p><p>não pode ser fornecido, tais como: • Não aplicável</p><p>• Desconhecido</p><p>• Conjunto vazio</p><p>Ao projetar uma tabela, você deve especi�car quais colunas têm permissão para</p><p>serem nulas (o padrão) e quais colunas não têm permissão para isso (por meio</p><p>da adição das palvras-chaves not null após a de�nição de tipo).</p><p>Agora que você criou a tabela person, seu próximo passo é criar a tabela</p><p>favorite_food: mysql> CREATE TABLE favorite_food -> (person_id</p><p>SMALLINT UNSIGNED, -> food VARCHAR(20),</p><p>-> CONSTRAINT pk_favorite_food PRIMARY KEY (person_id, food), -> CONSTRAINT</p><p>fk_fav_food_person_id FOREIGN KEY (person_id) -> REFERENCES person</p><p>(person_id) -> );</p><p>Query OK, 0 rows affected (0.10 sec)</p><p>Isso deve parecer bem similar à instrução create table da tabela person, com as</p><p>seguintes exceções: • Como uma pessoa pode ter mais de uma comida favorita (que</p><p>é a razão pela qual essa tabela foi criada), é preciso mais do que a coluna person_id</p><p>para garantir unicidade na tabela. Essa tabela, portanto, tem uma chave primária de</p><p>duas colunas: person_id e food.</p><p>• A tabela favorite_food contém outro tipo de restrição denominada restrição de</p><p>chave estrangeira (foreign key constraint). Ela restringe os valores da coluna</p><p>person_id na tabela favorite_food para que sejam incluídos apenas valores</p><p>existentes na tabela person. Com essa restrição de�nida, não conseguirei</p><p>adicionar uma linha na tabela favorite_food indicando que person_id 27 gosta</p><p>de pizza se ainda não houver uma linha na tabela person cujo person_id seja 27.</p><p>Caso se esqueça de criar a chave estrangeira no momento em que cria a tabela, é</p><p>possível adicioná-la mais tarde por meio da instrução alter table.</p><p>A instrução describe mostra o seguinte após a execução da instrução alter table:</p><p>Agora que as tabelas foram criadas, o próximo passo lógico é adicionar alguns</p><p>dados.</p><p>Povoando e modi�cando tabelas</p><p>Com as tabelas person e favorite_food em seus devidos lugares, agora você pode</p><p>iniciar a exploração das quatro</p>
<p>instruções de dados do SQL: insert, update, delete</p><p>e select.</p><p>Inserindo dados</p><p>Como ainda não há dados nas tabelas person e favorite_food, a primeira das</p><p>quatro instruções de dados SQL a ser explorada será a instrução insert. Existem</p><p>três componentes principais em uma instrução insert: • O nome da tabela na qual</p><p>serão adicionados os dados.</p><p>• Os nomes das colunas que serão populadas dentro da tabela.</p><p>• Os valores que serão usados para popular as colunas.</p><p>Você não é obrigado a fornecer dados para todas as colunas da tabela (a menos que</p><p>todas as colunas estejam de�nidas como not null). Em alguns casos, aquelas</p><p>colunas que não foram incluídas na instrução insert inicial receberão valores</p><p>posteriormente por meio de uma instrução update. Em outros casos, uma coluna</p><p>pode nunca receber um valor para uma linha de dados especí�ca (como um pedido</p><p>de cliente que é cancelado antes de ser enviado, tornando a coluna ship_date</p><p>inaplicável).</p><p>Gerando dados de chave numérica</p><p>Antes de inserir dados na tabela person, seria interessante discutir como valores são</p><p>gerados para chaves primárias numéricas. Além de escolher um número do nada,</p><p>você tem duas outras opções: • Observar o maior número que atualmente se</p><p>encontra na tabela e adicionar um a ele.</p><p>• Deixar que o servidor de banco de dados forneça um valor para você.</p><p>Apesar de a primeira opção ser válida, ela é comprovadamente problemática em um</p><p>ambiente multiusuário, pois dois usuários podem olhar na tabela ao mesmo tempo</p><p>e gerar o mesmo valor para a chave primária. Em vez disso, todos os servidores de</p><p>banco de dados disponíveis atualmente fornecem um método seguro e robusto de</p><p>gerar chaves numéricas. Em alguns servidores, como o Oracle Database, um objeto</p><p>de esquema separado é usado (chamado de sequência) – no caso do MySQL, no</p><p>entanto, você precisa apenas ativar o atributo de autoincremento para sua coluna de</p><p>chave primária. Normalmente isso é feito no momento de criação da tabela, mas</p><p>fazê-lo agora fornecerá a oportunidade de aprender outra instrução de esquema</p><p>SQL, alter table, que é usada para modi�car a de�nição de uma tabela existente:</p><p>ALTER TABLE person MODIFY person_id SMALLINT UNSIGNED</p><p>AUTO_INCREMENT; Essencialmente, essa instrução rede�ne a coluna person_id</p><p>da tabela person. Se você pedir para descrever a tabela, você verá o atributo de</p><p>autoincremento listado na coluna “Extra” para person_id:</p><p>Ao inserir dados na tabela person_id, apenas forneça null para a coluna person_id e</p><p>o MySQL populará a coluna com o próximo valor disponível (por padrão, o MySQL</p><p>inicia em 1 para coluna de autoincremento).</p><p>Instrução insert</p><p>Agora que todas as peças estão posicionadas, é hora de adicionar alguns dados. A</p><p>instrução seguinte cria uma linha na tabela person para William Turner: mysql></p><p>INSERT INTO person -> (person_id, fname, lname, gender,</p><p>birth_date) -> VALUES (null, 'William', 'Turner', 'M',</p><p>'1972-05-27'); Query OK, 1 row a�ected (0.01 sec)</p><p>O feedback (“Query OK, 1 row a�ected”) está lhe dizendo que a sintaxe da sua</p><p>instrução estava correta e que uma linha foi adicionada ao banco de dados (já que</p><p>foi uma instrução insert). Você pode observar os dados recém-adicionados à tabela</p><p>por meio de uma instrução select:</p><p>Como você pode ver, o servidor MySQL gerou um valor de 1 para a chave primária.</p><p>Já que existe apenas uma única linha na tabela person, não me preocupei em</p><p>especi�car qual linha me interessava e simplesmente recuperei todas as linhas da</p><p>tabela. No entanto, se houvesse mais de uma linha na tabela eu poderia adicionar</p><p>uma cláusula where para especi�car que queria apenas os dados da linha cuja</p><p>coluna person_id tivesse o valor 1:</p><p>Apesar de essa consulta especi�car um valor de chave primária, você pode usar</p><p>qualquer coluna da tabela como critério de pesquisa de linhas, como mostra a</p><p>consulta a seguir, que pesquisa todas as linhas cuja coluna lname tenha o valor</p><p>‘Turner’:</p><p>Antes de seguir em frente, vale mencionar mais algumas coisas a respeito da</p><p>instrução insert que construímos anteriormente: • Não foram fornecidos valores</p><p>para as colunas de endereço. Isso não é um problema, pois é permitido que essas</p><p>colunas sejam nulas.</p><p>• O valor fornecido para a coluna birth_date foi uma string. Desde que você</p><p>respeite o formato mostrado na tabela 2.5, o MySQL converterá a string em uma</p><p>data para você.</p><p>• Os nomes de colunas e os valores fornecidos devem corresponder em número e</p><p>tipo. Se você nomear sete colunas e fornecer apenas seis valores, ou se fornecer</p><p>valores que não possam ser convertidos ao tipo de dados apropriado à coluna</p><p>correspondente, você receberá um erro.</p><p>William também forneceu informações sobre seus três pratos favoritos. Veja as três</p><p>instruções insert que armazenam suas preferências: mysql> INSERT INTO</p><p>favorite_food (person_id, food) -> VALUES (1, 'pizza');</p><p>Query OK, 1 row affected (0.01 sec)</p><p>mysql> INSERT INTO favorite_food (person_id, food) -> VALUES (1,</p><p>'cookies');</p><p>Query OK, 1 row affected (0.00 sec)</p><p>mysql> INSERT INTO favorite_food (person_id, food) -> VALUES (1, 'nachos');</p><p>Query OK, 1 row affected (0.01 sec)</p><p>Aqui está uma consulta que retorna os pratos favoritos de William em ordem</p><p>alfabética usando uma cláusula order by: mysql> SELECT food</p><p>-> FROM favorite_food</p><p>-> WHERE person_id = 1</p><p>-> ORDER BY food;</p><p>+---------+</p><p>| food |</p><p>+---------+</p><p>| cookies |</p><p>| nachos |</p><p>| pizza |</p><p>+---------+</p><p>3 rows in set (0.02 sec)</p><p>A cláusula order by diz ao servidor como ordenar os dados retornados pela</p><p>consulta. Sem a cláusula order by, não há garantias de que os dados da tabela serão</p><p>retornados em alguma ordem especí�ca.</p><p>Para que William não se sinta sozinho, você pode executar outra instrução insert</p><p>para adicionar Susan Smith à tabela person: mysql> INSERT INTO person</p><p>-> (person_id, fname, lname, gender, birth_date, -> street, city, state,</p><p>country, postal_code) -> VALUES (null, 'Susan','Smith', 'F', '1975-11-02',</p><p>-> '23 Maple St.', 'Arlington', 'VA', 'USA', '20220'); Query OK, 1 row</p><p>affected (0.01 sec)</p><p>Como Susan foi gentil o su�ciente ao fornecer seu endereço, incluímos cinco colunas</p><p>a mais do que quando os dados de William foram inseridos. Se você consultar a</p><p>tabela novamente, verá que a linha de Susan teve o valor 2 atribuído à sua chave</p><p>primária:</p><p>Posso ter essas informações em XML?</p><p>Se você vai trabalhar com dados XML, �cará feliz em saber que a maioria dos</p><p>servidores de banco de dados fornece uma maneira simples de gerar saída XML</p><p>a partir de uma consulta. Com o MySQL, por exemplo, você pode usar a opção -</p><p>-xml ao invocar a ferramenta mysql, e toda sua saída será automaticamente</p><p>formatada usando XML. Aqui está como os dados de favorite_food �cariam no</p><p>formato de documento XML: C:\database> mysql -u lrngsql -p --xml bank</p><p>Enter password: xxxxxx</p><p>Welcome to the MySQL Monitor...</p><p>Mysql> SELECT * FROM favorite_food;</p><p>1</p><p>cookies</p><p>1</p><p>nachos</p><p>1</p><p>pizza</p><p>3 rows in set (0.00 sec)</p><p>Com o SQL Server, você não precisa con�gurar sua ferramenta de linha de</p><p>comando: basta apenas adicionar a cláusula for xml no �m de sua consulta,</p><p>como em: SELECT * FROM favorite_food</p><p>FOR XML AUTO, ELEMENTS</p><p>Atualizando dados</p><p>Quando os dados de William Turner foram inicialmente adicionados à tabela, várias</p><p>colunas de endereço foram omitidas da instrução insert. A próxima instrução</p><p>mostra como essas colunas podem ser populadas por meio de uma instrução</p><p>update: mysql> UPDATE person -> SET street = '1225 Tremont</p><p>St.', -> city = 'Boston',</p><p>-> state = 'MA',</p><p>-> country = 'USA',</p><p>-> postal_code = '02138'</p><p>-> WHERE person_id = 1;</p><p>Query OK, 1 row affected (0.04 sec)</p><p>Rows matched: 1 Changed: 1 Warnings: 0</p><p>O servidor respondeu com uma mensagem de duas linhas: o item “Rows matched:</p><p>1” informa que a condição na cláusula where correspondeu</p>
<p>a uma única linha da</p><p>tabela, e o item “Changed: 1” diz que uma única linha da tabela foi modi�cada.</p><p>Como a cláusula where especi�ca a chave primária da linha de William, isso é</p><p>exatamente o que você esperaria que acontecesse.</p><p>Dependendo das condições em sua cláusula where, também é possível modi�car</p><p>mais de uma linha com uma única instrução. Considere, por exemplo, o que</p><p>aconteceria se sua cláusula where �casse assim: WHERE person_id DELETE FROM person -> WHERE person_id =</p><p>2;</p><p>Query OK, 1 row affected (0.01 sec)</p><p>Mais uma vez, a chave primária foi usada para isolar a linha de interesse, de forma</p><p>que uma única linha seja apagada da tabela. Semelhantemente à instrução update,</p><p>mais de uma linha pode ser apagada dependendo das condições da cláusula where, e</p><p>todas as linhas serão apagadas se a cláusula where for omitida.</p><p>Quando boas instruções se dão mal</p><p>Até agora, todas as instruções de dados SQL mostradas neste capítulo têm sido bem-</p><p>formadas e seguiram corretamente todas as regras. No entanto, baseado nas</p><p>de�nições das tabelas person e favorite_food, existem várias maneiras com as</p><p>quais você pode gerar con�itos ao inserir ou modi�car dados. Esta seção mostrará a</p><p>você alguns dos erros mais comuns que podem surgir à sua frente e como o servidor</p><p>MySQL responderá a eles.</p><p>Chaves primárias não-únicas</p><p>Como a de�nição das tabelas incluiu a criação de restrições de chave primária, o</p><p>MySQL irá se certi�car de que valores duplicados de chaves não serão inseridos nas</p><p>tabelas. A instrução a seguir tenta burlar o atributo de autoincremento da coluna</p><p>person_id para criar outra linha na tabela person com o valor 1 em person_id:</p><p>mysql> INSERT INTO person -> (person_id, fname, lname,</p><p>gender, birth_date) -> VALUES (1, 'Charles','Fulton', 'M',</p><p>'1968-01-15'); ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'</p><p>Nada o impedirá (pelo menos com os objetos de esquema atuais) de criar duas</p><p>linhas com nomes idênticos, endereços idênticos, datas de nascimento idênticas e</p><p>assim por diante, desde que as linhas tenham valores diferentes na coluna</p><p>person_id.</p><p>Chave estrangeira não-existente</p><p>A de�nição da tabela favorite_food inclui a criação de uma restrição de chave</p><p>estrangeira na coluna person_id. Essa restrição garante que todos os valores</p><p>entrados em person_id na tabela favorite_food existem na tabela person. Veja o que</p><p>aconteceria caso você tentasse criar uma linha que violasse essa restrição: mysql></p><p>INSERT INTO favorite_food (person_id, food) -> VALUES</p><p>(999, 'lasagna');</p><p>ERROR 1452 (23000): Cannot add or update a child row: a foreign key</p><p>constraint</p><p>fails ('bank'.'favorite_food', CONSTRAINT 'fk_fav_food_person_id' FOREIGN</p><p>KEY</p><p>('person_id') REFERENCES 'person' ('person_id'))</p><p>Nesse caso, a tabela favorite_food é considerada a �lha, e a tabela person, a mãe, já</p><p>que a tabela favorite_food depende da tabela person para preencher alguns de seus</p><p>dados. Se você planeja inserir dados em ambas as tabelas, será necessário primeiro</p><p>criar uma linha na tabela-mãe antes de inserir dados em favorite_food.</p><p>Restrições de chave estrangeira são impostas apenas quando as tabelas são criadas</p><p>usando o mecanismo de armazenamento InnoDB. Discutiremos os mecanismos de</p><p>armazenamento do MySQL no capítulo 12.</p><p>Violações dos valores das colunas</p><p>A coluna gender da tabela person é restrita aos valores ‘M’ para masculino e ‘F’ para</p><p>feminino. Se você tentar por engano atribuir à coluna qualquer outro valor, receberá</p><p>a seguinte resposta: mysql> UPDATE person</p><p>-> SET gender = 'Z'</p><p>-> WHERE person_id = 1;</p><p>ERROR 1265 (01000): Data truncated for column 'gender' at row 1</p><p>A mensagem de erro é um pouco confusa, mas ela dá uma ideia geral de que o</p><p>servidor não �cou feliz com o valor fornecido para a coluna gender.</p><p>Conversões de data inválidas</p><p>Se você construir uma string com a qual pretende popular uma coluna de data, e</p><p>essa string não corresponder ao formato esperado, você receberá outro erro. Aqui</p><p>está um exemplo que usa um formato de data que não corresponde ao formato de</p><p>data padrão “AAAA-MM-DD”: mysql> UPDATE person</p><p>-> SET birth_date = 'DEC-21-1980'</p><p>-> WHERE person_id = 1;</p><p>ERROR 1292 (22007): Incorrect date value: 'DEC-21-1980' for column</p><p>'birth_date'</p><p>at row 1</p><p>Em geral, é sempre uma boa ideia especi�car explicitamente o formato da string em</p><p>vez de con�ar no formato padrão. Aqui está outra versão da instrução que usa a</p><p>função str_to_date para especi�car qual formato de string será usado: mysql></p><p>UPDATE person</p><p>-> SET birth_date = str_to_date('DEC-21-1980' , '%b-%d-%Y') -> WHERE</p><p>person_id = 1;</p><p>Query OK, 1 row affected (0.12 sec)</p><p>Rows matched: 1 Changed: 1 Warnings: 0</p><p>Não só o servidor de banco de dados �cou feliz, como William também �cou feliz</p><p>(nós o deixamos oito anos mais jovem, sem precisar fazer uma cirurgia estética</p><p>caríssima!).</p><p>Anteriormente no capítulo, quando comentei a respeito dos vários tipos de dados</p><p>temporais, mostrei strings de formatação de datas do tipo “AAAA-MM-DD”. Enquanto</p><p>vários servidores de banco de dados usam esse estilo de formatação, o MySQL usa %Y</p><p>para indicar um ano com quatro caracteres. Aqui estão mais alguns formatadores que</p><p>você talvez possa precisar ao converter strings em datas/horas no MySQL: %aO nome</p><p>do dia da semana abreviado: Sun, Mon, ...</p><p>%b O nome do mês abreviado: Jan, Feb, ...</p><p>%c O número correspondente ao mês (0..12) %d Os dias do mês (00..31)</p><p>%f Quantidade de microssegundos (000000..999999) %H A hora do dia, no formato de</p><p>24 horas (00..23) %h A hora do dia, no formato de 12 horas (01..12) %i Os minutos dentro</p><p>de uma hora (00..59) %M O nome completo do mês (January..December) %m O número</p><p>correspondente ao mês (01..12) %p AM ou PM</p><p>%s A quantidade de segundos (00..59)</p><p>%W O nome completo do dia da semana (Sunday..Saturday) %w O número</p><p>correspondente ao dia da semana (0=Domingo..6=Sábado) %Y O ano com quatro</p><p>dígitos</p><p>Esquema bank (bank schema)</p><p>No restante do livro, você usará um grupo de tabelas que modelam um banco</p><p>comunitário. Algumas das tabelas incluem employee (funcionário), branch (a�liada),</p><p>account (conta), customer (cliente), product (produto) e transaction (transação). O</p><p>esquema completo e os dados de exemplo devem ter sido criados depois de realizar</p><p>os passos �nais do carregamento do servidor MySQL, no início do capítulo, e da</p><p>geração dos dados de exemplo. Para ver um diagrama das tabelas e de suas colunas</p><p>e relacionamentos, veja o apêndice A.</p><p>A tabela 2.10 mostra todas as tabelas utilizadas no esquema bank, junto com suas</p><p>breves de�nições.</p><p>Tabela 2.10 – De�nições de esquema bank</p><p>Nome da tabela De�nição</p><p>account Um produto específico aberto para um cliente em particular</p><p>branch Uma localização em que transações bancárias são conduzidas</p><p>business Um cliente corporativo (subtipo da tabela customer)</p><p>customer Uma pessoa ou empresa reconhecida pelo banco</p><p>department Um grupo de funcionários do banco que executa uma determinada função bancária</p><p>employee Uma pessoa que trabalha para o banco</p><p>individual Um cliente não-corporativo (subtipo da tabela customer)</p><p>officer Uma pessoa autorizada a conduzir negócios para um cliente corporativo</p><p>product Um serviço bancário oferecido aos clientes</p><p>product_type Um grupo de produtos que têm funções similares</p><p>transaction Uma mudança realizada no balanço de uma conta</p><p>Sinta-se à vontade para fazer quantas experiências quiser com as tabelas, inclusive</p><p>adicionar suas próprias tabelas para expandir as funções de negócio do banco. Você</p><p>também pode descartar uma tabela e recriá-la a partir do arquivo que você baixou</p><p>para garantir que seus dados de exemplo estejam corretos.</p><p>Se quiser ver quais tabelas estão disponíveis em seu banco de dados, você</p>
<p>pode usar</p><p>o comando show tables, assim: mysql> SHOW TABLES; +----------------+</p><p>| Tables_in_bank |</p><p>+----------------+</p><p>| account |</p><p>| branch |</p><p>| business |</p><p>| customer |</p><p>| department |</p><p>| employee |</p><p>| favorite_food |</p><p>| individual |</p><p>| officer |</p><p>| person |</p><p>| product |</p><p>| product_type |</p><p>| transaction |</p><p>+----------------+</p><p>13 rows in set (0.10 sec)</p><p>Junto com as 11 tabelas do esquema de banco, a listagem de tabelas também inclui</p><p>as duas tabelas criadas neste capítulo: person e favorite_food. Essas tabelas não</p><p>serão usadas nos próximos capítulos, então sinta-se à vontade para descartá-las com</p><p>os seguintes comandos: mysql> DROP TABLE favorite_food; Query OK, 0</p><p>rows a�ected (0.56 sec)</p><p>mysql> DROP TABLE person; Query OK, 0 rows affected (0.05 sec)</p><p>Se quiser olhar as colunas de uma tabela, você pode usar o comando describe. Aqui</p><p>está um exemplo da saída de describe para a tabela customer:</p><p>Quanto mais confortável você estiver com o banco de dados de exemplo, melhor</p><p>você entenderá os exemplos e, consequentemente, maior será sua compreensão dos</p><p>conceitos dos próximos capítulos.</p><p>1 N. de T.: apesar de estarmos usando o padrão brasileiro de formatação de números para indicar os</p><p>escopos dos tipos numéricos, não se esqueça de que os bancos de dados utilizam o ponto (.) como</p><p>separador decimal.</p><p>CAPÍTULO 3</p><p>Introdução a consultas</p><p>Até agora, você viu alguns poucos exemplos de consultas de banco de dados</p><p>(também conhecidas como instruções select) espalhadas ao longo dos primeiros</p><p>dois capítulos. Agora chegou o momento de observar mais de perto as diferentes</p><p>partes da instrução select e como elas interagem entre si.</p><p>Funcionamento das consultas</p><p>Antes de dissecar a instrução select, pode ser interessante observar como as</p><p>consultas são executadas pelo servidor MySQL (ou, no caso, por qualquer servidor</p><p>de banco de dados). Se você estiver usando a ferramenta de linha de comando mysql</p><p>(e presumo que esteja), você já logou no servidor MySQL fornecendo um nome de</p><p>usuário e uma senha (e possivelmente um nome de host, caso o MySQL esteja</p><p>sendo executado em outro computador). Uma vez que o servidor tenha veri�cado</p><p>que seu nome de usuário e senha estão corretos, uma conexão de banco de dados é</p><p>gerada para uso. Essa conexão é mantida pela aplicação que a solicitou (que, nesse</p><p>caso, é a ferramenta mysql) até que a aplicação libere a conexão (ou seja, como</p><p>resultado de você digitar quit) ou que o servidor feche a conexão (ou seja, quando o</p><p>servidor é desligado). Cada conexão com o servidor MySQL recebe um identi�cador,</p><p>que é mostrado a você logo após o login: Welcome to the MySQL monitor.</p><p>Commands end with ; or \g.</p><p>Your MySQL connection id is 11</p><p>Server version: 6.0.3-alpha-community MySQL Community Server (GPL)</p><p>Type 'help;' or '\h' for help. Type '\c' to clear the buffer.</p><p>Nesse caso, meu ID de conexão é 11. Essa informação pode ser útil para seu</p><p>administrador de banco de dados caso algo saia errado, como uma consulta</p><p>malformada que �ca executando por horas, então provavelmente você vai querer</p><p>anotá-lo.</p><p>Uma vez que o servidor tenha veri�cado seu nome de usuário e senha e tenha</p><p>retornado uma conexão, você está pronto para executar consultas (junto com outras</p><p>instruções SQL). Cada vez que uma consulta é enviada ao servidor, ele veri�ca os</p><p>seguintes itens antes de executá-la: • Você tem permissão para executar a instrução?</p><p>• Você tem permissão para acessar os dados desejados?</p><p>• A sintaxe de sua instrução está correta?</p><p>Se sua instrução passar por esses testes, a consulta será passada para o otimizador de</p><p>consultas, cujo trabalho é determinar a maneira mais e�ciente de executá-la. O</p><p>otimizador observará coisas como a ordem em que deve juntar as tabelas nomeadas</p><p>na cláusula from e quais índices estão disponíveis, então ele traçará um plano de</p><p>execução que o servidor usará para executar sua consulta.</p><p>Compreender e in�uenciar a forma que seu servidor de banco de dados escolhe planos</p><p>de execução é um tópico fascinante que muitos de vocês desejarão explorar. Os leitores</p><p>que estiverem utilizando o MySQL deveriam considerar ler o livro High Performance</p><p>MySQL, de Baron Scwartz e colaboradores. (http://oreilly.com/catalog/9780596101718/)</p><p>(O’Reilly). Entre outras coisas, você aprenderá a gerar índices, analisar planos de</p><p>execução, in�uenciar o otimizador por meio de dicas de consulta e ajustar os</p><p>parâmetros de inicialização de seu servidor. Se estiver usando o Oracle Database ou o</p><p>SQL Server, dúzias de livros de otimização estão disponíveis no mercado.</p><p>Uma vez que o servidor tenha terminado de executar sua consulta, o conjunto-</p><p>resultado (result set) é retornado à aplicação que o invocou (que é, mais uma vez, a</p><p>ferramenta mysql). Como mencionei no capítulo 1, um conjunto-resultado é nada</p><p>além de outra tabela contendo linhas e colunas. Se sua consulta falhar em gerar</p><p>quaisquer resultados, a ferramenta mysql mostrará a mensagem que se encontra no</p><p>�nal do seguinte exemplo: mysql> SELECT emp_id, fname, lname -> FROM</p><p>employee</p><p>-> WHERE lname = 'Bkadfl';</p><p>Empty set(0.00 sec)</p><p>Se a consulta retornar uma ou mais linhas, a ferramenta mysql formatará os</p><p>resultados adicionando cabeçalhos de coluna e construindo caixas em torno das</p><p>colunas usando os símbolos -, | e +, como mostra o próximo exemplo: mysql></p><p>SELECT fname, lname</p><p>-> FROM employee;</p><p>+----------+-----------+</p><p>| fname | lname |</p><p>+----------+-----------+</p><p>| Michael | Smith |</p><p>| Susan | Barker |</p><p>| Robert | Tyler |</p><p>| Susan | Hawthorne |</p><p>| John | Gooding |</p><p>| Helen | Fleming |</p><p>| Chris | Tucker |</p><p>| Sarah | Parker |</p><p>| Jane | Grossman |</p><p>| Paula | Roberts |</p><p>| Thomas | Ziegler |</p><p>| Samantha | Jameson |</p><p>| John | Blake |</p><p>| Cindy | Mason |</p><p>| Frank | Portman |</p><p>| Theresa | Markham |</p><p>| Beth | Fowler |</p><p>| Rick | Tulman |</p><p>+----------+-----------+</p><p>18 rows in set (0.00 sec)</p><p>Essa consulta retornou o primeiro e o último nome de todos os funcionários da</p><p>tabela employee. Após a exibição da última linha de dados, a ferramenta mysql exibe</p><p>uma mensagem informando-lhe quantas linhas foram retornadas, que, nesse caso,</p><p>foram 18.</p><p>Cláusulas de consulta</p><p>Vários componentes ou cláusulas formam a instrução select. Enquanto apenas uma</p><p>delas é obrigatória no MySQL (a cláusula select), normalmente você incluirá pelo</p><p>menos duas ou três das seis cláusulas disponíveis. A tabela 3.1 mostra as diferentes</p><p>cláusulas e suas �nalidades.</p><p>Tabela 3.1 – Cláusulas de consulta</p><p>Nome da cláusula Finalidade</p><p>Select Determina quais colunas serão incluídas no conjunto-resultado da consulta</p><p>From Identifica as tabelas de onde serão retirados os dados e como as tabelas deverão ser juntadas</p><p>Where Filtra os dados indesejados</p><p>Group by Usada para agrupar linhas por meio de valores comuns de colunas</p><p>Having Filtra grupos indesejados</p><p>Order by Ordena as linhas do conjunto-resultado final usando uma ou mais colunas</p><p>Todas as cláusulas mostradas na tabela 3.1 estão incluídas na especi�cação ANSI.</p><p>Além disso, existem várias outras cláusulas exclusivas do MySQL que exploraremos</p><p>no apêndice B. As seções seguintes aprofundam o uso das seis principais cláusulas</p><p>de consulta.</p><p>Cláusula select</p><p>Apesar de a cláusula select ser a primeira cláusula de uma instrução select, ela é</p><p>uma das últimas a ser avaliada pelo servidor de banco de dados. A razão para isso é</p><p>que, antes de poder determinar o que incluir no conjunto-resultado �nal, é</p><p>necessário conhecer de antemão todas as colunas que podem ser incluídas nele.</p><p>Portanto, para entender completamente o papel da cláusula select, você precisará</p><p>conhecer um pouco sobre a cláusula from. Para começar, aqui vai uma consulta:</p><p>mysql> SELECT *</p><p>-> FROM department;</p><p>+---------+----------------+</p><p>| dept_id | name |</p><p>+---------+----------------+</p><p>| 1 | Operations |</p><p>| 2 | Loans |</p><p>| 3 | Administration |</p><p>+---------+----------------+</p><p>3 rows in set (0.04 sec)</p><p>Nessa consulta, a cláusula from lista uma única tabela (department), e a cláusula</p><p>select indica que todas as colunas (referenciadas pelo *) na tabela department</p><p>devem ser incluídas no conjunto-resultado. Essa consulta poderia ser descrita em</p><p>português da seguinte maneira: Mostre</p>
<p>todas as colunas e todas as linhas da tabela</p><p>departamento.</p><p>Além de especi�car todas as colunas por meio do caractere asterisco, você pode</p><p>nomear explicitamente as colunas nas quais está interessado, como em: mysql></p><p>SELECT dept_id, name</p><p>-> FROM department;</p><p>+---------+----------------+</p><p>| dept_id | name |</p><p>+---------+----------------+</p><p>| 1 | Operations |</p><p>| 2 | Loans |</p><p>| 3 | Administration |</p><p>+---------+----------------+</p><p>3 rows in set (0.01 sec)</p><p>Os resultados são idênticos aos da primeira consulta, já que todas as colunas da</p><p>tabela department (dept_id e name) foram nomeadas na cláusula select. Você</p><p>também pode optar por incluir apenas um subconjunto das colunas da tabela</p><p>department: mysql> SELECT name</p><p>-> FROM department;</p><p>+----------------+</p><p>| name |</p><p>+----------------+</p><p>| Operations |</p><p>| Loans |</p><p>| Administration |</p><p>+----------------+</p><p>3 rows in set (0.00 sec)</p><p>A função da cláusula select, portanto, é a seguinte: A cláusula select determina</p><p>quais entre todas as colunas possíveis deverão ser incluídas no conjunto-resultado da</p><p>consulta.</p><p>Se você estivesse limitado a incluir apenas as colunas da tabela ou das tabelas</p><p>nomeadas na cláusula from, as coisas seriam bem chatas. No entanto, você pode</p><p>apimentar as coisas incluindo em sua cláusula select o seguinte: • Literais, como</p><p>números ou strings.</p><p>• Expressões, como transaction.amount * -1.</p><p>• Funções nativas, como ROUND(transaction.amount, 2).</p><p>• Chamada a funções de�nidas pelo usuário.</p><p>A próxima consulta demonstra o uso de uma coluna da tabela, um literal, uma</p><p>expressão e uma chamada a uma função nativa em uma única consulta à tabela</p><p>employee:</p><p>Falaremos sobre expressões e funções nativas de forma mais detalhada</p><p>posteriormente, mas eu quis lhe dar um aperitivo das coisas que podem ser</p><p>incluídas em uma cláusula select. Se você precisa apenas executar uma função</p><p>nativa ou avaliar uma expressão simples, você pode omitir a cláusula select por</p><p>completo. Aqui está um exemplo:</p><p>Como essa consulta apenas chama três funções nativas e não retorna dados de</p><p>quaisquer tabelas, não há necessidade de uma cláusula from.</p><p>Aliases de colunas</p><p>Apesar de a ferramenta mysql gerar rótulos (cabeçalhos) para as colunas retornadas</p><p>por suas consultas, você pode querer atribuir seus próprios rótulos. Embora você</p><p>possa desejar atribuir um novo rótulo para uma coluna de uma tabela (se ela for</p><p>nomeada de forma pouco clara ou ambígua), você certamente desejará atribuir seus</p><p>próprios rótulos para aquelas colunas de seu conjunto-resultado que são geradas</p><p>por expressões ou chamadas a funções nativas. Você pode fazer isso adicionando um</p><p>alias (apelido) de coluna após cada elemento de sua cláusula select. Aqui está a</p><p>consulta anterior à tabela employee com aliases de colunas aplicados a três delas:</p><p>Se você olhar no cabeçalho das colunas, verá que a segunda, terceira e quarta</p><p>colunas agora têm nomes razoáveis, em vez de simplesmente serem rotuladas com a</p><p>função ou expressão que as gerou. Se você observar a cláusula select, verá como os</p><p>aliases de coluna status, empid_x_pi e last_name_upper são adicionados à segunda,</p><p>terceira e quarta colunas. Creio que você concordará comigo que �ca mais fácil de</p><p>entender a saída da instrução com o uso dos aliases de coluna e que �caria mais</p><p>fácil de trabalhar com ela programaticamente se você estiver requisitando a consulta</p><p>de dentro do Java ou do C# em vez da ferramenta mysql. Para destacar mais ainda</p><p>seus aliases de coluna, você também tem a opção de usar a palavra-chave as antes do</p><p>nome do alias, assim:</p><p>mysql> SELECT emp_id,</p><p>-> 'ACTIVE' AS status,</p><p>-> emp_id * 3.14159 AS empid_x_pi, -> UPPER(lname) AS last_name_upper -></p><p>FROM employee;</p><p>Muitas pessoas sentem que a inclusão da palavra-chave opcional as melhora a</p><p>legibilidade, apesar de eu ter optado por não usá-la nos exemplos deste livro.</p><p>Removendo linhas duplicadas</p><p>Em alguns casos, uma consulta pode retornar linhas de dados duplicadas. Por</p><p>exemplo, se você fosse retornar todos os IDs de todos os clientes que possuem</p><p>contas, você faria o seguinte: mysql> SELECT cust_id</p><p>-> FROM account;</p><p>+---------+</p><p>| cust_id |</p><p>+---------+</p><p>| 1 |</p><p>| 1 |</p><p>| 1 |</p><p>| 2 |</p><p>| 2 |</p><p>| 3 |</p><p>| 3 |</p><p>| 4 |</p><p>| 4 |</p><p>| 4 |</p><p>| 5 |</p><p>| 6 |</p><p>| 6 |</p><p>| 7 |</p><p>| 8 |</p><p>| 8 |</p><p>| 9 |</p><p>| 9 |</p><p>| 9 |</p><p>| 10 |</p><p>| 10 |</p><p>| 11 |</p><p>| 12 |</p><p>| 13 |</p><p>+---------+</p><p>24 rows in set (0.00 sec)</p><p>Como alguns clientes têm mais de uma conta, você verá o mesmo ID de cliente uma</p><p>vez para cada conta mantida por cada cliente. O que você provavelmente deseja</p><p>nesse caso é o conjunto especí�co de clientes que tenham contas, em vez de</p><p>visualizar o ID de cliente de cada linha da tabela account. Você pode conseguir isso</p><p>adicionando a palavra-chave distinct diretamente após a palavra-chave select,</p><p>como demonstrado a seguir: mysql> SELECT DISTINCT cust_id -> FROM</p><p>account;</p><p>+---------+</p><p>| cust_id |</p><p>+---------+</p><p>| 1 |</p><p>| 2 |</p><p>| 3 |</p><p>| 4 |</p><p>| 5 |</p><p>| 6 |</p><p>| 7 |</p><p>| 8 |</p><p>| 9 |</p><p>| 10 |</p><p>| 11 |</p><p>| 12 |</p><p>| 13 |</p><p>+---------+</p><p>13 rows in set (0.01 sec)</p><p>O conjunto-resultado agora contém 13 linhas, uma para cada cliente especí�co, em</p><p>vez de 24 linhas, uma para cada conta.</p><p>Se não quiser que o servidor remova dados duplicados do resultado ou se tiver</p><p>certeza de que não há duplicatas em seu conjunto-resultado, você pode especi�car a</p><p>palavra-chave ALL em vez de especi�car DISTINCT. No entanto, a palavra-chave ALL é</p><p>o padrão e nunca precisa ser nomeada explicitamente, então a maioria dos</p><p>programadores não inclui ALL em suas consultas.</p><p>Tenha em mente que a geração de um conjunto-resultado DISTINCT requer a ordenação</p><p>dos dados, o que pode demorar muito para grandes conjuntos-resultados. Não caia na</p><p>armadilha de usar DISTINCT apenas para ter certeza de que não haverá duplicatas; em</p><p>vez disso, gaste um pouco de tempo entendendo os dados com os quais está</p><p>trabalhando, para saber se duplicatas poderão, de fato, ocorrer.</p><p>Cláusula from</p><p>Até aqui, você viu consultas cujas cláusulas from continham apenas uma única</p><p>tabela. Apesar de a maioria dos livros de SQL de�nir a cláusula from simplesmente</p><p>como uma lista de uma ou mais tabelas, gostaria de ampliar a de�nição da seguinte</p><p>maneira: A cláusula from de�ne as tabelas usadas em uma consulta, junto com os</p><p>meios de vincular essas tabelas.</p><p>Essa de�nição é composta por dois conceitos separados, mas relacionados, que</p><p>exploraremos nas seções seguintes.</p><p>Tabelas</p><p>Quando confrontadas com o termo tabela, a maioria das pessoas pensa em um</p><p>conjunto de linhas relacionadas armazenadas em um banco de dados. Enquanto</p><p>isso realmente descreve um tipo de tabela, eu gostaria de usar a palavra de forma</p><p>mais geral, removendo qualquer noção de como os dados podem ser armazenados e</p><p>enfocando apenas o conjunto de linhas relacionadas. Três tipos diferentes de tabelas</p><p>se enquadram nessa de�nição menos restrita: • Tabelas permanentes (ou seja,</p><p>criadas usando a instrução create table).</p><p>• Tabelas temporárias (ou seja, linhas retornadas por uma subconsulta).</p><p>• Tabelas virtuais (ou seja, criadas usando a instrução create view).</p><p>Cada um desses tipos de tabelas pode ser incluído em uma cláusula from de uma</p><p>consulta. Por ora, você deve estar confortável com a inclusão de tabelas permanentes</p><p>em uma cláusula from, então descreverei brevemente os outros tipos de tabelas que</p><p>podem ser referenciados em uma cláusula from.</p><p>Tabelas geradas por subconsultas</p><p>Uma subconsulta é uma consulta contida dentro de outra consulta. Subconsultas</p><p>são englobadas por parênteses e podem ser encontradas em inúmeras partes de uma</p><p>instrução select, dentro da cláusula from, no entanto, uma subconsulta tem o papel</p><p>de gerar uma tabela temporária visível por todas as outras cláusulas da consulta e</p><p>que pode interagir com outras tabelas nomeadas na cláusula from. Aqui está um</p><p>exemplo simples:</p><p>Nesse exemplo, uma subconsulta à tabela employee retorna cinco colunas, e a</p><p>consulta que a contém referencia três das cinco colunas disponíveis. A subconsulta é</p><p>referenciada pela consulta-contêiner por meio de seu alias, que, nesse caso, é e. Este</p><p>foi um exemplo simplista e pouco útil de uma subconsulta em uma cláusula from;</p><p>você encontrará</p>
<p>uma abordagem detalhadas de subconsultas no capítulo 9.</p><p>Views</p><p>Uma view é uma consulta armazenada no dicionário de dados. Ela se parece com, e</p><p>age como uma tabela, mas não há dados associados a uma view (é por isso que eu a</p><p>chamo de tabela virtual). Quando você realiza uma consulta em uma view, sua</p><p>consulta é mesclada com a de�nição da view para criar uma consulta �nal a ser</p><p>executada.</p><p>Como demonstração, aqui está uma de�nição de view que consulta a tabela</p><p>employee e inclui uma chamada a uma função nativa: mysql> CREATE VIEW</p><p>employee_vw AS</p><p>-> SELECT emp_id, fname, lname,</p><p>-> YEAR(start_date) start_year</p><p>-> FROM employee;</p><p>Query OK, 0 rows affected (0.10 sec)</p><p>Quando a view é criada, nenhum dado adicional é gerado ou armazenado: o</p><p>servidor simplesmente esconde a instrução select no bolso para uso futuro. Agora</p><p>que a view existe, você pode usá-la em consultas, como em: mysql> SELECT</p><p>emp_id, start_year -> FROM employee_vw;</p><p>+--------+------------+</p><p>| emp_id | start_year |</p><p>+--------+------------+</p><p>| 1 | 2005 |</p><p>| 2 | 2006 |</p><p>| 3 | 2005 |</p><p>| 4 | 2006 |</p><p>| 5 | 2007 |</p><p>| 6 | 2008 |</p><p>| 7 | 2008 |</p><p>| 8 | 2006 |</p><p>| 9 | 2006 |</p><p>| 10 | 2006 |</p><p>| 11 | 2004 |</p><p>| 12 | 2007 |</p><p>| 13 | 2004 |</p><p>| 14 | 2006 |</p><p>| 15 | 2007 |</p><p>| 16 | 2005 |</p><p>| 17 | 2006 |</p><p>| 18 | 2006 |</p><p>+--------+------------+</p><p>18 rows in set (0.07 sec)</p><p>As views são criadas por inúmeras razões, inclusive para esconder colunas dos</p><p>usuários e simpli�car projetos de banco de dados complexos.</p><p>Vínculos de tabela</p><p>O segundo desvio da de�nição simples que �z da cláusula from é a regra obrigatória</p><p>que diz que, caso mais de uma tabela apareça na cláusula from, as condições para</p><p>vincular as tabelas também devem ser incluídas. Isso não é um requisito do MySQL</p><p>ou de qualquer outro servidor de banco de dados, e sim um método aprovado pelo</p><p>ANSI de junção de múltiplas tabelas, e é o que apresenta a melhor portabilidade</p><p>entre os vários servidores de banco de dados. Exploraremos a junção de múltiplas</p><p>tabelas mais a fundo nos capítulos 5 e 10, mas aqui vai um exemplo simples no caso</p><p>de eu ter aguçado sua curiosidade:</p><p>A consulta anterior exibe dados tanto da tabela employee (emp_id, fname, lname)</p><p>quanto da tabela department (name), então ambas as tabelas são incluídas na</p><p>cláusula from. O mecanismo de vinculação das duas tabelas (referido como uma</p><p>junção ou join) é a a�liação do funcionário a um departamento armazenada na</p><p>tabela employee. Então, o servidor de banco de dados é instruído a usar o valor da</p><p>coluna dept_id na tabela employee para procurar pelo nome do departamento</p><p>associado na tabela department. As condições de junção das duas tabelas são</p><p>encontradas na subcláusula on da cláusula from; nesse caso, a condição de junção é</p><p>ON employee.dept_id = department.dept_id. Novamente, por favor, consulte o</p><p>capítulo 5 para uma discussão mais aprofundada sobre junção de múltiplas tabelas.</p><p>De�nindo aliases de tabela</p><p>Quando múltiplas tabelas são juntadas em uma única consulta, você precisa de uma</p><p>maneira de identi�car a qual tabela você está se referindo quando referencia colunas</p><p>nas cláusulas select, where, group by, having e order by. Você tem duas escolhas ao</p><p>referenciar uma tabela fora da cláusula from: • Usar o nome completo da tabela,</p><p>como em employee.emp_id.</p><p>• Atribuir um alias a cada uma das tabelas e usá-lo ao longo da consulta.</p><p>Na consulta anterior, optei por usar o nome completo da tabela nas cláusulas select</p><p>e on. Aqui está como a mesma consulta �caria usando aliases: SELECT e.emp_id,</p><p>e.fname, e.lname,</p><p>d.name dept_name</p><p>FROM employee e INNER JOIN department d</p><p>ON e.dept_id = d.dept_id;</p><p>Se você olhar atentamente a cláusula from, verá que foi atribuído o alias e à tabela</p><p>employee, e o alias d à tabela department. Esses aliases são, então, utilizados na</p><p>cláusula on para de�nir a condição de junção, bem como na cláusula select ao</p><p>especi�car as colunas que serão incluídas no conjunto-resultado. Creio que você</p><p>concordará que o uso de aliases torna a instrução mais compacta sem causar</p><p>confusão (contanto que suas escolhas de nomes de aliases sejam razoáveis). Além</p><p>disso, você pode usar a palavra-chave as com seus aliases de tabela, de forma similar</p><p>à que foi demonstrada anteriormente com os aliases de coluna:</p><p>SELECT e.emp_id, e.fname, e.lname,</p><p>d.name dept_name</p><p>FROM employee AS e INNER JOIN department AS d ON e.dept_id = d.dept_id;</p><p>Descobri que aproximadamente metade dos desenvolvedores de banco de dados</p><p>com os quais trabalhei usam a palavra-chave as com seus aliases de coluna e tabela,</p><p>e metade não a utiliza.</p><p>Cláusula where</p><p>As consultas mostradas até agora neste capítulo selecionaram todas as linhas da</p><p>tabela employee, department ou account (exceto pela demonstração de distinct no</p><p>início do capítulo). Na maior parte do tempo, no entanto, você não vai querer</p><p>recuperar todas as linhas de uma tabela, mas vai querer uma maneira de �ltrar</p><p>aquelas linhas que não são de seu interesse. Isso é um trabalho para a cláusula</p><p>where.</p><p>A cláusula where é o mecanismo de �ltragem de linhas indesejadas de seu conjunto-</p><p>resultado.</p><p>Por exemplo, talvez você esteja interessado em recuperar dados da tabela employee,</p><p>mas apenas daqueles funcionários que estão empregados como gerentes de caixa. A</p><p>consulta a seguir utiliza uma cláusula where para recuperar apenas os quatro</p><p>gerentes de caixa (head teller):</p><p>Nesse caso, a cláusula where �ltrou 14 das 18 linhas de funcionários. Essa cláusula</p><p>where contém uma única condição de �ltragem, mas você pode incluir quantas</p><p>condições forem necessárias – condições individuais são separadas usando</p><p>operadores do tipo and, or e not (veja o capítulo 4 para uma discussão completa</p><p>da cláusula where e de condições de �ltragem). Aqui está uma extensão da consulta</p><p>anterior que inclui uma segunda condição, indicando que apenas os funcionários</p><p>com data de admissão após 1º de janeiro de 2006 devem ser incluídos:</p><p>A primeira condição (title = 'Head Teller') �ltrou 14 das 18 linhas de</p><p>funcionários, e a segunda (start_date > '2006-01-01') �ltrou mais duas linhas,</p><p>deixando apenas duas linhas no conjunto-resultado �nal. Vejamos o que aconteceria</p><p>se você mudasse o operador and, que separa as duas condições, para or:</p><p>Olhando a saída, você pode perceber que os quatro gerentes foram incluídos no</p><p>conjunto-resultado, junto com todos os outros funcionários que começaram a</p><p>trabalhar após 1º de janeiro de 2006. Pelo menos uma das duas condições é verdade</p><p>para 15 dos 18 funcionários da tabela employee. Portanto, quando você separa as</p><p>condições com um operador and, todas as condições devem ser verdadeiras para</p><p>que uma linha seja incluída no conjunto-resultado; quando você usa o operador or,</p><p>no entanto, apenas uma das condições precisa ser verdadeira para que a linha seja</p><p>incluída.</p><p>Então, o que você deve fazer caso precise usar os operadores and e or na mesma</p><p>cláusula where? Fico feliz que tenha perguntado. Você deve usar parênteses para</p><p>agrupar condições. A próxima consulta especi�ca que apenas os funcionários que</p><p>são gerentes de caixa e começaram a trabalhar na empresa depois do dia 1º de</p><p>janeiro de 2006, ou os funcionários que são caixas de banco e começaram a</p><p>trabalhar após 1º de janeiro de 2007, devem ser incluídos no conjunto-resultado:</p><p>Você deve sempre usar os parênteses para separar grupos de condições que</p><p>misturem diferentes operadores, de forma que você, o servidor de banco de dados e</p><p>qualquer pessoa que posteriormente venha a modi�car seu código entendam a</p><p>mesma coisa.</p><p>Cláusulas group by e having</p><p>Todas as consultas até agora recuperaram dados brutos sem qualquer tipo de</p><p>manipulação. Às vezes, no entanto, você precisará localizar detalhes em seus dados</p><p>que exigirão do servidor de banco de dados certo tratamento dos dados antes de</p><p>retornar o conjunto-resultado. Um desses mecanismos é a cláusula group by, que é</p><p>usada para agrupar dados por valores de colunas. Por exemplo, em vez de olhar a</p><p>lista de funcionários e de departamentos nos quais estão empregados, você pode</p><p>querer olhar para a lista de departamentos, junto com o número de funcionários</p><p>empregados em cada departamento.</p>
<p>Ao usar a cláusula group by, você também pode</p><p>usar a cláusula having, que permite �ltrar dados agrupados da mesma forma que a</p><p>cláusula where permite �ltrar dados brutos.</p><p>Aqui está uma consulta que conta todos os funcionários de cada departamento e</p><p>que retorna os nomes dos departamentos que contenham mais de dois</p><p>funcionários:</p><p>mysql> SELECT d.name, count(e.emp_id) num_employees -> FROM department d</p><p>INNER JOIN employee e -> ON d.dept_id = e.dept_id</p><p>-> GROUP BY d.name</p><p>-> HAVING count(e.emp_id) > 2;</p><p>+----------------+---------------+</p><p>| name | num_employees |</p><p>+----------------+---------------+</p><p>| Administration | 3 |</p><p>| Operations | 14 |</p><p>+----------------+---------------+</p><p>2 rows in set (0.00 sec)</p><p>Mencionei brevemente essas duas cláusulas para que elas não lhe peguem de</p><p>surpresa mais adiante no livro, mas elas são um pouco mais avançadas do que as</p><p>outras quatro cláusulas da instrução select. Portanto, peço que você espere até o</p><p>capítulo 8 para ver uma descrição completa de como e quando usar group by e</p><p>having.</p><p>Cláusula order by</p><p>Em geral, as linhas de um conjunto-resultado retornado de uma consulta não estão</p><p>em nenhuma ordem especí�ca. Se desejar que seu conjunto-resultado retorne em</p><p>uma ordem especí�ca, você precisará instruir o servidor a ordenar os resultados por</p><p>meio da cláusula order by: A cláusula order by é o mecanismo que ordena seus</p><p>conjuntos-resultados usando dados de coluna brutos ou expressões baseadas em dados</p><p>de coluna.</p><p>Por exemplo, vamos olhar uma consulta feita à tabela account: mysql> SELECT</p><p>open_emp_id, product_cd -> FROM account;</p><p>+-------------+------------+</p><p>| open_emp_id | product_cd |</p><p>+-------------+------------+</p><p>| 10 | CHK |</p><p>| 10 | SAV |</p><p>| 10 | CD |</p><p>| 10 | CHK |</p><p>| 10 | SAV |</p><p>| 13 | CHK |</p><p>| 13 | MM |</p><p>| 1 | CHK |</p><p>| 1 | SAV |</p><p>| 1 | MM |</p><p>| 16 | CHK |</p><p>| 1 | CHK |</p><p>| 1 | CD |</p><p>| 10 | CD |</p><p>| 16 | CHK |</p><p>| 16 | SAV |</p><p>| 1 | CHK |</p><p>| 1 | MM |</p><p>| 1 | CD |</p><p>| 16 | CHK |</p><p>| 16 | BUS |</p><p>| 10 | BUS |</p><p>| 16 | CHK |</p><p>| 13 | SBL |</p><p>+-------------+------------+</p><p>24 rows in set (0.00 sec)</p><p>Se você estiver tentando analisar os dados de cada funcionário, seria interessante</p><p>ordenar os resultados pela coluna open_emp_id - para isso, basta adicionar essa</p><p>coluna à cláusula order by: mysql> SELECT open_emp_id, product_cd -></p><p>FROM account</p><p>-> ORDER BY open_emp_id;</p><p>+-------------+------------+</p><p>| open_emp_id | product_cd |</p><p>+-------------+------------+</p><p>| 1 | CHK |</p><p>| 1 | SAV |</p><p>| 1 | MM |</p><p>| 1 | CHK |</p><p>| 1 | CD |</p><p>| 1 | CHK |</p><p>| 1 | MM |</p><p>| 1 | CD |</p><p>| 10 | CHK |</p><p>| 10 | SAV |</p><p>| 10 | CD |</p><p>| 10 | CHK |</p><p>| 10 | SAV |</p><p>| 10 | CD |</p><p>| 10 | BUS |</p><p>| 13 | CHK |</p><p>| 13 | MM |</p><p>| 13 | SBL |</p><p>| 16 | CHK |</p><p>| 16 | CHK |</p><p>| 16 | SAV |</p><p>| 16 | CHK |</p><p>| 16 | BUS |</p><p>| 16 | CHK |</p><p>+-------------+------------+</p><p>24 rows in set (0.00 sec)</p><p>Agora �cou mais fácil visualizar quais tipos de contas cada funcionário abriu. No</p><p>entanto, seria melhor ainda se você pudesse garantir que os tipos de contas fossem</p><p>mostrados na mesma ordem para cada funcionário - isso é possível pela adição da</p><p>coluna product_cd após a coluna open_emp_id na cláusula order by: mysql></p><p>SELECT open_emp_id, product_cd -> FROM account</p><p>-> ORDER BY open_emp_id, product_cd;</p><p>+-------------+------------+</p><p>| open_emp_id | product_cd |</p><p>+-------------+------------+</p><p>| 1 | CD |</p><p>| 1 | CD |</p><p>| 1 | CHK |</p><p>| 1 | CHK |</p><p>| 1 | CHK |</p><p>| 1 | MM |</p><p>| 1 | MM |</p><p>| 1 | SAV |</p><p>| 10 | BUS |</p><p>| 10 | CD |</p><p>| 10 | CD |</p><p>| 10 | CHK |</p><p>| 10 | CHK |</p><p>| 10 | SAV |</p><p>| 10 | SAV |</p><p>| 13 | CHK |</p><p>| 13 | MM |</p><p>| 13 | SBL |</p><p>| 16 | BUS |</p><p>| 16 | CHK |</p><p>| 16 | CHK |</p><p>| 16 | CHK |</p><p>| 16 | CHK |</p><p>| 16 | SAV |</p><p>+-------------+------------+</p><p>24 rows in set (0.00 sec)</p><p>O conjunto-resultado agora foi ordenado primeiro pelo ID do funcionário e depois</p><p>pelo tipo de conta. A ordem em que as colunas aparecem na sua cláusula order by</p><p>faz diferença.</p><p>Ordenação ascendente versus ordenação descendente</p><p>Ao ordenar, você tem a opção de especi�car ordenação ascendente ou descendente</p><p>por meio das palavras-chave asc e desc. Como o padrão de ordenação é ascendente,</p><p>será necessário adicionar uma palavra-chave (desc, no caso) apenas se você quiser</p><p>uma ordenação descendente. Por exemplo, a consulta a seguir lista todas as contas</p><p>ordenadas pelo saldo disponível com o maior saldo listado no topo:</p><p>Ordenações descendentes costumam ser usadas em consultas de ranqueamento, do</p><p>tipo “mostre-me os cinco maiores saldos”. O MySQL inclui uma cláusula limit que</p><p>permite ordenar os dados e, em seguida, descartar tudo menos as primeiras X linhas</p><p>– veja o apêndice B para uma discussão acerca da cláusula limit, junto com outras</p><p>extensões não-ANSI.</p><p>Ordenação por meio de expressões</p><p>Ordenar os resultados usando dados de coluna é muito bom, mas às vezes você</p><p>precisará ordenar por algo que não está armazenado no banco de dados e que</p><p>possivelmente não aparecerá em lugar nenhum de sua consulta. Você pode adicionar</p><p>uma expressão em sua cláusula order by para lidar com esse tipo de situação. Por</p><p>exemplo, talvez você queira ordenar seus dados de cliente pelos últimos três dígitos</p><p>do número de identi�cação federal do cliente (que é um número de previdência</p><p>social para pessoa física ou um ID corporativo para pessoa jurídica):</p><p>Essa consulta usa a função nativa right() para extrair os três últimos caracteres da</p><p>coluna fed_id e, então, ordena as linhas baseada nesse valor.</p><p>Ordenando por meio de referências numéricas</p><p>Se estiver ordenando usando as colunas da sua cláusula select, você pode optar por</p><p>referenciar as colunas por suas posições dentro da cláusula select em vez de usar</p><p>seus nomes. Por exemplo, se você quiser ordenar usando a segunda e quinta colunas</p><p>retornadas por uma consulta, poderia fazer o seguinte:</p><p>Você deve usar essa opção de forma moderada, pois a adição de uma coluna na</p><p>cláusula select sem a modi�cação dos números na cláusula order by pode causar</p><p>resultados inesperados. Pessoalmente, posso até referenciar colunas de forma</p><p>posicional ao escrever consultas ad hoc, mas sempre referencio as colunas pelo</p><p>nome quando escrevo meu código.</p><p>Teste seu conhecimento</p><p>Os seguintes exercícios foram projetados para fortalecer sua compreensão da</p><p>instrução select e de suas várias cláusulas. Por favor, consulte o apêndice C para</p><p>veri�car as soluções.</p><p>Exercício 3.1</p><p>Recupere o ID de funcionário, o nome e o sobrenome de todos os funcionários do</p><p>banco. Ordene pelo sobrenome e, então, pelo nome.</p><p>Exercício 3.2</p><p>Recupere o ID de conta, o ID de cliente e o saldo disponível de todas as contas cujo</p><p>status seja igual a 'ACTIVE' e cujo saldo disponível seja maior que U$2.500.</p><p>Exercício 3.3</p><p>Escreva uma consulta à tabela account que retorne os IDs dos empregados que</p><p>abriram as contas (use a coluna account.open_emp_id). Inclua uma única linha para</p><p>cada funcionário especí�co.</p><p>Exercício 3.4</p><p>Preencha as lacunas (denotadas por ) dessa consulta para conseguir os seguintes</p><p>resultados: mysql> SELECT p.product_cd, a.cust_id,</p><p>a.avail_balance -> FROM product p INNER JOIN account -></p><p>ON p.product_cd =</p><p>-> WHERE p. = 'ACCOUNT'</p><p>-> ORDER BY , ;</p><p>+------------+---------+---------------+</p><p>| product_cd | cust_id | avail_balance |</p><p>+------------+---------+---------------+</p><p>| CD | 1 | 3000.00 |</p><p>| CD | 6 | 10000.00 |</p><p>| CD | 7 | 5000.00 |</p><p>| CD | 9 | 1500.00 |</p><p>| CHK | 1 | 1057.75 |</p><p>| CHK | 2 | 2258.02 |</p><p>| CHK | 3 | 1057.75 |</p><p>| CHK | 4 | 534.12 |</p><p>| CHK | 5 | 2237.97 |</p><p>| CHK | 6 | 122.37 |</p><p>| CHK | 8 | 3487.19 |</p><p>| CHK | 9 | 125.67 |</p><p>| CHK | 10 | 23575.12 |</p><p>| CHK | 12 | 38552.05 |</p><p>| MM | 3 | 2212.50 |</p><p>| MM | 4 | 5487.09 |</p><p>| MM | 9 | 9345.55 |</p><p>| SAV | 1 | 500.00 |</p><p>| SAV | 2 | 200.00 |</p><p>| SAV | 4 | 767.77 |</p><p>| SAV | 8 | 387.99 |</p><p>+------------+---------+---------------+</p><p>21 rows in set (0.09 sec)</p><p>CAPÍTULO 4</p><p>Filtragem</p><p>Haverá momentos em que você vai querer trabalhar com todas as linhas de uma</p><p>tabela, tais como: • Limpar todos os dados de uma tabela usada para alimentar um</p><p>data warehouse.</p><p>• Modi�car todas as linhas de uma tabela após a adição de uma nova coluna.</p><p>• Recuperar todas as linhas de uma tabela de �la de mensagens.</p><p>Em casos como esses, suas instruções SQL não precisarão de uma cláusula where, já</p><p>que você não</p>
<p>precisará desconsiderar nenhuma linha. Na maior parte do tempo,</p><p>entretanto, você precisará estreitar seu foco para um subconjunto de uma tabela.</p><p>Portanto, todas as instruções de dados SQL (com exceção da instrução insert)</p><p>incluem uma cláusula opcional where para lidar com condições de �ltro usadas para</p><p>restringir o número de linhas afetadas pela instrução SQL. Além disso, a instrução</p><p>select inclui uma cláusula having na qual podem ser incluídas condições de �ltro</p><p>relativas a dados agrupados. Este capítulo explora os vários tipos de condições de</p><p>�ltro que você pode empregar nas cláusulas where das instruções select, update e</p><p>delete – exploraremos o uso das condições de �ltro na cláusula having de uma</p><p>instrução select no capítulo 8.</p><p>Avaliação de condições</p><p>Uma cláusula where pode conter uma ou mais condições, separadas pelos operadores</p><p>and e or. Se múltiplas condições forem separadas apenas por operadores and, todas</p><p>as condições devem ser verdadeiras para que a linha seja incluída no conjunto-</p><p>resultado. Considere a seguinte cláusula where:</p><p>WHERE title = 'Teller' AND start_date = '2007-01-01'</p><p>Mesmo tendo certeza de que o servidor não tem uma preferência, você</p><p>provavelmente entenderá mais facilmente essa versão da cláusula where.</p><p>Construindo uma condição</p><p>Agora que você já viu como o servidor avalia múltiplas condições, vamos voltar um</p><p>passo e estudar o que compõe uma única condição. Uma condição é composta por</p><p>uma ou mais expressões unidas por um ou mais operadores. Uma expressão pode</p><p>ser: • Um número.</p><p>• Uma coluna de uma tabela ou de uma view.</p><p>• Uma string literal, como 'Teller'.</p><p>• Uma função nativa, como concat('Learning', ' ', 'SQL').</p><p>• Uma subconsulta.</p><p>• Uma lista de expressões, como ('Teller', 'Head Teller', 'Operations</p><p>Manager').</p><p>Os operadores usados dentro das condições incluem: • Operadores de comparação,</p><p>como =, !=, , , LIKE, IN e BETWEEN</p><p>• Operadores aritméticos, como +, -, * e /</p><p>A próxima seção demonstra como você pode combinar essas expressões e</p><p>operadores para construir os vários tipos de condições.</p><p>Tipos de condições</p><p>Há várias maneiras diferentes de se �ltrar dados indesejados. Você pode procurar</p><p>por valores especí�cos, conjuntos de valores ou intervalos de valores que serão</p><p>incluídos ou excluídos, ou então você pode usar várias técnicas de pesquisa de</p><p>padrões para pesquisar correspondências parciais ao lidar com dados do tipo string.</p><p>As quatro próximas subseções exploram cada um desses tipos de condições</p><p>detalhadamente.</p><p>Condições de igualdade</p><p>Uma grande porcentagem das condições de �ltro que você escreverá ou com as</p><p>quais irá se deparar tem o formato 'coluna = expressão', como em: title = 'Teller'</p><p>fed_id = '111-11-1111'</p><p>amount = 375.25</p><p>dept_id = (SELECT dept_id FROM department WHERE name = 'Loans')</p><p>Condições como essas são chamadas de condições de igualdade porque igualam uma</p><p>expressão à outra. Os primeiros três exemplos igualam uma coluna a um literal</p><p>(duas strings e um número), e o quarto exemplo iguala uma coluna a um valor</p><p>retornado por uma subconsulta. A consulta a seguir usa duas condições de</p><p>igualdade – uma na cláusula on (uma condição de junção) e outra na cláusula where</p><p>(uma condição de �ltro):</p><p>Essa consulta mostra todos os produtos que sejam tipos de conta de clientes.</p><p>Condições de desigualdade</p><p>Outro tipo de condição muito comum é a condição de desigualdade, que a�rma que</p><p>duas expressões não são iguais. Aqui está a consulta anterior com a condição de</p><p>�ltro na cláusula where modi�cada para uma condição de desigualdade:</p><p>Essa consulta mostra todos os produtos</p>
<p>que não são tipos de conta de clientes</p><p>(customer accounts). Ao construir condições de desigualdade, você pode optar por</p><p>usar o operador != ou o operador .</p><p>Modi�cação de dados usando condições de igualdade</p><p>Condições de igualdade/desigualdade são muito usadas em modi�cação de dados.</p><p>Por exemplo, digamos que o banco tenha uma política de remover linhas de contas</p><p>antigas uma vez por ano. Sua tarefa é remover linhas da tabela account que tenham</p><p>sido fechadas em 2002. Aqui está uma maneira de fazer isso: DELETE FROM</p><p>account</p><p>WHERE status = 'CLOSED' AND YEAR(close_date) = 2002;</p><p>Essa instrução inclui duas condições de desigualdade: uma para encontrar apenas</p><p>contas fechadas e outra para buscar as contas fechadas em 2002.</p><p>Ao criar exemplos de instruções delete e update, eu procuro escrever cada instrução</p><p>de tal maneira que nenhuma linha seja modi�cada. Dessa forma, ao executar essas</p><p>instruções, seus dados não serão modi�cados, e a saída das instruções select sempre</p><p>re�etirá o que é mostrado no livro.</p><p>Como as seções do MySQL estão no modo de autocomissão (auto-commit) por padrão</p><p>(veja o capítulo 12), você não conseguirá retroceder (roll back) quaisquer mudanças</p><p>feitas nos dados de exemplo caso uma de minhas instruções modi�que os dados. Você</p><p>pode, claro, fazer o que quiser com os dados de exemplo, inclusive removê-los por</p><p>completo e executar novamente os scripts que forneci, mas eu tento mantê-los intactos.</p><p>Condições de intervalo</p><p>Junto com a veri�cação de que uma expressão é igual (ou desigual) a outra</p><p>expressão, você pode construir condições que veri�cam se uma expressão está</p><p>dentro de certo intervalo. Esse tipo de condição é comum quando se trabalha com</p><p>dados numéricos ou temporais. Considere a seguinte consulta:</p><p>Essa consulta pesquisa todos os funcionários contratados antes de 2007. Junto com</p><p>a especi�cação de um limite superior para a data de início, você também pode</p><p>querer especi�car um limite inferior para ela:</p><p>Essa versão da consulta retorna todos os funcionários contratados em 2005 ou</p><p>2006.</p><p>Operador between</p><p>Quando você tem tanto o limite superior quanto o inferior de seu intervalo, você</p><p>pode optar por usar uma única condição que utilize o operador between em vez de</p><p>usar duas condições separadas, como em:</p><p>Ao usar o operador between, há duas coisas que você deve ter em mente. Você deve</p><p>sempre especi�car o limite inferior do intervalo primeiro (após between) e o limite</p><p>superior do intervalo em segundo lugar (após and). Veja o que acontece caso você</p><p>especi�que sem querer o limite superior em primeiro lugar: mysql> SELECT</p><p>emp_id, fname, lname, start_date -> FROM employee</p><p>-> WHERE start_date BETWEEN '2007-01-01' AND '2005-01-01'; Empty set (0.00</p><p>sec)</p><p>Como você pode ver, nenhum dado é retornado. Isso acontece porque o servidor</p><p>está, de fato, gerando duas condições a partir de sua condição única usando os</p><p>operadores >= e SELECT emp_id, fname, lname,</p><p>start_date -> FROM employee</p><p>-> WHERE start_date >= '2007-01-01'</p><p>-> AND start_date SELECT cust_id, fed_id -> FROM customer</p><p>-> WHERE cust_type_cd = 'I'</p><p>-> AND fed_id BETWEEN '500-00-0000' AND '999-99-9999'; +---------+---------</p><p>----+</p><p>| cust_id | fed_id |</p><p>+---------+-------------+</p><p>| 5 | 555-55-5555 |</p><p>| 6 | 666-66-6666 |</p><p>| 7 | 777-77-7777 |</p><p>| 8 | 888-88-8888 |</p><p>| 9 | 999-99-9999 |</p><p>+---------+-------------+</p><p>5 rows in set (0.01 sec)</p><p>Para trabalhar com intervalos de strings, você precisa saber a ordem dos caracteres</p><p>dentro de seu conjunto de caracteres (a ordem em que os caracteres estão dispostos</p><p>dentro de um conjunto de caracteres é chamada de collation).</p><p>Condições de adesão</p><p>Em alguns casos, você não irá restringir uma expressão para um único valor ou um</p><p>intervalo de valores, e sim para um conjunto �nito de valores. Por exemplo, você</p><p>pode desejar localizar todas as contas cujo código de produto seja 'CHK', 'SAV', 'CD'</p><p>ou 'MM':</p><p>Embora não tenha sido muito cansativo gerar essa cláusula where (quatro condições</p><p>unidas por operadores or), imagine se o conjunto de expressões tivesse 10 ou 20</p><p>membros. Para essas situações, você pode usar o operador in: SELECT account_id,</p><p>product_cd, cust_id, avail_balance</p><p>FROM account</p><p>WHERE product_cd IN ('CHK','SAV','CD','MM'); Com o operador in, você pode</p><p>escrever uma única condição independentemente do número de expressões</p><p>existentes no conjunto.</p><p>Usando subconsultas</p><p>Além de de�nir seu próprio conjunto de expressões, como ('CHK','SAV','CD','MM'),</p><p>você também pode usar uma subconsulta para gerar um conjunto na hora. Por</p><p>exemplo, todos os quatro tipos de produtos usados na consulta anterior têm seu</p><p>campo product_type_cd de�nido como 'ACCOUNT', então por que não usar uma</p><p>subconsulta à tabela product para recuperar os códigos dos quatro produtos, em vez</p><p>de nomeá-los explicitamente:</p><p>A subconsulta retorna um conjunto de quatro valores, e a consulta principal veri�ca</p><p>se o valor da coluna product_cd pode ser encontrado no conjunto que a subconsulta</p><p>retornou.</p><p>Usando not in</p><p>Por vezes, você vai precisar saber se uma expressão em particular existe dentro de um</p><p>conjunto de expressões, e às vezes você terá que saber se ela não existe. Nessas</p><p>situações, você pode usar o operador not in:</p><p>Essa consulta pesquisa todas as contas que não sejam conta corrente (CHK),</p><p>poupança (SAV), certi�cado de depósito (CD) ou fundo de investimento (MM).</p><p>Condições de correspondência</p><p>Até agora, você conheceu condições que identi�cam uma string exata, um intervalo</p><p>de strings ou um conjunto de strings; o último tipo de condição lida com</p><p>correspondências parciais de strings. Você pode, por exemplo, querer encontrar todos</p><p>os funcionários cujo sobrenome comece com T. Você poderia usar uma função</p><p>nativa para extrair a primeira letra da coluna lname, como em: mysql> SELECT</p><p>emp_id, fname, lname -> FROM employee</p><p>-> WHERE LEFT(lname, 1) = 'T'; +--------+--------+--------+</p><p>| emp_id | fname | lname |</p><p>+--------+--------+--------+</p><p>| 3 | Robert | Tyler |</p><p>| 7 | Chris | Tucker |</p><p>| 18 | Rick | Tulman |</p><p>+--------+--------+--------+</p><p>3 rows in set (0.01 sec)</p><p>Apesar de a função nativa left() realizar bem seu trabalho, ela não oferece muita</p><p>�exibilidade. Em vez disso, você pode usar caracteres-curinga para construir</p><p>expressões de pesquisa, como será demonstrado na próxima seção.</p><p>Usando curingas</p><p>Ao pesquisar por correspondências parciais de strings, você pode estar interessado</p><p>em: • Strings iniciadas/terminadas por um caractere especí�co.</p>
<p>• Strings iniciadas/terminadas por uma substring.</p><p>• Strings contendo um caractere especí�co em qualquer posição dentro da string</p><p>em que será feita a pesquisa.</p><p>• Strings contendo uma substring em qualquer posição dentro da string em que</p><p>será feita a pesquisa.</p><p>• Strings com um formato especí�co, independentemente de caracteres</p><p>individuais.</p><p>Você pode construir expressões de pesquisa para identi�car essas e muitas outras</p><p>correspondências parciais de strings usando os caracteres-curinga mostrados na</p><p>tabela 4.4.</p><p>Tabela 4.4 – Caracteres-curinga</p><p>Caractere-curinga Correspondências</p><p>_ Exatamente um caractere</p><p>%</p><p>Qualquer número de caracteres (incluindo 0)</p><p>Caractere-curinga Correspondências</p><p>O caractere underscore assume o lugar de um único caractere, enquanto o caractere</p><p>de porcentagem pode assumir o lugar de um número variável de caracteres. Para</p><p>construir condições que utilizem expressões de pesquisa você deve usar o operador</p><p>like, como em: mysql> SELECT lname -> FROM employee</p><p>-> WHERE lname LIKE '_a%e%'; +-----------+</p><p>| lname |</p><p>+-----------+</p><p>| Barker |</p><p>| Hawthorne |</p><p>| Parker |</p><p>| Jameson |</p><p>+-----------+</p><p>4 rows in set (0.00 sec)</p><p>A expressão de pesquisa do exemplo anterior especi�ca strings que contenham um a</p><p>na segunda posição, seguido por um e em qualquer outra posição na string</p><p>(incluindo a última posição). A tabela 4.5 mostra mais algumas expressões de</p><p>pesquisa e suas interpretações.</p><p>Tabela 4.5 – Exemplos de expressões de pesquisa</p><p>Expressão de pesquisa Interpretação</p><p>F% Strings iniciadas por F</p><p>%t Strings terminadas por t</p><p>%bas% Strings que contenham a substring ‘bas’</p><p>t_ String de quatro caracteres com um t na terceira posição</p><p>_- - Strings de 11 caracteres com hífens na quarta e na sétima posições</p><p>Você poderia usar o último exemplo da tabela 4.5 para encontrar clientes cuja</p><p>identi�cação federal corresponda ao formato usado em números de Seguro Social,</p><p>como em: mysql> SELECT cust_id, fed_id -> FROM customer</p><p>-> WHERE fed_id LIKE '__--___'; +---------+-------------+</p><p>| cust_id | fed_id |</p><p>+---------+-------------+</p><p>| 1 | 111-11-1111 |</p><p>| 2 | 222-22-2222 |</p><p>| 3 | 333-33-3333 |</p><p>| 4 | 444-44-4444 |</p><p>| 5 | 555-55-5555 |</p><p>| 6 | 666-66-6666 |</p><p>| 7 | 777-77-7777 |</p><p>| 8 | 888-88-8888 |</p><p>| 9 | 999-99-9999 |</p><p>+---------+-------------+</p><p>9 rows in set (0.02 sec)</p><p>Os caracteres-curinga funcionam bem na construção de expressões de pesquisa</p><p>simples, no entanto, se você necessita de algo mais so�sticado, pode usar múltiplas</p><p>expressões de pesquisa, como demonstrado a seguir:</p><p>mysql> SELECT emp_id, fname, lname -> FROM employee</p><p>-> WHERE lname LIKE 'F%' OR lname LIKE 'G%'; +--------+-------+----------+</p><p>| emp_id | fname | lname |</p><p>+--------+-------+----------+</p><p>| 5 | John | Gooding |</p><p>| 6 | Helen | Fleming |</p><p>| 9 | Jane | Grossman |</p><p>| 17 | Beth | Fowler |</p><p>+--------+-------+----------+</p><p>4 rows in set (0.00 sec)</p><p>Essa consulta identi�ca todos os funcionários cujo sobrenome inicie com F ou G.</p><p>Usando expressões regulares</p><p>Se você achar que os caracteres-curinga não fornecem �exibilidade su�ciente, você</p><p>pode usar expressões regulares para construir expressões de pesquisa. Uma</p><p>expressão regular é, essencialmente, uma expressão de pesquisa com esteróides. Se</p><p>você é novo no mundo do SQL, mas já codi�cou usando linguagens de</p><p>programação como Perl, então você já deve estar intimamente familiarizado com</p><p>expressões regulares. Se nunca usou expressões regulares, talvez seja útil consultar o</p><p>livro Mastering Regular Expressions, de Je�rey E. F. Friedl</p><p>(http://oreilly.com/catalog/9780596528126/) (O’Reilly), já que este é um tópico grande</p><p>demais para abordar neste livro.</p><p>Veja como a consulta anterior (encontrar todos os funcionários cujo sobrenome</p><p>inicie com F ou G) �caria usando a implementação do MySQL de expressões</p><p>regulares: mysql> SELECT emp_id, fname, lname -> FROM employee</p><p>-> WHERE lname REGEXP '^[FG]'; +--------+-------+----------+</p><p>| emp_id | fname | lname |</p><p>+--------+-------+----------+</p><p>| 5 | John | Gooding |</p><p>| 6 | Helen | Fleming |</p><p>| 9 | Jane | Grossman |</p><p>| 17 | Beth | Fowler |</p><p>+--------+-------+----------+</p><p>4 rows in set (0.00 sec)</p><p>O operador regexp pega uma expressão regular ('^[FG]', nesse exemplo) e a aplica</p><p>na expressão à esquerda da condição (a coluna lname). A consulta agora contém uma</p><p>única condição que usa uma expressão regular, em vez de duas condições que usam</p><p>caracteres-curinga.</p><p>O Oracle Database e o Microsoft SQL Server também suportam expressões</p><p>regulares. No Oracle Database, você usaria a função regexp_like em vez do</p><p>operador regexp mostrado no exemplo anterior, ao passo que o SQL Server permite</p><p>que expressões regulares sejam usadas com o operador like.</p><p>Null: aquela palavra de quatro letras</p><p>Evitei o assunto enquanto pude, mas chegou a hora de discutir um tópico que</p><p>costuma ser encarado com medo, incerteza e horror: o valor null (nulo). Null é a</p><p>ausência de valor – antes de um funcionário ser mandado embora, por exemplo, sua</p><p>coluna end_date na tabela employee deve ser nula. Não há valor que possa ser</p><p>atribuído à coluna end_date e que faça sentido nessa situação. Mas null é um tanto</p><p>evasivo, pois pode aparecer em diversos sabores: Não aplicável</p><p>Tal como a coluna de ID de funcionário em uma transação que ocorreu em uma</p><p>máquina de autoatendimento.</p><p>Valor ainda não conhecido</p><p>Como no caso da identi�cação federal que não é conhecida no momento em que</p><p>uma linha de cliente é criada Valor inde�nido</p><p>Como quando uma conta é criada para um produto que ainda não foi</p><p>adicionado ao banco de dados</p><p>Alguns teóricos argumentam que devia haver uma expressão diferente para cada uma</p><p>dessas (entre outras) situações, mas a maioria dos pro�ssionais concorda que ter vários</p><p>tipos de valores null seria algo extremamente confuso.</p><p>Ao trabalhar com null, você deve se lembrar que: • Uma expressão pode ser nula,</p><p>mas nunca pode ser igual a null.</p><p>• Dois nulls nunca são equivalentes.</p><p>Para testar se uma expressão é nula, você precisa usar o operador is null, como</p><p>demonstrado a seguir:</p><p>Essa consulta retorna todos os funcionários que não tenham um chefe (já pensou</p><p>que maravilha?). Aqui está a mesma consulta, usando = null em vez de is null:</p><p>mysql> SELECT emp_id, fname, lname, superior_emp_id -> FROM employee</p><p>-> WHERE superior_emp_id = NULL; Empty set (0.01 sec)</p><p>Como você pode ver, a consulta é analisada e executada, mas não retorna nenhuma</p><p>linha. Isso é um erro comum cometido por programadores SQL inexperientes, e o</p><p>servidor de banco de dados não o alertará sobre seu erro, então seja cuidadoso ao</p><p>construir condições que testem valores nulos.</p><p>Se você quiser saber se algum valor foi atribuído a uma coluna, você pode usar o</p><p>operador is not null, como em:</p><p>Essa versão da consulta retorna os outros 17 funcionários que, ao contrário de</p><p>Michael Smith, têm um chefe.</p><p>Antes de colocar o null de lado por um momento, seria interessante investigar mais</p><p>uma armadilha em potencial. Suponha que alguém solicite que você identi�que</p><p>todos os funcionários que não sejam gerenciados por Helen Fleming (cujo ID de</p><p>funcionário é 6). Seu primeiro impulso poderia ser fazer o seguinte:</p><p>Apesar de ser verdade que esses 14 funcionários não trabalham para Helen Fleming,</p><p>se você olhar atentamente para os dados verá que existe mais um funcionário que</p><p>não trabalha para Helen e que não está listado aqui. Esse funcionário é Michael</p><p>Smith, cuja coluna superior_emp_id é nula (porque ele é o chefão). Portanto, para</p><p>responder corretamente à questão, você precisa levar em conta a possibilidade de</p><p>que algumas linhas podem conter um null na coluna superior_emp_id:</p><p>O conjunto-resultado agora inclui todos os 15 funcionários que não trabalham para</p><p>Helen. Ao trabalhar com um banco de dados com o qual você não está</p><p>familiarizado, é sempre uma boa ideia descobrir quais colunas em uma tabela</p><p>permitem nulls, para que você possa tomar as medidas apropriadas em suas</p><p>condições de �ltro, evitando que dados escapem pelas rachaduras.</p><p>Teste seu conhecimento</p><p>Os seguintes exercícios testam sua compreensão de condições de �ltro. Por favor,</p><p>consulte o apêndice C para ver as soluções.</p>
<p>as tentativas de destronar os bancos de dados relacionais e a</p><p>SQL. Portanto, mesmo que a SQL seja uma linguagem antiga, ela continuará nas</p><p>redondezas por muito mais tempo, e ainda tem uma longa carreira à sua frente.</p><p>Por que aprender SQL?</p><p>Se você pretende trabalhar com um banco de dados relacional, seja desenvolvendo</p><p>aplicações, realizando tarefas administrativas ou gerando relatórios, você precisará</p><p>saber como interagir com os dados em seu banco de dados. Mesmo que você esteja</p><p>usando uma ferramenta que gere o código SQL para você, como uma ferramenta de</p><p>relatórios, poderá haver momentos em que você precisará ignorar o sistema de</p><p>geração automática para escrever suas próprias instruções SQL.</p><p>Aprender SQL tem o benefício adicional de forçá-lo a confrontar e compreender as</p><p>estruturas de dados usadas para armazenar informações sobre sua própria empresa</p><p>ou organização. Conforme vai se sentindo mais confortável com as tabelas em seu</p><p>banco de dados, você pode se colocar na posição de propor modi�cações ou adições</p><p>a seu esquema de banco de dados.</p><p>Por que usar este livro para aprender SQL?</p><p>A linguagem SQL é dividida em várias categorias. Instruções usadas para criar</p><p>objetos de banco de dados (tabelas, índices, restrições etc.) são coletivamente</p><p>conhecidas como instruções de esquema SQL (schema statements). As instruções</p><p>usadas para criar, manipular e recuperar dados armazenados em um banco de dados</p><p>são conhecidas como instruções de dados SQL (data statements). Se você for um</p><p>administrador, usará tanto os esquemas SQL como as instruções de dados SQL. Se</p><p>for um programador ou autor de relatórios, provavelmente você precisará usar (ou</p><p>receberá permissão para usar) apenas instruções de dados SQL. Apesar de este livro</p><p>demonstrar várias instruções de esquema SQL, seu foco principal será nas</p><p>características de programação.</p><p>Com apenas alguns comandos, as instruções de dados SQL parecem enganosamente</p><p>simples. Em minha opinião, muitos dos livros disponíveis sobre SQL ajudam a</p><p>perpetuar essa noção, pois apenas tocam a superfície daquilo que é possível fazer</p><p>com a linguagem. No entanto, se você vai trabalhar com SQL, cabe a você entender</p><p>completamente os recursos da linguagem e como características diferentes podem</p><p>ser combinadas para produzir resultados poderosos. Sinto que este é o único livro</p><p>que fornece uma abordagem detalhada da linguagem SQL sem o “benefício”</p><p>adicional de imitar um “peso de porta” (você sabe, aquelas “referências completas”</p><p>de 1.250 páginas que tendem a acumular poeira nas prateleiras dos cubículos das</p><p>pessoas).</p><p>Apesar de os exemplos deste livro rodarem em MySQL, Oracle Database e SQL</p><p>Server, tive que escolher um desses produtos para hospedar meu banco de dados de</p><p>exemplo e formatar os conjuntos de resultados retornados pelas consultas de</p><p>exemplo. Dos três, escolhi o MySQL porque ele é obtido livremente, é fácil de</p><p>instalar e simples de administrar. Para aqueles leitores que estiverem usando um</p><p>servidor diferente, peço que baixem e instalem o MySQL e carreguem o banco de</p><p>dados de exemplo para que possam executar os exemplos e realizar testes com os</p><p>dados.</p><p>Estrutura deste livro</p><p>Este livro é dividido em 15 capítulos e três apêndices:</p><p>O capítulo 1, Uma breve introdução, explora a história dos bancos de dados</p><p>computadorizados, incluindo o surgimento do modelo relacional e da linguagem</p><p>SQL.</p><p>O capítulo 2, Criando e populando um banco de dados, demonstra como criar um</p><p>banco de dados MySQL, como criar as tabelas usadas pelos exemplos do livro e</p><p>populá-las com dados.</p><p>O capítulo 3, Introdução a consultas, apresenta a instrução select, além de</p><p>demonstrar as cláusulas mais comuns (select, from, where).</p><p>O capítulo 4, Filtragem, demonstra os diferentes tipos de condições que podem ser</p><p>usados na cláusula where de uma instrução select, update ou delete.</p><p>O capítulo 5, Pesquisando múltiplas tabelas, mostra como as consultas podem</p><p>utilizar múltiplas tabelas por meio de junção (join) de tabelas.</p><p>O capítulo 6, Trabalhando com conjuntos, aborda tudo o que diz respeito a conjuntos</p><p>de dados e como eles interagem dentro de consultas.</p><p>O capítulo 7, Geração de dados, conversão e manipulação, demonstra várias funções</p><p>nativas usadas para manipular ou converter dados.</p><p>O capítulo 8, Agrupamento e agregados, mostra como os dados podem ser agregados</p><p>e onde podem ser utilizados.</p><p>O capítulo 9, Subconsultas, apresenta a subconsulta (uma das minhas favoritas) e</p><p>mostra como e onde pode ser utilizada.</p><p>O capítulo 10, Junções revisitadas, explora mais a fundo os vários tipos de junções de</p><p>tabelas.</p><p>O capítulo 11, Lógica condicional, explora como a lógica condicional (ou seja, if-</p><p>then-else) pode ser utilizada em instruções select, insert, update e delete.</p><p>O capítulo 12, Transações, apresenta transações e mostra como usá-las.</p><p>O capítulo 13, Índices e restrições, explora índices e restrições.</p><p>O capítulo 14, Views, mostra como construir uma interface que protege os usuários</p><p>da complexidade dos dados.</p><p>O capítulo 15, Metadados, demonstra a utilidade do dicionário de dados.</p><p>O apêndice A, Diagrama E-R do banco de dados de exemplo, mostra o esquema de</p><p>banco de dados usado por todos os exemplos deste livro.</p><p>O apêndice B, Extensões MySQL à linguagem SQL, demonstra algumas das</p><p>características não-ANSI interessantes da implementação SQL do MySQL.</p><p>O apêndice C, Soluções dos exercícios, mostra as soluções dos exercícios dos</p><p>capítulos.</p><p>Convenções adotadas neste livro</p><p>As seguintes convenções tipográ�cas foram adotadas neste livro:</p><p>Itálico</p><p>Usado em nomes de arquivos, nomes de diretórios e URLs. Também é usado</p><p>para ênfase e para indicar a primeira ocorrência de um termo técnico.</p><p>Monoespaçado</p><p>Usado nos códigos-exemplo e para indicar palavras-chave SQL dentro do texto.</p><p>Monoespaçado itálico</p><p>Usada para indicar termos de�nidos pelo usuário.</p><p>CAIXA ALTA</p><p>Usada para indicar palavras-chave SQL dentro dos códigos-exemplo.</p><p>Monoespaçado negrito</p><p>Indica entradas do usuário em exemplos que demonstrem uma interação.</p><p>Também indica elementos de código enfatizados aos quais você deve prestar</p><p>atenção redobrada.</p><p>Indica uma dica, sugestão ou nota geral. Por exemplo, usarei notas para indicar</p><p>recursos novos e úteis do Oracle9i.</p><p>Indica um aviso ou alerta. Por exemplo, direi a você se uma cláusula SQL em</p><p>particular pode ter consequências adversas se não forem usadas com cuidado.</p><p>Como entrar em contato conosco</p><p>Envie comentários e dúvidas sobre este livro para:</p><p>novatec@novatec.com.br</p><p>Temos uma página da web para este livro, onde incluímos a lista de erratas,</p><p>exemplos e qualquer outra informação adicional.</p><p>• Página da edição traduzida, em português</p><p>http://www.novatec.com.br/</p><p>• Página da edição original, em inglês</p><p>http://www.oreilly.com/catalog/9780596520830</p><p>Para obter mais informações sobre livros da Novatec, acesse nosso site em:</p><p>http://www.novatec.com.br</p><p>Usando os códigos de exemplo</p><p>(texto da edição original, em inglês)</p><p>Este livro está aqui para ajudá-lo a fazer seu trabalho. Em geral, você pode usar o</p><p>código deste livro em seus programas e documentações. Não é preciso entrar em</p><p>contato conosco para obter permissão, a não ser que você esteja reproduzindo uma</p><p>parte signi�cativa do código. Por exemplo, escrever um programa que usa vários</p><p>blocos de código presentes neste livro não requer qualquer tipo de permissão.</p><p>Vender ou distribuir um CD-ROM composto por exemplos dos livros da O’Reilly</p><p>requer permissão. Responder uma pergunta citando este livro e o código de exemplo</p><p>não requer permissão. Incorporar uma quantidade signi�cativa de código de</p><p>amostra deste livro na documentação de seu produto requer permissão.</p><p>Agradecemos, mas não exigimos, a atribuição de créditos. Uma atribuição</p><p>geralmente inclui o título, o autor, a editora e o ISBN. Por exemplo, “Aprendendo</p><p>SQL, por Alan Beaulieu. Copyright 2009, O’Reilly Media, Inc., 978-0-59652083-0”.</p><p>Se acreditar que seu uso de exemplos de código ultrapassa o uso permitido ou a</p><p>permissão concedida aqui, sinta-se à vontade para entrar em contato pelo e-mail</p><p>permissions@oreilly.com.</p>
<p>Os seguintes dados de transação são usados nos dois primeiros exercícios:</p><p>Txn_id Txn_date Account_id Txn_type_cd Amount</p><p>1 2005-02-22 101 CDT 1000.00</p><p>2 2005-02-23 102 CDT 525.75</p><p>3 2005-02-24 101 DBT 100.00</p><p>4 2005-02-24 103 CDT 55</p><p>5 2005-02-25 101 DBT 50</p><p>6 2005-02-25 103 DBT 25</p><p>7 2005-02-25 102 CDT 125.37</p><p>8 2005-02-26 103 DBT 10</p><p>9 2005-02-27 101 CDT 75</p><p>Exercício 4.1</p><p>Quais IDs de transação seriam retornados pelas seguintes condições de �ltro?</p><p>txn_date 100)</p><p>Exercício 4.2</p><p>Quais IDs de transação seriam retornados pelas seguintes condições de �ltro?</p><p>account_id IN (101,103) AND NOT (txn_type_cd = 'DBT' OR amount > 100)</p><p>Exercício 4.3</p><p>Construa uma consulta que recupere todas as contas abertas em 2002.</p><p>Exercício 4.4</p><p>Construa uma consulta que encontre todos os clientes não-corporativos cujo</p><p>sobrenome contenha um a na segunda posição e um e em qualquer lugar após a.</p><p>CAPÍTULO 5</p><p>Consultando múltiplas tabelas</p><p>No capítulo 2, eu mostrei como conceitos relacionados são divididos em fragmentos</p><p>independentes por meio de um processo chamado normalização. O resultado �nal</p><p>daquele exercício foram duas tabelas: person e favorite_food. Se, no entanto, você</p><p>precisar gerar um único relatório que mostra o nome, o endereço e os pratos</p><p>favoritos de uma pessoa, será necessário um mecanismo que reúna os dados dessas</p><p>duas tabelas. Esse mecanismo é conhecido como junção (join), e este capítulo se</p><p>concentra na junção mais simples e mais comum que existe, a junção interna (inner</p><p>join). O capítulo 10 ilustrará todos os diferentes tipos de junções.</p><p>O que é uma junção?</p><p>Consultas a uma única tabela certamente não são raras, mas você descobrirá que a</p><p>maioria de suas consultas requer duas, três ou mais tabelas. A título de ilustração,</p><p>vamos dar uma olhada nas de�nições das tabelas employee e department e, então,</p><p>de�nir uma consulta que recupere dados de ambas as tabelas:</p><p>Digamos que você deseje recuperar o nome e o sobrenome de cada funcionário,</p><p>junto com o nome do departamento em que cada um deles trabalha. Sua consulta,</p><p>portanto, precisa recuperar as colunas employee.fname, employee.lname e</p><p>department.name. Mas como você recuperaria dados de ambas as tabelas na mesma</p><p>consulta? A resposta está na coluna employee.dept_id, que armazena o ID do</p><p>departamento ao qual cada funcionário está vinculado (em termos mais formais, a</p><p>coluna employee.dept_id é a chave estrangeira para a tabela department). A consulta,</p><p>que veremos em breve, instrui o servidor a usar a coluna employee.dept_id como</p><p>ponte entre as tabelas employee e department, permitindo, assim, que colunas de</p><p>ambas as tabelas sejam incluídas no conjunto-resultado da consulta. Esse tipo de</p><p>operação é conhecido como junção.</p><p>Produto cartesiano</p><p>A maneira mais fácil de começar é colocando as tabelas employee e department na</p><p>cláusula from de uma consulta e ver o que acontece. Aqui está uma consulta que</p><p>recupera o nome e sobrenome de cada funcionário junto com o nome do</p><p>departamento, por meio de uma cláusula from que nomeia ambas as tabelas</p><p>separadas pela palavra-chave join: mysql> SELECT e.fname, e.lname,</p><p>d.name -> FROM employee e JOIN department d;</p><p>+----------+-----------+----------------+</p><p>| fname | lname | name |</p><p>+----------+-----------+----------------+</p><p>| Michael | Smith | Operations |</p><p>| Michael | Smith | Loans |</p><p>| Michael | Smith | Administration |</p><p>| Susan | Barker | Operations |</p><p>| Susan | Barker | Loans |</p><p>| Susan | Barker | Administration |</p><p>| Robert | Tyler | Operations |</p><p>| Robert | Tyler | Loans |</p><p>| Robert | Tyler | Administration |</p><p>| Susan | Hawthorne | Operations |</p><p>| Susan | Hawthorne | Loans |</p><p>| Susan | Hawthorne | Administration |</p><p>| John | Gooding | Operations |</p><p>| John | Gooding | Loans |</p><p>| John | Gooding | Administration |</p><p>| Helen | Fleming | Operations |</p><p>| Helen | Fleming | Loans |</p><p>| Helen | Fleming | Administration |</p><p>| Chris | Tucker | Operations |</p><p>| Chris | Tucker | Loans |</p><p>| Chris | Tucker | Administration |</p><p>| Sarah | Parker | Operations |</p><p>| Sarah | Parker | Loans |</p><p>| Sarah | Parker | Administration |</p><p>| Jane | Grossman | Operations |</p><p>| Jane | Grossman | Loans |</p><p>| Jane | Grossman | Administration |</p><p>| Paula | Roberts | Operations |</p><p>| Paula | Roberts | Loans |</p><p>| Paula | Roberts | Administration |</p><p>| Thomas | Ziegler | Operations |</p><p>| Thomas | Ziegler | Loans |</p><p>| Thomas | Ziegler | Administration |</p><p>| Samantha | Jameson | Operations |</p><p>| Samantha | Jameson | Loans |</p><p>| Samantha | Jameson | Administration |</p><p>| John | Blake | Operations |</p><p>| John | Blake | Loans |</p><p>| John | Blake | Administration |</p><p>| Cindy | Mason | Operations |</p><p>| Cindy | Mason | Loans |</p><p>| Cindy | Mason | Administration |</p><p>| Frank | Portman | Operations |</p><p>| Frank | Portman | Loans |</p><p>| Frank | Portman | Administration |</p><p>| Theresa | Markham | Operations |</p><p>| Theresa | Markham | Loans |</p><p>| Theresa | Markham | Administration |</p><p>| Beth | Fowler | Operations |</p><p>| Beth | Fowler | Loans |</p><p>| Beth | Fowler | Administration |</p><p>| Rick | Tulman | Operations |</p><p>| Rick | Tulman | Loans |</p><p>| Rick | Tulman | Administration |</p><p>+----------+-----------+----------------+</p><p>54 rows in set (0.23 sec)</p><p>Hummm... Existem apenas 18 funcionários e três departamentos diferentes, então</p><p>como o conjunto-resultado resultou em 54 linhas? Olhando mais atentamente, você</p><p>verá que o conjunto de 18 funcionários foi repetido três vezes, com todos os dados</p><p>idênticos, exceto pelo nome de departamento. Como a consulta não especi�cou a</p><p>forma de junção das duas tabelas, o servidor de banco de dados gerou o produto</p><p>cartesiano, que é conjunto das combinações entre os elementos das duas tabelas (18</p><p>funcionários x 3 departamentos = 54 combinações). Esse tipo de junção é</p><p>conhecido como uma junção cruzada (cross join), e é raramente usado</p><p>(intencionalmente, pelo menos). Junções cruzadas são um dos tipos de junções que</p><p>estudaremos no capítulo 10.</p><p>Junções internas</p><p>Para modi�car a consulta anterior de forma que apenas 18 linhas sejam incluídas no</p><p>conjunto-resultado (uma para cada funcionário), você precisa descrever como as</p><p>duas tabelas se relacionam. Anteriormente, mencionei que a coluna</p><p>employee.dept_id serviria de vínculo entre as duas tabelas, então essa informação</p><p>precisa ser adicionada à subcláusula on da cláusula from:</p><p>mysql> SELECT e.fname, e.lname, d.name -> FROM employee e JOIN department d</p><p>-> ON e.dept_id = d.dept_id;</p><p>+----------+-----------+----------------+</p><p>| fname | lname | name |</p><p>+----------+-----------+----------------+</p><p>| Michael | Smith | Administration |</p><p>| Susan | Barker | Administration |</p><p>| Robert | Tyler | Administration |</p><p>| Susan | Hawthorne | Operations |</p><p>| John | Gooding | Loans |</p><p>| Helen | Fleming | Operations |</p><p>| Chris | Tucker | Operations |</p><p>| Sarah | Parker | Operations |</p><p>| Jane | Grossman | Operations |</p><p>| Paula | Roberts | Operations |</p><p>| Thomas | Ziegler | Operations |</p><p>| Samantha | Jameson | Operations |</p><p>| John | Blake | Operations |</p><p>| Cindy | Mason | Operations |</p><p>| Frank | Portman | Operations |</p><p>| Theresa | Markham | Operations |</p><p>| Beth | Fowler | Operations |</p><p>| Rick | Tulman | Operations |</p><p>+----------+-----------+----------------+</p><p>18 rows in set (0.00 sec)</p><p>Em vez de 54 linhas, agora você tem as 18 linhas esperadas devido à adição da</p><p>subcláusula on, que instrui o servidor a juntar as tabelas employee e department</p><p>utilizando a coluna dept_id para percorrer de uma tabela a outra. Por exemplo, a</p><p>linha de Susan Hawthorne na tabela employee contém um valor de 1 na coluna</p><p>dept_id (que não é mostrada no exemplo). O servidor usa esse valor para procurar a</p><p>linha da tabela department que tenha um valor de 1 em sua coluna department e,</p><p>então, recupera o valor ‘Operations’ da coluna name daquela linha.</p><p>Se um valor existe na coluna dept_id de uma tabela mas não existe na outra, a</p><p>junção falha para as linhas que contenham esse valor, e tais linhas são excluídas do</p><p>conjunto-resultado. Esse tipo de junção é conhecido como junção interna (inner</p><p>join) e é o tipo de junção mais utilizado. Para esclarecer as coisas: se, por exemplo, a</p><p>tabela department tivesse uma quarta linha para o</p>
<p>departamento de marketing, mas</p><p>nenhum funcionário tivesse sido alocado nele, o departamento de marketing não</p><p>seria incluído no conjunto-resultado. De forma semelhante, se um ou mais</p><p>funcionários tivessem sido alocados ao departamento de ID 99, que não existe na</p><p>tabela department, esses funcionários �cariam de fora do conjunto-resultado. Se você</p><p>quiser incluir todas as linhas de uma das tabelas, independentemente de existirem</p><p>ou não correspondências, você precisará de�nir uma junção externa (outer join), mas</p><p>falaremos sobre isso posteriormente.</p><p>No exemplo anterior, não especi�quei na cláusula from o tipo de junção a ser usado.</p><p>No entanto, quando você deseja juntar duas tabelas por meio de uma junção</p><p>interna, você deve especi�car isso explicitamente em sua cláusula from; aqui está o</p><p>mesmo exemplo, com a adição do tipo de junção (perceba a palavra-chave INNER):</p><p>mysql> SELECT e.fname, e.lname, d.name -> FROM employee e</p><p>INNER JOIN department d -> ON e.dept_id = d.dept_id;</p><p>+----------+-----------+----------------+</p><p>| fname | lname | name |</p><p>+----------+-----------+----------------+</p><p>| Michael | Smith | Administration |</p><p>| Susan | Barker | Administration |</p><p>| Robert | Tyler | Administration |</p><p>| Susan | Hawthorne | Operations |</p><p>| John | Gooding | Loans |</p><p>| Helen | Fleming | Operations |</p><p>| Chris | Tucker | Operations |</p><p>| Sarah | Parker | Operations |</p><p>| Jane | Grossman | Operations |</p><p>| Paula | Roberts | Operations |</p><p>| Thomas | Ziegler | Operations |</p><p>| Samantha | Jameson | Operations |</p><p>| John | Blake | Operations |</p><p>| Cindy | Mason | Operations |</p><p>| Frank | Portman | Operations |</p><p>| Theresa | Markham | Operations |</p><p>| Beth | Fowler | Operations |</p><p>| Rick | Tulman | Operations |</p><p>+----------+-----------+----------------+</p><p>18 rows in set (0.00 sec)</p><p>Se você não especi�car o tipo de junção, o servidor fará uma junção interna por</p><p>padrão. No entanto, como você verá mais tarde no livro, há vários tipos de junções,</p><p>então você deve cultivar o hábito de especi�car o tipo exato de junção pretendida.</p><p>Se os nomes das colunas usadas para juntar as duas tabelas forem idênticos, o que é</p><p>o caso da consulta anterior, você pode usar a subcláusula using em vez da</p><p>subcláusula on, como em: mysql> SELECT e.fname, e.lname, d.name -></p><p>FROM employee e INNER JOIN department d -> USING (dept_id);</p><p>+----------+-----------+----------------+</p><p>| fname | lname | name |</p><p>+----------+-----------+----------------+</p><p>| Michael | Smith | Administration |</p><p>| Susan | Barker | Administration |</p><p>| Robert | Tyler | Administration |</p><p>| Susan | Hawthorne | Operations |</p><p>| John | Gooding | Loans |</p><p>| Helen | Fleming | Operations |</p><p>| Chris | Tucker | Operations |</p><p>| Sarah | Parker | Operations |</p><p>| Jane | Grossman | Operations |</p><p>| Paula | Roberts | Operations |</p><p>| Thomas | Ziegler | Operations |</p><p>| Samantha | Jameson | Operations |</p><p>| John | Blake | Operations |</p><p>| Cindy | Mason | Operations |</p><p>| Frank | Portman | Operations |</p><p>| Theresa | Markham | Operations |</p><p>| Beth | Fowler | Operations |</p><p>| Rick | Tulman | Operations |</p><p>+----------+-----------+----------------+</p><p>18 rows in set (0.01 sec)</p><p>Como using é uma notação compacta que pode ser usada em apenas uma situação</p><p>especí�ca, pre�ro sempre usar a subcláusula on para evitar confusão.</p><p>A sintaxe ANSI de junção</p><p>A notação usada ao longo deste livro para juntar tabelas foi introduzida na versão</p><p>SQL92 do padrão ANSI SQL. Todos os principais bancos de dados (Oracle</p><p>Database, Microsoft SQL Server, MySQL, IBM DB2 Universal Database e Sybase</p><p>Adaptive Server) adotaram a sintaxe de junção do SQL92. Como a maioria desses</p><p>servidores já estava no mercado antes do lançamento da especi�cação SQL92, todos</p><p>eles também incluem uma sintaxe de junção mais antiga. Por exemplo, todos esses</p><p>servidores entenderiam a seguinte variação da consulta anterior: mysql> SELECT</p><p>e.fname, e.lname, d.name -> FROM employee e, department d</p><p>-> WHERE e.dept_id = d.dept_id;</p><p>+----------+-----------+----------------+</p><p>| fname | lname | name |</p><p>+----------+-----------+----------------+</p><p>| Michael | Smith | Administration |</p><p>| Susan | Barker | Administration |</p><p>| Robert | Tyler | Administration |</p><p>| Susan | Hawthorne | Operations |</p><p>| John | Gooding | Loans |</p><p>| Helen | Fleming | Operations |</p><p>| Chris | Tucker | Operations |</p><p>| Sarah | Parker | Operations |</p><p>| Jane | Grossman | Operations |</p><p>| Paula | Roberts | Operations |</p><p>| Thomas | Ziegler | Operations |</p><p>| Samantha | Jameson | Operations |</p><p>| John | Blake | Operations |</p><p>| Cindy | Mason | Operations |</p><p>| Frank | Portman | Operations |</p><p>| Theresa | Markham | Operations |</p><p>| Beth | Fowler | Operations |</p><p>| Rick | Tulman | Operations |</p><p>+----------+-----------+----------------+</p><p>18 rows in set (0.01 sec)</p><p>Esse método mais antigo de especi�car junções não inclui a subcláusula on – em vez</p><p>disso, as tabelas são nomeadas na cláusula from separados por vírgulas, e as</p><p>condições de junção são incluídas na cláusula where. Apesar de você poder decidir</p><p>ignorar a sintaxe do SQL92 em favor da sintaxe de junção mais antiga, a sintaxe de</p><p>junção ANSI tem as seguintes vantagens: • Condições de junção e condições de</p><p>�ltro são separadas em duas cláusulas diferentes (a subcláusula on e a cláusula</p><p>where, respectivamente), tornando a consulta mais fácil de entender.</p><p>• As condições de junção para cada par de tabelas estão contidas em suas próprias</p><p>cláusulas on, tornando menos provável que parte de uma junção seja omitida por</p><p>engano.</p><p>• Consultas que usam a sintaxe de junção do SQL92 são portáveis entre</p><p>servidores de banco de dados, ao passo que a sintaxe mais antiga é ligeiramente</p><p>diferente entre os diferentes servidores.</p><p>Os benefícios da sintaxe de junção do SQL92 são visualizados mais facilmente no</p><p>caso de consultas complexas que incluam tanto condições de junção quanto de</p><p>�ltro. Considere a seguinte consulta, que retorna todas as contas abertas por caixas</p><p>experientes (contratados antes de 2007) que estejam atualmente alocados na �lial de</p><p>Woburn:</p><p>No caso dessa consulta, não é tão fácil assim determinar quais condições da</p><p>cláusula where são condições de junção e quais são condições de �ltro. Também não</p><p>�ca tão aparente que tipo de junção está sendo empregado (para identi�car o tipo de</p><p>junção, você teria que observar mais de perto as condições de junção na cláusula</p><p>where para ver se algum tipo de caractere especial foi utilizado), e não é fácil</p><p>determinar se alguma condição de junção foi deixada de fora por engano. Aqui está</p><p>a mesma consulta usando a sintaxe de junção do SQL92:</p><p>Creio que você concordará que a versão utilizando a sintaxe de junção do SQL92 é a</p><p>mais fácil de entender.</p><p>Juntando três ou mais tabelas</p><p>Juntar três tabelas é semelhante a juntar duas tabelas, mas tem um pequeno truque.</p><p>Em uma junção de duas tabelas, existem duas tabelas e um tipo de junção na</p><p>cláusula from, além de uma única subcláusula on que de�ne como as tabelas serão</p><p>juntadas. Em uma junção de três tabelas, existem três tabelas e dois tipos de junção</p><p>na cláusula from, além de duas subcláusulas on. Aqui está outro exemplo de uma</p><p>consulta com uma junção de duas tabelas: mysql> SELECT a.account_id,</p><p>c.fed_id -> FROM account a INNER JOIN customer c -> ON</p><p>a.cust_id = c.cust_id</p><p>-> WHERE c.cust_type_cd = 'B';</p><p>+------------+------------+</p><p>| account_id | fed_id |</p><p>+------------+------------+</p><p>| 24 | 04-1111111 |</p><p>| 25 | 04-1111111 |</p><p>| 27 | 04-2222222 |</p><p>| 28 | 04-3333333 |</p><p>| 29 | 04-4444444 |</p><p>+------------+------------+</p><p>5 rows in set (0.15 sec)</p><p>Essa consulta, que retorna o ID da conta e o número de imposto federal de todas as</p><p>contas corporativas, deve parecer bem simples para você a essa altura. Se, no</p><p>entanto, você adicionar a tabela employee à consulta para também recuperar o nome</p><p>do caixa que abriu cada conta, a consulta �cará assim:</p><p>Agora três tabelas, dois tipos de junção e duas subcláusulas on estão listadas na</p><p>cláusula from, tornando as coisas um pouco mais complexas. À primeira vista, a</p><p>ordem em que as tabelas são nomeadas pode fazer você pensar que a tabela</p><p>employee está sendo juntada à tabela customer, pois a tabela account é nomeada</p><p>primeiro, seguida pela tabela customer</p>
<p>e, então, pela tabela employee. No entanto, se</p><p>você trocar a ordem em que as primeiras duas tabelas aparecem, você obterá</p><p>exatamente o mesmo resultado:</p><p>A tabela customer agora está listada primeiro, seguida pela tabela account e, então,</p><p>pela tabela employee. Como as subcláusulas on não mudaram, os resultados são os</p><p>mesmos. Para esgotar a questão, aqui está a mesma consulta uma última vez, mas</p><p>com uma ordem de tabelas completamente invertida (de employee para account para</p><p>customer):</p><p>A ordem das junções importa?</p><p>Se você estiver confuso sobre o porquê de as três versões da consulta a</p><p>account/employee/customer terem dado o mesmo resultado, tenha em mente que</p><p>SQL é uma linguagem não-procedural, o que signi�ca que você descreve o que</p><p>quer recuperar e quais objetos do banco de dados precisam estar envolvidos, mas</p><p>�ca a critério do servidor de banco de dados determinar a melhor maneira de</p><p>executar sua consulta. Usando estatísticas reunidas a partir de seus objetos de</p><p>banco de dados, o servidor deve pegar uma das três tabelas como ponto de</p><p>partida (a tabela escolhida �ca conhecida como tabela condutora), e então decide</p><p>em qual ordem juntar as tabelas restantes. Portanto, a ordem em que as tabelas</p><p>aparecem em sua cláusula from é irrelevante.</p><p>Se, no entanto, você acredita que as tabelas em sua consulta devem ser juntadas</p><p>sempre em uma ordem especí�ca, você pode colocar as tabelas na ordem</p><p>desejada e, então, especi�car a palavra-chave STRAIGHT_JOIN no MySQL,</p><p>requisitar a opção FORCE ORDER no SQL Server ou usar a dica de otimização</p><p>ORDERED ou a dica LEADING no Oracle Database. Por exemplo, para dizer ao</p><p>servidor MySQL para usar a tabela customer como a tabela condutora e, então,</p><p>juntar as tabelas account e employee, você poderia fazer o seguinte: mysql></p><p>SELECT STRAIGHT_JOIN a.account_id, c.fed_id, e.fname,</p><p>e.lname -> FROM customer c INNER JOIN account a -> ON</p><p>a.cust_id = c.cust_id</p><p>-> INNER JOIN employee e</p><p>-> ON a.open_emp_id = e.emp_id</p><p>-> WHERE c.cust_type_cd = ‘B’;</p><p>Uma maneira de pensar a respeito de uma consulta que use três ou mais tabelas</p><p>seria como uma bola de neve rolando morro abaixo. As primeiras duas tabelas</p><p>rolam a bola, e cada tabela subsequente se anexa à bola de neve conforme ela desce</p><p>o morro. Você pode imaginar a bola de neve como um conjunto-resultado</p><p>intermediário, que vai reunindo mais e mais colunas conforme tabelas subsequentes</p><p>vão sendo juntadas. Portanto, a tabela employee não está realmente sendo juntada à</p><p>tabela account, e sim ao conjunto intermediário que foi criado quando as tabelas</p><p>customer e account foram juntadas. (No caso de você estar imaginando por que</p><p>escolhi a analogia da bola de neve, é porque escrevi este capítulo no meio de um</p><p>inverno na Nova Inglaterra: 280 centímetros de neve até agora, e mais chegando</p><p>amanhã. Que maravilha.)</p><p>Usando subconsultas como tabelas</p><p>Você já viu vários exemplos de consultas que usam três tabelas, mas existe uma</p><p>variante que vale a pena mencionar: o que fazer no caso de um ou mais conjuntos</p><p>de dados serem gerados por subconsultas. Subconsultas será o foco do capítulo 9,</p><p>mas já introduzi o conceito de uma subconsulta dentro da cláusula from no capítulo</p><p>anterior. Aqui está outra versão da consulta anterior (encontrar todas as contas</p><p>abertas por caixas experientes atualmente alocados na �lial Woburn) que une a</p><p>tabela account às subconsultas feitas às tabelas branch e employee: 1 SELECT</p><p>a.account_id, a.cust_id, a.open_date, a.product_cd</p><p>2 FROM account a INNER JOIN</p><p>3 (SELECT emp_id, assigned_branch_id</p><p>4 FROM employee</p><p>5 WHERE start_date SELECT emp_id,</p><p>assigned_branch_id -> FROM employee</p><p>-> WHERE start_date AND (title = 'Teller' OR title = 'Head Teller'); +--------+-------------</p><p>-------+</p><p>| emp_id | assigned_branch_id |</p><p>+--------+--------------------+</p><p>| 8 | 1 |</p><p>| 9 | 1 |</p><p>| 10 | 2 |</p><p>| 11 | 2 |</p><p>| 13 | 3 |</p><p>| 14 | 3 |</p><p>| 16 | 4 |</p><p>| 17 | 4 |</p><p>| 18 | 4 |</p><p>+--------+--------------------+</p><p>9 rows in set (0.03 sec)</p><p>Assim, esse conjunto-resultado consiste em um conjunto de IDs de funcionário e de</p><p>seus IDs de �lial correspondentes. Ao serem juntados à tabela account por meio da</p><p>coluna emp_id, você tem um conjunto-resultado intermediário consistindo em todas</p><p>as linhas da tabelas account mais uma coluna adicional que retorna o ID de �lial do</p><p>funcionário que abriu cada conta. Aqui estão os resultados da segunda subconsulta</p><p>feita à tabela branch: mysql> SELECT branch_id</p><p>-> FROM branch</p><p>-> WHERE name = 'Woburn Branch';</p><p>+-----------+</p><p>| branch_id |</p><p>+-----------+</p><p>| 2 |</p><p>+-----------+</p><p>1 row in set (0.02 sec)</p><p>Essa consulta retorna uma única linha contendo uma única coluna: o ID da �lial</p><p>Woburn. Essa tabela é juntada à coluna assigend_branch_id do conjunto-resultado</p><p>intermediário, fazendo com que todas as contas abertas por funcionários não-</p><p>alocados em Woburn sejam excluídas do conjunto-resultado �nal.</p><p>Usando a mesma tabela duas vezes</p><p>Se você estiver juntando múltiplas tabelas, você pode acabar descobrindo que</p><p>precisa juntar as mesmas tabelas mais de uma vez. No banco de dados de exemplo,</p><p>por exemplo, há chaves estrangeiras da tabela branch tanto na tabela account (a �lial</p><p>em que a conta foi aberta) quanto na tabela employee (a �lial em que o funcionário</p><p>trabalha). Se desejar incluir ambas as chaves de �liais em seu conjunto resultado,</p><p>você pode incluir a tabela branch duas vezes na cláusula from, juntada uma vez com</p><p>a tabela employee e outra vez com a tabela account. Para isso funcionar, você</p><p>precisará dar a cada instância da tabela branch um alias diferente para que o</p><p>servidor saiba a quem você está se referindo nas várias cláusulas, como em:</p><p>Essa consulta mostra quem abriu cada conta corrente, em qual �lial ela foi aberta e</p><p>em qual �lial o funcionário que abriu a conta está trabalhando atualmente. A tabela</p><p>branch foi incluída duas vezes, com os aliases b_a e b_e. Ao atribuir diferentes aliases</p><p>para cada instância da tabela branch, o servidor é capaz de entender a qual instância</p><p>você está se referindo: a que foi juntada à tabela account ou a que foi juntada à</p><p>tabela employee. Portanto, este é um exemplo de uma consulta que requer o uso de</p><p>aliases de tabelas.</p><p>Autojunções</p><p>Além de poder incluir a mesma tabela mais de uma vez na mesma consulta, você</p><p>também pode juntar uma tabela a ela mesma. Isso pode parecer um pouco estranho</p><p>de se fazer na primeira vez, mas existem boas razões para fazê-lo. A tabela employee,</p><p>por exemplo, inclui uma chave estrangeira autorreferenciadora, o que signi�ca que ela</p><p>inclui uma coluna (superior_emp_id) que aponta para a chave primária dentro da</p><p>mesma tabela. Essa coluna aponta para o gerente do funcionário (a menos que o</p><p>funcionário seja o chefão: nesse caso,</p>
<p>a coluna seria null). Usando uma autojunção,</p><p>você pode escrever uma consulta que lista o nome de cada funcionário junto com o</p><p>nome de seu gerente:</p><p>Essa consulta inclui duas instâncias da tabela employee: uma para fornecer os nomes</p><p>dos funcionários (com o alias de tabela e) e a outra para fornecer nomes de gerentes</p><p>(com o alias de tabela e_mgr). A subcláusula on usa esses aliases para juntar a tabela</p><p>employee a si mesma por meio da chave estrangeira superior_emp_id. Este é outro</p><p>exemplo de uma consulta que requer aliases de tabela – de outra forma, o servidor</p><p>não saberia se você está se referindo a um funcionário ou ao gerente de um</p><p>funcionário.</p><p>Apesar de haver 18 linhas na tabela employee, a consulta retornou apenas 17: o</p><p>presidente do banco, Michael Smith, não tem superior (sua coluna superior_emp_id</p><p>é null), então a junção falha na linha dele. Para incluir Michael Smith no conjunto-</p><p>resultado, você precisaria usar uma junção externa, que veremos no capítulo 10.</p><p>Junções equivalentes versus não-equivalentes</p><p>Todas as consultas de múltiplas tabelas mostradas até agora empregaram junções</p><p>equivalentes, o que signi�ca que os valores das duas tabelas devem corresponder</p><p>para que a junção seja bem-sucedida. Uma junção equivalente sempre emprega o</p><p>sinal de igual, como em: ON e.assigned_branch_id = b.branch_id</p><p>Enquanto a maioria de suas consultas empregará junções equivalentes, você</p><p>também pode juntar suas tabelas por meio de intervalos de valores, que são</p><p>referidos como junções não-equivalentes. Con�ra um exemplo de uma consulta que</p><p>une por meio de um intervalo de valores: SELECT e.emp_id, e.fname, e.lname,</p><p>e.start_date</p><p>FROM employee e INNER JOIN product p</p><p>ON e.start_date >= p.date_offered</p><p>AND e.start_date e2.emp_id se quiser):</p><p>Agora você tem uma lista de 36 pareamentos, que é o número correto quando se</p><p>quer os pares únicos de nove coisas distintas.</p><p>Condições de junção versus condições de �ltro</p><p>Agora você está familiarizado com o fato de que condições de junção pertencem à</p><p>subcláusula on, enquanto condições de �ltro pertencem à cláusula where. No</p><p>entanto, a SQL é bem �exível em relação ao local em que suas condições são</p><p>colocadas – por isso, é preciso tomar cuidado ao construir suas consultas. Por</p><p>exemplo, a seguinte consulta junta duas tabelas usando uma única condição de</p><p>junção e também inclui uma única condição de �ltro na cláusula where:</p><p>Essa foi bem simples, mas o que aconteceria se você, por engano, colocasse a</p><p>condição de �ltro na subcláusula on em vez de colocá-lo na cláusula where?</p><p>Como você pode ver, a segunda versão, que coloca ambas as condições na</p><p>subcláusula on e que não tem cláusula where, gera os mesmos resultados que a</p><p>primeira versão. E se ambas as condições forem colocadas na cláusula where, com a</p><p>cláusula from ainda utilizando a sintaxe ANSI de junção?</p><p>Mais uma vez, o servidor MySQL gerou o mesmo conjunto-resultado. Cabe a você</p><p>colocar suas condições nos lugares corretos para que suas consultas sejam fáceis de</p><p>entender e manter.</p><p>Teste seu conhecimento</p><p>Os seguintes exercícios foram projetados para testar sua compreensão de junções</p><p>internas. Por favor, consulte o apêndice C para ver as soluções dos exercícios.</p><p>Exercício 5.1</p><p>Preencha as lacunas (denotadas por ) da seguinte consulta para obter os</p><p>resultados mostrados em seguida:</p><p>Exercício 5.2</p><p>Escreva uma consulta que retorne o ID de conta de cada cliente não-corporativo</p><p>(customer.cust_type_cd = ‘I’) com o ID federal do cliente (customer.fed_id) e o</p><p>nome do produto no qual a conta é baseada (product.name).</p><p>Exercício 5.3</p><p>Construa uma consulta que encontre todos os funcionários cujo supervisor esteja</p><p>alocado em um departamento diferente. Recupere o ID, o nome e o sobrenome do</p><p>funcionário.</p><p>CAPÍTULO 6</p><p>Trabalhando com conjuntos</p><p>Apesar de você poder interagir com os dados em um banco de dados uma linha por</p><p>vez, a verdadeira razão de ser dos bancos de dados relacionais consiste nos</p><p>conjuntos. Você já viu como criar tabelas por meio de consultas ou subconsultas,</p><p>torná-las persistentes por meio de instruções insert e juntá-las por meio de uniões –</p><p>este capítulo explora como você pode combinar múltiplas tabelas usando vários</p><p>operadores de conjunto.</p><p>Introdução à teoria dos conjuntos</p><p>Em muitas partes do mundo, o básico da teoria de conjuntos é incluído no</p><p>currículo dos níveis elementares de matemática. Talvez você se lembre de ter visto</p><p>algo parecido com o que é mostrado na �gura 6.1.</p><p>Figura 6.1 – Operação de união.</p><p>A área sombreada na �gura 6.1 representa a união dos conjuntos A e B, que é a</p><p>combinação dos dois conjuntos (com quaisquer regiões sobrepostas sendo incluídas</p><p>apenas uma vez). Isso está começando a soar familiar? Se estiver, então você</p><p>�nalmente terá uma chance de colocar esse conhecimento em prática – se não</p><p>estiver, não se preocupe, porque isso é fácil de visualizar usando alguns diagramas.</p><p>Usando círculos para representar dois conjuntos de dados (A e B), imagine um</p><p>subconjunto de dados que seja comum a ambos os conjuntos: esses dados comuns</p><p>são representados pela área sobreposta mostrada na �gura 6.1. Já que a teoria dos</p><p>conjuntos �ca extremamente desinteressante sem uma sobreposição entre conjuntos</p><p>de dados, eu uso o mesmo diagrama para ilustrar cada operação de conjunto. Existe</p><p>outra operação de conjunto voltada apenas para a sobreposição entre dois conjuntos</p><p>de dados: essa operação é conhecida como intersecção e é demonstrada na �gura 6.2.</p><p>Figura 6.2 – Operação de intersecção.</p><p>O conjunto de dados gerado pela intersecção dos conjuntos A e B é a mesma área de</p><p>sobreposição entre os dois conjuntos. Se os dois conjuntos não se sobrepuserem, a</p><p>operação de intersecção resulta em um conjunto vazio.</p><p>A terceira e última operação de conjunto, que é demonstrada na �gura 6.3, é</p><p>conhecida como a operação de diferença.</p><p>Figura 6.3 – Operação de diferença.</p><p>A �gura 6.3 mostra o resultado de A diferença B, que é o total do conjunto A menos</p><p>qualquer sobreposição com o conjunto B. Se os dois conjuntos não se sobrepuserem,</p><p>a operação A diferença B resulta no conjunto A completo.</p><p>Usando essas três operações, ou combinando diferentes operações, você pode gerar</p><p>qualquer resultado que precisar. Por exemplo, imagine que você queira construir um</p><p>conjunto demonstrado pela �gura 6.4.</p><p>Figura 6.4 – Conjunto de dados misterioso.</p><p>O conjunto de dados que você está procurando inclui tudo dos conjuntos A e B sem</p><p>a região sobreposta. Você não conseguirá esse resultado com apenas uma das três</p><p>operações mostradas anteriormente – em vez disso, precisará primeiro construir um</p><p>conjunto de dados que englobe tudo dos conjuntos A e B, e então utilizar uma</p><p>segunda operação para remover a região sobreposta. Se o conjunto combinado for</p><p>descrito como A união</p>
<p>B, e a região sobreposta for descrita como A intersecção B, a</p><p>operação necessária para gerar o conjunto de dados representado pela �gura 6.4 se</p><p>parecerá com o seguinte: (A união B) diferença (A intersecção B)</p><p>Claro, há várias maneiras de se obter os mesmos resultados – você poderia</p><p>conseguir um resultado semelhante usando a seguinte operação: (A diferença B)</p><p>união (B diferença A)</p><p>Apesar de esses conceitos serem relativamente fáceis de serem entendidos com o uso</p><p>de diagramas, as seções seguintes mostrarão como esses conceitos são aplicados em</p><p>um banco de dados relacional usando os operadores de conjunto da SQL.</p><p>Teoria dos conjuntos na prática</p><p>Os círculos usados nos diagramas da seção anterior representam conjuntos de</p><p>dados que não informam nada a respeito dos dados que os compõem. No entanto,</p><p>ao lidar com dados reais, existe uma necessidade de descrever a composição dos</p><p>conjuntos de dados envolvidos caso sejam combinados. Imagine, por exemplo, o que</p><p>aconteceria se você tentasse gerar a das tabelas product e customer, cujas de�nições</p><p>de tabelas são as seguintes:</p><p>Quando combinadas, a primeira coluna da tabela resultante seria a combinação das</p><p>colunas product.product_cd e customer.cust_id, a segunda seria a combinação das</p><p>colunas product.name e customer.fed_id e assim por diante. Apesar de alguns pares</p><p>serem fáceis de combinar (por exemplo, duas colunas numéricas), não �ca claro</p><p>como outros pares de colunas deveriam ser combinados, tais como uma coluna</p><p>numérica com uma coluna de string ou uma de string com uma de data. Além disso,</p><p>a sexta e a sétima colunas das tabelas combinadas incluem apenas os dados da</p><p>sexta e da sétima colunas da tabela customer, já que a tabela product contém apenas</p><p>cinco colunas. Está claro que deve haver algum tipo de associação entre as duas</p><p>tabelas que você deseja combinar.</p><p>Portanto, ao realizar operações de conjunto sobre dois conjuntos de dados, as</p><p>seguintes linhas gerais devem ser seguidas: • Ambos os conjuntos de dados devem</p><p>ter o mesmo número de colunas.</p><p>• Os tipos de dados de cada coluna ao longo dos dois conjuntos de dados devem</p><p>ser os mesmos (ou o servidor deve ser capaz de converter um no outro).</p><p>Com essas regras postas, �ca mais fácil visualizar o que “sobreposição de dados”</p><p>signi�ca na prática: todas as colunas pareadas dos dois conjuntos que estão sendo</p><p>combinados devem conter o mesmo número, data ou string para que as linhas nas</p><p>duas tabelas sejam consideradas as mesmas.</p><p>Você realiza uma operação de conjunto colocando um operador de conjunto entre</p><p>duas instruções select, como demonstrado a seguir: mysql> SELECT 1 num,</p><p>'abc' str -> UNION</p><p>-> SELECT 9 num, 'xyz' str; +-----+-----+</p><p>| num | str |</p><p>+-----+-----+</p><p>| 1 | abc |</p><p>| 9 | xyz |</p><p>+-----+-----+</p><p>2 rows in set (0.02 sec)</p><p>Cada uma das consultas individuais resulta em um conjunto de dados consistindo</p><p>em uma única linha que contém uma coluna numérica e uma de string. O operador</p><p>de conjunto, que nesse caso é union, diz ao servidor de banco de dados para</p><p>combinar todas as linhas dos dois conjuntos. Essa consulta é conhecida como uma</p><p>consulta composta porque abrange consultas múltiplas que, de outra forma, seriam</p><p>independentes. Como você verá mais adiante, consultas compostas podem incluir</p><p>mais de duas consultas se várias operações de conjunto forem necessárias para obter</p><p>o resultado �nal.</p><p>Operadores de conjunto</p><p>A linguagem SQL inclui três operadores de conjunto que permitem realizar cada</p><p>uma das operações de conjunto descritas anteriormente neste capítulo. Além disso,</p><p>cada operador de conjunto vem em dois sabores, um que inclui duplicatas e outro</p><p>que as remove (mas não necessariamente todas). As próximas subseções de�nem</p><p>cada operador e demonstram como eles são usados.</p><p>Operador union</p><p>Os operadores union e union all permitem combinar vários conjuntos de dados. A</p><p>diferença entre os dois é que union ordena o conjunto combinado e remove</p><p>duplicatas, enquanto union all não o faz. Com union all, o número de linhas no</p><p>conjunto de dados �nal sempre será igual à soma do número de linhas dos</p><p>conjuntos que estão sendo combinados. Essa operação é a mais simples de ser</p><p>realizada (do ponto de vista do servidor), pois não há necessidade de o servidor</p><p>veri�car a sobreposição de dados. O exemplo a seguir demonstra como você pode</p><p>usar o operador union all para gerar um conjunto completo de dados de clientes a</p><p>partir das duas tabelas de subtipos de clientes: mysql> SELECT 'IND'</p><p>type_cd, cust_id, lname name -> FROM individual</p><p>-> UNION ALL</p><p>-> SELECT 'BUS' type_cd, cust_id, name -> FROM business;</p><p>+---------+---------+------------------------+</p><p>| type_cd | cust_id | name |</p><p>+---------+---------+------------------------+</p><p>| IND | 1 | Hadley |</p><p>| IND | 2 | Tingley |</p><p>| IND | 3 | Tucker |</p><p>| IND | 4 | Hayward |</p><p>| IND | 5 | Frasier |</p><p>| IND | 6 | Spencer |</p><p>| IND | 7 | Young |</p><p>| IND | 8 | Blake |</p><p>| IND | 9 | Farley |</p><p>| BUS | 10 | Chilton Engineering |</p><p>| BUS | 11 | Northeast Cooling Inc. |</p><p>| BUS | 12 | Superior Auto Body |</p><p>| BUS | 13 | AAA Insurance Inc. |</p><p>+---------+---------+------------------------+</p><p>13 rows in set (0.04 sec)</p><p>A consulta retorna os 13 clientes, com nove linhas vindo da tabela individual e as</p><p>outras quatro da tabela business. Enquanto a tabela business inclui uma única</p><p>coluna para armazenar o nome da companhia, a tabela individual inclui duas</p><p>colunas de nome, uma para o nome e outra para o sobrenome da pessoa. Nesse</p><p>caso, optei por incluir apenas o sobrenome da tabela individual.</p><p>Apenas para encerrar a discussão sobre o fato de que o operador union all não</p><p>remove duplicatas, veja a mesma consulta feita no exemplo anterior, mas com uma</p><p>consulta adicional à tabela business: mysql> SELECT 'IND' type_cd,</p><p>cust_id, lname name -> FROM individual</p><p>-> UNION ALL</p><p>-> SELECT 'BUS' type_cd, cust_id, name -> FROM business</p><p>-> UNION ALL</p><p>-> SELECT 'BUS' type_cd, cust_id, name -> FROM business;</p><p>+---------+---------+------------------------+</p><p>| type_cd | cust_id | name |</p><p>+---------+---------+------------------------+</p><p>| IND | 1 | Hadley |</p><p>| IND | 2 | Tingley |</p><p>| IND | 3 | Tucker |</p><p>| IND | 4 | Hayward |</p><p>| IND | 5 | Frasier |</p><p>| IND | 6 | Spencer |</p><p>| IND | 7 | Young |</p><p>| IND | 8 | Blake |</p><p>| IND | 9 | Farley |</p><p>| BUS | 10 | Chilton Engineering |</p><p>| BUS | 11 | Northeast Cooling Inc. |</p><p>| BUS | 12 | Superior Auto Body |</p><p>| BUS | 13 | AAA Insurance Inc. |</p><p>| BUS | 10 | Chilton Engineering |</p><p>| BUS | 11 | Northeast Cooling Inc. |</p><p>| BUS | 12 | Superior Auto Body |</p><p>| BUS | 13 | AAA Insurance Inc. |</p><p>+---------+---------+------------------------+</p><p>17 rows in set (0.01 sec)</p><p>Essa consulta composta inclui três instruções select, duas das quais são idênticas.</p><p>Como você pode ver pelos resultados, as quatro linhas da tabela business são</p><p>incluídas duas vezes (IDs de cliente 10, 11, 12 e 13).</p><p>Apesar de ser improvável que repita a mesma consulta duas vezes em uma consulta</p><p>composta, con�ra outra consulta composta que retorna dados duplicados: mysql></p><p>SELECT emp_id -> FROM employee -> WHERE assigned_branch_id =</p><p>2</p><p>-> AND (title = 'Teller' OR title = 'Head Teller') -> UNION ALL</p><p>-> SELECT DISTINCT open_emp_id -> FROM account</p><p>-> WHERE open_branch_id = 2; +--------+</p><p>| emp_id |</p><p>+--------+</p><p>| 10 |</p><p>| 11 |</p><p>| 12 |</p><p>| 10 |</p><p>+--------+</p><p>4 rows in set (0.01 sec)</p><p>A primeira consulta da instrução composta recupera todos os caixas alocados na</p><p>�lial Woburn, enquanto a segunda retorna o conjunto distinto de caixas que</p><p>abriram contas na �lial Woburn. Das quatro linhas no conjunto-resultado, uma</p><p>delas é uma duplicata (o empregado com ID 10). Se você quiser que sua tabela</p><p>combinada exclua linhas duplicadas, você precisará usar o operador union em vez de</p><p>union all: mysql> SELECT emp_id -> FROM employee</p><p>-> WHERE assigned_branch_id = 2</p><p>-> AND (title = 'Teller' OR title = 'Head Teller') -> UNION</p><p>-> SELECT DISTINCT open_emp_id -> FROM account</p><p>-> WHERE open_branch_id = 2; +--------+</p><p>| emp_id |</p><p>+--------+</p><p>| 10 |</p><p>| 11 |</p><p>| 12 |</p><p>+--------+</p><p>3 rows in set (0.01 sec)</p><p>Para essa versão da consulta, apenas as três linhas distintas são incluídas no</p><p>conjunto-resultado, em vez das quatro</p>
<p>colunas (três distintas, uma duplicata)</p><p>retornadas ao usar union all.</p><p>Operador intersect</p><p>A especi�cação ANSI SQL inclui o operador intersect para realizar intersecções.</p><p>Infelizmente, a versão 5.1 do MySQL não implementa o operador intersect. Se</p><p>estiver usando o Oracle ou o SQL Server 2008, você conseguirá utilizar o intersect</p><p>– no entanto, como estou usando o MySQL em todos os exemplos deste livro, os</p><p>conjuntos-resultados das consultas de exemplo dessa seção são inventados e não</p><p>podem ser executados em nenhuma versão do MySQL igual ou inferior à 5.1.</p><p>Também me abstenho de mostrar o prompt do MySQL (mysql>), pois as instruções</p><p>não estão sendo executadas pelo servidor MySQL.</p><p>Se as duas consultas de uma consulta composta retornarem conjuntos de dados</p><p>não-sobrepostos, a intersecção será um conjunto vazio. Considere a seguinte</p><p>consulta: SELECT emp_id, fname, lname FROM employee</p><p>INTERSECT</p><p>SELECT cust_id, fname, lname FROM individual;</p><p>Empty set (0.04 sec)</p><p>A primeira consulta retorna o ID e o nome de cada funcionário, enquanto a segunda</p><p>retorna o ID e o nome de cada cliente. Esses conjuntos estão completamente não-</p><p>sobrepostos, de modo que a intersecção dos dois conjuntos resulta no conjunto</p><p>vazio.</p><p>O próximo passo é identi�car duas consultas que, de fato, tenham dados</p><p>sobrepostos e, então, aplicar o operador intersect. Para tanto, uso a mesma consulta</p><p>que foi utilizada para demonstrar a diferença entre union e union all, mas dessa vez</p><p>aplicando o operador intersect: SELECT emp_id</p><p>FROM employee</p><p>WHERE assigned_branch_id = 2</p><p>AND (title = 'Teller' OR title = 'Head Teller') INTERSECT</p><p>SELECT DISTINCT open_emp_id FROM account</p><p>WHERE open_branch_id = 2; +--------+</p><p>| emp_id |</p><p>+--------+</p><p>| 10 |</p><p>+--------+</p><p>1 row in set (0.01 sec)</p><p>A intersecção dessas duas consultas resulta no ID de funcionário 10, que é o único</p><p>valor encontrado em ambos os conjuntos-resultados.</p><p>Junto com o operador intersect, que remove quaisquer linhas duplicadas na região</p><p>sobreposta, a especi�cação ANSI SQL também cita o operador intersect all, que</p><p>não remove duplicatas. O único servidor de banco de dados que atualmente</p><p>implementa o operador intersect all é o DB2 Universal Server da IBM.</p><p>Operador except</p><p>A especi�cação ANSI SQL inclui o operador except para realizar operações de</p><p>diferença. Mais uma vez, infelizmente, a versão 5.1 do MySQL não implementa o</p><p>operador except, então as mesmas regras da seção anterior se aplicam a esta seção.</p><p>Se estiver utilizando o Oracle Database, você precisará usar o operador não-ANSI minus</p><p>no lugar de except.</p><p>O operador except retorna a primeira tabela menos qualquer sobreposição com a</p><p>segunda tabela. Aqui está o exemplo da seção anterior, mas usando except em vez de</p><p>intersect: SELECT emp_id</p><p>FROM employee</p><p>WHERE assigned_branch_id = 2</p><p>AND (title = 'Teller' OR title = 'Head Teller') EXCEPT</p><p>SELECT DISTINCT open_emp_id FROM account</p><p>WHERE open_branch_id = 2; +--------+</p><p>| emp_id |</p><p>+--------+</p><p>| 11 |</p><p>| 12 |</p><p>+--------+</p><p>2 rows in set (0.01 sec)</p><p>Nessa versão da consulta, o conjunto-resultado consiste nas três linhas da primeira</p><p>consulta menos o ID de funcionário 10, que existe em ambos os conjuntos-</p><p>resultados. Também existe um operador except all na especi�cação ANSI SQL, mas,</p><p>novamente, apenas o DB2 Universal Server da IBM o implementa.</p><p>O operador except all é um pouco traiçoeiro, então aqui vai um exemplo que</p><p>demonstra como os dados duplicados são tratados. Digamos que você tenha dois</p><p>conjuntos de dados parecidos com os seguintes: Conjunto A</p><p>+--------+</p><p>| emp_id |</p><p>+--------+</p><p>| 10 |</p><p>| 11 |</p><p>| 12 |</p><p>| 10 |</p><p>| 10 |</p><p>+--------+</p><p>Conjunto B</p><p>+--------+</p><p>| emp_id |</p><p>+--------+</p><p>| 10 |</p><p>| 10 |</p><p>+--------+</p><p>A operação A except B resulta no seguinte: +--------+</p><p>| emp_id |</p><p>+--------+</p><p>| 11 |</p><p>| 12 |</p><p>+--------+</p><p>Se você mudar a operação para A except all B, você verá o seguinte: +--------+</p><p>| emp_id |</p><p>+--------+</p><p>| 10 |</p><p>| 11 |</p><p>| 12 |</p><p>+--------+</p><p>Portanto, a diferença entre as duas operações é que except remove todas as</p><p>ocorrências de dados duplicados do conjunto A, enquanto except all remove</p><p>apenas uma ocorrência de dado duplicado para cada ocorrência no conjunto B.</p><p>Regras das operações de conjunto</p><p>As seguintes seções esboçam algumas regras que você deve seguir ao trabalhar com</p><p>consultas compostas.</p><p>Ordenando resultados de consultas compostas</p><p>Se quiser que os resultados de sua consulta composta sejam ordenados, você pode</p><p>adicionar uma cláusula order by após a última consulta. Ao especi�car nomes de</p><p>colunas na cláusula order by, você deverá escolhê-los na primeira consulta da</p><p>consulta composta. Frequentemente, os nomes das colunas são os mesmos em</p><p>ambas as consultas de uma consulta composta, mas isso não precisa ser</p><p>necessariamente verdade, como é demonstrado a seguir: mysql> SELECT</p><p>emp_id, assigned_branch_id -> FROM employee</p><p>-> WHERE title = 'Teller'</p><p>-> UNION</p><p>-> SELECT open_emp_id, open_branch_id -> FROM account</p><p>-> WHERE product_cd = 'SAV'</p><p>-> ORDER BY emp_id;</p><p>+--------+--------------------+</p><p>| emp_id | assigned_branch_id |</p><p>+--------+--------------------+</p><p>| 1 | 1 |</p><p>| 7 | 1 |</p><p>| 8 | 1 |</p><p>| 9 | 1 |</p><p>| 10 | 2 |</p><p>| 11 | 2 |</p><p>| 12 | 2 |</p><p>| 14 | 3 |</p><p>| 15 | 3 |</p><p>| 16 | 4 |</p><p>| 17 | 4 |</p><p>| 18 | 4 |</p><p>+--------+--------------------+</p><p>12 rows in set (0.04 sec)</p><p>Os nomes de colunas especi�cados nas duas consultas são diferentes nesse exemplo.</p><p>Se você especi�car um nome de coluna da segunda consulta em sua cláusula order</p><p>by, você verá o seguinte erro: mysql> SELECT emp_id,</p><p>assigned_branch_id -> FROM employee</p><p>-> WHERE title = 'Teller'</p><p>-> UNION</p><p>-> SELECT open_emp_id, open_branch_id -> FROM account</p><p>-> WHERE product_cd = 'SAV'</p><p>-> ORDER BY open_emp_id; ERROR 1054 (42S22): Unknown column 'open_emp_id'</p><p>in 'order clause'</p><p>Recomendo dar aliases idênticos às colunas de ambas as consultas para evitar esse</p><p>problema.</p><p>Precedência das operações de conjunto</p><p>Se a sua consulta composta tiver mais de duas consultas usando diferentes</p><p>operadores de conjunto, você precisará pensar a respeito da ordem em que as</p><p>consultas serão colocadas em sua instrução composta para obter os resultados</p><p>desejados. Considere a seguinte instrução composta de três consultas: mysql></p><p>SELECT cust_id -> FROM account</p><p>-> WHERE product_cd IN ('SAV', 'MM') -> UNION ALL</p><p>-> SELECT a.cust_id</p><p>-> FROM account a INNER JOIN branch b -> ON a.open_branch_id = b.branch_id</p><p>-> WHERE b.name = 'Woburn Branch'</p><p>-> UNION</p><p>-> SELECT cust_id</p><p>-> FROM account</p><p>-> WHERE avail_balance BETWEEN 500 AND 2500; +---------+</p><p>| cust_id |</p><p>+---------+</p><p>| 1 |</p><p>| 2 |</p><p>| 3 |</p><p>| 4 |</p><p>| 8 |</p><p>| 9 |</p><p>| 7 |</p><p>| 11 |</p><p>| 5 |</p><p>+---------+</p><p>9 rows in set (0.00 sec)</p><p>Essa consulta composta inclui três consultas que retornam conjuntos de IDs não-</p><p>únicos de clientes; a primeira e a segunda são separadas pelo operador union all,</p><p>enquanto a segunda e a terceira consultas são separadas pelo operador union.</p><p>Apesar de não parecer fazer muita diferença onde os operadores union e union all</p><p>são colocados, isso, de fato, faz a diferença. Aqui está a mesma consulta composta</p><p>com os operadores invertidos: mysql> SELECT cust_id -> FROM account -></p><p>WHERE product_cd IN ('SAV', 'MM') -> UNION</p><p>-> SELECT a.cust_id</p><p>-> FROM account a INNER JOIN branch b -> ON a.open_branch_id = b.branch_id</p><p>-> WHERE b.name = 'Woburn Branch'</p><p>-> UNION ALL</p><p>-> SELECT cust_id</p><p>-> FROM account</p><p>-> WHERE avail_balance BETWEEN 500 AND 2500; +---------+</p><p>| cust_id |</p><p>+---------+</p><p>| 1 |</p><p>| 2 |</p><p>| 3 |</p><p>| 4 |</p><p>| 8 |</p><p>| 9 |</p><p>| 7 |</p><p>| 11 |</p><p>| 1 |</p><p>| 1 |</p><p>| 2 |</p><p>| 3 |</p><p>| 3 |</p><p>| 4 |</p><p>| 4 |</p><p>| 5 |</p><p>| 9 |</p><p>+---------+</p><p>17 rows in set (0.00 sec)</p><p>Observando os resultados, �ca óbvio que faz diferença como a consulta composta é</p><p>organizada ao usar diferentes operadores de conjunto. Em geral, consultas</p><p>compostas contendo três ou mais consultas são avaliadas em ordem, de cima para</p><p>baixo, mas com as seguintes advertências: • A especi�cação ANSI SQL diz que o</p><p>operador intersect deve ter precedência sobre os outros operadores de conjunto.</p><p>• Você pode ditar a ordem em que as consultas são combinadas colocando</p><p>consultas múltiplas entre parênteses.</p><p>No entanto, como o MySQL ainda não implementa o operador</p>
<p>intersect nem</p><p>permite parênteses em consultas compostas, você precisará dispor cuidadosamente</p><p>as consultas de sua consulta composta para conseguir os resultados esperados. Se</p><p>estiver usando um banco de dados diferente, você pode colocar consultas adjacentes</p><p>entre parênteses para substituir o processamento padrão cima-para-baixo de</p><p>consultas compostas, como em: (SELECT cust_id</p><p>FROM account</p><p>WHERE product_cd IN ('SAV', 'MM')</p><p>UNION ALL</p><p>SELECT a.cust_id</p><p>FROM account a INNER JOIN branch b</p><p>ON a.open_branch_id = b.branch_id</p><p>WHERE b.name = 'Woburn Branch')</p><p>INTERSECT</p><p>(SELECT cust_id</p><p>FROM account</p><p>WHERE avail_balance BETWEEN 500 AND 2500</p><p>EXCEPT</p><p>SELECT cust_id</p><p>FROM account</p><p>WHERE product_cd = 'CD'</p><p>AND avail_balance INSERT INTO string_tbl</p><p>(char_fld, vchar_fld, text_fld) -> VALUES ('This is char</p><p>data',</p><p>-> 'This is varchar data',</p><p>-> 'This is text data');</p><p>Query OK, 1 row affected (0.00 sec)</p><p>Ao inserir dados de string em uma tabela, lembre-se de que, caso a largura exceda o</p><p>tamanho máximo da coluna de caracteres (o máximo designado ou o máximo</p><p>permitido para o tipo de dados), o servidor lançará uma exceção. Apesar de isso ser</p><p>o comportamento padrão para os três servidores, você pode con�gurar o MySQL e o</p><p>SQL Server para truncar silenciosamente a string, em vez de lançar uma exceção.</p><p>Para demonstrar como o MySQL lida com essa situação, a instrução update a seguir</p><p>tenta modi�car a coluna vchar_fld column, cujo tamanho máximo está de�nido</p><p>como 30, com uma string que contém 46 caracteres: mysql> UPDATE</p><p>string_tbl</p><p>-> SET vchar_fld = 'This is a piece of extremely long varchar data'; ERROR</p><p>1406 (22001): Data too long for column 'vchar_fld' at row 1</p><p>No MySQL 5.1, o comportamento padrão agora é o modo “strict” (estrito), o que</p><p>signi�ca que exceções serão lançadas quando surgirem problemas, ao passo que em</p><p>versões anteriores do servidor a string seria truncada e um aviso seria emitido. Caso</p><p>você queira que o mecanismo do servidor trunque a string e emita um aviso, em vez</p><p>de lançar uma exceção, você pode optar por se colocar em modo ANSI. O exemplo a</p><p>seguir mostra como veri�car em qual modo você está e, então, mostra como alterar</p><p>o modo com o comando SET:</p><p>Se você executar novamente a instrução UPDATE anterior, descobrirá que a coluna foi</p><p>modi�cada, mas que o seguinte aviso foi gerado:</p><p>Se você recuperar a coluna vchar_fld, verá que a string foi, de fato, truncada: mysql></p><p>SELECT vchar_fld</p><p>-> FROM string_tbl;</p><p>+--------------------------------+</p><p>| vchar_fld |</p><p>+--------------------------------+</p><p>| This is a piece of extremely l |</p><p>+--------------------------------+</p><p>1 row in set (0.05 sec)</p><p>Como você pode ver, apenas os primeiros 30 caracteres da string de 46 caracteres</p><p>foram inseridos na coluna vchar_fld. A melhor maneira de evitar a truncagem de</p><p>strings (ou as exceções, no caso do Oracle Database ou do MySQL em modo strict)</p><p>ao trabalhar com colunas varchar seria con�gurando o limite máximo de uma</p><p>coluna com um valor alto o su�ciente que consiga tratar as strings mais extensas</p><p>que possam vir a ser armazenadas na coluna (lembrando que o servidor aloca</p><p>apenas espaço su�ciente para armazenar a string, então não é um desperdício</p><p>con�gurar um limite superior alto para uma coluna varchar).</p><p>Incluindo aspas simples</p><p>Como as strings são demarcadas por aspas simples, você precisará �car alerta para</p><p>strings que incluam aspas simples ou apóstrofos. Por exemplo, você não conseguirá</p><p>inserir a seguinte string porque o servidor pensará que o apóstrofo na palavra</p><p>“doesn’t” marca o �m da string: UPDATE string_tbl</p><p>SET text_fld = 'This string doesn't work';</p><p>Para comandar o servidor a ignorar o apóstrofo na palavra doesn’t, você precisará</p><p>adicionar um escape à string, de forma que o servidor trate o apóstrofo como</p><p>qualquer outro caractere da string. Os três servidores permitem que você escape</p><p>uma aspa simples adicionando outra aspa simples imediatamente antes, como em:</p><p>mysql> UPDATE string_tbl</p><p>-> SET text_fld = 'This string didn''t work, but it does now'; Query OK, 1</p><p>row affected (0.01 sec)</p><p>Rows matched: 1 Changed: 1 Warnings: 0</p><p>Os usuários do Oracle</p>
<p>Database e do MySQL também podem optar por escapar uma</p><p>aspa simples adicionando um caractere de barra invertida imediatamente antes, como</p><p>em: UPDATE string_tbl SET text_�d = 'This string didn\'t work, but it does now'</p><p>Se você recuperar uma string para uso em uma tela ou em um campo de relatório,</p><p>não precisará fazer nada especial para tratar as aspas embutidas: mysql> SELECT</p><p>text_fld</p><p>-> FROM string_tbl;</p><p>+------------------------------------------+</p><p>| text_fld |</p><p>+------------------------------------------+</p><p>| This string didn't work, but it does now |</p><p>+------------------------------------------+</p><p>1 row in set (0.00 sec)</p><p>No entanto, se você estiver recuperando a string para adicioná-la a um arquivo que</p><p>outro programa lerá, talvez você precise incluir o escape como parte da string</p><p>recuperada. Se você estiver usando o MySQL, você pode usar a função nativa</p><p>quote(), que coloca a string inteira entre aspas e adiciona escapes para quaisquer</p><p>aspas/apóstrofes dentro da string. Aqui está como �caria nossa string ao ser</p><p>recuperada por meio da função quote():</p><p>Ao recuperar dados para exportação de dados, você pode querer usar a função</p><p>quote() para todas as colunas de caracteres não geradas pelo sistema, como em uma</p><p>coluna customer_notes.</p><p>Incluindo caracteres especiais</p><p>Se a sua aplicação tem abrangência internacional, você pode acabar trabalhando</p><p>com strings que incluam caracteres que não aparecem em seu teclado. Ao trabalhar</p><p>com as línguas francesa e alemã, por exemplo, você pode precisar incluir caracteres</p><p>acentuados como é e ö.1 O SQL Server e o MySQL incluem a função nativa char(),</p><p>que permite construir strings a partir de qualquer um dos 255 caracteres do</p><p>conjuntos de caracteres ASCII (os usuários do Oracle Database podem usar a</p><p>função chr()). Para demonstrá-la, o próximo exemplo recupera uma string digitada</p><p>e sua equivalente construída por meio de caracteres individuais: mysql> SELECT</p><p>'abcdefg', CHAR(97,98,99,100,101,102,103); +---------+----------------</p><p>----------------+</p><p>| abcdefg | CHAR(97,98,99,100,101,102,103) |</p><p>+---------+--------------------------------+</p><p>| abcdefg | abcdefg |</p><p>+---------+--------------------------------+</p><p>1 row in set (0.01 sec)</p><p>Portanto, o 97º caractere do conjunto de caracteres ASCII é a letra a. Apesar de os</p><p>caracteres mostrados nesse exemplo não serem especiais, os seguintes exemplos</p><p>mostram a localização dos caracteres acentuados junto com outros caracteres</p><p>especiais, como símbolos de moedas:</p><p>Estou usando o conjunto de caracteres latin1 nos exemplos desta seção. Se a sua sessão</p><p>estiver con�gurada para um conjunto de caracteres diferente, você verá um conjunto de</p><p>caracteres diferente do que é mostrado aqui. Os mesmos conceitos são aplicáveis, mas</p><p>você precisará se familiarizar com o layout de seu conjunto de caracteres para localizar</p><p>caracteres especí�cos.</p><p>Construir strings caractere por caractere pode ser bem chato, especialmente quando</p><p>apenas alguns dos caracteres da string são especiais. Felizmente, você pode usar a</p><p>função concat() para concatenar strings individuais, algumas das quais você pode</p><p>digitar normalmente, enquanto as demais você pode gerar por meio da função</p><p>char(). Por exemplo, o comando a seguir cria a frase danke schön usando as funções</p><p>concat() e char(): mysql> SELECT CONCAT('danke sch', CHAR(148),</p><p>'n'); +-------------------------------------+</p><p>| CONCAT('danke sch', CHAR(148), 'n') |</p><p>+-------------------------------------+</p><p>| danke schön |</p><p>+-------------------------------------+</p><p>1 row in set (0.00 sec)</p><p>Os usuários do Oracle Database podem usar o operador de concatenação ( || ) em vez</p><p>da função concat(), como em: SELECT 'danke sch' || CHR(148) || 'n' FROM dual; O</p><p>SQL Server não inclui uma função concat(), então você precisará usar o operador de</p><p>concatenação ( + ), como em: SELECT 'danke sch' + CHAR(148) + 'n'</p><p>Se você tiver um caractere e precisar encontrar seu equivalente ASCII, você pode usar</p><p>a função ascii(), que pega o caractere mais à esquerda da string e retorna um</p><p>número: mysql> SELECT ASCII('ö');</p><p>+------------+</p><p>| ASCII('ö') |</p><p>+------------+</p><p>| 148 |</p><p>+------------+</p><p>1 row in set (0.00 sec)</p><p>Com o uso das funções char(), ascii() e concat() (ou dos operadores de</p><p>concatenação), você conseguirá trabalhar com qualquer língua de origem latina,</p><p>mesmo se estiver usando um teclado que não inclua caracteres acentuados ou</p><p>especiais.</p><p>Manipulação de strings</p><p>Todos os servidores de banco de dados incluem várias funções de manipulação de</p><p>strings. Esta seção explora dois tipos de funções de string: aquelas que retornam</p><p>números e aquelas que retornam strings. No entanto, antes de começar, vamos</p><p>recon�gurar os dados da tabela string_tbl para o seguinte: mysql> DELETE FROM</p><p>string_tbl;</p><p>Query OK, 1 row affected (0.02 sec)</p><p>mysql> INSERT INTO string_tbl (char_fld, vchar_fld, text_fld) -> VALUES</p><p>('This string is 28 characters',</p><p>-> 'This string is 28 characters',</p><p>-> 'This string is 28 characters');</p><p>Query OK, 1 row affected (0.00 sec)</p><p>Funções de string que retornam números</p><p>Das funções de string que retornam números, uma das mais comumente usadas é a</p><p>função length(), que retorna o número de caracteres da string (usuários do SQL</p><p>Server deverão usar a função len()). A seguinte consulta aplica a função length()</p><p>em cada coluna da tabela string_tbl:</p><p>Embora o tamanho das colunas de tipo varchar e text seja o esperado, você poderia</p><p>estar esperando um tamanho de 30 para a coluna char, pois eu disse anteriormente</p><p>que strings armazenadas em colunas de tipo char são preenchidas à direita com</p><p>espaços. No entanto, o servidor MySQL remove os espaços extras dos dados de tipo</p><p>char que são recuperados, então você verá o mesmo resultado em todas as funções</p><p>de string, independentemente do tipo de coluna em que uma string é armazenada.</p><p>Além de encontrar o tamanho de uma string, você pode precisar encontrar a</p><p>localização de uma substring dentro de uma string. Por exemplo, se você quiser</p><p>encontrar a posição em que a string 'characters' aparece na coluna vchar_fld, você</p><p>pode usar a função position(), como demonstrado a seguir: mysql> SELECT</p><p>POSITION('characters' IN vchar_fld) -> FROM string_tbl;</p><p>+-------------------------------------+</p><p>| POSITION('characters' IN vchar_fld) |</p><p>+-------------------------------------+</p><p>| 19 |</p><p>+-------------------------------------+</p><p>1 row in set (0.12 sec)</p><p>Se a substring não for encontrada, a função position() retorna 0.</p><p>Para aqueles que programam em linguagens como C ou C++, em que o primeiro</p><p>elemento de um array aparece na posição 0, lembre-se de que, ao trabalhar com bancos</p><p>de dados, o primeiro caractere de uma string aparece na posição 1. Um valor de retorno 0</p><p>da função position() indica que a substring não foi encontrada, e não que a substring</p><p>foi encontrada na primeira posição da string.</p><p>Se quiser começar sua pesquisa a partir de um caractere que não seja o primeiro de</p><p>sua string-alvo, você precisará da função locate(), que é similar à função</p><p>position(), exceto pelo fato de que ela tem um terceiro parâmetro opcional, usado</p><p>para de�nir a posição inicial da pesquisa. A função locate() é proprietária,</p><p>enquanto a função position() faz parte do padrão SQL:2003. Aqui está um exemplo</p><p>que requisita a posição da string 'is', iniciando a pesquisa no quinto caractere da</p><p>coluna vchar_fld: mysql> SELECT LOCATE('is', vchar_fld, 5) -> FROM</p><p>string_tbl;</p><p>+----------------------------+</p><p>| LOCATE('is', vchar_fld, 5) |</p><p>+----------------------------+</p><p>| 13 |</p><p>+----------------------------+</p><p>1 row in set (0.02 sec)</p><p>O Oracle Database não inclui as funções position() e locate(), mas inclui a função</p><p>instr(), que imita a função position() quando recebe dois argumentos e imita a</p><p>função locate() quando recebe três. O SQL Server também não tem as funções</p><p>position() e locate(), mas inclui a função charindx(), que também aceita dois ou</p><p>três argumentos, de forma similar à função instr() do Oracle.</p><p>Outra função que recebe strings como argumentos e que retorna números é a</p><p>função de comparação de strings strcmp(). Strcmp(), que é implementada apenas</p><p>pelo MySQL e não tem análogas no</p>
<p>Oracle Database e no SQL Server, recebe duas</p><p>strings como argumentos e retorna um dos seguintes valores: • -1, caso a primeira</p><p>string apareça antes da segunda em ordem de classi�cação.</p><p>• 0, caso as strings sejam idênticas.</p><p>• 1, caso a primeira string apareça após a segunda em ordem de classi�cação.</p><p>Para ilustrar como ela funciona, primeiro mostrarei a ordem de classi�cação de</p><p>cinco strings usando uma consulta e, então, mostrarei como as strings comparam</p><p>umas com as outras usando strcmp(). Aqui estão as cinco strings que inseri na</p><p>tabela string_tbl: mysql> DELETE FROM string_tbl;</p><p>Query OK, 1 row affected (0.00 sec)</p><p>mysql> INSERT INTO string_tbl(vchar_fld) VALUES ('abcd'); Query OK, 1 row</p><p>affected (0.03 sec)</p><p>mysql> INSERT INTO string_tbl(vchar_fld) VALUES ('xyz'); Query OK, 1 row</p><p>affected (0.00 sec)</p><p>mysql> INSERT INTO string_tbl(vchar_fld) VALUES ('QRSTUV'); Query OK, 1 row</p><p>affected (0.00 sec)</p><p>mysql> INSERT INTO string_tbl(vchar_fld) VALUES ('qrstuv'); Query OK, 1 row</p><p>affected (0.00 sec)</p><p>mysql> INSERT INTO string_tbl(vchar_fld) VALUES ('12345'); Query OK, 1 row</p><p>affected (0.00 sec)</p><p>Aqui estão as cinco strings em ordem de classi�cação:</p><p>mysql> SELECT vchar_fld</p><p>-> FROM string_tbl</p><p>-> ORDER BY vchar_fld;</p><p>+-----------+</p><p>| vchar_fld |</p><p>+-----------+</p><p>| 12345 |</p><p>| abcd |</p><p>| QRSTUV |</p><p>| qrstuv |</p><p>| xyz |</p><p>+-----------+</p><p>5 rows in set (0.00 sec)</p><p>A próxima consulta faz seis comparações entre as cinco strings:</p><p>A primeira comparação retorna 0, que é o esperado, já que comparei a string com ela</p><p>mesma. A quarta comparação também retorna 0, o que pode ser um pouco</p><p>surpreendente, pois as strings são compostas pelas mesmas letras, mas uma está</p><p>toda em maiúsculas e a outra está em minúsculas. A razão desse resultado é que a</p><p>função strcmp() do MySQL não distingue maiúsculas de minúsculas, fato este que</p><p>deve ser lembrado ao se usar a função. As outras comparações retornam -1 ou 1,</p><p>dependendo de a primeira string aparecer antes ou depois da segunda string na</p><p>ordem de classi�cação. Por exemplo, strcmp('abcd','xyz') retorna -1, pois a string</p><p>'abcd' vem antes de 'xyz'.</p><p>Junto com a função strcmp(), o MySQL também permite a você usar os operadores</p><p>like e regexp para comparar strings na cláusula select. Tais comparações retornam</p><p>1 (para true) e 0 (para false). Portanto, esses operadores permitem construir</p><p>expressões que retornam um número, de forma semelhante às funções descritas</p><p>nesta seção. Aqui está um exemplo que usa o operador like: mysql> SELECT</p><p>name, name LIKE '%ns' ends_in_ns -> FROM department;</p><p>+----------------+------------+</p><p>| name | ends_in_ns |</p><p>+----------------+------------+</p><p>| Operations | 1 |</p><p>| Loans | 1 |</p><p>| Administration | 0 |</p><p>+----------------+------------+</p><p>3 rows in set (0.25 sec)</p><p>Esse exemplo recuperou todos os nomes de departamentos, junto com uma</p><p>expressão que retorna 1 caso o nome do departamento termine em “ns” ou 0 em</p><p>caso contrário. Se quiser realizar comparações de padrões mais complexos, você</p><p>pode usar o operador regexp, como demonstrado a seguir:</p><p>A quarta coluna retorna 1 caso o valor armazenado na coluna fed_id corresponda</p><p>ao formato do número de documento de identidade.</p><p>Usuários do SQL Server e do Oracle Database podem obter resultados semelhantes</p><p>construindo expressões case, as quais descrevo de forma detalhada no capítulo 11.</p><p>Funções de string que retornam strings</p><p>Em alguns casos, você precisará modi�car strings existentes, seja extraindo parte</p><p>dela ou adicionando mais texto à string. Todos os servidores de banco de dados</p><p>incluem várias funções que auxiliam nessas tarefas. Antes de iniciar, vou</p><p>recon�gurar mais uma vez os dados da tabela string_tbl: mysql> DELETE FROM</p><p>string_tbl;</p><p>Query OK, 5 rows affected (0.00 sec)</p><p>mysql> INSERT INTO string_tbl (text_fld) -> VALUES ('This string was 29</p><p>characters');</p><p>Query OK, 1 row affected (0.01 sec)</p><p>Anteriormente no capítulo, eu demonstrei o uso da função concat() para auxiliar na</p><p>construção de palavras que incluam caracteres especiais. A função concat() é útil em</p><p>muitas outras situações, incluindo quando você precisa anexar caracteres adicionais</p><p>em uma string armazenada. Por exemplo, o seguinte exemplo modi�ca a string</p><p>armazenada na coluna text_fld anexando uma frase adicional no �nal: mysql></p><p>UPDATE string_tbl</p><p>-> SET text_fld = CONCAT(text_fld, ', but now it is longer'); Query OK, 1</p><p>row affected (0.03 sec)</p><p>Rows matched: 1 Changed: 1 Warnings: 0</p><p>O conteúdo da coluna text_fld agora �cou assim:</p><p>Então, como todas as funções que retornam uma string, você pode usar concat()</p><p>para substituir dados armazenados em uma coluna de caracteres.</p><p>Outro uso comum da função concat() é construir uma string a partir de fragmentos</p><p>individuais de dados. Por exemplo, a seguinte consulta gera uma string narrativa</p><p>para cada caixa do banco:</p><p>A função concat() pode manipular qualquer expressão que retorne uma string, e até</p><p>mesmo converterá números e datas em formato string, como sugere a coluna de tipo</p><p>date (start_date) usada como argumento. Apesar de o Oracle incluir a função</p><p>concat(), ela aceita apenas dois argumentos do tipo string, de modo que a consulta</p><p>anterior não funcionará no Oracle. Em vez disso, você precisaria usar o operador de</p><p>concatenação ( || ) no lugar da função concat(), como em: SELECT fname || ' ' ||</p><p>lname || ' has been a ' ||</p><p>title || ' since ' || start_date emp_narrative</p><p>FROM employee</p><p>WHERE title = 'Teller' OR title = 'Head Teller';</p><p>O SQL Server não inclui uma função concat(), portanto, você precisará usar a</p><p>mesma abordagem da consulta anterior, exceto pelo fato de que você usaria o</p><p>operador de concatenação do SQL Server ( + ) no lugar de ||.</p><p>Apesar de concat() ser útil para adicionar caracteres no início ou no �nal de strings,</p><p>você também pode precisar adicionar ou substituir caracteres no meio de uma</p><p>string. Os três servidores de banco de dados fornecem funções para esse propósito,</p><p>mas são todas diferentes entre si, então demonstrarei a função do MySQL e, em</p><p>seguida, mostrarei as funções dos outros dois servidores.</p><p>O MySQL inclui a função insert(), que recebe quatro argumentos: a string original,</p><p>a posição de início da inserção, o número de caracteres que serão substituídos e a</p><p>string de substituição. Dependendo do valor passado ao terceiro argumento, a</p><p>função pode ser usada para inserir ou substituir caracteres de uma string. Com um</p><p>valor 0 no terceiro argumento, a string de substituição é inserida e qualquer</p><p>caractere subsequente é empurrado para a direita, como em: mysql> SELECT</p><p>INSERT('goodbye world', 9, 0, 'cruel ') string; +-----------------</p><p>----+</p><p>| string |</p><p>+---------------------+</p><p>| goodbye cruel world |</p><p>+---------------------+</p><p>1 row in set (0.00 sec)</p><p>Nesse exemplo, todos os caracteres a partir da posição 9 são empurrados para a</p><p>direita, e a string 'cruel' é inserida. Se o terceiro argumento for maior que zero, o</p><p>número de caracteres especi�cado nele será substituído pela string de substituição,</p><p>como em: mysql> SELECT INSERT('goodbye world', 1, 7, 'hello')</p><p>string; +-------------+</p><p>| string |</p><p>+-------------+</p><p>| hello world |</p><p>+-------------+</p><p>1 row in set (0.00 sec)</p><p>Nesse exemplo, os primeiros sete caracteres são substituídos pela string 'hello'. O</p><p>Oracle Database não fornece uma única função que tenha a �exibilidade da função</p><p>insert() do MySQL, mas o Oracle fornece a função replace(), que é útil para</p><p>substituir uma substring por outra. Aqui está o exemplo anterior refeito para usar</p><p>replace(): SELECT REPLACE('goodbye world', 'goodbye', 'hello')</p><p>FROM dual;</p><p>Todas as instâncias da string 'goodbye' serão substituídas pela string 'hello',</p><p>resultando na string ‘hello world’. A função replace() substituirá cada instância da</p><p>string de pesquisa pela string de substituição, então você precisará tomar cuidado</p><p>para não terminar com mais substituições do que o esperado.</p><p>O SQL Server também tem uma função replace() com as mesmas funcionalidades</p><p>da função do Oracle, mas o SQL Server também inclui uma função chamada</p><p>stuff() com funcionalidade similar à função insert() do MySQL. Aqui está um</p><p>exemplo: SELECT STUFF('hello world', 1,</p>
<p>5, 'goodbye cruel')</p><p>Quando executada, cinco caracteres são removidos a partir da posição 1 e, então, a</p><p>string 'goodbye cruel' é inserida na posição inicial, resultando na string 'goodbye</p><p>cruel world'.</p><p>Além de inserir caracteres em uma string, você pode precisar extrair uma substring</p><p>de uma string. Para isso, os três servidores incluem a função substring() (apesar de</p><p>a versão do Oracle se chamar substr()), que extrai um número especí�co de</p><p>caracteres a partir de uma posição especí�ca. O exemplo a seguir extrai cinco</p><p>caracteres de uma string, iniciando na nona posição: mysql> SELECT</p><p>SUBSTRING('goodbye cruel world', 9, 5); +---------------------------------</p><p>-------+</p><p>| SUBSTRING('goodbye cruel world', 9, 5) |</p><p>+----------------------------------------+</p><p>| cruel |</p><p>+----------------------------------------+</p><p>1 row in set (0.00 sec)</p><p>Além das funções demonstradas aqui, os três servidores incluem muitas outras</p><p>funções nativas para manipular dados de string. Apesar de muitas delas serem</p><p>projetadas para propósitos especí�cos, como gerar uma string equivalente a</p><p>números octais ou hexadecimais, há também muitas outras funções de propósito</p><p>geral, como as funções que adicionam ou removem espaços adicionais. Para obter</p><p>mais informações, consulte o guia de referência de seu servidor SQL ou um guia de</p><p>referência genérico de SQL, como SQL in a Nutshell (O’Reilly).</p><p>Trabalhando com dados numéricos</p><p>Diferentemente dos dados de string (e dos dados temporais, como você verá em</p><p>breve), a geração de dados numéricos é relativamente simples. Você pode digitar um</p><p>número, recuperá-lo de outra coluna ou gerá-lo por meio de cálculos. Todas as</p><p>operações aritméticas básicas (+, -, *, /) estão disponíveis para realizar cálculos, e</p><p>parênteses podem ser usados para ditar precedências, como em: mysql> SELECT</p><p>(37 * 59) / (78 - (8 * 6)); +----------------------------+</p><p>| (37 * 59) / (78 - (8 * 6)) |</p><p>+----------------------------+</p><p>| 72.77 |</p><p>+----------------------------+</p><p>1 row in set (0.00 sec)</p><p>Como mencionei no capítulo 2, a principal preocupação ao armazenar dados</p><p>numéricos é que os números podem ser arredondados caso sejam maiores do que o</p><p>tamanho especi�cado em uma coluna numérica. Por exemplo, o número 9.96 pode</p><p>ser arredondado para 10.0 se for armazenado em uma coluna de�nida como float</p><p>(3,1).</p><p>Realizando funções aritméticas</p><p>A maioria das funções numéricas nativas é usada para propósitos aritméticos</p><p>especí�cos, como determinar a raiz quadrada de um número. A tabela 7.1 lista</p><p>algumas das funções numéricas que recebem um único argumento numérico e que</p><p>retornam um número.</p><p>Tabela 7.1 – Funções numéricas com um argumento</p><p>Nome da função Descrição</p><p>Acos(x) Calcula o arco-cosseno de x</p><p>Asin(x) Calcula o arco-seno de x</p><p>Atan(x) Calcula o arco-tangente de x</p><p>Cos(x) Calcula o cosseno de x</p><p>Cot(x) Calcula a cotangente de x</p><p>Exp(x) Calcula ex</p><p>Ln(x) Calcula o logaritmo natural de x</p><p>Sin(x) Calcula o seno de x</p><p>Sqrt(x) Calcula a raiz quadrada de x</p><p>Tan(x) Calcula a tangente de x</p><p>Essas funções realizam tarefas bem especí�cas, e vou me abster de mostrar exemplos</p><p>delas (se você não reconhecer uma função pelo nome ou pela descrição,</p><p>provavelmente não precisará dela). Outras funções numéricas usadas em cálculos,</p><p>no entanto, são um pouco mais �exíveis e merecem alguma explicação.</p><p>Por exemplo, o operador de módulo, que calcula o resto de um número dividido por</p><p>outro número, é implementado no MySQL e, no Oracle Database, por meio da</p><p>função mod(). O exemplo a seguir calcula o resto de 10 dividido por 4: mysql></p><p>SELECT MOD(10,4);</p><p>+-----------+</p><p>| MOD(10,4) |</p><p>+-----------+</p><p>| 2 |</p><p>+-----------+</p><p>1 row in set (0.02 sec)</p><p>Apesar de a função mod() ser usada tipicamente com argumentos inteiros, no</p><p>MySQL você também pode usar números reais, como em: mysql> SELECT</p><p>MOD(22.75, 5);</p><p>+---------------+</p><p>| MOD(22.75, 5) |</p><p>+---------------+</p><p>| 2.75 |</p><p>+---------------+</p><p>1 row in set (0.02 sec)</p><p>O SQL Server não tem uma função mod. Em vez disso, o operador % é usado para</p><p>encontrar o resto de uma divisão. A expressão 10 % 4, portanto, resulta no valor 2.</p><p>Outra função numérica que recebe dois argumentos é a função pow() (ou power(),</p><p>caso você esteja usando o Oracle Database ou o SQL Server), que retorna um</p><p>número elevado à potência de um segundo número, como em: mysql> SELECT</p><p>POW(2,8);</p><p>+----------+</p><p>| POW(2,8) |</p><p>+----------+</p><p>| 256 |</p><p>+----------+</p><p>1 row in set (0.03 sec)</p><p>Então, pow(2,8) é o equivalente em MySQL a especi�car 28. Como a memória do</p><p>computador é alocada em blocos de 2x bytes, a função pow() pode ser uma maneira</p><p>útil de determinar o número exato de bytes em uma certa quantidade de memória:</p><p>Não sei quanto a você, mas acho mais fácil lembrar que um gigabyte é 230 bytes do</p><p>que me lembrar do número 1.073.741.824.</p><p>Controlando a precisão numérica</p><p>Ao trabalhar com números de ponto �utuante, você talvez não queira interagir com</p><p>ou exibir um número com a precisão total. Por exemplo, você pode armazenar dados</p><p>de transações monetárias com uma precisão de seis casas decimais, mas pode querer</p><p>arredondar para o centésimo mais próximo por uma questão de formato de</p><p>apresentação. Quatro funções são úteis na limitação da precisão de números de</p><p>ponto �utuante: ceil(), floor(), round() e truncate(). Os três servidores incluem</p><p>essas funções, apesar de o Oracle Database incluir trunc() no lugar de truncate() e</p><p>do SQL Server incluir ceiling() no lugar de ceil().</p><p>As funções ceil() e floor() são usadas para arredondar um número para cima ou</p><p>para baixo, respectivamente, como demonstrado a seguir: mysql> SELECT</p><p>CEIL(72.445), FLOOR(72.445); +--------------+---------------+</p><p>| CEIL(72.445) | FLOOR(72.445) |</p><p>+--------------+---------------+</p><p>| 73 | 72 |</p><p>+--------------+---------------+</p><p>1 row in set (0.06 sec)</p><p>Portanto, qualquer número entre 72 e 73 seria avaliado como 73 pela função ceil()</p><p>e como 72 pela função floor(). Lembre-se que ceil() arredonda para cima, mesmo</p><p>que a parte decimal seja bem pequena, e que floor() arredonda para baixo, mesma</p><p>que a parte decimal seja signi�cativa, como em: mysql> SELECT</p><p>CEIL(72.000000001), FLOOR(72.999999999); +--------------------+---------</p><p>------------+</p><p>| CEIL(72.000000001) | FLOOR(72.999999999) |</p><p>+--------------------+---------------------+</p><p>| 73 | 72 |</p><p>+--------------------+---------------------+</p><p>1 row in set (0.00 sec)</p><p>Se isso for rígido demais para sua aplicação, você pode usar a função round() para</p><p>arredondar para cima ou para baixo a partir do ponto médio entre dois números</p><p>inteiros, como em:</p><p>Ao usar round(), qualquer número cuja parte decimal esteja bem no meio de dois</p><p>inteiros ou acima disso será arredondado para cima, enquanto o número será</p><p>arredondado para baixo caso a parte decimal seja menor do que o valor médio entre</p><p>dois inteiros.</p><p>Na maior parte do tempo, você desejará manter pelo menos uma parte da porção</p><p>decimal de um número, em vez de arredondá-lo para o inteiro mais próximo. A</p><p>função round() permite um segundo argumento opcional que especi�ca em qual</p><p>casa após o ponto decimal será feito o arredondamento. O próximo exemplo mostra</p><p>como você pode usar o segundo argumento para arredondar o número 72,0909</p><p>usando uma, duas e três casas decimais:</p><p>Tal como a função round(), a função truncate() permite um segundo argumento</p><p>opcional que especi�ca o número de dígitos à direita do decimal, mas truncate()</p><p>simplesmente descarta os dígitos indesejados sem arredondá-los.</p><p>O SQL Server não inclui uma função truncate(). Em vez disso, a função round()</p><p>permite um terceiro argumento opcional que, se estiver presente e for diferente de zero,</p><p>trunca o número em vez de arredondá-lo.</p><p>Tanto truncate() quanto round() também permitem um valor negativo no segundo</p><p>argumento, signi�cando que números à esquerda do ponto decimal serão truncados</p><p>ou arredondados. A princípio isso pode parecer uma coisa estranha, mas existem</p><p>aplicações válidas para essa funcionalidade. Por exemplo, talvez você venda um</p><p>produto que só pode ser vendido em unidades de 10. Se um cliente encomendar 17</p><p>unidades, você pode optar por um dos seguintes métodos</p>
<p>Agradecimentos</p><p>Gostaria de agradecer a minha editora, Mary Treseler, por ajudar a tornar essa</p><p>segunda edição uma realidade, e a Kevin Kline, Roy Owens, Richard Sonen e</p><p>Matthew Russell, que foram muito gentis ao revisar o livro para mim durante os</p><p>feriados de �m de ano. Também gostaria de agradecer aos leitores que</p><p>atenciosamente enviaram questões, comentários e correções. Por �m, agradeço a</p><p>minha esposa, Nancy, e a minhas �lhas, Michelle e Nicole, por seu encorajamento e</p><p>inspiração.</p><p>Sobre o autor</p><p>Alan Beaulieu tem projetado, construído e implementado aplicações de banco de</p><p>dados customizadas há cerca de 15 anos. Atualmente possui sua própria empresa de</p><p>consultoria que se especializou em projetar bancos de dados Oracle e dar suporte a</p><p>serviços nos campos de �nanças e telecomunicações. Ao construir grandes bancos</p><p>de dados para os ambientes OLTP e OLAP, Alan utilizou funcionalidades do Oracle</p><p>como Consultas Paralelas, Particionamento, e Servidores Paralelos. Alan é bacharel</p><p>em Pesquisas Operacionais pela Escola de Engenharia da Universidade de Cornell.</p><p>Ele vive em Massachusetts com sua esposa e duas �lhas, e pode ser encontrado no</p><p>endereço albeau_mosql@yahoo.com.</p><p>CAPÍTULO 1</p><p>Uma breve introdução</p><p>Antes de arregaçarmos as mangas e começarmos a trabalhar, pode ser útil apresentar</p><p>alguns conceitos básicos de banco de dados, além de aprender um pouco mais sobre</p><p>a história do armazenamento e da recuperação de dados em computadores.</p><p>Introdução aos bancos de dados</p><p>Um banco de dados é nada além de um conjunto de informações relacionadas. Uma</p><p>lista telefônica, por exemplo, é um banco de dados dos nomes, números de telefones</p><p>e endereços de todas as pessoas que moram em uma região especí�ca. Apesar de a</p><p>lista telefônica ser um banco de dados presente em todos os lugares e</p><p>frequentemente utilizado, ela apresenta os seguintes problemas:</p><p>• Encontrar o número de telefone de uma pessoa pode demorar muito tempo,</p><p>principalmente se a lista telefônica contiver um número muito grande de</p><p>entradas.</p><p>• Uma lista telefônica é indexada apenas pelo sobrenome e nome do titular da</p><p>conta telefônica – portanto, pesquisar o nome de todas as pessoas que morem</p><p>em um endereço em particular, apesar de ser teoricamente possível, não é um</p><p>exemplo de uso prático desse banco de dados.</p><p>• A partir do momento em que a lista telefônica é impressa, as informações</p><p>tornam-se cada vez menos precisas, pois as pessoas mudam-se de/para uma</p><p>região, mudam seus números de telefone ou mudam-se para outros endereços</p><p>dentro de uma mesma região.</p><p>Os mesmos pontos fracos atribuídos à lista telefônica também podem ser aplicados</p><p>a qualquer sistema manual de armazenamento de dados, tais como os registros de</p><p>pacientes armazenados em um arquivo. Devido à natureza inconveniente dos bancos</p><p>de dados em papel, algumas das primeiras aplicações desenvolvidas para os</p><p>computadores foram sistemas de banco de dados, que são mecanismos de</p><p>armazenamento e recuperação de dados computadorizados.</p><p>Por armazenar dados eletronicamente, em vez de armazenar em papel, um sistema</p><p>de banco de dados é capaz de recuperar dados mais rapidamente, indexar dados de</p><p>diversas maneiras e fornecer informações atualizadas para sua comunidade de</p><p>usuários.</p><p>Os primeiros sistemas de banco de dados gerenciavam dados armazenados em �tas</p><p>magnéticas. Como normalmente existiam bem mais �tas magnéticas do que leitores</p><p>de �tas, os técnicos tinham a tarefa de carregar e descarregar �tas conforme os dados</p><p>eram solicitados. Como os computadores daquela época continham pouca</p><p>memória, múltiplas requisições dos mesmos dados geralmente exigiam que os</p><p>dados fossem lidos da �ta várias vezes. Apesar desses sistemas de banco de dados</p><p>representarem uma melhoria signi�cativa em relação aos bancos de dados em papel,</p><p>eles são um eco distante daquilo que é possível realizar com a tecnologia atual.</p><p>(Sistemas modernos de banco de dados podem gerenciar terabytes de dados</p><p>espalhados em inúmeros dispositivos de armazenamento de acesso rápido,</p><p>mantendo dezenas de gigabytes desses dados em memória de alta velocidade, mas</p><p>isso está um pouco além da discussão atual.)</p><p>Sistemas de banco de dados não-relacional</p><p>Esta seção contém informações históricas sobre sistemas de banco de dados pré-</p><p>relacionais. Para os leitores ávidos por mergulhar direto na linguagem SQL, sintam-se à</p><p>vontade para pular algumas páginas até a próxima seção.</p><p>Ao longo das primeiras décadas dos sistemas de banco de dados computadorizados,</p><p>dados foram armazenados e representados aos usuários de diversas maneiras. Em</p><p>um sistema de banco de dados hierárquico, por exemplo, os dados são representados</p><p>como uma ou mais estruturas em árvore. A �gura 1.1 mostra como os dados</p><p>bancários relacionados às contas bancárias de George Blake e Sue Smith poderiam</p><p>ser representados por meio de estruturas em árvore.</p><p>Tanto George quanto Sue possuem sua própria árvore contendo suas contas e as</p><p>transações dessas mesmas contas. O sistema de banco de dados hierárquico fornece</p><p>ferramentas para localizar uma árvore de um cliente em particular e, então, percorrê-</p><p>la para encontrar as contas e/ou transações desejadas. Cada nó da árvore pode ter</p><p>nenhum ou um nó-pai, e nenhum, um ou mais nós-�lhos. Essa con�guração é</p><p>conhecida como uma hierarquia de pai único.</p><p>Outra abordagem comum, denominada sistema de banco de dados em rede, expõe</p><p>conjuntos de registros e conjuntos de ligações que de�nem relacionamentos entre</p><p>diferentes registros. A �gura 1.2 mostra como as contas bancárias de George e Sue,</p><p>previamente apresentadas, seriam representadas em tal sistema.</p><p>Figura 1.1 – Exibição hierárquica dos dados de contas bancárias.</p><p>Figura 1.2 – Exibição em rede dos dados das contas.</p><p>Para encontrar as transações ligadas à conta de aplicações de Sue, você precisaria</p><p>realizar os seguintes passos:</p><p>1. Encontrar o registro de cliente de Sue Smith.</p><p>2. Seguir a conexão do registro de cliente de Sue Smith até sua lista de contas.</p><p>3. Percorrer a cadeia de contas até encontrar a conta de aplicações.</p><p>4. Seguir a conexão do registro de aplicações até sua lista de transações.</p><p>Uma característica interessante dos sistemas de banco de dados em rede é</p><p>demonstrada pelo conjunto de registros de produtos na extrema direita da �gura 1.2.</p><p>Perceba que cada registro de produto (conta-corrente, conta-poupança etc.) aponta</p><p>para uma lista de registros de conta que são daquele tipo de produto. Registros de</p><p>conta, portanto, podem ser acessados de vários lugares (tanto dos registros de cliente</p><p>quanto dos registros de produtos), permitindo que um banco de dados em rede aja</p><p>como uma hierarquia de múltiplos pais.</p><p>Tanto o sistema hierárquico quanto o sistema em rede de banco de dados estão</p><p>ativos e em bom funcionamento atualmente, apesar de normalmente existirem</p><p>apenas no mundo dos mainframes. Além disso, sistemas de banco de dados</p><p>hierárquicos desfrutaram de um renascimento no reino dos serviços de diretórios,</p><p>tais como o Active Directory, da Microsoft, e o Red Hat Directory Server, bem como</p><p>por meio da Extensible Markup Language (XML).</p><p>No entanto, iniciando na década de 1970, uma nova forma de representar dados</p><p>começou a �ncar suas raízes – uma forma que era mais rigorosa e, ao mesmo tempo,</p><p>fácil de entender e de implementar.</p><p>O modelo relacional</p><p>Em 1970, o Dr. E. F. Codd, do laboratório de pesquisas da IBM, publicou um artigo</p><p>intitulado “A Relational Model of Data for Large Shared Data Banks” (“Um modelo</p><p>relacional de dados para bancos de dados volumosos compartilhados”) que</p><p>propunha a representação dos dados como conjuntos de tabelas. Em vez de usar</p><p>ponteiros para navegar entre entidades relacionadas, dados redundantes seriam</p><p>usados para conectar registros em diferentes tabelas. A �gura 1.3 mostra como as</p><p>informações das contas de George e Sue seriam representadas nesse contexto.</p><p>Há quatro tabelas na �gura 1.3 representando as quatro entidades discutidas até</p><p>aqui: customer (cliente), product (produto), account (conta bancária) e transaction</p><p>(transação). Olhando ao longo do topo da tabela customer (cliente),</p>
<p>para modi�car a</p><p>quantidade do pedido do cliente: mysql> SELECT ROUND(17, -1),</p><p>TRUNCATE(17, -1); +---------------+------------------+</p><p>| ROUND(17, -1) | TRUNCATE(17, -1) |</p><p>+---------------+------------------+</p><p>| 20 | 10 |</p><p>+---------------+------------------+</p><p>1 row in set (0.00 sec)</p><p>Se o produto em questão for percevejos, pode não fazer muita diferença no resultado</p><p>�nal dizer que você vendeu 10 ou 20 percevejos quando, na verdade, foram</p><p>requisitados 17. No entanto, se você estiver vendendo relógios Rolex, seu negócio</p><p>renderá mais com o arredondamento do que com a truncagem.</p><p>Tratando dados sinalizados</p><p>Se você estiver trabalhando com colunas numéricas que permitam valores negativos</p><p>(no capítulo 2, mostrei como uma coluna numérica pode ser rotulada como não-</p><p>sinalizada, signi�cando que apenas números positivos são permitidos), várias</p><p>funções numéricas poderão ser úteis. Digamos, por exemplo, que você tenha que</p><p>gerar um relatório mostrando a situação atual de cada conta bancária. A consulta a</p><p>seguir retorna três colunas úteis para gerar o relatório:</p><p>A segunda coluna usa a função sign() para retornar -1 caso o saldo da conta seja</p><p>negativo, 0 se o saldo for zero ou 1 se o saldo da conta for positivo. A terceira coluna</p><p>retorna o valor absoluto do saldo da conta por meio da função abs().</p><p>Trabalhando com dados temporais</p><p>Dos três tipos de dados discutidos neste capítulo (caractere, numérico e temporal),</p><p>os dados temporais são os mais complexos quando se fala em geração e</p><p>manipulação de dados. Parte da complexidade dos dados temporais é causada pela</p><p>miríade de formas pelas quais uma única data e hora pode ser descrita. Por</p><p>exemplo, a data em que escrevi este parágrafo pode ser descrita em todas as formas</p><p>mostradas a seguir: • Quarta-feira, 17 de setembro de 2008</p><p>• 9/17/2008 2:14:56 P.M. EST</p><p>• 9/17/2008 19:14:56 GMT</p><p>• 2612008 (formato juliano)</p><p>• Data estelar [−4] 85712.03 14:14:56 (formato Jornada nas Estrelas)</p><p>Enquanto algumas dessas diferenças são meramente uma questão de formatação, a</p><p>maior parte da complexidade tem a ver com seu referencial, assunto que</p><p>exploraremos na próxima seção.</p><p>Trabalhando com fusos horários</p><p>Como as pessoas em todo o mundo preferem que o meio-dia coincida</p><p>aproximadamente com o sol a pino, nunca houve uma tentativa séria de forçar todo</p><p>mundo a usar um relógio universal. Em vez disso, o mundo foi fatiado em 24 seções</p><p>imaginárias, chamadas fusos horários (ou zonas horárias): dentro de um fuso</p><p>horário especí�co, todos concordam com o horário atual, enquanto pessoas em</p><p>fusos horários diferentes não concordarão. Apesar de isso parecer bem simples,</p><p>algumas regiões geográ�cas adiantam seus horários em uma hora durante uma</p><p>parte do ano (implementando o que é conhecido com horário de verão), enquanto</p><p>outras não o fazem. Assim, a diferença de horário entre dois pontos da Terra pode</p><p>ser de quatro horas durante uma parte do ano e de cinco em outra parte do ano.</p><p>Mesmo com um único fuso horário, diferentes regiões podem ou não aderir ao</p><p>horário de verão, fazendo com que relógios diferentes em um mesmo fuso horário</p><p>coincidam durante uma parte do ano, mas apresentem diferença de uma hora no</p><p>resto do ano.</p><p>Apesar de a era do computador ter exacerbado essa questão, as pessoas vêm lidando</p><p>com as diferenças de fuso horário desde os primórdios da exploração naval. Para</p><p>garantir um ponto de referência comum no registro de horários, os navegadores do</p><p>século XV acertaram seus relógios com o horário de Greenwich, na Inglaterra. Isso</p><p>�cou conhecido como Greenwich Mean Time (Hora Média de Greenwich), ou GMT.</p><p>Todas as outras zonas horárias podem ser descritas pela diferença no número de</p><p>horas relativa ao GMT – por exemplo, a zona horária da costa leste dos Estados</p><p>Unidos, conhecida como Eastern Standard Time, pode ser descrita como GMT -5:00,</p><p>ou cinco horas mais cedo que o GMT.</p><p>Atualmente, usamos uma variação do GMT chamada Coordinated Universal Time</p><p>(Horário Universal Coordenado) ou UTC, que é baseada em um relógio atômico</p><p>(ou, para ser mais preciso, o tempo médio de 200 relógios atômicos em 50 locais</p><p>diferentes do mundo, o que é conhecido como Horário Universal). Tanto o SQL</p><p>Server quanto o MySQL fornecem funções que retornarão o timestamp UTC atual</p><p>(getutcdate() no SQL Server e utc_timestamp() no MySQL).</p><p>A maioria dos servidores de banco de dados usa como padrão a con�guração de</p><p>zona horária dos servidores em que eles residem e fornecem ferramentas para</p><p>modi�car a zona horária, caso seja necessário. Por exemplo, um banco de dados</p><p>usado para armazenar transações das bolsas de valores de todo o mundo geralmente</p><p>seria con�gurado para usar o horário UTC, enquanto um banco de dados usado</p><p>para armazenar transações de uma loja de varejo em particular poderia usar a zona</p><p>horária do servidor.</p><p>O MySQL mantém duas con�gurações de zonas horárias diferentes: uma zona</p><p>horária global e uma zona horária de sessão, que pode ser diferente para cada</p><p>usuário logado em um banco de dados. Você pode ver ambas as con�gurações por</p><p>meio da seguinte consulta: mysql> SELECT @@global.time_zone,</p><p>@@session.time_zone; +--------------------+---------------------+</p><p>| @@global.time_zone | @@session.time_zone |</p><p>+--------------------+---------------------+</p><p>| SYSTEM | SYSTEM |</p><p>+--------------------+---------------------+</p><p>1 row in set (0.00 sec)</p><p>O valor system indica que o servidor está usando a con�guração de zona horária do</p><p>computador-servidor em que o banco de dados reside.</p><p>Se você estiver sentado na frente de um computador em Zurique, na Suíça, e abrir</p><p>uma sessão pela rede em um servidor MySQL situado em Nova Iorque, você pode</p><p>querer mudar a con�guração de fuso horário de sua sessão, por meio do seguinte</p><p>comando: mysql> SET time_zone = 'Europe/Zurich'; Query OK, 0 rows</p><p>a�ected (0.18 sec)</p><p>Se veri�car as con�gurações de fuso horário novamente, você verá o seguinte:</p><p>mysql> SELECT @@global.time_zone, @@session.time_zone; +-------------------</p><p>-+---------------------+</p><p>| @@global.time_zone | @@session.time_zone |</p><p>+--------------------+---------------------+</p><p>| SYSTEM | Europe/Zurich |</p><p>+--------------------+---------------------+</p><p>1 row in set (0.00 sec)</p><p>Todas as datas exibidas em sua sessão agora se conformarão ao horário de Zurique.</p><p>Usuários do Oracle Database podem alterar a con�guração de zona horária por meio</p><p>do seguinte comando:</p><p>ALTER SESSION TIMEZONE = 'Europe/Zurich'</p><p>Gerando dados temporais</p><p>Você pode gerar dados temporais por meio de qualquer uma dessas maneiras:</p><p>• Copiando dados de uma coluna do tipo date, datetime ou time.</p><p>• Executando uma função nativa que retorne dados do tipo date, datetime ou</p><p>time.</p><p>• Construindo uma representação em string do dado temporal que será avaliado</p><p>pelo servidor.</p><p>Para usar o último método, você precisará entender os vários componentes usados</p><p>na formatação de datas.</p><p>Representações em string de dados temporais</p><p>A tabela 2.5 no capítulo 2 apresentou os componentes de data mais populares. Para</p><p>refrescar sua memória, a tabela 7.2 mostra esses mesmos componentes.</p><p>Carregando dados de zonas horárias no MySQL</p><p>Se estiver usando o servidor MySQL em uma plataforma Windows, você</p><p>precisará carregar manualmente os dados das zonas horárias antes de poder</p><p>con�gurar zonas horárias globais ou de sessão. Para tanto, você deve seguir estes</p><p>passos: 1. Baixe os dados das zonas horárias no endereço</p><p>http://dev.mysql.com/downloads/timezones.html.</p><p>2. Desative seu servidor MySQL.</p><p>3. Extraia os arquivos do arquivo ZIP baixado (em meu caso, o arquivo</p><p>chamava-se timezone-2006p.zip) e coloque-os no diretório de instalação de seu</p><p>MySQL, dentro de datamysql (o caminho completo de minha instalação �cou</p><p>Program FilesMySQL/MySQL Server 5.1datamysql).</p><p>4. Reinicie seu servidor MySQL.</p><p>Para consultar os dados de zonas horárias, mude para o banco de dados mysql</p><p>por meio do comando use mysql e execute a seguinte consulta: mysql> SELECT</p><p>name FROM time_zone_name;</p><p>+----------------------------------+</p><p>| name |</p><p>+----------------------------------+</p><p>| Africa/Abidjan |</p><p>| Africa/Accra |</p><p>| Africa/Addis_Ababa |</p><p>| Africa/Algiers</p>
<p>|</p><p>| Africa/Asmera |</p><p>| Africa/Bamako |</p><p>| Africa/Bangui |</p><p>| Africa/Banjul |</p><p>| Africa/Bissau |</p><p>| Africa/Blantyre |</p><p>| Africa/Brazzaville |</p><p>| Africa/Bujumbura |</p><p>...</p><p>| US/Alaska |</p><p>| US/Aleutian |</p><p>| US/Arizona |</p><p>| US/Central |</p><p>| US/East-Indiana |</p><p>| US/Eastern |</p><p>| US/Hawaii |</p><p>| US/Indiana-Starke |</p><p>| US/Michigan |</p><p>| US/Mountain |</p><p>| US/Pacific |</p><p>| US/Pacific-New |</p><p>| US/Samoa |</p><p>| UTC |</p><p>| W-SU |</p><p>| WET |</p><p>| Zulu |</p><p>+----------------------------------+</p><p>546 rows in set (0.01 sec)</p><p>Para mudar sua con�guração de zona horária, escolha um dos nomes da</p><p>consulta anterior que melhor corresponda à sua localização.</p><p>Tabela 7.2 – Componentes de formato de data</p><p>Componente De�nição Escopo</p><p>AAAA Ano, incluindo século 1000 a 9999</p><p>MM Mês 01 (janeiro) a 12 (dezembro)</p><p>DD Dia 01 a 31</p><p>HH Hora 00 a 23</p><p>HHH Horas (transcorridas) −838 a 838</p><p>MI Minuto 00 a 59</p><p>SS Segundo 00 a 59</p><p>Para construir uma string que o servidor possa interpretar como um tipo date,</p><p>datetime ou time, você precisa agrupar vários componentes na ordem mostrada na</p><p>tabela 7.3.</p><p>Tabela 7.3 – Componentes de data necessários</p><p>Tipo Formato default</p><p>Date AAAA-MM-DD</p><p>Datetime AAAA-MM-DD HH:MI:SS</p><p>Timestamp AAAA-MM-DD HH:MI:SS</p><p>Time HHH:MI:SS</p><p>Então, para popular uma coluna datetime com 17 de setembro de 2008, às 15:30</p><p>horas, você precisará construir a seguinte string: '2008-09-17 15:30:00'</p><p>Se o servidor estiver esperando um valor datetime (por exemplo, ao atualizar uma</p><p>coluna datetime ou ao chamar uma função nativa que aceite um argumento</p><p>datetime), você pode fornecer uma string apropriadamente formatada com os</p><p>componentes de data necessários, e o servidor fará a conversão para você. Por</p><p>exemplo, aqui está uma instrução usada para modi�car a data de uma transação de</p><p>banco: UPDATE transaction</p><p>SET txn_date = '2008-09-17 15:30:00'</p><p>WHERE txn_id = 99999;</p><p>O servidor determina que a string fornecida na cláusula set deve ser um valor</p><p>datetime, já que a string está sendo usada para popular uma coluna datetime.</p><p>Portanto, o servidor tentará converter a string para você, analisando e quebrando a</p><p>string nos seis componentes (ano, mês, dia, hora, minuto, segundo) incluídos no</p><p>formato datetime padrão.</p><p>Conversões de string para data</p><p>Se o servidor não estiver esperando um valor datetime ou se você quiser representar</p><p>o valor datetime usando um formato fora do padrão, então você precisará dizer ao</p><p>servidor para converter a string em um datetime. Por exemplo, aqui está uma</p><p>consulta simples que retorna um valor datetime usando a função cast(): mysql></p><p>SELECT CAST('2008-09-17 15:30:00' AS DATETIME); +-----------------</p><p>------------------------+</p><p>| CAST('2008-09-17 15:30:00' AS DATETIME) |</p><p>+-----------------------------------------+</p><p>| 2008-09-17 15:30:00 |</p><p>+-----------------------------------------+</p><p>1 row in set (0.00 sec)</p><p>Falaremos sobre a função cast() no �nal deste capítulo. Apesar de esse exemplo</p><p>demonstrar como construir valores datetime, a mesma lógica se aplica aos tipos date</p><p>e time também. A seguinte consulta usa a função cast() para gerar um valor date e</p><p>um valor time: mysql> SELECT CAST('2008-09-17' AS DATE)</p><p>date_field, -> CAST('108:17:57' AS TIME) time_field;</p><p>+------------+------------+</p><p>| date_field | time_field |</p><p>+------------+------------+</p><p>| 2008-09-17 | 108:17:57 |</p><p>+------------+------------+</p><p>1 row in set (0.00 sec)</p><p>Você pode, é claro, converter explicitamente suas strings mesmo quando o servidor</p><p>estiver esperando um valor date, datetime ou time, em vez de deixar que o servidor</p><p>faça uma conversão implícita.</p><p>Ao converter strings em valores temporais – seja explícita ou implicitamente – você</p><p>deve fornecer todos os componentes de data na ordem correta. Apesar de alguns</p><p>servidores serem bem rígidos quanto ao formato de data, o servidor MySQL é bem</p><p>tolerante em relação aos separadores usados entre os componentes. Por exemplo, o</p><p>MySQL aceitará todas as strings a seguir como representações válidas de 17 de</p><p>setembro de 2008, às 15:30: '2008-09-17 15:30:00'</p><p>'2008/09/17 15:30:00'</p><p>'2008,09,17,15,30,00'</p><p>'20080917153000'</p><p>Apesar de isso oferecer um pouco mais de �exibilidade, você pode acabar se</p><p>pegando em uma tentativa de gerar um valor temporal sem os componentes de data</p><p>padrão – a próxima seção mostra uma função nativa que é bem mais �exível do que</p><p>a função cast().</p><p>Funções de geração de datas</p><p>Se você precisa gerar dados temporais a partir de uma string, e a string não estiver</p><p>no formato correto para usar a função cast(), você pode usar uma função nativa</p><p>que permitirá fornecer uma string de formato junto com a string de data. O MySQL</p><p>inclui a função str_to_date() para esse propósito. Digamos, por exemplo, que você</p><p>recupere a string ‘September 17, 2008’ de um arquivo e precise usá-la para atualizar</p><p>uma coluna de data. Como a string não está no formato AAAA-MM-DD necessário,</p><p>você pode usar str_to_date() em vez de reformatar a string como precisaria ser feito</p><p>com cast(), assim: UPDATE individual</p><p>SET birth_date = STR_TO_DATE('September 17, 2008', '%M %d, %Y') WHERE</p><p>cust_id = 9999;</p><p>O segundo argumento na chamada a str_to_date() de�ne o formato da string de</p><p>data, que, nesse caso, tem o nome do mês (%M), um dia numérico (%d) e um ano</p><p>numérico com quatro dígitos (%Y). Apesar de existirem cerca de 30 componentes de</p><p>formato reconhecíveis, a tabela 7.4 de�ne cerca de uma dúzia daqueles componentes</p><p>mais utilizados.</p><p>Tabela 7.4 – Componentes de formato de data</p><p>Componente de formato Descrição</p><p>%M Nome do mês (de janeiro a dezembro)</p><p>%m Mês numérico (de 01 a 12)</p><p>%d Dia numérico (de 01 a 31)</p><p>%j Dia do ano (de 001 a 366)</p><p>%W Nome do dia da semana (de domingo a sábado)</p><p>%Y Ano, numérico com quatro dígitos</p><p>%y Ano, numérico com dois dígitos</p><p>%H Hora (de 00 a 23)</p><p>%h Hora (de 01 a 12)</p><p>%i Minutos (de 00 a 59)</p><p>%s Segundos (de 00 a 59)</p><p>%f Microssegundos (de 000000 a 999999)</p><p>%p A.M. ou P.M.</p><p>A função str_to_date() retorna um valor datetime, date ou time, dependendo do</p><p>conteúdo da string de formato. Por exemplo, se a string de formato incluir apenas %H,</p><p>%i e %s, um valor time será retornado.</p><p>Usuários do Oracle Database podem usar a função to_date() da mesma forma que a</p><p>função str_to_date do MySQL. O SQL Server inclui uma função convert() que</p><p>não é tão �exível quanto o MySQL e o Oracle Database: em vez de fornecer um formato</p><p>de string personalizado, sua string de data deve se conformar a um entre 21 formatos</p><p>prede�nidos.</p><p>Se estiver tentando gerar a data/hora atual, você não precisará construir uma string,</p><p>pois as seguintes funções nativas acessam o relógio do sistema e retornam a data</p><p>e/ou o horário atuais como uma string:</p><p>Os valores retornados por essas funções estão no formato padrão do tipo temporal</p><p>retornado. O Oracle Database inclui current_date() e current_timestamp(), mas</p><p>não current_time(), e o SQL Server inclui apenas a função current_timestamp().</p><p>Manipulando dados temporais</p><p>Esta seção explora as funções nativas que aceitam datas como argumento e que</p><p>retornam datas, strings ou números.</p><p>Funções temporais que retornam datas</p><p>Muitas das funções temporais recebem uma data como argumento e retornam</p><p>outra. A função date_add() do MySQL, por exemplo, permite adicionar qualquer</p><p>tipo de intervalo (por exemplo, dias, meses, anos) a uma data especí�ca para gerar</p><p>outra data. Con�ra um exemplo que demonstra como adicionar cinco dias à data</p><p>atual: mysql> SELECT DATE_ADD(CURRENT_DATE(), INTERVAL 5</p><p>DAY); +------------------------------------------+</p><p>| DATE_ADD(CURRENT_DATE(), INTERVAL 5 DAY) |</p><p>+------------------------------------------+</p><p>| 2008-09-22 |</p><p>+------------------------------------------+</p><p>1 row in set (0.06 sec)</p><p>O segundo argumento é composto de três elementos: a palavra-chave interval, a</p><p>quantidade desejada e o tipo de intervalo. A tabela 7.5 mostra alguns dos tipos de</p><p>intervalos mais usados.</p><p>Tabela 7.5 – Tipos de intervalos mais comuns</p><p>Nome do intervalo Descrição</p><p>Second Número de segundos</p><p>Minute Número de minutos</p><p>Hour Número de horas</p><p>Day Número de dias</p><p>Month Número de meses</p><p>Nome do intervalo Descrição</p><p>Year Número de anos</p><p>Minute_second Número de minutos e segundos, separados</p>
<p>por “:”</p><p>Hour_second Número de horas, minutos e segundos, separados por “:”</p><p>Year_month Número de anos e meses, separados por “-”</p><p>Enquanto os primeiros seis tipos listados na tabela 7.5 são simples, os três últimos</p><p>requerem um pouco mais de explicação, já que têm mais de um elemento. Por</p><p>exemplo, se lhe disserem que a transação de ID 9999 ocorreu, na verdade, 3 horas,</p><p>27 minutos e 11 segundos antes do que foi armazenado na tabela transaction, você</p><p>poderia corrigi-la assim: UPDATE transaction</p><p>SET txn_date = DATE_ADD(txn_date, INTERVAL '3:27:11' HOUR_SECOND) WHERE</p><p>txn_id = 9999;</p><p>Nesse exemplo, a função date_add() pega o valor da coluna txn_date, adiciona 3</p><p>horas, 27 minutos e 11 segundos a ela e usa o valor resultante para modi�car a</p><p>coluna txn_date.</p><p>Ou, se você trabalha no setor de recursos humanos e descobre que o funcionário de</p><p>ID 4789 a�rmou ser mais jovem do que realmente é, você poderia adicionar 9 anos e</p><p>11 meses à sua data de nascimento assim: UPDATE employee</p><p>SET birth_date = DATE_ADD(birth_date, INTERVAL '9-11' YEAR_MONTH)</p><p>WHERE emp_id = 4789;</p><p>Usuários do SQL Server podem realizar o exemplo anterior usando a função</p><p>dateadd(): UPDATE employee</p><p>SET birth_date =</p><p>DATEADD(MONTH, 119, birth_date)</p><p>WHERE emp_id = 4789</p><p>O SQL Server não tem intervalos combinados (ou seja, year_month), então eu converti</p><p>9 anos e 11 meses em 119 meses.</p><p>Os usuários do Oracle Database podem usar a função add_months() para esse</p><p>exemplo, como em: UPDATE employee</p><p>SET birth_date = ADD_MONTHS(birth_date, 119)</p><p>WHERE emp_id = 4789;</p><p>Há alguns casos em que é desejável adicionar um intervalo a uma data, e você sabe</p><p>em que data quer chegar, mas não sabe quantos dias existem entre as duas datas.</p><p>Por exemplo, digamos que um cliente do banco se conecte ao sistema de on-line</p><p>banking e agende uma transferência para o �m do mês. Em vez de escrever algum</p><p>código que descubra em qual mês você está atualmente e que procure pelo número</p><p>de dias daquele mês, você pode chamar a função last_day(), que faz esse trabalho</p><p>para você (tanto o MySQL quanto o Oracle Database incluem a função last_day();</p><p>o SQL Server não tem uma função desse tipo). Se o cliente solicitou a transferência</p><p>no dia 17 de setembro de 2008, você poderia encontrar o último dia de setembro</p><p>desta maneira: mysql> SELECT LAST_DAY('2008-09-17'); +-------------------</p><p>-----+</p><p>| LAST_DAY('2008-09-17') |</p><p>+------------------------+</p><p>| 2008-09-30 |</p><p>+------------------------+</p><p>1 row in set (0.10 sec)</p><p>Independentemente de fornecer um valor do tipo date ou datetime, a função</p><p>last_day() sempre retorna um valor do tipo date. Apesar de essa função não</p><p>parecer economizar muito tempo, a lógica por trás dela pode �car complicada caso</p><p>você esteja tentando encontrar o último dia de fevereiro e precise descobrir se o ano</p><p>atual é bissexto.</p><p>Outra função temporal que retorna uma data é a que converte um valor datetime de</p><p>uma zona horária em outra. Para esse propósito, o MySQL inclui a função</p><p>convert_tz(), e o Oracle Database inclui a função new_time(). Se eu quisesse</p><p>converter meu horário local atual para UTC, por exemplo, eu poderia fazer o</p><p>seguinte: mysql> SELECT CURRENT_TIMESTAMP() current_est, -></p><p>CONVERT_TZ(CURRENT_TIMESTAMP(), 'US/Eastern', 'UTC')</p><p>current_utc; +---------------------+---------------------+</p><p>| current_est | current_utc |</p><p>+---------------------+---------------------+</p><p>| 2008-09-18 20:01:25 | 2008-09-19 00:01:25 |</p><p>+---------------------+---------------------+</p><p>1 row in set (0.76 sec)</p><p>Essa função é bem útil nos casos em que são recebidas datas em zonas horárias</p><p>diferentes daquela que é armazenada em seu banco de dados.</p><p>Funções temporais que retornam strings</p><p>A maioria das funções temporais que retornam valores em string é usada para</p><p>extrair uma porção de uma data ou de um horário. Por exemplo, o MySQL inclui a</p><p>função dayname() para determinar em qual dia da semana uma certa data cai, como</p><p>em: mysql> SELECT DAYNAME('2008-09-18'); +-----------------------+</p><p>| DAYNAME('2008-09-18') |</p><p>+-----------------------+</p><p>| Thursday |</p><p>+-----------------------+</p><p>1 row in set (0.08 sec)</p><p>Muitas dessas funções estão incluídas no MySQL para extrair informações de</p><p>valores de data, mas recomendo que você use a função extract() no lugar delas,</p><p>pois é mais fácil lembrar-se de umas poucas variações de uma função do que de</p><p>uma dúzia de funções diferentes. Além disso, a função extract() é parte do padrão</p><p>SQL:2003 e foi implementada tanto no Oracle Database quanto no MySQL.</p><p>A função extract() usa os mesmos tipos de intervalos da função date_add() (veja a</p><p>tabela 7.5) para de�nir qual elemento da data lhe interessa. Por exemplo, se quiser</p><p>extrair apenas o ano de um valor datetime, você pode fazer o seguinte: mysql></p><p>SELECT EXTRACT(YEAR FROM '2008-09-18 22:19:05'); +---------------</p><p>---------------------------+</p><p>| EXTRACT(YEAR FROM '2008-09-18 22:19:05') |</p><p>+------------------------------------------+</p><p>| 2008 |</p><p>+------------------------------------------+</p><p>1 row in set (0.00 sec)</p><p>O SQL Server não inclui uma implementação de extract(), mas inclui a função</p><p>datepart(). Veja como você extrairia o ano de um valor datetime usando</p><p>datepart(); SELECT DATEPART(YEAR, GETDATE())</p><p>Funções temporais que retornam números</p><p>Anteriormente no capítulo, mostrei uma função usada para adicionar certo intervalo</p><p>a um valor de data, gerando, assim, outro valor de data. Outra atividade comum</p><p>quando se trabalha com datas é pegar dois valores de data e determinar o número de</p><p>intervalos (dias, semanas, anos) entre as duas datas. Para esse propósito, o MySQL</p><p>inclui a função datediff(), que retorna o número de dias completos entre duas</p><p>datas. Por exemplo, se eu quiser saber o número de dias que meus �lhos �carão de</p><p>férias nesse verão, posso fazer o seguinte: mysql> SELECT DATEDIFF('2009-</p><p>09-03', '2009-06-24'); +--------------------------------------+</p><p>| DATEDIFF('2009-09-03', '2009-06-24') |</p><p>+--------------------------------------+</p><p>| 71 |</p><p>+--------------------------------------+</p><p>1 row in set (0.05 sec)</p><p>Portanto, terei que aguentar 71 dias de heras venenosas, picadas de mosquitos e</p><p>joelhos arranhados antes de as crianças retornarem com segurança à escola. A</p><p>função datediff() ignora o horário do dia em seus argumentos. Mesmo que eu</p><p>inclua um horário, con�gurando-o para um segundo antes da meia-noite na</p><p>primeira data e para um segundo após a meia-noite na segunda data, esses horários</p><p>não terão efeito sobre o cálculo:</p><p>Se eu inverter os argumentos e colocar a data mais antiga primeiro, datediff()</p><p>retornará um número negativo, como em: mysql> SELECT DATEDIFF('2009-</p><p>06-24', '2009-09-03'); +--------------------------------------+</p><p>| DATEDIFF('2009-06-24', '2009-09-03') |</p><p>+--------------------------------------+</p><p>| -71 |</p><p>+--------------------------------------+</p><p>1 row in set (0.01 sec)</p><p>O SQL Server também inclui a função datediff(), mas é mais �exível do que a</p><p>implementação do MySQL, pois você pode especi�car o tipo de intervalo (ou seja, ano,</p><p>mês, dia, hora) em vez de contar apenas o número de dias entre duas datas. Veja como o</p><p>SQL Server realizaria o exemplo anterior: SELECT DATEDIFF(DAY, ‘2009-06-24’, ‘2009-</p><p>09-03’)</p><p>O Oracle Database permite determinar o número de dias entre duas datas simplesmente</p><p>subtraindo uma da outra.</p><p>Funções de conversão</p><p>Anteriormente neste capítulo, mostrei como usar a função cast() para converter</p><p>uma string em um valor datetime. Apesar de cada servidor de banco de dados</p><p>incluir um número de funções proprietárias usadas para converter dados de um tipo</p><p>em outro, recomendo o uso da função cast(), que está incluída no padrão</p><p>SQL:2003 e que foi implementada no MySQL, no Oracle Database e no SQL Server.</p><p>Para usar cast(), você fornece um valor ou expressão, a palavra-chave as e o tipo</p><p>para o qual você quer que o valor seja convertido. Aqui está um exemplo que</p><p>converte uma string em um número inteiro: mysql> SELECT CAST('1456328'</p><p>AS SIGNED INTEGER); +-----------------------------------+</p><p>| CAST('1456328' AS SIGNED INTEGER) |</p><p>+-----------------------------------+</p><p>| 1456328 |</p><p>+-----------------------------------+</p><p>1 row in set</p>
<p>(0.01 sec)</p><p>Ao converter uma string em um número, a função cast() tentará converter toda a</p><p>string, da esquerda para a direita; se quaisquer caracteres não-numéricos forem</p><p>encontrados na string, a conversão para sem erros. Considere o seguinte exemplo:</p><p>Nesse caso, os primeiros três dígitos da string são convertidos, enquanto o resto da</p><p>string é descartado, resultando no valor 999. No entanto, o servidor emite um aviso</p><p>para que você saiba que nem toda a string foi convertida.</p><p>Se estiver convertendo uma string em um valor date, time ou datetime, você</p><p>precisará se conformar aos formatos-padrão de cada tipo, já que você não pode</p><p>fornecer uma string de formato à função cast(). Se a sua string de data não estiver</p><p>no formato padrão (ou seja, AAAA-MM-DD HH:MI:SS para tipos datetime), você</p><p>terá que fazer uso de outra função, como a str_to_date() do MySQL, descrita</p><p>anteriormente no capítulo.</p><p>Teste seu conhecimento</p><p>Estes exercícios foram desenvolvidos para testar sua compreensão de algumas das</p><p>funções nativas mostradas neste capítulo. Consulte o apêndice C para veri�car as</p><p>respostas.</p><p>Exercício 7.1</p><p>Escreva uma consulta que retorne do 17º ao 25º caractere da string 'Please find the</p><p>substring in this string'.</p><p>Exercício 7.2</p><p>Escreva uma consulta que retorne o valor absoluto e o sinal (-1, 0 ou 1) do número</p><p>-25,76823. Também retorne o número arredondado à centena mais próxima.</p><p>Exercício 7.3</p><p>Escreva uma consulta que retorne apenas o mês da data atual.</p><p>1 N. de T.: os caracteres é e ö estão disponíveis no padrão português de teclado, mas isso não</p><p>invalida a explicação para a língua portuguesa, pois existem outros caracteres (ß, por exemplo,</p><p>usado no alemão) que nem sempre estão disponíveis para nós.</p><p>CAPÍTULO 8</p><p>Agrupamentos e agregações</p><p>Os dados geralmente são armazenados no nível mais baixo de granularidade que</p><p>qualquer usuário do banco de dados possa vir a necessitar. Se Chuck, da</p><p>contabilidade, precisa enxergar transações de clientes individuais, há necessidade de</p><p>se ter uma tabela no banco de dados que armazene transações individuais. No</p><p>entanto, isso não signi�ca que todos os usuários precisem lidar com os dados tais</p><p>como estão armazenados no banco de dados. O foco deste capítulo está em como os</p><p>dados podem ser agrupados e agregados para permitir que os usuários interajam</p><p>com eles em algum nível mais alto de granularidade do que aquele que está</p><p>armazenado no banco de dados.</p><p>Conceitos de agrupamento</p><p>Por vezes, você desejará extrair certas características de seus dados que exigirão que</p><p>o servidor de banco de dados os cozinhe um pouco antes de poder gerar os</p><p>resultados que você está procurando. Por exemplo, digamos que você seja o</p><p>responsável pelas operações do banco e gostaria de descobrir quantas contas foram</p><p>abertas por cada caixa. Você poderia realizar uma consulta simples para observar os</p><p>dados brutos: mysql> SELECT open_emp_id -> FROM account;</p><p>+-------------+</p><p>| open_emp_id |</p><p>+-------------+</p><p>| 1 |</p><p>| 1 |</p><p>| 1 |</p><p>| 1 |</p><p>| 1 |</p><p>| 1 |</p><p>| 1 |</p><p>| 1 |</p><p>| 10 |</p><p>| 10 |</p><p>| 10 |</p><p>| 10 |</p><p>| 10 |</p><p>| 10 |</p><p>| 10 |</p><p>| 13 |</p><p>| 13 |</p><p>| 16 |</p><p>| 16 |</p><p>| 16 |</p><p>| 16 |</p><p>| 16 |</p><p>| 16 |</p><p>+-------------+</p><p>24 rows in set (0.01 sec)</p><p>Com apenas 24 linhas na tabela account, �ca relativamente fácil ver que quatro</p><p>funcionários diferentes abriram contas e que o funcionário cujo ID é 16 abriu seis</p><p>contas. No entanto, se o banco tiver dúzias de funcionários e milhares de contas,</p><p>essa abordagem se torna cansativa e passível de erros.</p><p>Em vez disso, você pode pedir ao servidor de banco de dados que agrupe os dados</p><p>para você usando a cláusula group by. Aqui está a mesma consulta, mas empregando</p><p>uma cláusula group by para agrupar os dados de conta por ID de funcionário:</p><p>mysql> SELECT open_emp_id -> FROM account</p><p>-> GROUP BY open_emp_id;</p><p>+-------------+</p><p>| open_emp_id |</p><p>+-------------+</p><p>| 1 |</p><p>| 10 |</p><p>| 13 |</p><p>| 16 |</p><p>+-------------+</p><p>4 rows in set (0.00 sec)</p><p>O conjunto-resultado contém uma linha para cada valor distinto da coluna</p><p>open_emp_id, totalizando quatro linhas, em vez do resultado completo de 24 linhas.</p><p>A razão de o conjunto-resultado ser menor é que cada um dos quatro funcionários</p><p>abriu mais de uma conta. Para ver quantas contas cada caixa abriu, você pode usar</p><p>uma função de agregação na cláusula select para contar o número de linhas em</p><p>cada grupo: mysql> SELECT open_emp_id, COUNT(*) how_many -> FROM</p><p>account</p><p>-> GROUP BY open_emp_id;</p><p>+-------------+----------+</p><p>| open_emp_id | how_many |</p><p>+-------------+----------+</p><p>| 1 | 8 |</p><p>| 10 | 7 |</p><p>| 13 | 3 |</p><p>| 16 | 6 |</p><p>+-------------+----------+</p><p>4 rows in set (0.00 sec)</p><p>A função de agregação count() conta o número de linhas em cada grupo, e o</p><p>asterisco diz ao servidor para contar tudo no grupo. Usando a combinação de uma</p><p>cláusula group by com a função de agregação count(), você consegue gerar</p><p>exatamente os dados necessários para responder às questões orientadas ao negócio</p><p>sem ter que olhar os dados brutos.</p><p>Ao agrupar dados, você pode precisar descartar dados indesejados de seu conjunto-</p><p>resultado baseado em grupos de dados, em vez de remover baseado nos dados</p><p>brutos. Como a cláusula group by é executada após a avaliação da cláusula where,</p><p>você não pode adicionar condições de �ltro à sua cláusula where para esse propósito.</p><p>Por exemplo, aqui está uma tentativa de descartar quaisquer casos em que um</p><p>funcionário tenha aberto menos de cinco contas: mysql> SELECT</p><p>open_emp_id, COUNT(*) how_many -> FROM account</p><p>-> WHERE COUNT(*) > 4</p><p>-> GROUP BY open_emp_id;</p><p>ERROR 1111 (HY000): Invalid use of group function</p><p>Você não pode fazer referência à função de agregação count(*) na cláusula where,</p><p>pois os grupos ainda não foram gerados no momento em que a cláusula where é</p><p>avaliada. Em vez disso, você pode colocar suas condições de �ltro de grupo na</p><p>cláusula having. Veja como a consulta �caria usando having: mysql> SELECT</p><p>open_emp_id, COUNT(*) how_many -> FROM account</p><p>-> GROUP BY open_emp_id</p><p>-> HAVING COUNT(*) > 4;</p><p>+-------------+----------+</p><p>| open_emp_id | how_many |</p><p>+-------------+----------+</p><p>| 1 | 8 |</p><p>| 10 | 7 |</p><p>| 16 | 6 |</p><p>+-------------+----------+</p><p>3 rows in set (0.00 sec)</p><p>Como os grupos que contêm menos de cinco membros foram descartados por meio</p><p>da cláusula having, o conjunto-resultado agora contém apenas os funcionários que</p><p>abriram cinco ou mais contas, assim eliminando o funcionário de ID 13 dos</p><p>resultados.</p><p>Funções de agregação</p><p>As funções de agregação realizam uma operação especí�ca em todas as linhas de um</p><p>grupo. Apesar de cada servidor de banco de dados ter seu próprio conjunto</p><p>especializado de funções de agregação, as funções comuns de agregação</p><p>implementadas por todos os principais servidores incluem: Max()</p><p>Retorna o valor máximo dentro de um conjunto.</p><p>Min()</p><p>Retorna o valor mínimo dentro de um conjunto.</p><p>Avg()</p><p>Retorna o valor médio de um conjunto.</p><p>Sum()</p><p>Retorna a soma dos valores de um conjunto.</p><p>Count()</p><p>Retorna a quantidade de valores em um conjunto.</p><p>Veja uma consulta que usa todas as funções comuns de agregação para analisar os</p><p>saldos disponíveis de todas as contas correntes:</p><p>Os resultados dessa consulta informam que, entre as dez contas correntes da tabela</p><p>account, há um saldo máximo de U$ 38.552,05, um mínimo de U$ 122,37, um saldo</p><p>médio de U$ 7.300,80 e um saldo total, ao longo de todas as dez contas, de U$</p><p>73.008,01. Felizmente, isso proporciona uma visão do papel dessas funções de</p><p>agregação – as próximas subseções esclarecerão como você pode utilizar essas</p><p>funções.</p><p>Grupos implícitos versus grupos explícitos</p><p>No exemplo anterior, cada valor retornado pela consulta foi gerado por uma função</p><p>de agregação, e as funções de agregação são aplicadas ao longo do grupo de linhas</p><p>especi�cado pela condição de �ltro product_cd = 'CHK'. Como não há uma cláusula</p><p>group by, há apenas um grupo implícito (todas as linhas retornadas pela consulta).</p><p>No entanto, na maioria dos casos você vai querer recuperar colunas adicionais junto</p><p>com as colunas geradas pelas funções de agregação. E se, por exemplo, você quisesse</p><p>estender a consulta anterior para que executasse as mesmas cinco funções de</p><p>agregação em cada</p>
<p>tipo de produto, em vez de apenas nas contas correntes? Nessa</p><p>consulta, você poderia querer recuperar a coluna product_cd junto com as cinco</p><p>funções de agregação, como em: SELECT product_cd, MAX(avail_balance)</p><p>max_balance,</p><p>MIN(avail_balance) min_balance,</p><p>AVG(avail_balance) avg_balance,</p><p>SUM(avail_balance) tot_balance,</p><p>COUNT(*) num_accounts</p><p>FROM account;</p><p>No entanto, se tentar executar a consulta, você receberá o seguinte erro:</p><p>ERROR 1140 (42000): Mixing of GROUP columns (MIN(),MAX(),COUNT(),...) with</p><p>no GROUP</p><p>columns is illegal if there is no GROUP BY clause</p><p>Apesar de poder parecer óbvio que você queira aplicar as funções de agregação em</p><p>cada conjunto de produtos encontrado na tabela account, essa consulta falha porque</p><p>você não especi�cou explicitamente como os dados devem ser agrupados. Portanto,</p><p>você precisará adicionar uma cláusula group by para especi�car em quais grupos de</p><p>linhas as funções de agregação deverão ser aplicadas:</p><p>Com a inclusão da cláusula group by, o servidor sabe que deve primeiro agrupar as</p><p>linhas que contenham o mesmo valor na coluna product_cd para, em seguida,</p><p>aplicar as cinco funções de agregação em cada um dos seis grupos.</p><p>Contando valores distintos</p><p>Ao usar a função count() para determinar o número de membros de cada grupo,</p><p>você tem a opção de contar todos os membros do grupo ou contar apenas os valores</p><p>distintos inseridos em uma coluna ao longo de todos os membros do grupo. Por</p><p>exemplo, considere os seguintes dados, que mostram cada funcionário responsável</p><p>pela abertura de cada conta: mysql> SELECT account_id, open_emp_id -></p><p>FROM account</p><p>-> ORDER BY open_emp_id;</p><p>+------------+-------------+</p><p>| account_id | open_emp_id |</p><p>+------------+-------------+</p><p>| 8 | 1 |</p><p>| 9 | 1 |</p><p>| 10 | 1 |</p><p>| 12 | 1 |</p><p>| 13 | 1 |</p><p>| 17 | 1 |</p><p>| 18 | 1 |</p><p>| 19 | 1 |</p><p>| 1 | 10 |</p><p>| 2 | 10 |</p><p>| 3 | 10 |</p><p>| 4 | 10 |</p><p>| 5 | 10 |</p><p>| 14 | 10 |</p><p>| 22 | 10 |</p><p>| 6 | 13 |</p><p>| 7 | 13 |</p><p>| 24 | 13 |</p><p>| 11 | 16 |</p><p>| 15 | 16 |</p><p>| 16 | 16 |</p><p>| 20 | 16 |</p><p>| 21 | 16 |</p><p>| 23 | 16 |</p><p>+------------+-------------+</p><p>24 rows in set (0.00 sec)</p><p>Como você pode ver, várias contas foram abertas por quatro funcionários diferentes</p><p>(cujos IDs são 1, 10, 13 e 16). Digamos que, em vez de realizar uma contagem manual,</p><p>você queira criar uma consulta que conte o número de funcionários que abriram</p><p>contas. Se aplicar a função count() à coluna open_emp_id, você verá o seguinte</p><p>resultado: mysql> SELECT COUNT(open_emp_id) -> FROM account;</p><p>+--------------------+</p><p>| COUNT(open_emp_id) |</p><p>+--------------------+</p><p>| 24 |</p><p>+--------------------+</p><p>1 row in set (0.00 sec)</p><p>Nesse caso, especi�car a coluna open_emp_id como a coluna a ser contada gera os</p><p>mesmos resultados da especi�cação count(*). Se desejar contar valores distintos no</p><p>grupo, em vez de apenas contar o número de linhas do grupo, você precisará</p><p>especi�car a palavra-chave distinct, como em: mysql> SELECT</p><p>COUNT(DISTINCT open_emp_id) -> FROM account;</p><p>+-----------------------------+</p><p>| COUNT(DISTINCT open_emp_id) |</p><p>+-----------------------------+</p><p>| 4 |</p><p>+-----------------------------+</p><p>1 row in set (0.00 sec)</p><p>Portanto, ao especi�car distinct, a função count() examina os valores de uma</p><p>coluna para cada membro do grupo para encontrar e remover duplicatas, em vez de</p><p>simplesmente contar o número de valores do grupo.</p><p>Usando expressões</p><p>Além de usar colunas como argumentos de funções de agregação, você pode</p><p>construir expressões para usar como argumentos. Por exemplo, você pode querer</p><p>encontrar o valor máximo de depósito pendente ao longo de todas as contas, que é</p><p>calculado subtraindo-se o saldo disponível do saldo pendente. Isso é possível por</p><p>meio da seguinte consulta: mysql> SELECT MAX(pending_balance -</p><p>avail_balance) max_uncleared -> FROM account;</p><p>+---------------+</p><p>| max_uncleared |</p><p>+---------------+</p><p>| 660.00 |</p><p>+---------------+</p><p>1 row in set (0.00 sec)</p><p>Apesar de esse exemplo usar uma expressão relativamente simples, as expressões</p><p>usadas como argumentos de funções de agregação podem ser tão complexas quanto</p><p>forem necessárias, desde que retornem um número, uma string ou uma data. No</p><p>capítulo 11, mostrarei como você pode usar expressões case junto com as funções de</p><p>agregação para determinar se uma linha especí�ca deve ou não ser incluída em uma</p><p>agregação.</p><p>Como nulls são tratados</p><p>Ao realizar agregações, ou melhor, ao realizar qualquer tipo de cálculo numérico,</p><p>você deve sempre considerar de que forma os valores null afetarão o resultado de</p><p>seu cálculo. Para ilustrar isso, vou construir uma tabela simples que armazena dados</p><p>numéricos e populá-la com o conjunto {1, 3, 5}: mysql> CREATE TABLE</p><p>number_tbl -> (val SMALLINT);</p><p>Query OK, 0 rows affected (0.01 sec)</p><p>mysql> INSERT INTO number_tbl VALUES (1); Query OK, 1 row affected (0.00</p><p>sec)</p><p>mysql> INSERT INTO number_tbl VALUES (3); Query OK, 1 row affected (0.00</p><p>sec)</p><p>mysql> INSERT INTO number_tbl VALUES (5); Query OK, 1 row affected (0.00</p><p>sec)</p><p>Considere a seguinte consulta, que realiza cinco funções de agregação nesse</p><p>conjunto de valores: mysql> SELECT COUNT(*) num_rows, -> COUNT(val)</p><p>num_vals,</p><p>-> SUM(val) total,</p><p>-> MAX(val) max_val,</p><p>-> AVG(val) avg_val</p><p>-> FROM number_tbl;</p><p>+----------+----------+-------+---------+---------+</p><p>| num_rows | num_vals | total | max_val | avg_val |</p><p>+----------+----------+-------+---------+---------+</p><p>| 3 | 3 | 9 | 5 | 3.0000 |</p><p>+----------+----------+-------+---------+---------+</p><p>1 row in set (0.08 sec)</p><p>Os resultados são os esperados: tanto count(*) quanto count(val) retornam o valor</p><p>3, sum(val) retorna o valor 9, max(val) retorna 5 e avg(val) retorna 3. Em seguida,</p><p>adicionarei um valor null à tabela number_tbl e executarei novamente a consulta:</p><p>mysql> INSERT INTO number_tbl VALUES (NULL); Query OK, 1 row</p><p>a�ected (0.01 sec)</p><p>mysql> SELECT COUNT(*) num_rows, -> COUNT(val) num_vals,</p><p>-> SUM(val) total,</p><p>-> MAX(val) max_val,</p><p>-> AVG(val) avg_val</p><p>-> FROM number_tbl;</p><p>+----------+----------+-------+---------+---------+</p><p>| num_rows | num_vals | total | max_val | avg_val |</p><p>+----------+----------+-------+---------+---------+</p><p>| 4 | 3 | 9 | 5 | 3.0000 |</p><p>+----------+----------+-------+---------+---------+</p><p>1 row in set (0.00 sec)</p><p>Mesmo com a adição do valor null à tabela, as funções sum(), max() e avg()</p><p>retornam os mesmos valores, indicando que eles ignoram os valores null</p><p>encontrados. A função count(*) agora retorna o valor 4, que é válido, pois a tabela</p><p>number_tbl contém quatro linhas, enquanto a função count(val) ainda retorna o</p><p>valor 3. A diferença é que count(*) conta o número de linhas, enquanto count(val)</p><p>conta o número de valores contidos na coluna val e ignora qualquer valor null</p><p>encontrado.</p><p>Gerando grupos</p><p>As pessoas raramente estão interessadas em observar dados brutos: em vez disso, os</p><p>envolvidos em análise de dados desejarão manipular os dados brutos para melhor</p><p>se adequarem às suas necessidades. Exemplos comuns de manipulação de dados</p><p>incluem: • Gerar os totais de uma região geográ�ca, como o total de vendas na</p><p>Europa.</p><p>• Encontrar os destaques, como os maiores vendedores de 2005.</p><p>• Determinar frequências, como o número de novas contas abertas em cada �lial.</p><p>Para responder a esses tipos de indagações, você precisará solicitar ao servidor de</p><p>banco de dados que agrupe as linhas por uma ou mais colunas ou expressões.</p><p>Como você já viu em vários exemplos, a cláusula group by é o mecanismo de</p><p>agrupamento de dados dentro de uma consulta. Nesta seção, você verá como</p><p>agrupar dados por uma ou mais colunas, como agrupar dados usando expressões e</p><p>como gerar resumos (rollups) dentro de grupos.</p><p>Agrupamento por uma coluna</p><p>Grupos de uma coluna formam o tipo mais simples e mais utilizado de</p><p>agrupamento. Se você quiser encontrar os balanços totais de cada produto, por</p><p>exemplo, você precisa apenas agrupar pela coluna account.product_cd, como em:</p><p>mysql> SELECT product_cd, SUM(avail_balance) prod_balance -></p><p>FROM account</p><p>-> GROUP BY product_cd;</p><p>+------------+--------------+</p><p>| product_cd | prod_balance |</p><p>+------------+--------------+</p><p>| BUS | 9345.55 |</p><p>| CD | 19500.00 |</p><p>| CHK | 73008.01 |</p><p>| MM | 17045.14 |</p><p>| SAV | 1855.76 |</p><p>| SBL | 50000.00</p>
<p>|</p><p>+------------+--------------+</p><p>6 rows in set (0.00 sec)</p><p>Essa consulta gera seis grupos, um para cada produto, e então soma os saldos</p><p>disponíveis para cada membro do grupo.</p><p>Agrupamento por múltiplas colunas</p><p>Em alguns casos, você pode querer gerar grupos que envolvam mais de uma coluna.</p><p>Expandindo o exemplo anterior, imagine que você queira encontrar os balanços</p><p>totais não apenas de cada produto, mas de cada produto em cada �lial (por exemplo,</p><p>qual é o balanço total de todas as contas correntes abertas na �lial de Woburn?). O</p><p>exemplo a seguir mostra como você poderia realizar isso: mysql> SELECT</p><p>product_cd, open_branch_id, -> SUM(avail_balance)</p><p>tot_balance -> FROM account</p><p>-> GROUP BY product_cd, open_branch_id; +------------+----------------+----</p><p>---------+</p><p>| product_cd | open_branch_id | tot_balance |</p><p>+------------+----------------+-------------+</p><p>| BUS | 2 | 9345.55 |</p><p>| BUS | 4 | 0.00 |</p><p>| CD | 1 | 11500.00 |</p><p>| CD | 2 | 8000.00 |</p><p>| CHK | 1 | 782.16 |</p><p>| CHK | 2 | 3315.77 |</p><p>| CHK | 3 | 1057.75 |</p><p>| CHK | 4 | 67852.33 |</p><p>| MM | 1 | 14832.64 |</p><p>| MM | 3 | 2212.50 |</p><p>| SAV | 1 | 767.77 |</p><p>| SAV | 2 | 700.00 |</p><p>| SAV | 4 | 387.99 |</p><p>| SBL | 3 | 50000.00 |</p><p>+------------+----------------+-------------+</p><p>14 rows in set (0.00 sec)</p><p>Essa versão da consulta gera 14 grupos, um para cada combinação de produto e �lial</p><p>encontrada na tabela account. Além de adicionar a coluna open_branch_id à cláusula</p><p>select, também a adicionei à cláusula group by, já que open_branch_id é recuperado</p><p>de uma tabela e não é gerado por meio de uma função de agregação.</p><p>Agrupamento por meio de expressões</p><p>Além de usar colunas para agrupar dados, você pode construir grupos baseados nos</p><p>valores gerados por expressões. Considere a seguinte consulta, que agrupa os</p><p>funcionários pelo ano em que começaram a trabalhar no banco: mysql> SELECT</p><p>EXTRACT(YEAR FROM start_date) year, -> COUNT(*) how_many -></p><p>FROM employee</p><p>-> GROUP BY EXTRACT(YEAR FROM start_date); +------+----------+</p><p>| year | how_many |</p><p>+------+----------+</p><p>| 2004 | 2 |</p><p>| 2005 | 3 |</p><p>| 2006 | 8 |</p><p>| 2007 | 3 |</p><p>| 2008 | 2 |</p><p>+------+----------+</p><p>5 rows in set (0.15 sec)</p><p>Essa consulta emprega uma expressão relativamente simples, que usa a função</p><p>extract() para retornar apenas a porção do ano de uma data, para agrupar as linhas</p><p>da tabela employee.</p><p>Gerando resumos (rollups)</p><p>Em “Agrupamento por múltiplas colunas”, eu mostrei um exemplo que gerou</p><p>balanços totais de contas agrupadas por produto e �lial. Digamos, no entanto, que</p><p>junto com os balanços totais de cada combinação produto/�lial, você também</p><p>queira os balanços de cada produto em particular. Você poderia executar uma</p><p>consulta adicional e mesclá-la aos resultados, construir um script em Perl, um</p><p>programa em Java ou algum outro mecanismo que pegue os dados e realize os</p><p>cálculos adicionais. Melhor ainda, você poderia usar a opção with rollup para que o</p><p>servidor faça o serviço por você. Con�ra a consulta modi�cada, que usa with</p><p>rollup na cláusula group by: mysql> SELECT product_cd,</p><p>open_branch_id, -> SUM(avail_balance) tot_balance -> FROM</p><p>account</p><p>-> GROUP BY product_cd, open_branch_id WITH ROLLUP; +------------+---------</p><p>-------+-------------+</p><p>| product_cd | open_branch_id | tot_balance |</p><p>+------------+----------------+-------------+</p><p>| BUS | 2 | 9345.55 |</p><p>| BUS | 4 | 0.00 |</p><p>| BUS | NULL | 9345.55 |</p><p>| CD | 1 | 11500.00 |</p><p>| CD | 2 | 8000.00 |</p><p>| CD | NULL | 19500.00 |</p><p>| CHK | 1 | 782.16 |</p><p>| CHK | 2 | 3315.77 |</p><p>| CHK | 3 | 1057.75 |</p><p>| CHK | 4 | 67852.33 |</p><p>| CHK | NULL | 73008.01 |</p><p>| MM | 1 | 14832.64 |</p><p>| MM | 3 | 2212.50 |</p><p>| MM | NULL | 17045.14 |</p><p>| SAV | 1 | 767.77 |</p><p>| SAV | 2 | 700.00 |</p><p>| SAV | 4 | 387.99 |</p><p>| SAV | NULL | 1855.76 |</p><p>| SBL | 3 | 50000.00 |</p><p>| SBL | NULL | 50000.00 |</p><p>| NULL | NULL | 170754.46 |</p><p>+------------+----------------+-------------+</p><p>21 rows in set (0.02 sec)</p><p>Agora existem sete linhas adicionais no conjunto-resultado, uma para cada um dos</p><p>seis produtos distintos e uma para o total geral (todos os produtos combinados).</p><p>Nos seis resumos de produtos, um valor null é fornecido para a coluna</p><p>open_branch_id, já que o resumo está sendo realizado ao longo de todas as �liais.</p><p>Olhando na terceira linha da saída, por exemplo, você verá que um total de U$</p><p>9.345,55 foi depositado em contas BUS ao longo de todas as �liais. Para a linha do</p><p>total geral, um valor null é fornecido para as colunas product_cd e open_branch_id.</p><p>A última linha da saída mostra um total de U$ 170.754,46 ao longo de todos os</p><p>produtos e �liais.</p><p>Se estiver usando o Oracle Database, você precisará empregar uma sintaxe ligeiramente</p><p>diferente para indicar que deseja que um resumo seja realizado. A cláusula group by</p><p>da consulta anterior �caria assim no Oracle: GROUP BY ROLLUP(product_cd,</p><p>open_branch_id)</p><p>A vantagem dessa sintaxe é que ela permite realizar resumos em um subconjunto das</p><p>colunas da cláusula group by. Se estiver agrupando pelas colunas a, b e c, por</p><p>exemplo, você poderia indicar que o servidor deve realizar resumos em apenas b e c</p><p>assim: GROUP BY a, ROLLUP(b, c)</p><p>Se, junto aos totais por produto, você também quiser calcular totais por �lial, você</p><p>pode usar a opção with cube, que gera linhas de resumo para todas as combinações</p><p>possíveis das colunas de agrupamento. Infelizmente, with cube não está disponível</p><p>na versão 5.1 do MySQL, mas está disponível no SQL Server e no Oracle Database.</p><p>Aqui está um exemplo que usa with cube, mas eu removi o prompt mysql> para</p><p>indicar que a consulta ainda não pode ser realizada no MySQL: SELECT</p><p>product_cd, open_branch_id,</p><p>SUM(avail_balance) tot_balance</p><p>FROM account</p><p>GROUP BY product_cd, open_branch_id WITH CUBE; +------------+--------------</p><p>--+-------------+</p><p>| product_cd | open_branch_id | tot_balance |</p><p>+------------+----------------+-------------+</p><p>| NULL | NULL | 170754.46 |</p><p>| NULL | 1 | 27882.57 |</p><p>| NULL | 2 | 21361.32 |</p><p>| NULL | 3 | 53270.25 |</p><p>| NULL | 4 | 68240.32 |</p><p>| BUS | 2 | 9345.55 |</p><p>| BUS | 4 | 0.00 |</p><p>| BUS | NULL | 9345.55 |</p><p>| CD | 1 | 11500.00 |</p><p>| CD | 2 | 8000.00 |</p><p>| CD | NULL | 19500.00 |</p><p>| CHK | 1 | 782.16 |</p><p>| CHK | 2 | 3315.77 |</p><p>| CHK | 3 | 1057.75 |</p><p>| CHK | 4 | 67852.33 |</p><p>| CHK | NULL | 73008.01 |</p><p>| MM | 1 | 14832.64 |</p><p>| MM | 3 | 2212.50 |</p><p>| MM | NULL | 17045.14 |</p><p>| SAV | 1 | 767.77 |</p><p>| SAV | 2 | 700.00 |</p><p>| SAV | 4 | 387.99 |</p><p>| SAV | NULL | 1855.76 |</p><p>| SBL | 3 | 50000.00 |</p><p>| SBL | NULL | 50000.00 |</p><p>+------------+----------------+-------------+</p><p>25 rows in set (0.02 sec)</p><p>O uso de with cube gera quatro linhas a mais do que a versão da consulta que usa</p><p>with rollup, uma para cada um dos quatro IDs de �lial. De forma semelhante ao</p><p>with rollup, valores null são colocados na coluna product_cd para indicar que um</p><p>resumo de �lial está sendo realizado.</p><p>Mais uma vez, se estiver usando o Oracle Database, você precisará usar uma sintaxe</p><p>ligeiramente diferente para indicar que você quer que uma operação de cubo seja</p><p>realizada. A cláusula group by da consulta anterior �caria assim usando o Oracle:</p><p>GROUP BY CUBE(product_cd, open_branch_id)</p><p>Condições de �ltro de grupo</p><p>No capítulo 4, apresentei vários tipos de condições de �ltro e mostrei como você</p><p>pode usá-las na cláusula where. Ao agrupar dados, você também pode aplicar</p><p>condições de �ltro a eles após a geração dos grupos. A cláusula having é o lugar em</p><p>que você deve colocar esses tipos de condições de �ltro. Considere o seguinte</p><p>exemplo: mysql> SELECT product_cd, SUM(avail_balance)</p><p>prod_balance -> FROM account</p><p>-> WHERE status = 'ACTIVE'</p><p>-> GROUP BY product_cd</p><p>-> HAVING SUM(avail_balance) >= 10000; +------------+--------------+</p><p>| product_cd | prod_balance |</p><p>+------------+--------------+</p><p>| CD | 19500.00 |</p><p>| CHK | 73008.01 |</p><p>| MM | 17045.14 |</p><p>| SBL | 50000.00 |</p><p>+------------+--------------+</p><p>4 rows in set (0.00 sec)</p><p>Essa consulta tem duas condições de �ltro: uma na cláusula where, que descarta</p><p>contas inativas, e a outra na cláusula having, que descarta qualquer produto cujo</p><p>balanço total disponível seja inferior a $ 10.000. Então, um dos �ltros age sobre os</p><p>dados antes de eles serem agrupados, e o outro �ltro age sobre os dados após a</p><p>criação dos grupos. Se, por</p>
<p>engano, você colocar ambos os �ltros na cláusula where,</p><p>você verá o seguinte erro: mysql> SELECT product_cd,</p><p>SUM(avail_balance) prod_balance -> FROM account</p><p>-> WHERE status = 'ACTIVE'</p><p>-> AND SUM(avail_balance) > 10000</p><p>-> GROUP BY product_cd;</p><p>ERROR 1111 (HY000): Invalid use of group function</p><p>Essa consulta falha porque você não pode incluir uma função de agregação na</p><p>cláusula where de uma consulta. Isso ocorre porque os �ltros na cláusula where são</p><p>avaliados antes que os agrupamentos ocorram, então o servidor ainda não pode</p><p>realizar quaisquer funções sobre grupos.</p><p>Ao adicionar �ltros em uma consulta que inclua uma cláusula group by, preste atenção</p><p>para ver se o �ltro age sobre dados brutos, nesse caso pertencendo à cláusula where, ou</p><p>se age sobre dados agrupados, nesse caso pertencendo à cláusula having.</p><p>No entanto, você pode incluir funções de agregação à cláusula having que não</p><p>apareçam na cláusula select, como demonstrado a seguir: mysql> SELECT</p><p>product_cd, SUM(avail_balance) prod_balance -> FROM account</p><p>-> WHERE status = 'ACTIVE'</p><p>-> GROUP BY product_cd</p><p>-> HAVING MIN(avail_balance) >= 1000</p><p>-> AND MAX(avail_balance) SELECT MAX(account_id) FROM account; +----------------</p><p>-+</p><p>| MAX(account_id) |</p><p>+-----------------+</p><p>| 29 |</p><p>+-----------------+</p><p>1 row in set (0.00 sec)</p><p>Assim, a subconsulta retorna uma única linha com uma única coluna, o que</p><p>permite que ela seja usada como uma das expressões em uma condição de igualdade</p><p>(se a subconsulta retornasse duas ou mais linhas, ela poderia ser comparada, mas</p><p>não igualada a algo, mas falaremos sobre isso mais tarde). Nesse caso, você pode</p><p>pegar o valor retornado pela subconsulta e colocá-lo no lado direito da expressão da</p><p>condição de �ltro usada na instrução-contêiner, como em:</p><p>A subconsulta é útil nesse caso porque permite recuperar informações sobre a conta</p><p>com a numeração mais alta em uma única consulta, em vez de recuperar o</p><p>account_id máximo em uma consulta para, em seguida, escrever uma segunda</p><p>consulta que recupere os dados desejados da tabela account. Como você verá, as</p><p>subconsultas também são úteis em muitas outras situações, e podem vir a se tornar</p><p>uma das ferramentas mais poderosas de seu kit de ferramentas SQL.</p><p>Tipos de subconsultas</p><p>Além das diferenças citadas anteriormente a respeito do tipo de conjunto-resultado</p><p>que uma subconsulta retorna (linha/coluna únicas, linha única/múltiplas colunas,</p><p>linhas/colunas múltiplas), você pode usar outro fator para diferenciar subconsultas;</p><p>algumas subconsultas são completamente autocontidas (denominadas subconsultas</p><p>não-correlatas), enquanto outras referenciam colunas da instrução-contêiner</p><p>(denominadas subconsultas correlatas). As próximas seções exploram esses dois tipos</p><p>de subconsultas e mostram os diferentes operadores que você pode empregar para</p><p>interagir com elas.</p><p>Subconsultas não-correlatas</p><p>O exemplo mostrado anteriormente no capítulo era uma subconsulta não-correlata:</p><p>ela pode ser executada de forma independente e não referencia qualquer coisa da</p><p>instrução-contêiner. A maioria das subconsultas que você encontrará pelo caminho</p><p>é desse tipo, a não ser que você esteja escrevendo instruções update ou delete, que</p><p>frequentemente utilizam subconsultas correlatas (falaremos sobre isso adiante).</p><p>Além de ser não-correlato, o exemplo mostrado anteriormente neste capítulo é</p><p>conhecido como uma subconsulta escalar e pode aparecer em ambos os lados de</p><p>uma condição que utilize os operadores comuns (=, , , =). O próximo</p><p>exemplo mostra como você pode usar uma subconsulta escalar em uma condição de</p><p>desigualdade:</p><p>Essa consulta retorna dados relativos a todas as consultas que não foram abertas</p><p>pelo gerente de caixa na �lial de Woburn (a subconsulta é escrita partindo do</p><p>pressuposto de que há apenas um gerente de caixa em cada �lial). A subconsulta</p><p>nesse exemplo é um pouco mais complexa do que no exemplo anterior, pois junta</p><p>duas tabelas e inclui duas condições de �ltro. As subconsultas podem ser tão</p><p>simples ou tão complexas quanto você precise que elas sejam, e podem utilizar</p><p>quaisquer cláusulas de consulta disponíveis (select, from, where, group by, having e</p><p>order by).</p><p>Se usar uma subconsulta em uma condição de desigualdade que retorne mais de</p><p>uma linha, você receberá uma mensagem de erro. Por exemplo, se modi�car o</p><p>exemplo anterior de maneira tal que a subconsulta retorne todos os caixas da �lial</p><p>de Woburn, em vez de retornar apenas o único gerente de caixa, você receberá o</p><p>seguinte erro: mysql> SELECT account_id, product_cd, cust_id,</p><p>avail_balance -> FROM account</p><p>-> WHERE open_emp_id (SELECT e.emp_id -> FROM employee e INNER JOIN</p><p>branch b -> ON e.assigned_branch_id = b.branch_id -> WHERE e.title =</p><p>'Teller' AND b.city = 'Woburn'); ERROR 1242 (21000): Subquery returns more</p><p>than 1 row</p><p>Se executar a subconsulta de forma independente, verá os seguintes resultados:</p><p>mysql> SELECT e.emp_id</p><p>-> FROM employee e INNER JOIN branch b -> ON e.assigned_branch_id =</p><p>b.branch_id -> WHERE e.title = 'Teller' AND b.city = 'Woburn'; +--------+</p><p>| emp_id |</p><p>+--------+</p><p>| 11 |</p><p>| 12 |</p><p>+--------+</p><p>2 rows in set (0.02 sec)</p><p>A consulta-contêiner falha</p>
<p>porque uma expressão (open_emp_id) não pode ser</p><p>igualada a um conjunto de expressões (emp_ids 11 e 12). Em outras palavras, um</p><p>elemento único não pode ser igualado a um conjunto de elementos. Na próxima</p><p>seção, veremos como consertar esse problema usando um operador diferente.</p><p>Subconsultas de linhas múltiplas e coluna única</p><p>Se a sua subconsulta retornar mais de uma linha, você não conseguirá usá-la em um</p><p>dos lados de uma condição de igualdade, como foi demonstrado pelo exemplo</p><p>anterior. No entanto, existem quatro operadores adicionais que você pode usar para</p><p>construir condições com esses tipos de subconsultas.</p><p>Operadores in e not in</p><p>Apesar de não ser possível igualar um valor único a um conjunto de valores, você</p><p>pode veri�car se um valor único pode ser encontrado dentro de um conjunto de</p><p>valores. O próximo exemplo, apesar de não usar uma subconsulta, demonstra como</p><p>construir uma condição que usa o operador in para pesquisar um valor dentro de</p><p>um conjunto de valores: mysql> SELECT branch_id, name, city -> FROM</p><p>branch</p><p>-> WHERE name IN ('Headquarters', 'Quincy Branch'); +-----------+----------</p><p>-----+---------+</p><p>| branch_id | name | city |</p><p>+-----------+---------------+---------+</p><p>| 1 | Headquarters | Waltham |</p><p>| 3 | Quincy Branch | Quincy |</p><p>+-----------+---------------+---------+</p><p>2 rows in set (0.03 sec)</p><p>A expressão no lado esquerdo da condição é a coluna name, enquanto o lado direito</p><p>da condição é um conjunto de strings. O operador in veri�ca se uma das strings</p><p>pode ser encontrada na coluna name; em caso positivo, a condição é preenchida e a</p><p>linha é adicionada ao conjunto-resultado. Você pode obter os mesmos resultados</p><p>usando duas condições de igualdade, como em: mysql> SELECT branch_id,</p><p>name, city -> FROM branch</p><p>-> WHERE name = 'Headquarters' OR name = 'Quincy Branch'; +-----------+----</p><p>-----------+---------+</p><p>| branch_id | name | city |</p><p>+-----------+---------------+---------+</p><p>| 1 | Headquarters | Waltham |</p><p>| 3 | Quincy Branch | Quincy |</p><p>+-----------+---------------+---------+</p><p>2 rows in set (0.01 sec)</p><p>Apesar de essa abordagem parecer razoável quando o conjunto contém apenas duas</p><p>expressões, é fácil enxergar o porquê de uma única condição que use o operador in</p><p>ser preferível no caso do conjunto conter dúzias (ou centenas, milhares etc.) de</p><p>valores.</p><p>Embora você crie ocasionalmente conjuntos de strings, datas ou números para usar</p><p>em um dos lados de uma condição, é mais provável que você gere o conjunto por</p><p>meio de uma subconsulta que retorne uma ou mais linhas. A consulta a seguir usa o</p><p>operador in com uma subconsulta no lado direito da condição de �ltro para</p><p>descobrir quais funcionários supervisionam outros funcionários:</p><p>A subconsulta retorna os IDs de todos os funcionários que supervisionam outros</p><p>funcionários, e a consulta-contêiner recupera quatro colunas da tabela employee</p><p>para esses funcionários. Veja os resultados da subconsulta: mysql> SELECT</p><p>superior_emp_id -> FROM employee;</p><p>+-----------------+</p><p>| superior_emp_id |</p><p>+-----------------+</p><p>| NULL |</p><p>| 1 |</p><p>| 1 |</p><p>| 3 |</p><p>| 4 |</p><p>| 4 |</p><p>| 4 |</p><p>| 4 |</p><p>| 4 |</p><p>| 6 |</p><p>| 6 |</p><p>| 6 |</p><p>| 10 |</p><p>| 10 |</p><p>| 13 |</p><p>| 13 |</p><p>| 16 |</p><p>| 16 |</p><p>+-----------------+</p><p>18 rows in set (0.00 sec)</p><p>Como você pode ver, alguns IDs de funcionário são listados mais de uma vez, já que</p><p>alguns funcionários supervisionam várias pessoas. Isso não afeta negativamente os</p><p>resultados da consulta-contêiner, pois não importa se um ID de funcionário pode</p><p>ser encontrado uma vez ou mais de uma vez no conjunto-resultado da subconsulta.</p><p>É claro que você poderia adicionar a palavra-chave distinct à cláusula select da</p><p>subconsulta se lhe incomoda ter duplicatas na tabela retornada pela subconsulta,</p><p>mas isso não mudará o conjunto-resultado da consulta-contêiner.</p><p>Além de enxergar se um valor existe dentro de um conjunto de valores, você pode</p><p>veri�car o contrário usando o operador not in. Aqui está outra versão da consulta</p><p>anterior que usa not in no lugar de in:</p><p>Essa consulta encontra todos os funcionários que não supervisionam outras</p><p>pessoas. Para essa consulta, precisei adicionar uma condição de �ltro à subconsulta</p><p>para garantir que valores null não aparecessem na tabela retornada pela</p><p>subconsulta – consulte a próxima seção para ver uma explicação do porquê de esse</p><p>�ltro ser necessário neste caso.</p><p>Operador all</p><p>Enquanto o operador in é usado para veri�car se uma expressão pode ser</p><p>encontrada dentro de um conjunto de valores, o operador all permite fazer</p><p>comparações entre um único valor e cada valor de um conjunto. Para construir esse</p><p>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</p><p>todos os funcionários cujos IDs de funcionário não sejam iguais a qualquer um dos</p><p>IDs de funcionário dos supervisores:</p><p>Mais uma vez, a subconsulta retorna o conjunto de IDs daqueles funcionários que</p><p>supervisionam outras pessoas, e a consulta-contêiner retorna dados de todos os</p><p>funcionários cujo ID de funcionário não seja igual a qualquer um dos IDs</p><p>retornados pela subconsulta. Em outras palavras, a consulta encontra todos os</p><p>funcionários que não sejam supervisores. Se essa abordagem parecer um pouco</p><p>desajeitada, você não está sozinho: a maioria das pessoas prefere formular a</p><p>consulta de maneira diferente, evitando o uso do operador all. Por exemplo, essa</p><p>consulta gera os mesmos resultados que o último exemplo da seção anterior, que</p><p>usava o operador not in. É uma questão de preferência, mas creio que a maioria das</p><p>pessoas acharia que a versão que usa not in é mais fácil de entender.</p><p>Ao usar not in ou all para comparar um valor a um conjunto de valores, certi�que-</p><p>se de que o conjunto de valores não contenha valores null, pois o servidor iguala o</p><p>valor no lado esquerdo da expressão a cada membro do conjunto, e qualquer tentativa</p><p>de igualar um valor a null resulta em unknown (desconhecido). Portanto, a consulta a</p><p>seguir retorna um conjunto vazio: mysql> Select emp_id, fname, lname, title -></p><p>from employee -> WHERE emp_id NOT IN (1, 2, Null); Empty set (0.00 sec)</p><p>Em alguns casos, o operador all se torna um pouco mais natural. O próximo</p><p>exemplo usa all para encontrar as contas que tenham um saldo disponível menor</p><p>do que todas as contas de Frank Tucker:</p><p>Con�ra os dados retornados pela subconsulta, que consistem no saldo disponível</p><p>em cada conta de Frank: mysql> SELECT a.avail_balance -> FROM</p><p>account a INNER JOIN individual i -> ON a.cust_id =</p><p>i.cust_id</p><p>-> WHERE i.fname = 'Frank' AND i.lname = 'Tucker'; +---------------+</p><p>| avail_balance |</p><p>+---------------+</p><p>| 1057.75 |</p><p>| 2212.50 |</p><p>+---------------+</p><p>2 rows in set (0.01 sec)</p><p>Frank tem duas contas, com o saldo menor sendo U$ 1.057,75. A consulta-contêiner</p><p>encontra todas as contas que tenham um saldo inferior a todas as contas de Frank,</p><p>então o conjunto-resultado inclui todas as contas que tenham um saldo inferior a</p><p>U$ 1.057,75.</p><p>Operador any</p><p>Tal como o operador all, o operador any permite que um valor seja comparado aos</p><p>membros de um conjunto de valores. No entanto, diferentemente de all, uma</p><p>condição que use o operador any é avaliada como verdadeira tão logo uma única</p><p>comparação seja favorável. Isso é diferente do exemplo anterior, que usava o</p><p>operador all, que é avaliado como verdadeiro apenas quando as comparações feitas</p><p>a todos os membros do conjunto são favoráveis. Por exemplo, você pode querer</p><p>encontrar todas as contas que tenham um saldo disponível superior a qualquer</p><p>conta de Frank Tucker:</p><p>Frank tem duas contas com saldos de U$ 1.057,75 e U$ 2.212,50; para ter um saldo</p><p>superior a qualquer uma dessas duas contas, uma delas deve ter um saldo de pelo</p><p>menos U$ 1.057,75.</p><p>Apesar da maioria das pessoas preferir usar in, usar = any é equivalente a usar o</p><p>operador in.</p><p>Subconsultas de múltiplas colunas</p><p>Até agora, todos os exemplos de subconsulta deste capítulo retornaram uma única</p><p>coluna e uma ou mais linhas. No entanto, em determinadas situações, você pode</p><p>usar subconsultas que retornem duas ou mais colunas. Para mostrar a utilidade de</p><p>subconsultas de</p>
<p>múltiplas colunas, pode ser interessante observar primeiro um</p><p>exemplo que use múltiplas subconsultas de uma única coluna: mysql> SELECT</p><p>account_id, product_cd, cust_id -> FROM account</p><p>-> WHERE open_branch_id = (SELECT branch_id -> FROM branch</p><p>-> WHERE name = 'Woburn Branch') -> AND open_emp_id IN (SELECT emp_id -></p><p>FROM employee</p><p>-> WHERE title = 'Teller' OR title = 'Head Teller'); +------------+--------</p><p>----+---------+</p><p>| account_id | product_cd | cust_id |</p><p>+------------+------------+---------+</p><p>| 1 | CHK | 1 |</p><p>| 2 | SAV | 1 |</p><p>| 3 | CD | 1 |</p><p>| 4 | CHK | 2 |</p><p>| 5 | SAV | 2 |</p><p>| 17 | CD | 7 |</p><p>| 27 | BUS | 11 |</p><p>+------------+------------+---------+</p><p>7 rows in set (0.09 sec)</p><p>Essa consulta usa duas subconsultas para identi�car o ID da �lial de Woburn e os</p><p>IDs de todos os caixas do banco e, em seguida, a consulta-contêiner usa essas</p><p>informações para recuperar todas as contas correntes abertas por um caixa da �lial</p><p>Woburn. No entanto, como a tabela employee inclui informações a respeito de qual</p><p>�lial cada funcionário está alocado, você pode conseguir o mesmo resultado</p><p>comparando as colunas account.open_branch_id e account.open_emp_id em uma</p><p>única subconsulta às tabelas employee e branch. Para tanto, sua condição de �ltro</p><p>deve nomear ambas as colunas da tabela account entre parênteses e na mesma</p><p>ordem em que são retornadas pela subconsulta, como em: mysql> SELECT</p><p>account_id, product_cd, cust_id -> FROM account</p><p>-> WHERE (open_branch_id, open_emp_id) IN</p><p>-> (SELECT b.branch_id, e.emp_id -> FROM branch b INNER JOIN employee e -></p><p>ON b.branch_id = e.assigned_branch_id -> WHERE b.name = 'Woburn Branch'</p><p>-> AND (e.title = 'Teller' OR e.title = 'Head Teller')); +------------+----</p><p>--------+---------+</p><p>| account_id | product_cd | cust_id |</p><p>+------------+------------+---------+</p><p>| 1 | CHK | 1 |</p><p>| 2 | SAV | 1 |</p><p>| 3 | CD | 1 |</p><p>| 4 | CHK | 2 |</p><p>| 5 | SAV | 2 |</p><p>| 17 | CD | 7 |</p><p>| 27 | BUS | 11 |</p><p>+------------+------------+---------+</p><p>7 rows in set (0.00 sec)</p><p>Essa versão da consulta realiza a mesma função do exemplo anterior, mas com uma</p><p>única subconsulta que retorna duas colunas em vez das duas subconsultas que</p><p>retornavam uma única coluna cada.</p><p>É claro que você poderia reescrever o exemplo anterior simplesmente juntando as</p><p>três tabelas em vez de usar uma subconsulta, mas é muito útil durante o processo de</p><p>aprendizado de SQL ver múltiplas maneiras de se alcançar os mesmos resultados.</p><p>No entanto, aqui vai um exemplo que necessita de uma subconsulta. Seu trabalho é</p><p>encontrar todas as contas cujos saldos não correspondam à soma dos valores das</p><p>transações daquela conta.</p><p>Veja uma solução parcial para o problema:</p><p>SELECT 'ALERT! : Account #1 Has Incorrect Balance!'</p><p>FROM account</p><p>WHERE (avail_balance, pending_balance)</p><p>(SELECT SUM(),</p><p>SUM()</p><p>FROM transaction</p><p>WHERE account_id = 1)</p><p>AND account_id = 1;</p><p>Como você pode ver, não preenchi as expressões usadas para somar os valores das</p><p>transações dos saldos atual e pendente, mas prometo terminar o serviço no capítulo</p><p>11, depois que você aprender a construir expressões case. Mesmo assim, a consulta</p><p>está su�cientemente completa para mostrar que a subconsulta está gerando duas</p><p>somas a partir da tabela transaction que são, no �nal, comparadas às colunas</p><p>avail_balance e pending_balance da tabela account. Tanto a subconsulta quanto a</p><p>consulta-contêiner incluem a condição de �ltro account_id = 1, então a consulta em</p><p>sua forma atual veri�ca apenas uma conta por vez. Na próxima seção, você</p><p>aprenderá a escrever uma forma mais geral dessa consulta que veri�cará todas as</p><p>contas em uma única execução.</p><p>Subconsultas correlatas</p><p>Todas as subconsultas mostradas até agora foram independentes de suas instruções-</p><p>contêineres, o que signi�ca que você pode executá-las de forma isolada e</p><p>inspecionar os resultados. Uma subconsulta correlata, no entanto, é dependente de</p><p>sua instrução-contêiner, da qual ela referencia uma ou mais colunas. Diferentemente</p><p>de uma subconsulta não-correlata, uma subconsulta correlata não é executada uma</p><p>vez, anteriormente à execução da instrução-contêiner: em vez disso, a subconsulta</p><p>correlata é executada uma vez para cada linha-candidata (linhas que podem ser</p><p>incluídas no resultado �nal). Por exemplo, a seguinte consulta usa uma subconsulta</p><p>correlata para contar o número de contas de cada cliente, e então a consulta-</p><p>contêiner recupera aqueles clientes que têm exatamente duas contas: mysql></p><p>SELECT c.cust_id, c.cust_type_cd, c.city -> FROM customer c</p><p>-> WHERE 2 = (SELECT COUNT(*)</p><p>-> FROM account a</p><p>-> WHERE a.cust_id = c.cust_id); +---------+--------------+---------+</p><p>| cust_id | cust_type_cd | city |</p><p>+---------+--------------+---------+</p><p>| 2 | I | Woburn |</p><p>| 3 | I | Quincy |</p><p>| 6 | I | Waltham |</p><p>| 8 | I | Salem |</p><p>| 10 | B | Salem |</p><p>+---------+--------------+---------+</p><p>5 rows in set (0.01 sec)</p><p>A referência a c.cust_id bem no �nal da subconsulta é o que torna a subconsulta</p><p>correlata: a consulta-contêiner deve fornecer valores de c.cust_id para que a</p><p>subconsulta possa ser executada. Nesse caso, a consulta-contêiner recupera as 13</p><p>linhas da tabela customer e executa a subconsulta uma vez para cada cliente,</p><p>passando o ID de cliente apropriado a cada execução. Se a subconsulta retornar o</p><p>valor 2, a condição de �ltro é preenchida e a linha é adicionada ao conjunto-</p><p>resultado.</p><p>Além das condições de igualdade, você pode usar as subconsultas correlatas em</p><p>outros tipos de condições, como na condição de intervalo ilustrada aqui: mysql></p><p>SELECT c.cust_id, c.cust_type_cd, c.city -> FROM customer c</p><p>-> WHERE (SELECT SUM(a.avail_balance) -> FROM account a</p><p>-> WHERE a.cust_id = c.cust_id)</p><p>-> BETWEEN 5000 AND 10000;</p><p>+---------+--------------+------------+</p><p>| cust_id | cust_type_cd | city |</p><p>+---------+--------------+------------+</p><p>| 4 | I | Waltham |</p><p>| 7 | I | Wilmington |</p><p>| 11 | B | Wilmington |</p><p>+---------+--------------+------------+</p><p>3 rows in set (0.02 sec)</p><p>Essa variante da consulta anterior encontra todos os clientes cujo saldo total</p><p>disponível ao longo de todas as contas �ca entre $ 5.000 e $ 10.000. Mais uma vez, a</p><p>subconsulta correlata é executada 13 vezes (uma vez para cada linha de cliente) e</p><p>cada execução da subconsulta retorna o saldo em conta total de um determinado</p><p>cliente.</p><p>Outra diferença sutil na consulta anterior é que a subconsulta está no lado esquerdo da</p><p>condição, o que pode parecer estranho, mas é perfeitamente válido.</p><p>No �nal da seção anterior, demonstrei como se veri�cam os saldos disponível e</p><p>pendente de uma conta relativos às transações efetuadas nessa mesma conta, e</p><p>prometi mostrar a você como modi�car o exemplo para rodar todas as contas em</p><p>uma única execução. Con�ra o exemplo novamente: SELECT 'ALERT! : Account #1</p><p>Has Incorrect Balance!'</p><p>FROM account</p><p>WHERE (avail_balance, pending_balance)</p><p>(SELECT SUM(),</p><p>SUM()</p><p>FROM transaction</p><p>WHERE account_id = 1)</p><p>AND account_id = 1;</p><p>Usando uma subconsulta correlata em vez de uma subconsulta não-correlata, você</p><p>pode executar a consulta-contêiner uma vez, e a subconsulta será executada uma vez</p><p>para cada conta. Con�ra a versão atualizada: SELECT CONCAT('ALERT! : Account</p><p>#', a.account_id,</p><p>' Has Incorrect Balance!')</p><p>FROM account a</p><p>WHERE (a.avail_balance, a.pending_balance)</p><p>(SELECT SUM(),</p><p>SUM()</p><p>FROM transaction t</p><p>WHERE t.account_id = a.account_id);</p><p>A subconsulta agora inclui uma condição de �ltro vinculando o ID de conta da</p><p>transação ao ID de conta da consulta-contêiner. A cláusula select também foi</p><p>modi�cada para concatenar uma mensagem de alerta que inclui o ID de conta em</p><p>vez do valor literal 1.</p><p>Operador exists</p><p>Apesar de ser comum encontrar subconsultas correlatas sendo usadas em condições</p><p>de igualdade e de intervalo, o operador mais comumente usado para construir</p><p>condições que utilizem subconsultas correlatas é o operador exists. Ele é usado</p><p>quando se quer identi�car que um relacionamento existe,</p>
<p>independentemente da</p><p>quantidade. Por exemplo, a consulta a seguir encontra todas as contas nas quais</p><p>uma transação foi efetuada em um dia em particular, independentemente de</p><p>quantas transações foram efetuadas: SELECT a.account_id, a.product_cd, a.cust_id,</p><p>a.avail_balance</p><p>FROM account a</p><p>WHERE EXISTS (SELECT 1</p><p>FROM transaction t</p><p>WHERE t.account_id = a.account_id</p><p>AND t.txn_date = '2008-09-22');</p><p>Ao usar o operador exists, sua subconsulta pode retornar zero, uma ou várias</p><p>linhas, e a condição simplesmente veri�ca se a subconsulta retornou alguma linha.</p><p>Se olhar para a cláusula select da subconsulta, verá que ela consiste em um valor</p><p>literal (1): já que a condição da consulta-contêiner precisa saber apenas quantas</p><p>linhas foram retornadas, os dados reais da subconsulta retornada são irrelevantes.</p><p>Sua subconsulta pode retornar o que você quiser, como demonstrado a seguir:</p><p>SELECT a.account_id, a.product_cd, a.cust_id, a.avail_balance</p><p>FROM account a</p><p>WHERE EXISTS (SELECT t.txn_id, 'hello', 3.1415927</p><p>FROM transaction t</p><p>WHERE t.account_id = a.account_id</p><p>AND t.txn_date = '2008-09-22');</p><p>No entanto, a convenção é especi�car select 1 ou select * quando se estiver</p><p>usando exists. Você também pode usar not exists para veri�car se as subconsultas</p><p>não retornam linhas, como demonstrado a seguir: mysql> SELECT</p><p>a.account_id, a.product_cd, a.cust_id -> FROM account a</p><p>-> WHERE NOT EXISTS (SELECT 1</p><p>-> FROM business b</p><p>-> WHERE b.cust_id = a.cust_id); +------------+------------+---------+</p><p>| account_id | product_cd | cust_id |</p><p>+------------+------------+---------+</p><p>| 1 | CHK | 1 |</p><p>| 2 | SAV | 1 |</p><p>| 3 | CD | 1 |</p><p>| 4 | CHK | 2 |</p><p>| 5 | SAV | 2 |</p><p>| 7 | CHK | 3 |</p><p>| 8 | MM | 3 |</p><p>| 10 | CHK | 4 |</p><p>| 11 | SAV | 4 |</p><p>| 12 | MM | 4 |</p><p>| 13 | CHK | 5 |</p><p>| 14 | CHK | 6 |</p><p>| 15 | CD | 6 |</p><p>| 17 | CD | 7 |</p><p>| 18 | CHK | 8 |</p><p>| 19 | SAV | 8 |</p><p>| 21 | CHK | 9 |</p><p>| 22 | MM | 9 |</p><p>| 23 | CD | 9 |</p><p>+------------+------------+---------+</p><p>19 rows in set (0.99 sec)</p><p>Essa consulta encontra todos os clientes cujo ID de cliente não apareça na tabela</p><p>business, que é uma maneira indireta de encontrar todos os clientes não-</p><p>corporativos.</p><p>Manipulação de dados usando subconsultas correlatas</p><p>Todos os exemplos mostrados até agora no capítulo foram instruções select, mas</p><p>não pense que isso signi�ca que as subconsultas não sejam úteis em outras</p><p>instruções SQL. Subconsultas também são muito usadas em instruções update,</p><p>delete e insert, com as subconsultas correlatas aparecendo frequentemente nas</p><p>instruções update e delete. Aqui está um exemplo de uma subconsulta correlata</p><p>usada para modi�car a coluna last_activity_date da tabela account: UPDATE</p><p>account a</p><p>SET a.last_activity_date =</p><p>(SELECT MAX(t.txn_date)</p><p>FROM transaction t</p><p>WHERE t.account_id = a.account_id);</p><p>Essa instrução modi�ca cada linha da tabela account (pois não há uma cláusula</p><p>where) encontrando a data da última transação feita em cada conta. Apesar de</p><p>parecer razoável esperar que cada conta tenha pelo menos uma transação vinculada</p><p>a ela, seria melhor veri�car se uma conta tem alguma transação antes de tentar</p><p>atualizar a coluna last_activity_data; senão, a coluna será con�gurada como null,</p><p>já que a subconsulta não retornaria nenhuma linha. Aqui está outra versão da</p><p>instrução update, dessa vez empregando uma cláusula where com uma segunda</p><p>subconsulta correlata: UPDATE account a</p><p>SET a.last_activity_date =</p><p>(SELECT MAX(t.txn_date)</p><p>FROM transaction t</p><p>WHERE t.account_id = a.account_id)</p><p>WHERE EXISTS (SELECT 1</p><p>FROM transaction t</p><p>WHERE t.account_id = a.account_id);</p><p>As duas subconsultas correlatas são idênticas, exceto pelas cláusulas select. No</p><p>entanto, a subconsulta na cláusula set é executada apenas se a condição na cláusula</p><p>where da instrução update for avaliada como true (signi�cando que pelo menos uma</p><p>transação foi encontrada para a conta), protegendo, assim, os dados da coluna</p><p>last_activity_date de serem sobrescritos com um null.</p><p>Subconsultas correlatas também são comuns em instruções delete. Por exemplo,</p><p>você pode executar um script de manutenção de dados no �nal de cada mês que</p><p>remova dados desnecessários. O script pode incluir a seguinte instrução, que remove</p><p>os dados da tabela department que não tenham linhas-�lhas na tabela employee:</p><p>DELETE FROM department</p><p>WHERE NOT EXISTS (SELECT 1</p><p>FROM employee</p><p>WHERE employee.dept_id = department.dept_id);</p><p>Ao usar subconsultas correlatas com instruções delete no MySQl, lembre-se de que</p><p>aliases de tabela não são permitidos por nenhum motivo, e foi por isso que tive que</p><p>usar o nome completo da tabela na subconsulta. Com a maioria dos outros</p><p>servidores de banco de dados, você poderia fornecer aliases para as tabelas</p><p>department e employee, como em: DELETE FROM department d</p><p>WHERE NOT EXISTS (SELECT 1</p><p>FROM employee e</p><p>WHERE e.dept_id = d.dept_id);</p><p>Quando usar subconsultas</p><p>Agora que você aprendeu sobre os diferentes tipos de subconsultas e os diferentes</p><p>operadores que você pode empregar para interagir com os dados retornados pelas</p><p>subconsultas, chegou a hora de explorar as várias maneiras pelas quais você pode</p><p>usar subconsultas para construir instruções SQL poderosas. As próximas três seções</p><p>demonstram como você pode usar subconsultas para construir tabelas</p><p>personalizadas, para construir condições e para gerar valores de colunas em</p><p>conjuntos-resultado.</p><p>Subconsultas como fontes de dados</p><p>Lá no capítulo 3, eu a�rmei que a cláusula from de uma instrução select nomeia as</p><p>tabelas a serem usadas pela consulta. Já que uma subconsulta gera um conjunto-</p><p>resultado contendo linhas e colunas de dados, é perfeitamente válido incluir</p><p>subconsultas em sua cláusula from junto com tabelas. Apesar de isso parecer, à</p><p>primeira vista, ser uma funcionalidade interessante sem muitos méritos práticos, o</p><p>uso de subconsultas junto com tabelas é uma das ferramentas mais poderosas</p><p>disponíveis ao se escrever consultas. Veja um exemplo simples: mysql> SELECT</p><p>d.dept_id, d.name, e_cnt.how_many num_employees -> FROM</p><p>department d INNER JOIN</p><p>-> (SELECT dept_id, COUNT(*) how_many -> FROM employee</p><p>-> GROUP BY dept_id) e_cnt</p><p>-> ON d.dept_id = e_cnt.dept_id; +---------+----------------+--------------</p><p>-+</p><p>| dept_id | name | num_employees |</p><p>+---------+----------------+---------------+</p><p>| 1 | Operations | 14 |</p><p>| 2 | Loans | 1 |</p><p>| 3 | Administration | 3 |</p><p>+---------+----------------+---------------+</p><p>3 rows in set (0.04 sec)</p><p>Nesse exemplo, uma subconsulta gerou uma lista de IDs departamento junto com o</p><p>número de funcionários alocados em cada departamento. Con�ra o conjunto-</p><p>resultado gerado pela subconsulta: mysql> SELECT dept_id, COUNT(*)</p><p>how_many -> FROM employee</p><p>-> GROUP BY dept_id;</p><p>+---------+----------+</p><p>| dept_id | how_many |</p><p>+---------+----------+</p><p>| 1 | 14 |</p><p>| 2 | 1 |</p><p>| 3 | 3 |</p><p>+---------+----------+</p><p>3 rows in set (0.00 sec)</p><p>A subconsulta recebe o nome e_cnt e é juntada à tabela department por meio da</p><p>coluna dept_id. A consulta-contêiner, então, recupera o ID e o nome de</p><p>departamento da tabela department, junto com a contagem de funcionários da</p><p>subconsulta e_cnt.</p><p>Subconsultas usadas na cláusula from devem ser não-correlatas: elas são executadas</p><p>primeiro, e os dados são mantidos em memória até a consulta-contêiner terminar</p><p>sua execução. As subconsultas oferecem uma �exibilidade enorme ao se escrever</p><p>consultas, porque você pode ir muito além do conjunto de tabelas disponíveis para</p><p>criar praticamente qualquer view dos dados que você escolher e, então, juntar os</p><p>resultados com outras tabelas ou subconsultas. Se estiver gerando relatórios ou</p><p>mecanismos de alimentação de dados para sistemas externos, você conseguirá fazer</p><p>coisas com uma única consulta que demandariam múltiplas consultas ou o uso de</p><p>uma linguagem procedural para alcançar o mesmo resultado.</p><p>Fabricação de dados</p><p>Além de usar subconsultas para resumir dados existentes, você pode usá-las para</p><p>gerar dados que não existam em qualquer forma dentro de seu banco de dados. Por</p><p>exemplo, você pode querer agrupar seus clientes pela quantia de dinheiro existente</p><p>em suas contas de depósito, mas usando de�nições de grupo que não estejam</p><p>armazenadas em seu banco</p>
<p>na �gura 1.3,</p><p>você verá três colunas: cust_id (que contém o número identi�cador do cliente),</p><p>fname (que contém o prenome do cliente) e lname (que contém o último sobrenome</p><p>do cliente). A seguir na tabela customer (cliente), olhando na horizontal, você verá</p><p>duas linhas, uma contendo os dados de George Blake e a outra contendo os de Sue</p><p>Smith. O número de colunas que uma tabela pode conter difere de servidor para</p><p>servidor, mas costuma ser grande o su�ciente a ponto de não ser uma preocupação</p><p>(o Microsoft SQL Server, por exemplo, permite até 1.024 colunas por tabela). O</p><p>número de linhas que uma tabela pode conter é mais uma questão de limites físicos</p><p>(ou seja, quanto espaço em disco está disponível) e de manutenibilidade (ou seja, o</p><p>quão grande uma tabela pode �car antes de �car difícil de trabalhar com ela) do que</p><p>de limitações do servidor de banco de dados.</p><p>Figura 1.3 – Exibição relacional dos dados de conta.</p><p>Cada tabela em um banco de dados relacional inclui informações que identi�cam</p><p>de forma única uma linha em uma tabela (conhecida como chave primária), além de</p><p>informações adicionais necessárias para descrever completamente a entidade.</p><p>Olhando novamente para a tabela customer, a coluna cust_id tem um número</p><p>diferente para cada cliente; George Blake, por exemplo, pode ser identi�cado de</p><p>forma inequívoca por meio do ID de cliente #1. Nenhum outro cliente terá aquele</p><p>identi�cador e nenhuma outra informação é necessária para localizar os dados de</p><p>George Blake na tabela customer.</p><p>Cada servidor de banco de dados fornece um mecanismo para gerar conjuntos únicos</p><p>de números a serem usados como valores de chave primária, então você não precisa se</p><p>preocupar em rastrear quais números já foram utilizados.</p><p>Embora eu pudesse ter optado por usar a combinação das colunas fname e lname</p><p>como chave primária (uma chave primária consistindo em duas ou mais colunas é</p><p>denominada chave composta), pode muito bem acontecer de duas ou mais pessoas</p><p>com os mesmos nomes e sobrenomes possuírem conta no banco. Portanto, optei por</p><p>incluir a coluna cust_id na tabela customer especi�camente para ser usada como</p><p>uma coluna de chave primária.</p><p>Nesse exemplo, a escolha do par fname/lname como chave primária poderia ser</p><p>referida como uma chave natural, ao passo que a escolha de cust_id poderia ser</p><p>referida como uma chave substituta. A decisão de empregar chaves naturais ou</p><p>substitutas é um tópico de amplo debate, mas nesse caso particular a escolha é clara, já</p><p>que o sobrenome de uma pessoa pode mudar (tal como quando uma pessoa adota o</p><p>sobrenome do cônjuge), e as colunas da chave primária nunca devem ter seus valores</p><p>modi�cados após a atribuição inicial.</p><p>Algumas das tabelas também contêm informações usadas para navegar para outras</p><p>tabelas; é aqui que entram os “dados redundantes” mencionados anteriormente. Por</p><p>exemplo, a tabela account inclui uma coluna denominada cust_id, que contém o</p><p>identi�cador único do cliente que abriu a conta, junto com a coluna denominada</p><p>product_cd, que contém o identi�cador único do produto ao qual a conta</p><p>corresponderá. Essas colunas são conhecidas como chaves estrangeiras e servem ao</p><p>mesmo propósito das linhas que conectam as entidades nas versões hierárquicas e</p><p>em rede das informações de conta. Se estiver procurando um registro de conta</p><p>especí�co e desejar saber mais informações sobre o cliente que abriu a conta, você</p><p>poderia pegar o valor da coluna cust_id e usá-lo para encontrar a linha apropriada</p><p>na tabela customer (esse processo é conhecido, no jargão do banco de dados</p><p>relacional, como uma junção [join]; as junções são introduzidas no capítulo 3 e são</p><p>analisadas em profundidade nos capítulos 5 e 10).</p><p>Pode parecer um desperdício armazenar os mesmos dados várias vezes, mas o</p><p>modelo relacional é bem claro sobre quais dados redundantes devem ser</p><p>armazenados. Por exemplo, é apropriado para a tabela account incluir uma coluna</p><p>do identi�cador único do cliente que abriu a conta, mas não é adequado incluir</p><p>também o nome e o sobrenome do cliente nessa tabela. Se um cliente mudar de</p><p>sobrenome, por exemplo, você vai querer ter certeza de que existe apenas um lugar</p><p>no banco de dados que armazena o nome do cliente; de outra forma, o dado</p><p>poderia ser modi�cado em um lugar, mas não no outro, prejudicando a</p><p>con�abilidade dos dados no banco de dados. O lugar apropriado para esses dados é</p><p>a tabela customer, e apenas o valor de cust_id deveria ser incluído em outras</p><p>tabelas. Também não é indicado para uma coluna conter vários segmentos de</p><p>informação, tal como uma coluna de nome que contenha tanto o nome quanto o</p><p>sobrenome, ou uma coluna de endereço que contenha informações de rua, cidade,</p><p>estado e CEP. O processo de re�namento do projeto de banco de dados com o</p><p>objetivo de garantir que cada peça de informação independente esteja em apenas</p><p>um lugar (com exceção das chaves estrangeiras) é conhecido como normalização.</p><p>Voltando às quatro tabelas da �gura 1.3, você deve estar se perguntando como</p><p>poderia usá-las para encontrar as transações de conta corrente de George Blake.</p><p>Primeiro, você encontraria o identi�cador único de George Blake na tabela customer.</p><p>Então, você encontraria a linha da tabela account cuja coluna cust_id contenha o</p><p>identi�cador único do George e cuja coluna product_cd corresponda à linha da</p><p>tabela product cuja coluna name seja igual a "CHK". Por �m, você localizaria as linhas</p><p>na tabela transaction cuja coluna account_iid corresponda ao identi�cador único</p><p>da tabela account. Isso pode soar complicado, mas você pode fazê-lo com apenas um</p><p>comando usando a linguagem SQL, como veremos em breve.</p><p>Um pouco de terminologia</p><p>Introduzi algumas terminologias novas nas seções anteriores, então talvez agora seja</p><p>o momento para algumas de�nições formais. A tabela 1.1 mostra os termos que</p><p>usaremos no restante do livro, junto com suas de�nições.</p><p>Tabela 1.1 – Termos e de�nições</p><p>Termo De�nição</p><p>Entidade</p><p>Algo de interesse para a comunidade de usuários do banco de dados. Exemplos incluem clientes, partes,</p><p>localizações geográficas etc.</p><p>Coluna Um dado armazenado em uma tabela.</p><p>Linha</p><p>Um conjunto de colunas que, coletivamente, descreve de forma completa uma entidade ou alguma ação em</p><p>uma entidade. Também chamada de registro.</p><p>Tabela</p><p>Um conjunto de linhas, mantidas em memória volátil (não-persistente) ou em armazenamento permanente</p><p>(persistente).</p><p>Conjunto-</p><p>resultado</p><p>Outro nome para uma tabela não-persistente, geralmente o resultado de uma consulta SQL.</p><p>Chave primária Uma ou mais colunas que podem ser usadas como um identificador único de cada linha em uma tabela.</p><p>Chave</p><p>estrangeira</p><p>Uma ou mais colunas que podem ser usadas em conjunto para identificar uma única linha em outra tabela.</p><p>O que é SQL?</p><p>Junto com a de�nição de Codd do modelo relacional, ele propôs uma linguagem</p><p>chamada DSL/Alpha para manipular os dados em tabelas relacionais. Logo após a</p><p>publicação do artigo de Codd, a IBM comissionou um grupo para construir um</p><p>protótipo de suas ideias. Esse grupo criou uma versão simpli�cada da DSL/Alpha,</p><p>que foi chamada de SQUARE. Re�namentos da SQUARE levaram a uma linguagem</p><p>denominada SEQUEL, que foi, �nalmente, renomeada para SQL.</p><p>A SQL está entrando na meia-idade (assim como este autor...) e ela passou por um</p><p>grande número de mudanças ao longo do caminho. Em meados da década de 1980,</p><p>o American National Standars Institute (ANSI) começou a trabalhar no primeiro</p><p>padrão da linguagem SQL, que foi publicado em 1986. Re�namentos subsequentes</p><p>levaram a novos lançamentos do padrão SQL em 1989, 1992, 1999, 2003 e 2006. Junto</p><p>com os re�namentos do núcleo da linguagem, novas funções foram adicionadas a</p><p>ela para incorporar funcionalidades orientadas a objeto, entre outras coisas. O</p><p>último padrão, SQL 2006, focou na integração entre SQL e XML e de�ne a</p><p>linguagem chamada Xquery, que é usada para consultar dados em documentos</p><p>XML.</p><p>A SQL anda de mãos dadas com o modelo relacional porque o resultado de uma</p><p>consulta SQL é uma tabela (também chamada, nesse contexto, de conjunto-</p><p>resultado ou, em inglês, result set). Portanto,</p>
<p>de dados. Digamos, por exemplo, que você queira</p><p>ordenar seus clientes pelos grupos mostrados na tabela 9.1.</p><p>Table 9.1. Grupos de clientes por saldo1</p><p>Nome do grupo Limite inferior Limite superior</p><p>Small Fry 0 $4.999,99</p><p>Average Joes $5.000 $9.999,99</p><p>Heavy Hitters $10.000 $9.999.999,99</p><p>Para gerar esses grupos dentro de uma única consulta, você precisaria de uma</p><p>maneira de de�nir esses três grupos. O primeiro passo é de�nir uma consulta que</p><p>gere as de�nições de grupo:</p><p>Eu usei o operador de conjunto union all para mesclar os resultados das três</p><p>consultas separadas em um único conjunto-resultado. Cada consulta retorna três</p><p>literais, e os resultados das três consultas são agrupados para gerar um conjunto-</p><p>resultado com três linhas e três colunas. Agora você tem uma consulta que gera os</p><p>grupos desejados e pode colocá-la dentro da cláusula from de outra consulta para</p><p>gerar seus grupos de clientes: mysql> SELECT groups.name, COUNT(*)</p><p>num_customers -> FROM</p><p>-> (SELECT SUM(a.avail_balance) cust_balance -> FROM account a INNER JOIN</p><p>product p -> ON a.product_cd = p.product_cd -> WHERE p.product_type_cd =</p><p>'ACCOUNT'</p><p>-> GROUP BY a.cust_id) cust_rollup -> INNER JOIN</p><p>-> (SELECT 'Small Fry' name, 0 low_limit, 4999.99 high_limit -> UNION ALL</p><p>-> SELECT 'Average Joes' name, 5000 low_limit, -> 9999.99 high_limit</p><p>-> UNION ALL</p><p>-> SELECT 'Heavy Hitters' name, 10000 low_limit, -> 9999999.99 high_limit)</p><p>groups -> ON cust_rollup.cust_balance</p><p>-> BETWEEN groups.low_limit AND groups.high_limit -> GROUP BY groups.name;</p><p>+---------------+---------------+</p><p>| name | num_customers |</p><p>+---------------+---------------+</p><p>| Average Joes | 2 |</p><p>| Heavy Hitters | 4 |</p><p>| Small Fry | 5 |</p><p>+---------------+---------------+</p><p>3 rows in set (0.01 sec)</p><p>A cláusula from contém duas subconsultas: a primeira, nomeada cust_rollup,</p><p>retorna os saldos de depósito atuais de cada cliente, enquanto a segunda, nomeada</p><p>groups, gera os três agrupamentos de clientes. Veja os dados gerados por</p><p>cust_rollup: mysql> SELECT SUM(a.avail_balance) cust_balance -></p><p>FROM account a INNER JOIN product p -> ON a.product_cd =</p><p>p.product_cd -> WHERE p.product_type_cd = 'ACCOUNT'</p><p>-> GROUP BY a.cust_id;</p><p>+--------------+</p><p>| cust_balance |</p><p>+--------------+</p><p>| 4557.75 |</p><p>| 2458.02 |</p><p>| 3270.25 |</p><p>| 6788.98 |</p><p>| 2237.97 |</p><p>| 10122.37 |</p><p>| 5000.00 |</p><p>| 3875.18 |</p><p>| 10971.22 |</p><p>| 23575.12 |</p><p>| 38552.05 |</p><p>+--------------+</p><p>11 rows in set (0.05 sec)</p><p>Os dados gerados por cust_rollup são, então, juntados à tabela groups por meio de</p><p>uma condição de intervalo (cust_rollup.cust_balance Between groups.low_limit</p><p>and groups.high_limit). Por �m, os dados juntados são agrupados e o número de</p><p>clientes em cada grupo é contado para gerar o conjunto-resultado �nal.</p><p>É claro que você poderia simplesmente decidir construir uma tabela permanente</p><p>para armazenar as de�nições de grupo em vez de usar uma subconsulta. Usando</p><p>essa abordagem, e após um certo tempo, você acabaria com um banco de dados</p><p>abarrotado de pequenas tabelas com propósitos especí�cos, e não se lembraria da</p><p>razão pela qual a maioria delas foi criada. Já trabalhei em ambientes nos quais os</p><p>usuários de banco de dados tinham permissão para criar suas próprias tabelas com</p><p>propósitos especiais, e os resultados foram desastrosos (tabelas não incluídas em</p><p>backups, tabelas perdidas durante atualizações do servidor, servidor ocioso devido a</p><p>problemas de alocação de espaço etc.). Armado com as subconsultas, você</p><p>conseguirá aderir a uma política em que tabelas são adicionadas a um banco de</p><p>dados apenas quando há uma necessidade de negócio clara para se armazenar novos</p><p>dados.</p><p>Subconsultas orientadas a tarefas</p><p>Em sistemas usados em relatórios ou na geração de alimentação de dados, você</p><p>normalmente encontrará o seguinte:</p><p>Essa consulta soma os saldos de todas as contas de depósito por tipo de conta,</p><p>mostra o funcionário que abriu as contas e as �liais onde as contas foram abertas.</p><p>Se observar atentamente a consulta, verá que as tabelas product, branch e employee</p><p>são necessárias apenas por questões de exibição, e que a tabela account tem todos os</p><p>dados necessários para gerar os agrupamentos (product_cd, open_branch_id,</p><p>open_emp_id e avail_balance). Portanto, você pode separar a tarefa de gerar os</p><p>grupos em uma subconsulta e, então, juntar as outras três tabelas geradas pela</p><p>subconsulta para obter o resultado �nal desejado. Aqui está a subconsulta de</p><p>agrupamento:</p><p>Isso é o coração da consulta: as outras tabelas são necessárias apenas para fornecer</p><p>strings signi�cativas no lugar das colunas de chave estrangeira product_cd,</p><p>open_branch_id e open_emp_id. A próxima consulta engloba a consulta à tabela</p><p>account em uma subconsulta e junta a tabela resultante às outras três tabelas:</p><p>Eu sei que a beleza está nos olhos de quem a vê, mas acho que essa versão da</p><p>consulta é muito mais satisfatória do que a versão anterior, que era grande e</p><p>plani�cada. Essa versão também pode acabar executando mais rápido, porque os</p><p>agrupamentos são feitos sobre colunas de chave estrangeira pequenas e numéricas</p><p>(product_cd, open_branch_id, open_emp_id), em vez das colunas de string</p><p>potencialmente grandes (branch.name, product.name, employee.fname,</p><p>employee.lname).</p><p>Subconsultas em condições de �ltro</p><p>Muitos dos exemplos deste capítulo usaram subconsultas como expressões em</p><p>condições de �ltro, então não deve lhe surpreender o fato de que esse é um dos</p><p>principais usos das subconsultas. No entanto, condições de �ltro que usam</p><p>subconsultas não são encontradas apenas na cláusula where. Por exemplo, a consulta</p><p>a seguir usa uma subconsulta na cláusula having para encontrar o funcionário</p><p>responsável pela abertura da maioria das contas: mysql> SELECT</p><p>open_emp_id, COUNT(*) how_many -> FROM account</p><p>-> GROUP BY open_emp_id</p><p>-> HAVING COUNT(*) = (SELECT MAX(emp_cnt.how_many) -> FROM (SELECT COUNT(*)</p><p>how_many -> FROM account</p><p>-> GROUP BY open_emp_id) emp_cnt); +-------------+----------+</p><p>| open_emp_id | how_many |</p><p>+-------------+----------+</p><p>| 1 | 8 |</p><p>+-------------+----------+</p><p>1 row in set (0.01 sec)</p><p>A subconsulta na cláusula having encontra o número máximo de contas abertas por</p><p>um funcionário, e a consulta-contêiner encontra o funcionário que abriu aquele</p><p>número de contas. Se vários funcionários empatarem com o número máximo de</p><p>contas abertas, a consulta retornará múltiplas linhas.</p><p>Subconsultas como geradoras de expressões</p><p>Nesta última seção do capítulo, termino onde comecei: com subconsultas escalares</p><p>de coluna e linha únicas. Além de serem usadas em condições de �ltro, subconsultas</p><p>escalares podem ser usadas em qualquer lugar que possa aparecer uma expressão,</p><p>incluindo as cláusulas select e order by de uma consulta e a cláusula values de</p><p>uma instrução insert.</p><p>Em “Subconsultas orientadas a tarefas”, mostrei como você usaria uma subconsulta</p><p>para separar o mecanismo de agrupamento do resto da consulta. Aqui está outra</p><p>versão da mesma consulta, que usa as subconsultas com o mesmo propósito, mas de</p><p>forma diferente:</p><p>Existem duas diferenças principais entre essa consulta e a versão anterior que usava</p><p>uma subconsulta na cláusula from: • Em vez de juntar as tabelas product, branch e</p><p>employee aos dados de conta, subconsultas escalares correlatas são usadas na</p><p>cláusula select para procurar os nomes de produto, �lial e funcionário.</p><p>• O conjunto-resultado tem 14 linhas em vez de 11, e três dos nomes de produtos</p><p>são null.</p><p>A razão das três linhas extras no conjunto-resultado é que a versão anterior da</p><p>consulta incluía a condição de �ltro p.product_type_cd = 'Account'. Esse �ltro</p><p>eliminava as linhas com tipo de produto INSURANCE e LOAN, como no caso de</p><p>empréstimos (loans) a pequenas empresas. Como essa versão da consulta não inclui</p><p>uma junção à tabela product, não há como incluir a condição de �ltro na consulta</p><p>principal. A subconsulta correlata feita à tabela product inclui esse �ltro, mas o</p><p>único efeito acaba sendo deixar o nome de produto como null. Se quiser se livrar</p><p>das três linhas extras, você poderia juntar a tabela product à tabela account e incluir</p><p>a condição de �ltro, ou poderia</p>
<p>simplesmente fazer o seguinte:</p><p>Simplesmente envolvendo a consulta anterior em uma subconsulta (chamada</p><p>all_prods) e adicionando uma condição de �ltro para excluir os valores null da</p><p>coluna product, a consulta agora retorna as 11 linhas desejadas. O resultado �nal é</p><p>uma consulta que realiza todos os agrupamentos relativos aos dados brutos da</p><p>tabelas account e, então, embeleza a saída usando dados das outras três tabelas –</p><p>isso sem usar nenhuma junção.</p><p>Como citado anteriormente, as subconsultas escalares também podem aparecer na</p><p>cláusula order by. A consulta a seguir recupera os dados dos funcionários</p><p>ordenados pelo sobrenome do chefe de cada funcionário e, em seguida, pelo</p><p>sobrenome do funcionário:</p><p>A consulta usa duas subconsultas escalares correlatas: uma na cláusula select para</p><p>recuperar o nome completo do chefe de cada funcionário e outra na cláusula order</p><p>by para retornar apenas o sobrenome do chefe de cada funcionário por questões de</p><p>ordenação.</p><p>Além de usar subconsultas escalares correlatas em instruções select, você pode usar</p><p>subconsultas escalares não-correlatas para gerar valores para uma instrução insert.</p><p>Por exemplo, digamos que você vai gerar uma nova linha de conta e que lhe</p><p>passaram os seguintes dados: • O nome do produto (“savings account”).</p><p>• O ID federal do cliente (“555-55-5555”).</p><p>• O nome da �lial onde a conta foi aberta (“Quincy Branch”).</p><p>• O nome e o sobrenome do caixa que abriu a conta (“Frank Portman”).</p><p>Antes de criar uma linha na tabela account, você precisará procurar os valores-chave</p><p>de todos esses fragmentos de dados para poder popular as colunas de chave</p><p>estrangeira da tabela account. Você tem duas escolhas de como fazê-lo: executar</p><p>quatro consultas para recuperar os valores de chave primária e colocar esses valores</p><p>em uma instrução insert, ou usar subconsultas para recuperar os quatro valores-</p><p>chave dentro de uma instrução insert. Veja um exemplo da segunda abordagem:</p><p>INSERT INTO account</p><p>(account_id, product_cd, cust_id, open_date, last_activity_date,</p><p>status, open_branch_id, open_emp_id, avail_balance, pending_balance)</p><p>VALUES (NULL,</p><p>(SELECT product_cd FROM product WHERE name = 'savings account'),</p><p>(SELECT cust_id FROM customer WHERE fed_id = '555-55-5555'),</p><p>'2008-09-25', '2008-09-25', 'ACTIVE',</p><p>(SELECT branch_id FROM branch WHERE name = 'Quincy Branch'),</p><p>(SELECT emp_id FROM employee WHERE lname = 'Portman' AND fname = 'Frank'),</p><p>0, 0);</p><p>Usando uma única instrução SQL, você pode criar uma linha na tabela account e</p><p>pesquisar quatro valores de chave estrangeira ao mesmo tempo. No entanto, há um</p><p>problema nessa abordagem. Quando você usa subconsultas para gerar dados de</p><p>colunas que permitam valores null, sua instrução insert pode ser bem-sucedida</p><p>mesmo se uma das subconsultas falhar em retornar um valor. Por exemplo, se você</p><p>errar a digitação do nome Frank Portman na quarta subconsulta, ainda será criada</p><p>uma linha na tabela account, mas a coluna open_emp_id será con�gurada como null.</p><p>Resumo das subconsultas</p><p>Vimos muita coisa neste capítulo, então é uma boa ideia revisá-lo. Os exemplos que</p><p>usei neste capítulo demonstraram subconsultas que: • Retornam uma única coluna</p><p>e linha, uma única coluna com múltiplas linhas, e múltiplas colunas e linhas.</p><p>• São independentes da instrução-contêiner (subconsultas não-correlatas).</p><p>• Referenciam uma ou mais colunas da instrução-contêiner (subconsultas</p><p>correlatas).</p><p>• São usadas em condições que utilizam operadores de comparação, bem como os</p><p>operadores de propósito mais especí�co in, not in, exists e not exists.</p><p>• Podem ser encontradas em instruções select, update, delete e insert.</p><p>• Geram conjuntos-resultados que podem ser juntados a outras tabelas (ou</p><p>subconsultas) em uma consulta.</p><p>• Podem ser usadas na geração de valores para popular uma tabela ou para</p><p>popular colunas do conjunto-resultado de uma consulta.</p><p>• São usadas nas cláusulas select, from, where e order by das consultas.</p><p>Obviamente, as subconsultas são uma ferramenta versátil. Portanto, não se sinta mal</p><p>se todos esses conceitos não foram assimilados após a primeira leitura deste</p><p>capítulo. Continue testando os vários usos das subconsultas, e logo você se verá</p><p>pensando em como utilizar uma subconsulta toda vez que precisar escrever uma</p><p>instrução SQL incomum.</p><p>Teste seu conhecimento</p><p>Estes exercícios foram desenvolvidos para testar sua compreensão de subconsultas.</p><p>Por favor, consulte o apêndice C para conferir as soluções.</p><p>Exercício 9.1</p><p>Construa uma consulta à tabela account que use uma condição de �ltro com uma</p><p>subconsulta não-correlata à tabela product para encontrar todas as contas de</p><p>empréstimo (product.product_type_cd = 'LOAN'). Recupere o ID de conta, o código</p><p>de produto, o ID de cliente e o saldo disponível.</p><p>Exercício 9.2</p><p>Refaça a consulta do exercício 9.1 usando uma subconsulta correlata à tabela product</p><p>para obter os mesmos resultados.</p><p>Exercício 9.3</p><p>Junte a consulta a seguir à tabela employee para mostrar o nível de experiência de</p><p>cada funcionário: SELECT 'trainee' name, '2004-01-01' start_dt, '2005-12-31' end_dt</p><p>UNION ALL</p><p>SELECT 'worker' name, '2002-01-01' start_dt, '2003-12-31' end_dt</p><p>UNION ALL</p><p>SELECT 'mentor' name, '2000-01-01' start_dt, '2001-12-31' end_dt</p><p>Atribua à subconsulta o alias levels e inclua o ID de funcionário, nome, sobrenome</p><p>e nível de experiência (levels.name). (Dica: construa uma condição de junção</p><p>usando uma condição de desigualdade para determinar em qual nível a coluna</p><p>employee.start_date se enquadra).</p><p>Exercício 9.4</p><p>Construa uma consulta à tabela employee que recupere o ID de funcionário, o nome</p><p>e o sobrenome, junto com os nomes do departamento e da �lial aos quais o</p><p>funcionário foi alocado. Não junte nenhuma tabela.</p><p>1 N. de T.: os três grupos poderiam ser traduzidos como “Peixe pequeno”, “Sujeitos comuns” e</p><p>“Pesos-pesados”.</p><p>CAPÍTULO 10</p><p>Junções revisitadas</p><p>A esta altura, você deve estar confortável com o conceito de junção interna (inner</p><p>join), que foi apresentado no capítulo 5. Este capítulo se concentra nas outras</p><p>maneiras de juntar tabelas, incluindo a junção externa (outer join) e a junção</p><p>cruzada (cross join).</p><p>Junções externas</p><p>Em todos os exemplos mostrados até agora que incluíam múltiplas tabelas, não nos</p><p>preocupamos com o fato de que as condições de junção poderiam falhar em</p><p>encontrar correspondências para todas as linhas das tabelas. Por exemplo, ao juntar</p><p>a tabela account à tabela customer, não mencionei a possibilidade de um valor na</p><p>coluna cust_id da tabela account poder não corresponder a um valor da coluna</p><p>cust_id na tabela customer. Se esse fosse o caso, algumas das linhas em uma tabela</p><p>ou outra �cariam de fora do conjunto-resultado.</p><p>Apenas para ter certeza, vamos veri�car os dados nas tabelas. Con�ra as colunas</p><p>account_id e cust_id da tabela account: mysql> SELECT account_id,</p><p>cust_id -> FROM account;</p><p>+------------+---------+</p><p>| account_id | cust_id |</p><p>+------------+---------+</p><p>| 1 | 1 |</p><p>| 2 | 1 |</p><p>| 3 | 1 |</p><p>| 4 | 2 |</p><p>| 5 | 2 |</p><p>| 7 | 3 |</p><p>| 8 | 3 |</p><p>| 10 | 4 |</p><p>| 11 | 4 |</p><p>| 12 | 4 |</p><p>| 13 | 5 |</p><p>| 14 | 6 |</p><p>| 15 | 6 |</p><p>| 17 | 7 |</p><p>| 18 | 8 |</p><p>| 19 | 8 |</p><p>| 21 | 9 |</p><p>| 22 | 9 |</p><p>| 23 | 9 |</p><p>| 24 | 10 |</p><p>| 25 | 10 |</p><p>| 27 | 11 |</p><p>| 28 | 12 |</p><p>| 29 | 13 |</p><p>+------------+---------+</p><p>24 rows in set (1.50 sec)</p><p>Existem 24 contas pertencentes a 13 clientes diferentes, com os IDs de cliente de 1 a</p><p>13 tendo pelo menos uma conta. Veja o conjunto de IDs de cliente da tabela</p><p>customer: mysql> SELECT cust_id</p><p>-> FROM customer;</p><p>+---------+</p><p>| cust_id |</p><p>+---------+</p><p>| 1 |</p><p>| 2 |</p><p>| 3 |</p><p>| 4 |</p><p>| 5 |</p><p>| 6 |</p><p>| 7 |</p><p>| 8 |</p><p>| 9 |</p><p>| 10 |</p><p>| 11 |</p><p>| 12 |</p><p>| 13 |</p><p>+---------+</p><p>13 rows in set (0.02 sec)</p><p>Há 13 linhas na tabela customer com IDs de 1 a 13, então cada ID de cliente está</p><p>incluído pelo menos uma vez na tabela account. Portanto, quando as duas tabelas</p><p>são juntadas pela coluna cust_id, você esperaria que todas as 24 linhas fossem</p><p>incluídas no conjunto-resultado (excetuando qualquer outra condição de �ltro):</p><p>mysql> SELECT a.account_id, c.cust_id -> FROM account a INNER</p><p>JOIN customer c -> ON a.cust_id = c.cust_id;</p><p>+------------+---------+</p><p>| account_id</p>
<p>| cust_id |</p><p>+------------+---------+</p><p>| 1 | 1 |</p><p>| 2 | 1 |</p><p>| 3 | 1 |</p><p>| 4 | 2 |</p><p>| 5 | 2 |</p><p>| 7 | 3 |</p><p>| 8 | 3 |</p><p>| 10 | 4 |</p><p>| 11 | 4 |</p><p>| 12 | 4 |</p><p>| 13 | 5 |</p><p>| 14 | 6 |</p><p>| 15 | 6 |</p><p>| 17 | 7 |</p><p>| 18 | 8 |</p><p>| 19 | 8 |</p><p>| 21 | 9 |</p><p>| 22 | 9 |</p><p>| 23 | 9 |</p><p>| 24 | 10 |</p><p>| 25 | 10 |</p><p>| 27 | 11 |</p><p>| 28 | 12 |</p><p>| 29 | 13 |</p><p>+------------+---------+</p><p>24 rows in set (0.06 sec)</p><p>Como esperado, as 24 linhas estão presentes no conjunto-resultado. Mas o que</p><p>acontece se você juntar a tabela account a uma das tabelas de clientes especializadas,</p><p>como a tabela business?</p><p>mysql> SELECT a.account_id, b.cust_id, b.name -> FROM account a INNER JOIN</p><p>business b -> ON a.cust_id = b.cust_id;</p><p>+------------+---------+------------------------+</p><p>| account_id | cust_id | name |</p><p>+------------+---------+------------------------+</p><p>| 24 | 10 | Chilton Engineering |</p><p>| 25 | 10 | Chilton Engineering |</p><p>| 27 | 11 | Northeast Cooling Inc. |</p><p>| 28 | 12 | Superior Auto Body |</p><p>| 29 | 13 | AAA Insurance Inc. |</p><p>+------------+---------+------------------------+</p><p>5 rows in set (0.10 sec)</p><p>Em vez de 24 linhas no conjunto-resultado, agora existem apenas cinco. Vamos olhar</p><p>a tabela business para ver o porquê disso: mysql> SELECT cust_id, name</p><p>-> FROM business;</p><p>+---------+------------------------+</p><p>| cust_id | name |</p><p>+---------+------------------------+</p><p>| 10 | Chilton Engineering |</p><p>| 11 | Northeast Cooling Inc. |</p><p>| 12 | Superior Auto Body |</p><p>| 13 | AAA Insurance Inc. |</p><p>+---------+------------------------+</p><p>4 rows in set (0.01 sec)</p><p>Das 13 linhas na tabela customer, apenas quatro representam clientes corporativos, e</p><p>como um desses clientes corporativos tem duas contas, um total de cinco linhas da</p><p>tabela account é vinculado aos clientes corporativos.</p><p>Mas e se você quiser que sua consulta retorne todas as contas, incluindo o nome da</p><p>empresa, apenas se a conta estiver vinculada a um cliente corporativo? Este é um</p><p>exemplo em que você precisaria de uma junção externa entre as tabelas account e</p><p>business, como em: mysql> SELECT a.account_id, a.cust_id, b.name</p><p>-> FROM account a LEFT OUTER JOIN business b -> ON a.cust_id</p><p>= b.cust_id;</p><p>+------------+---------+------------------------+</p><p>| account_id | cust_id | name |</p><p>+------------+---------+------------------------+</p><p>| 1 | 1 | NULL |</p><p>| 2 | 1 | NULL |</p><p>| 3 | 1 | NULL |</p><p>| 4 | 2 | NULL |</p><p>| 5 | 2 | NULL |</p><p>| 7 | 3 | NULL |</p><p>| 8 | 3 | NULL |</p><p>| 10 | 4 | NULL |</p><p>| 11 | 4 | NULL |</p><p>| 12 | 4 | NULL |</p><p>| 13 | 5 | NULL |</p><p>| 14 | 6 | NULL |</p><p>| 15 | 6 | NULL |</p><p>| 17 | 7 | NULL |</p><p>| 18 | 8 | NULL |</p><p>| 19 | 8 | NULL |</p><p>| 21 | 9 | NULL |</p><p>| 22 | 9 | NULL |</p><p>| 23 | 9 | NULL |</p><p>| 24 | 10 | Chilton Engineering |</p><p>| 25 | 10 | Chilton Engineering |</p><p>| 27 | 11 | Northeast Cooling Inc. |</p><p>| 28 | 12 | Superior Auto Body |</p><p>| 29 | 13 | AAA Insurance Inc. |</p><p>+------------+---------+------------------------+</p><p>24 rows in set (0.04 sec)</p><p>Uma junção externa inclui todas as linhas de uma das tabelas e inclui os dados da</p><p>segunda tabela apenas quando linhas correspondentes são encontradas. Nesse caso,</p><p>todas as linhas da tabela account são incluídas, já que especi�quei left outer join</p><p>e a tabela account está no lado esquerdo da de�nição de junção. A coluna name</p><p>recebe null em todas as linhas, exceto no caso das linhas dos quatro clientes</p><p>corporativos (cust_ids 10, 11, 12 e 13). Veja uma consulta semelhante com uma</p><p>junção externa à tabela individual em vez da tabela business:</p><p>Essa consulta é, essencialmente, o inverso da anterior: o nome e o sobrenome são</p><p>fornecidos para clientes pessoa física, enquanto essas colunas recebem null no caso</p><p>de clientes corporativos.</p><p>Junção externa esquerda versus junção externa direita</p><p>Em cada um dos exemplos de junção externa da seção anterior, especi�quei left</p><p>outer join. A palavra-chave left (esquerda) indica que a tabela no lado esquerdo da</p><p>junção é responsável por determinar o número de linhas do conjunto-resultado,</p><p>enquanto a tabela no lado direito é usada para fornecer valores de coluna sempre</p><p>que uma correspondência é encontrada. Considere a seguinte consulta: mysql></p><p>SELECT c.cust_id, b.name -> FROM customer c LEFT OUTER JOIN</p><p>business b -> ON c.cust_id = b.cust_id;</p><p>+---------+------------------------+</p><p>| cust_id | name |</p><p>+---------+------------------------+</p><p>| 1 | NULL |</p><p>| 2 | NULL |</p><p>| 3 | NULL |</p><p>| 4 | NULL |</p><p>| 5 | NULL |</p><p>| 6 | NULL |</p><p>| 7 | NULL |</p><p>| 8 | NULL |</p><p>| 9 | NULL |</p><p>| 10 | Chilton Engineering |</p><p>| 11 | Northeast Cooling Inc. |</p><p>| 12 | Superior Auto Body |</p><p>| 13 | AAA Insurance Inc. |</p><p>+---------+------------------------+</p><p>13 rows in set (0.00 sec)</p><p>A cláusula from especi�ca uma junção externa à esquerda, então todas as 13 linhas</p><p>da tabela customer são incluídas no conjunto-resultado, com a tabela business</p><p>contribuindo com valores para a segunda coluna do conjunto-resultado no caso dos</p><p>quatro clientes corporativos. Se você executar a mesma consulta, mas indicando</p><p>right outer join, você veria os seguintes resultados: mysql> SELECT</p><p>c.cust_id, b.name -> FROM customer c RIGHT OUTER JOIN</p><p>business b -> ON c.cust_id = b.cust_id;</p><p>+---------+------------------------+</p><p>| cust_id | name |</p><p>+---------+------------------------+</p><p>| 10 | Chilton Engineering |</p><p>| 11 | Northeast Cooling Inc. |</p><p>| 12 | Superior Auto Body |</p><p>| 13 | AAA Insurance Inc. |</p><p>+---------+------------------------+</p><p>4 rows in set (0.00 sec)</p><p>O número de linhas do conjunto-resultado agora é determinado pelo número de</p><p>linhas da tabela business, o que explica o fato de o conjunto-resultado ter apenas</p><p>quatro linhas.</p><p>Tenha em mente que ambas as consultas estão realizando junções externas: as</p><p>palavras-chave left (esquerda) e right (direita) estão ali apenas para dizer ao</p><p>servidor qual tabela tem permissão para deixar lacunas nos dados. Se você quiser</p><p>juntar externamente as tabelas A e B e quiser todas as linhas de A com colunas</p><p>adicionais de B sempre que houver dados correspondentes, você pode especi�car A</p><p>left outer join B ou B right outer join A.</p><p>Junções externas de três tabelas</p><p>Em alguns casos, você pode querer juntar externamente uma tabela com duas</p><p>outras tabelas. Por exemplo, você pode querer uma lista de todas as contas</p><p>mostrando o nome e o sobrenome no caso de clientes pessoa física, ou o nome da</p><p>empresa no caso de clientes corporativos, como em:</p><p>Os resultados incluem todas as 24 linhas da tabela account, junto com o nome da</p><p>pessoa ou o nome da empresa vindo das duas outras tabelas juntadas externamente.</p><p>Não conheço nenhuma restrição do MySQL com relação ao número de tabelas que</p><p>podem ser juntadas externamente em uma mesma tabela, mas você sempre pode</p><p>usar subconsultas para limitar o número de junções em sua consulta. Por exemplo,</p><p>você poderia rescrever o exemplo anterior da seguinte maneira: mysql> SELECT</p><p>account_ind.account_id, account_ind.product_cd, -></p><p>account_ind.person_name,</p><p>-> b.name business_name</p><p>-> FROM</p><p>-> (SELECT a.account_id, a.product_cd, a.cust_id, -> CONCAT(i.fname, ' ',</p><p>i.lname) person_name -> FROM account a LEFT OUTER JOIN individual i -> ON</p><p>a.cust_id = i.cust_id) account_ind -> LEFT OUTER JOIN business b</p><p>-> ON account_ind.cust_id = b.cust_id; +------------+------------+---------</p><p>--------+------------------------+</p><p>| account_id | product_cd | person_name | business_name |</p><p>+------------+------------+-----------------+------------------------+</p><p>| 1 | CHK | James Hadley | NULL |</p><p>| 2 | SAV | James Hadley | NULL |</p><p>| 3 | CD | James Hadley | NULL |</p><p>| 4 | CHK | Susan Tingley | NULL |</p><p>| 5 | SAV | Susan Tingley | NULL |</p><p>| 7 | CHK | Frank Tucker | NULL |</p><p>| 8 | MM | Frank Tucker | NULL |</p><p>| 10 | CHK | John Hayward | NULL |</p><p>| 11 | SAV | John Hayward | NULL |</p><p>| 12 | MM | John Hayward | NULL |</p><p>| 13 | CHK | Charles Frasier | NULL |</p><p>| 14 | CHK | John Spencer | NULL |</p><p>| 15 | CD | John Spencer | NULL |</p><p>| 17 | CD | Margaret Young | NULL |</p><p>| 18 | CHK | George Blake | NULL |</p><p>| 19 | SAV | George Blake | NULL |</p><p>| 21 | CHK | Richard Farley | NULL |</p><p>| 22 | MM | Richard Farley | NULL |</p><p>| 23 | CD | Richard Farley | NULL |</p><p>| 24 | CHK | NULL | Chilton Engineering |</p><p>| 25 | BUS | NULL | Chilton Engineering |</p><p>| 27 | BUS | NULL | Northeast Cooling Inc. |</p><p>| 28 | CHK | NULL | Superior Auto Body |</p><p>|</p>
<p>29 | SBL | NULL | AAA Insurance Inc. |</p><p>+------------+------------+-----------------+------------------------+</p><p>24 rows in set (0.08 sec)</p><p>Nessa versão da consulta, a tabela individual é juntada externamente à tabela</p><p>account dentro de uma subconsulta nomeada account_ind, cujos resultados são,</p><p>então, juntados externamente à tabela business. Portanto, cada consulta (a</p><p>subconsulta e a consulta-contêiner) usa apenas uma única junção externa. Se estiver</p><p>usando um banco de dados que não seja o MySQL, você pode precisar utilizar essa</p><p>estratégia caso queira juntar externamente mais de duas tabelas.</p><p>Autojunções externas</p><p>No capítulo 5, apresentei o conceito de autojunção, em que uma tabela é da a si</p><p>mesma. Aqui está um exemplo de autojunção do capítulo 5, que junta a tabela</p><p>employee a si mesma para gerar uma lista de funcionários e seus supervisores:</p><p>Essa consulta funciona bem, exceto por um pequeno detalhe: funcionários que não</p><p>tenham um supervisor são descartados do conjunto-resultado. No entanto,</p><p>mudando a junção interna para uma junção externa, o conjunto-resultado incluirá</p><p>todos os funcionários, inclusive aqueles sem supervisores:</p><p>O conjunto-resultado agora inclui Michael Smith, que é o presidente do banco e</p><p>que, portanto, não tem um supervisor. A consulta utiliza uma junção externa à</p><p>esquerda para gerar uma lista de todos os funcionários e, se aplicável, seu</p><p>supervisor. Se mudar a junção para uma junção externa à direita, você verá os</p><p>seguintes resultados:</p><p>Essa consulta mostra cada supervisor (ainda a terceira e a quarta colunas) junto</p><p>com o conjunto de funcionários que ele (ou ela) supervisiona. Portanto, Michael</p><p>Smith aparece duas vezes, como supervisor de Susan Barker e Robert Tyler; Susan</p><p>Barker aparece uma vez, como supervisora de ninguém (valores null na primeira e</p><p>na segunda colunas). Todos os 18 funcionários aparecem pelo menos uma vez na</p><p>terceira e quarta colunas, com alguns deles aparecendo mais de uma vez, caso</p><p>supervisionem mais de um funcionário, formando um total de 28 linhas no</p><p>conjunto-resultado. Esse resultado é diferente da consulta anterior, e isso aconteceu</p><p>mudando apenas uma palavra-chave (de left para right). Portanto, ao usar junções</p><p>externas, certi�que-se de que especi�cou corretamente o uso de junções externas à</p><p>esquerda ou à direita.</p><p>Junções cruzadas</p><p>No capítulo 5, apresentei o conceito de produto cartesiano, que é essencialmente o</p><p>resultado da junção de múltiplas tabelas sem especi�car quaisquer condições de</p><p>junção. Produtos cartesianos são usados muito frequentemente por acidente (por</p><p>exemplo, esquecendo de adicionar a condição de união à cláusula from) e, fora isso,</p><p>não são muito comuns. Se, no entanto, você realmente pretende gerar um produto</p><p>cartesiano de duas tabelas, é necessário especi�car uma junção cruzada, como em:</p><p>Essa consulta gera o produto cartesiano das tabelas product e product_type,</p><p>resultando em 24 linhas (8 linhas da tabela product x 3 linhas da tabela</p><p>product_type). Mas agora que você sabe o que é uma junção cruzada e como</p><p>especi�cá-la, para que ela serve? A maioria dos livros de SQL descreverá o que é</p><p>uma junção cruzada e, então, mostrará que ela é raramente usada, mas eu gostaria</p><p>de compartilhar com você uma situação na qual creio que a junção cruzada seja</p><p>bem útil.</p><p>No capítulo 9, discuti sobre como usar subconsultas para produzir tabelas. O</p><p>exemplo que usei mostrava como construir uma tabela de três linhas que poderia</p><p>ser juntada a outras tabelas. Aqui está a tabela fabricada daquele exemplo: mysql></p><p>SELECT 'Small Fry' name, 0 low_limit, 4999.99 high_limit -</p><p>> UNION ALL</p><p>-> SELECT 'Average Joes' name, 5000 low_limit, 9999.99 high_limit -> UNION</p><p>ALL</p><p>-> SELECT 'Heavy Hitters' name, 10000 low_limit, 9999999.99 high_limit; +--</p><p>-------------+-----------+------------+</p><p>| name | low_limit | high_limit |</p><p>+---------------+-----------+------------+</p><p>| Small Fry | 0 | 4999.99 |</p><p>| Average Joes | 5000 | 9999.99 |</p><p>| Heavy Hitters | 10000 | 9999999.99 |</p><p>+---------------+-----------+------------+</p><p>3 rows in set (0.00 sec)</p><p>Apesar de essa tabela ter sido exatamente o necessário para distribuir os clientes em</p><p>três grupos baseados em seus saldos agregados de conta, essa estratégia de mesclar</p><p>tabelas de linha única usando o operador de conjunto union all não funciona</p><p>muito bem caso você precise fabricar tabelas grandes.</p><p>Digamos, por exemplo, que você queira criar uma consulta que gere uma linha para</p><p>cada dia do ano de 2008, mas você não tem uma tabela em seu banco de dados que</p><p>contenha uma linha para cada dia. Usando a estratégia do exemplo no capítulo 9,</p><p>você poderia fazer algo parecido com o seguinte: SELECT '2008-01-01' dt</p><p>UNION ALL</p><p>SELECT '2008-01-02' dt</p><p>UNION ALL</p><p>SELECT '2008-01-03' dt</p><p>UNION ALL</p><p>...</p><p>...</p><p>...</p><p>SELECT '2008-12-29' dt</p><p>UNION ALL</p><p>SELECT '2008-12-30' dt</p><p>UNION ALL</p><p>SELECT '2008-12-31' dt</p><p>Construir uma consulta que mescle os resultados de 366 consultas é um tanto</p><p>cansativo, então talvez seja necessária uma estratégia diferente. E se você gerasse</p><p>uma tabela com 366 linhas (2008 foi um ano bissexto), uma única coluna contendo</p><p>um número entre 0 e 366 e, então, adicionasse aquele número de dias a 1o de</p><p>janeiro de 2008? Con�ra um dos possíveis métodos de geração dessa tabela: mysql></p><p>SELECT ones.num + tens.num + hundreds.num -> FROM</p><p>-> (SELECT 0 num UNION ALL</p><p>-> SELECT 1 num UNION ALL</p><p>-> SELECT 2 num UNION ALL</p><p>-> SELECT 3 num UNION ALL</p><p>-> SELECT 4 num UNION ALL</p><p>-> SELECT 5 num UNION ALL</p><p>-> SELECT 6 num UNION ALL</p><p>-> SELECT 7 num UNION ALL</p><p>-> SELECT 8 num UNION ALL</p><p>-> SELECT 9 num) ones</p><p>-> CROSS JOIN</p><p>-> (SELECT 0 num UNION ALL</p><p>-> SELECT 10 num UNION ALL</p><p>-> SELECT 20 num UNION ALL</p><p>-> SELECT 30 num UNION ALL</p><p>-> SELECT 40 num UNION ALL</p><p>-> SELECT 50 num UNION ALL</p><p>-> SELECT 60 num UNION ALL</p><p>-> SELECT 70 num UNION ALL</p><p>-> SELECT 80 num UNION ALL</p><p>-> SELECT 90 num) tens</p><p>-> CROSS JOIN</p><p>-> (SELECT 0 num UNION ALL</p><p>-> SELECT 100 num UNION ALL</p><p>-> SELECT 200 num UNION ALL</p><p>-> SELECT 300 num) hundreds;</p><p>+------------------------------------+</p><p>| ones.num + tens.num + hundreds.num |</p><p>+------------------------------------+</p><p>| 0 |</p><p>| 1 |</p><p>| 2 |</p><p>| 3 |</p><p>| 4 |</p><p>| 5 |</p><p>| 6 |</p><p>| 7 |</p><p>| 8 |</p><p>| 9 |</p><p>| 10 |</p><p>| 11 |</p><p>| 12 |</p><p>...</p><p>...</p><p>...</p><p>| 391 |</p><p>| 392 |</p><p>| 393 |</p><p>| 394 |</p><p>| 395 |</p><p>| 396 |</p><p>| 397 |</p><p>| 398 |</p><p>| 399 |</p><p>+------------------------------------+</p><p>400 rows in set (0.00 sec)</p><p>Se você pegar o produto cartesiano dos três conjuntos {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 10,</p><p>20, 30, 40, 50, 60, 70, 80, 90} e {0, 100, 200, 300} e adicionar os valores das três</p><p>colunas, você obtém um conjunto-resultado de 400 linhas contendo todos os</p><p>números entre 0 e 399. Apesar de isso ser mais do que as 366 linhas necessárias para</p><p>gerar o conjunto de dias de 2008, é muito fácil se livrar das linhas excessivas, e</p><p>mostrarei a você como fazê-lo em breve.</p><p>O próximo passo é converter o conjunto de números em um conjunto de datas. Para</p><p>fazê-lo, usarei a função date_add() para adicionar cada um dos números do</p><p>conjunto-resultado a 1o de janeiro de 2008. Então, adicionarei uma condição de</p><p>�ltro para descartar quaisquer datas que adentrem 2009: mysql> SELECT</p><p>DATE_ADD('2008-01-01', -> INTERVAL (ones.num + tens.num +</p><p>hundreds.num) DAY) dt -> FROM</p><p>-> (SELECT 0 num UNION ALL</p><p>-> SELECT 1 num UNION ALL</p><p>-> SELECT 2 num UNION ALL</p><p>-> SELECT 3 num UNION ALL</p><p>-> SELECT 4 num UNION ALL</p><p>-> SELECT 5 num UNION ALL</p><p>-> SELECT 6 num UNION ALL</p><p>-> SELECT 7 num UNION ALL</p><p>-> SELECT 8 num UNION ALL</p><p>-> SELECT 9 num) ones</p><p>-> CROSS JOIN</p><p>-> (SELECT 0 num UNION ALL</p><p>-> SELECT 10 num UNION ALL</p><p>-> SELECT 20 num UNION ALL</p><p>-> SELECT 30 num UNION ALL</p><p>-> SELECT 40 num UNION ALL</p><p>-> SELECT 50 num UNION ALL</p><p>-> SELECT 60 num UNION ALL</p><p>-> SELECT 70 num UNION ALL</p><p>-> SELECT 80 num UNION ALL</p><p>-> SELECT 90 num) tens</p><p>-> CROSS JOIN</p><p>-> (SELECT 0 num UNION ALL</p><p>-> SELECT 100 num UNION ALL</p><p>-> SELECT 200 num UNION ALL</p><p>-> SELECT 300 num) hundreds</p><p>-> WHERE DATE_ADD('2008-01-01',</p><p>-> INTERVAL (ones.num + tens.num + hundreds.num) DAY) ORDER BY 1;</p><p>+------------+</p><p>| dt |</p><p>+------------+</p><p>| 2008-01-01 |</p><p>| 2008-01-02 |</p><p>| 2008-01-03 |</p><p>| 2008-01-04 |</p><p>| 2008-01-05</p>
<p>|</p><p>| 2008-01-06 |</p><p>| 2008-01-07 |</p><p>| 2008-01-08 |</p><p>| 2008-01-09 |</p><p>| 2008-01-10 |</p><p>...</p><p>...</p><p>...</p><p>| 2008-02-20 |</p><p>| 2008-02-21 |</p><p>| 2008-02-22 |</p><p>| 2008-02-23 |</p><p>| 2008-02-24 |</p><p>| 2008-02-25 |</p><p>| 2008-02-26 |</p><p>| 2008-02-27 |</p><p>| 2008-02-28 |</p><p>| 2008-02-29 |</p><p>| 2008-03-01 |</p><p>...</p><p>...</p><p>...</p><p>| 2008-12-20 |</p><p>| 2008-12-21 |</p><p>| 2008-12-22 |</p><p>| 2008-12-23 |</p><p>| 2008-12-24 |</p><p>| 2008-12-25 |</p><p>| 2008-12-26 |</p><p>| 2008-12-27 |</p><p>| 2008-12-28 |</p><p>| 2008-12-29 |</p><p>| 2008-12-30 |</p><p>| 2008-12-31 |</p><p>+------------+</p><p>366 rows in set (0.01 sec)</p><p>A parte legal dessa abordagem é que o conjunto-resultado automaticamente inclui o</p><p>dia bissexto extra (29 de fevereiro) sem a sua intervenção, já que o servidor de banco</p><p>de dados percebe esse fato ao adicionar 59 dias a 1o de janeiro de 2008.</p><p>Agora que você tem um mecanismo para fabricar todos os dias de 2008, o que você</p><p>faria com isso? Bem, podem pedir a você que gere uma consulta mostrando cada dia</p><p>de 2008 junto com o número de transações bancárias conduzidas naquele dia, o</p><p>número de contas abertas naquele dia e assim por diante. Veja um exemplo que</p><p>responde à primeira questão: mysql> SELECT days.dt, COUNT(t.txn_id)</p><p>-> FROM transaction t RIGHT OUTER JOIN</p><p>-> (SELECT DATE_ADD('2008-01-01',</p><p>-> INTERVAL (ones.num + tens.num + hundreds.num) DAY) dt -> FROM</p><p>-> (SELECT 0 num UNION ALL</p><p>-> SELECT 1 num UNION ALL</p><p>-> SELECT 2 num UNION ALL</p><p>-> SELECT 3 num UNION ALL</p><p>-> SELECT 4 num UNION ALL</p><p>-> SELECT 5 num UNION ALL</p><p>-> SELECT 6 num UNION ALL</p><p>-> SELECT 7 num UNION ALL</p><p>-> SELECT 8 num UNION ALL</p><p>-> SELECT 9 num) ones</p><p>-> CROSS JOIN</p><p>-> (SELECT 0 num UNION ALL</p><p>-> SELECT 10 num UNION ALL</p><p>-> SELECT 20 num UNION ALL</p><p>-> SELECT 30 num UNION ALL</p><p>-> SELECT 40 num UNION ALL</p><p>-> SELECT 50 num UNION ALL</p><p>-> SELECT 60 num UNION ALL</p><p>-> SELECT 70 num UNION ALL</p><p>-> SELECT 80 num UNION ALL</p><p>-> SELECT 90 num) tens</p><p>-> CROSS JOIN</p><p>-> (SELECT 0 num UNION ALL</p><p>-> SELECT 100 num UNION ALL</p><p>-> SELECT 200 num UNION ALL</p><p>-> SELECT 300 num) hundreds</p><p>-> WHERE DATE_ADD('2008-01-01',</p><p>-> INTERVAL (ones.num + tens.num + hundreds.num) DAY) '2009-01-01')</p><p>days</p><p>-> ON days.dt = t.txn_date</p><p>-> GROUP BY days.dt</p><p>-> ORDER BY 1;</p><p>+------------+-----------------+</p><p>| dt | COUNT(t.txn_id) |</p><p>+------------+-----------------+</p><p>| 2008-01-01 | 0 |</p><p>| 2008-01-03 | 0 |</p><p>| 2008-01-04 | 0 |</p><p>| 2008-01-05 | 21 |</p><p>| 2008-01-06 | 0 |</p><p>| 2008-01-07 | 0 |</p><p>| 2008-01-08 | 0 |</p><p>| 2008-01-09 | 0 |</p><p>| 2008-01-10 | 0 |</p><p>| 2008-01-11 | 0 |</p><p>| 2008-01-12 | 0 |</p><p>| 2008-01-13 | 0 |</p><p>| 2008-01-14 | 0 |</p><p>| 2008-01-15 | 0 |</p><p>...</p><p>| 2008-12-31 | 0 |</p><p>+------------+-----------------+</p><p>366 rows in set (0.03 sec)</p><p>Essa é uma das consultas mais interessantes feitas até agora neste livro, pois inclui</p><p>junções cruzadas, junções externas, uma função de data, agrupamentos, operações</p><p>de conjunto (union all) e uma função de agregação (count()). Ela também não é</p><p>uma das soluções mais elegantes para o problema dado, mas serve de exemplo para</p><p>mostrar como, com um pouco de criatividade e um conhecimento sólido da</p><p>linguagem, você pode tornar uma funcionalidade pouco utilizada, como as junções</p><p>cruzadas, em uma ferramenta poderosa de seu kit de ferramentas SQL.</p><p>Junções naturais</p><p>Se você for preguiçoso (e não somos todos?), você pode escolher um tipo de junção</p><p>que permita nomear as tabelas a serem juntadas, mas que deixe a cabo do servidor</p><p>determinar quais serão as condições de junção. Conhecida como junção natural,</p><p>esse tipo de junção depende da existência de nomes de coluna idênticos ao longo de</p><p>múltiplas tabelas para inferir as condições de junção apropriadas. Por exemplo, a</p><p>tabela account inclui uma coluna nomeada cust_id, que é a chave estrangeira da</p><p>tabela customer, cuja chave primária também é nomeada cust_id. Então, você pode</p><p>escrever uma consulta que use junções naturais para juntar as duas tabelas:</p><p>Como você especi�cou uma junção natural, o servidor inspecionou as de�nições</p><p>das tabelas e adicionou a condição de junção a.cust_id = c.cust_id para juntar as</p><p>duas tabelas.</p><p>Tudo está indo muito bem, mas e se as colunas não tiverem o mesmo nome ao longo</p><p>das tabelas? Por exemplo, a tabela account também tem uma chave estrangeira da</p><p>tabela branch, mas a coluna na tabela account é nomeada open_branch_id em vez de</p><p>apenas branch_id. Vejamos o que acontece se eu usar natural join entre as tabelas</p><p>account e branch:</p><p>accou t e b a c :</p><p>Parece que algo deu errado: a consulta deveria retornar não mais do que 24 linhas,</p><p>já que existem 24 linhas na tabela account. O que aconteceu foi que, como o</p><p>servidor não pôde encontrar duas colunas nomeadas de forma idêntica nas duas</p><p>tabelas, nenhuma condição de união foi gerada e, com isso, foi realizada uma junção</p><p>cruzada entre elas, resultando em 96 linhas (24 contas x 4 �liais).</p><p>Então, no �nal das contas, poupar os pobres dedos cansados do esforço de digitar a</p><p>condição de junção valeu a pena? Absolutamente não: você deve evitar esse tipo de</p><p>junção e, em seu lugar, usar junções internas com condições explícitas de junção.</p><p>Teste seu conhecimento</p><p>Os seguintes exercícios testam seu conhecimento de junções externas e cruzadas. Por</p><p>favor, consulte o apêndice C para veri�car as soluções.</p><p>Exercício 10.1</p><p>Escreva uma consulta que retorne todos os nomes de produtos junto com as contas</p><p>baseadas naquele produto (use a coluna product_cd da tabela account para vinculá-</p><p>la à tabela product). Inclua todos os produtos, mesmo que nenhuma conta tenha</p><p>sido aberta para aquele produto.</p><p>Exercício 10.2</p><p>Reformule a sua consulta do exercício 10.1 para usar o outro tipo de junção externa</p><p>(por exemplo, se você usou uma junção externa à esquerda no exercício 10.1, use uma</p><p>junção externa à direita desta vez), de forma que os resultados sejam idênticos aos</p><p>do exercício 10.1.</p><p>Exercício 10.3</p><p>Junte externamente a tabela account às tabelas individual e business (por meio da</p><p>coluna account.cust_id) de tal forma que o conjunto-resultado contenha uma linha</p><p>por conta. As colunas a serem incluídas são account.account_id,</p><p>account.product_cd, individual.fname, individual.lname e business.name.</p><p>Exercício 10.4 (Crédito extra)</p><p>Invente uma consulta que gere o conjunto {1, 2, 3,..., 99, 100}. (Dica: use uma junção</p><p>cruzada com pelo menos duas subconsultas na cláusula from).</p><p>CAPÍTULO 11</p><p>Lógica condicional</p><p>Em certas situações, você pode querer que a sua lógica SQL siga em uma direção ou</p><p>outra, dependendo dos valores de certas colunas ou expressões. Este capítulo</p><p>focaliza em como escrever instruções que podem se comportar diferentemente</p><p>dependendo dos dados encontrados durante a execução da instrução.</p><p>O que é lógica condicional?</p><p>Lógica condicional é simplesmente a habilidade de seguir um entre vários caminhos</p><p>durante a execução do programa. Por exemplo, ao consultar informações de clientes,</p><p>você pode desejar recuperar as colunas fname/lname da tabela individual ou a</p><p>coluna name da tabela business, dependendo de qual tipo de cliente é encontrado.</p><p>Usando junções externas, você poderia retornar ambas as strings e deixar que o</p><p>usuário descubra qual usar, como em:</p><p>O usuário pode observar o valor da coluna cust_type_cd e decidir se deve usar a</p><p>coluna indiv_name ou a coluna business_name. No entanto, em vez disso, você</p><p>poderia usar a lógica condicional por meio de uma expressão case para determinar</p><p>o tipo de cliente e retornar a string apropriada, como em:</p><p>Essa versão da consulta retorna uma única coluna de nome que é gerada pela</p><p>expressão case iniciando na segunda linha da consulta, que, nesse exemplo, veri�ca o</p><p>valor da coluna cust_type_cd e retorna o primeiro e o último nome da pessoa ou o</p><p>nome da empresa.</p><p>Expressão case</p><p>Todos os principais servidores de banco de dados incluem funções nativas</p><p>projetadas para imitar a instrução if-then-else encontrada na maioria das</p><p>linguagens de programação (exemplos incluem a função decode() do Oracle, a</p><p>função if() do MySQL e a função coalesce() do SQL Server). Expressões case</p><p>também são projetadas para facilitar a lógica if-then-else, mas gozam de duas</p><p>vantagens sobre as funções nativas:</p><p>• A expressão case é parte do padrão SQL (versão SQL92) e foi implementada pelo</p><p>Oracle Database, MySQL,</p>
<p>Sybase, PostgreSQL, IBM UDB e por outros.</p><p>• Expressões case fazem parte da sintaxe SQL e podem ser incluídas em instruções</p><p>select, insert, update e delete.</p><p>As próximas duas subseções apresentam os dois diferentes tipos de expressões case</p><p>e, no �nal, eu mostro alguns exemplos de expressões case em ação.</p><p>Expressões case pesquisadas</p><p>A expressão case demonstrada anteriormente é um exemplo de uma expressão case</p><p>pesquisada, que tem a seguinte sintaxe: CASE</p><p>WHEN C1 THEN E1</p><p>WHEN C2 THEN E2</p><p>...</p><p>WHEN CN THEN EN</p><p>[ELSE EP]</p><p>END</p><p>Na de�nição anterior, os símbolos C1, C2, ..., CN representam condições, e os símbolos</p><p>E1, E2, ..., EN representam expressões que serão retornadas pela expressão case. Se a</p><p>condição de uma cláusula when retorna true, a expressão case retorna a expressão</p><p>correspondente. Além disso, o símbolo EP representa a expressão padrão, retornada</p><p>pela expressão case se nenhuma das condições C1, C2, ..., CN retornar true (a cláusula</p><p>else é opcional, por isso está entre colchetes). Todas as expressões retornadas pelas</p><p>várias cláusulas when devem retornar o mesmo tipo de dados (por exemplo, date,</p><p>number, varchar).</p><p>Con�ra um exemplo de uma expressão case pesquisada: CASE</p><p>WHEN employee.title = 'Head Teller'</p><p>THEN 'Head Teller'</p><p>WHEN employee.title = 'Teller'</p><p>AND YEAR(employee.start_date) > 2007</p><p>THEN 'Teller Trainee'</p><p>WHEN employee.title = 'Teller'</p><p>AND YEAR(employee.start_date)</p><p>SELECT YEAR(open_date) year, COUNT(*) how_many -> FROM</p><p>account -> WHERE open_date > '1999-12-31'</p><p>-> AND open_date GROUP BY YEAR(open_date); +------+----------+</p><p>| year | how_many |</p><p>+------+----------+</p><p>| 2000 | 3 |</p><p>| 2001 | 4 |</p><p>| 2002 | 5 |</p><p>| 2003 | 3 |</p><p>| 2004 | 9 |</p><p>+------+----------+</p><p>5 rows in set (0.00 sec)</p><p>No entanto, você foi instruído a retornar uma única linha de dados com seis colunas</p><p>(uma para cada ano do intervalo solicitado). Para transformar esse conjunto-</p><p>resultado em uma única linha, você precisará criar seis colunas e, dentro de cada</p><p>coluna, somar apenas as linhas pertencentes ao ano em questão:</p><p>As seis colunas da consulta anterior são idênticas, exceto pelo valor do ano. Quando</p><p>a função extract() retorna o ano desejado para aquela coluna, a expressão case</p><p>retorna o valor 1: caso contrário, ela retorna 0. Ao somar todas as contas abertas</p><p>desde 2000, cada coluna retorna o número de contas abertas para aquele ano.</p><p>Obviamente, tais transformações são práticas apenas quando se tem uma</p><p>quantidade pequena de valores: gerar uma coluna para cada ano desde 1905 logo se</p><p>transformaria em uma tarefa cansativa.</p><p>Apesar de ser um pouco avançado para este livro, vale notar que tanto o SQL Server</p><p>quanto o Oracle Database 11g incluem cláusulas PIVOT especi�camente para esses tipos</p><p>de consultas.</p><p>Agregação seletiva</p><p>No capítulo 9, mostrei uma solução parcial para um exemplo que demonstrava</p><p>como encontrar contas cujos saldos não correspondessem aos dados brutos da</p><p>tabela transaction. A razão da solução parcial era que uma solução completa</p><p>necessitaria do uso de lógica condicional, mas agora temos todas as peças no lugar</p><p>para terminar o serviço. Parei neste ponto no capítulo 9: SELECT</p><p>CONCAT('ALERT! : Account #', a.account_id, ' Has Incorrect Balance!')</p><p>FROM account a</p><p>WHERE (a.avail_balance, a.pending_balance) (SELECT SUM(), SUM()</p><p>FROM transaction t</p><p>WHERE t.account_id = a.account_id);</p><p>A consulta usa uma subconsulta correlata na tabela transaction para somar as</p><p>transações individuais de uma determinada conta. Ao somar as transações, você</p><p>deve considerar as seguintes questões: • As quantidades das transações são sempre</p><p>positivas, então você deverrá observar o tipo de transação para ver se ela é um débito</p><p>ou um crédito e inverter o sinal (multiplicar por -1) no caso de transações de débito.</p><p>• Se a data da coluna funds_avail_date for maior do que o dia atual, a transação</p><p>deve ser adicionada ao saldo total pendente, mas não ao saldo total disponível.</p><p>Enquanto algumas transações precisam ser excluídas do saldo disponível, todas as</p><p>transações são incluídas no saldo pendente, tornando-o o mais simples dos dois</p><p>cálculos. Veja a expressão case usada para calcular o saldo pendente: CASE</p><p>WHEN transaction.txn_type_cd = 'DBT'</p><p>THEN transaction.amount * -1</p><p>ELSE transaction.amount</p><p>END</p><p>Assim, todas as quantias das transações são multiplicadas por -1 no caso de</p><p>transações</p>
<p>de débito e �cam como estão no caso de transações de crédito. Essa</p><p>mesma lógica também se aplica ao cálculo de saldo disponível, mas apenas as</p><p>transações que já se tornaram disponíveis devem ser incluídas. Portanto, a expressão</p><p>case usada para calcular o saldo disponível inclui mais uma cláusula when: CASE</p><p>WHEN transaction.funds_avail_date > CURRENT_TIMESTAMP() THEN 0</p><p>WHEN transaction.txn_type_cd = 'DBT'</p><p>THEN transaction.amount * -1</p><p>ELSE transaction.amount</p><p>END</p><p>Com a primeira cláusula when a postos, fundos indisponíveis, como os cheques que</p><p>não foram compensados, contribuirão com $0 à soma. Con�ra a consulta �nal com</p><p>as duas expressões case devidamente posicionadas: SELECT CONCAT('ALERT! :</p><p>Account #', a.account_id, ' Has Incorrect Balance!')</p><p>FROM account a</p><p>WHERE (a.avail_balance, a.pending_balance) (SELECT</p><p>SUM(CASE</p><p>WHEN t.funds_avail_date > CURRENT_TIMESTAMP() THEN 0</p><p>WHEN t.txn_type_cd = 'DBT'</p><p>THEN t.amount * -1</p><p>ELSE t.amount</p><p>END),</p><p>SUM(CASE</p><p>WHEN t.txn_type_cd = 'DBT'</p><p>THEN t.amount * -1</p><p>ELSE t.amount</p><p>END)</p><p>FROM transaction t</p><p>WHERE t.account_id = a.account_id);</p><p>Usando lógica condicional, as funções de agregação sum() são alimentadas com</p><p>dados manipulados pelas duas expressões case, permitindo que as quantias</p><p>apropriadas sejam somadas.</p><p>Veri�cando a existência</p><p>Algumas vezes será desejável determinar se existe um relacionamento entre duas</p><p>entidades sem se importar com quantidade. Por exemplo, você pode querer saber se</p><p>um cliente tem alguma conta corrente ou conta poupança, mas não é relevante saber</p><p>se um cliente tem mais de uma conta de cada tipo. Veja uma consulta que usa</p><p>múltiplas expressões case para gerar duas colunas de saída, uma para mostrar se o</p><p>cliente tem alguma conta corrente e outra para mostrar se o cliente tem alguma</p><p>conta poupança:</p><p>Cada expressão case inclui uma subconsulta correlata à tabela account: uma</p><p>pesquisa por contas correntes, a outra por contas poupança. Como todas as</p><p>cláusulas when usam o operador exists, as condições são avaliadas como true</p><p>sempre que o cliente possuir pelo menos uma das contas pesquisadas.</p><p>Em outros casos, você pode querer saber quantas linhas foram encontradas, mas só</p><p>até um certo ponto. Por exemplo, a próxima consulta usa uma expressão case</p><p>simples para contar o número de contas de cada cliente e, então, retornar 'None', '1',</p><p>'2' ou '3+':</p><p>Para essa consulta, eu não quis diferenciar os clientes que tenham mais de duas</p><p>contas, então a expressão case simplesmente cria uma categoria '3+'. Esse tipo de</p><p>consulta pode ser útil se você estiver procurando clientes que devem ser contatados</p><p>a respeito de abertura de uma nova conta no banco.</p><p>Erros de divisão por zero</p><p>Ao realizar cálculos que incluam divisão, você deve sempre tomar o cuidado de</p><p>veri�car que os denominadores não sejam iguais a zero. Apesar de alguns servidores</p><p>de banco de dados, como o Oracle Database, lançarem um erro quando um</p><p>denominador zero é encontrado, o MySQL simplesmente con�gura o resultado do</p><p>cálculo para null, como demonstrado a seguir: mysql> SELECT 100 / 0; +------</p><p>---+</p><p>| 100 / 0 |</p><p>+---------+</p><p>| NULL |</p><p>+---------+</p><p>1 row in set (0.00 sec)</p><p>Para evitar que seus cálculos encontrem erros ou, ainda pior, que sejam</p><p>misteriosamente con�gurados como null, você deve englobar todos os</p><p>denominadores dentro de uma lógica condicional, como mostrado a seguir: mysql></p><p>SELECT a.cust_id, a.product_cd, a.avail_balance /</p><p>-> CASE</p><p>-> WHEN prod_tots.tot_balance = 0 THEN 1</p><p>-> ELSE prod_tots.tot_balance -> END percent_of_total -> FROM account a</p><p>INNER JOIN</p><p>-> (SELECT a.product_cd, SUM(a.avail_balance) tot_balance -> FROM account a</p><p>-> GROUP BY a.product_cd) prod_tots -> ON a.product_cd =</p><p>prod_tots.product_cd; +---------+------------+------------------+</p><p>| cust_id | product_cd | percent_of_total |</p><p>+---------+------------+------------------+</p><p>| 10 | BUS | 0.000000 |</p><p>| 11 | BUS | 1.000000 |</p><p>| 1 | CD | 0.153846 |</p><p>| 6 | CD | 0.512821 |</p><p>| 7 | CD | 0.256410 |</p><p>| 9 | CD | 0.076923 |</p><p>| 1 | CHK | 0.014488 |</p><p>| 2 | CHK | 0.030928 |</p><p>| 3 | CHK | 0.014488 |</p><p>| 4 | CHK | 0.007316 |</p><p>| 5 | CHK | 0.030654 |</p><p>| 6 | CHK | 0.001676 |</p><p>| 8 | CHK | 0.047764 |</p><p>| 9 | CHK | 0.001721 |</p><p>| 10 | CHK | 0.322911 |</p><p>| 12 | CHK | 0.528052 |</p><p>| 3 | MM | 0.129802 |</p><p>| 4 | MM | 0.321915 |</p><p>| 9 | MM | 0.548282 |</p><p>| 1 | SAV | 0.269431 |</p><p>| 2 | SAV | 0.107773 |</p><p>| 4 | SAV | 0.413723 |</p><p>| 8 | SAV | 0.209073 |</p><p>| 13 | SBL | 1.000000 |</p><p>+---------+------------+------------------+</p><p>24 rows in set (0.13 sec)</p><p>Essa consulta calcula a proporção entre o saldo de cada conta e o saldo total de</p><p>todas as contas do mesmo tipo de produto. Já que alguns tipos de produtos, como os</p><p>empréstimos para empresas, poderiam ter um saldo de zero no caso de todos os</p><p>empréstimos terem sido completamente pagos, é melhor incluir a expressão case</p><p>para garantir que o denominador nunca seja zero.</p><p>Atualizações condicionais</p><p>Ao atualizar linhas em uma tabela, às vezes você precisa decidir quais valores serão</p><p>alocados em quais colunas. Por exemplo, depois de inserir uma nova transação, você</p><p>precisa modi�car as colunas avail_balance, pending_balance e last_activity da</p><p>tabela account. Apesar de as duas últimas colunas serem facilmente atualizáveis,</p><p>para modi�car corretamente a coluna avail_balance você precisa saber se os fundos</p><p>da transação estão imediatamente disponíveis veri�cando a coluna</p><p>funds_avail_date da tabela transaction. Supondo que a transação de ID 999 tenha</p><p>acabado de ser inserida, você pode usar a seguinte instrução update para modi�car</p><p>as três colunas da tabela account: 1 UPDATE account</p><p>2 SET last_activity_date = CURRENT_TIMESTAMP(), 3 pending_balance =</p><p>pending_balance +</p><p>4 (SELECT t.amount *</p><p>5 CASE t.txn_type_cd WHEN 'DBT' THEN -1 ELSE 1 END</p><p>6 FROM transaction t</p><p>7 WHERE t.txn_id = 999),</p><p>8 avail_balance = avail_balance +</p><p>9 (SELECT</p><p>10 CASE</p><p>11 WHEN t.funds_avail_date > CURRENT_TIMESTAMP() THEN 0</p><p>12 ELSE t.amount *</p><p>13 CASE t.txn_type_cd WHEN 'DBT' THEN -1 ELSE 1 END</p><p>14 END</p><p>15 FROM transaction t</p><p>16 WHERE t.txn_id = 999)</p><p>17 WHERE account.account_id =</p><p>18 (SELECT t.account_id</p><p>19 FROM transaction t</p><p>20 WHERE t.txn_id = 999);</p><p>Essa instrução contém um total de três expressões case: duas delas (linhas 5 e 13)</p><p>são usadas para inverter o sinal do total da transação em transações de débito, e a</p><p>terceira expressão case (linha 10) é usada para veri�car a data de disponibilidade</p><p>dos fundos. Se a data estiver no futuro, um zero é adicionado ao saldo disponível:</p><p>caso contrário, a quantia da transação é adicionada.</p><p>Tratando valores null</p><p>Apesar de valores null serem a coisa certa a ser armazenada em uma tabela se o</p><p>valor de uma coluna for desconhecido, nem sempre é apropriado recuperar valores</p><p>null para �ns de exibição ou para tomar parte em expressões. Por exemplo, você</p><p>pode querer exibir a palavra unknown (desconhecido) em um campo de dados na</p><p>tela em vez de deixar o campo em branco. Ao recuperar os dados, você pode usar</p><p>uma expressão case para substituir a string se o valor for null, como em: SELECT</p><p>emp_id, fname, lname,</p><p>CASE</p><p>WHEN title IS NULL THEN 'Unknown'</p><p>ELSE title</p><p>END</p><p>FROM employee;</p><p>Em cálculos, valores null costumam provocar um retorno null, como demonstrado</p><p>a seguir: mysql> SELECT (7 5) / ((3 + 14) null); +--------------------------</p><p>---+</p><p>| (7 5) / ((3 + 14) null) |</p><p>+-----------------------------+</p><p>| NULL |</p><p>+-----------------------------+</p><p>1 row in set (0.08 sec)</p><p>Ao realizar cálculos, expressões case são úteis para converter um valor null em um</p><p>número (geralmente 0 ou 1) que permitirá ao cálculo retornar um valor não-nulo. Se</p><p>estiver realizando um cálculo que inclua a coluna account.avail_balance, por</p><p>exemplo, você poderia substituir o valor null por 0 (se estiver somando ou</p><p>subtraindo) ou por 1 (se estiver multiplicando ou dividindo) no caso das contas que</p><p>foram estabelecidas, mas que ainda não foram movimentadas: SELECT +</p><p>CASE</p><p>WHEN avail_balance IS NULL THEN 0</p><p>ELSE avail_balance</p><p>END</p><p>+</p><p>...</p><p>Se uma coluna numérica permitir valores null, geralmente é uma boa ideia usar</p><p>lógica condicional em quaisquer cálculos que incluam a coluna,</p>
<p>para que os</p><p>resultados sejam utilizáveis.</p><p>Teste seu conhecimento</p><p>Desa�e sua habilidade de resolver problemas de lógica condicional com os exemplos</p><p>a seguir. Ao terminar, compare suas soluções com as apresentadas no apêndice C.</p><p>Exercício 11.1</p><p>Rescreva a seguinte consulta, que usa uma expressão case simples, de forma que os</p><p>mesmos resultados sejam alcançados utilizando-se uma expressão case. Tente usar o</p><p>menor número possível de cláusulas when.</p><p>SELECT emp_id,</p><p>CASE title</p><p>WHEN 'President' THEN 'Management'</p><p>WHEN 'Vice President' THEN 'Management'</p><p>WHEN 'Treasurer' THEN 'Management'</p><p>WHEN 'Loan Manager' THEN 'Management'</p><p>WHEN 'Operations Manager' THEN 'Operations'</p><p>WHEN 'Head Teller' THEN 'Operations'</p><p>WHEN 'Teller' THEN 'Operations'</p><p>ELSE 'Unknown'</p><p>END</p><p>FROM employee;</p><p>Exercício 11.2</p><p>Reescreva a consulta a seguir de forma que o conjunto-resultado contenha uma</p><p>única linha com quatro colunas (uma para cada �lial). Nomeie as quatro colunas de</p><p>branch_1 a branch_4.</p><p>mysql> SELECT open_branch_id, COUNT(*) -> FROM account -> GROUP BY</p><p>open_branch_id; +----------------+----------+</p><p>| open_branch_id | COUNT(*) |</p><p>+----------------+----------+</p><p>| 1 | 8 |</p><p>| 2 | 7 |</p><p>| 3 | 3 |</p><p>| 4 | 6 |</p><p>+----------------+----------+</p><p>4 rows in set (0.00 sec)</p><p>CAPÍTULO 12</p><p>Transações</p><p>Todos os exemplos mostrados até agora neste livro foram instruções SQL</p><p>individuais e independentes. Embora isso seja a norma no caso de relatórios sob</p><p>demanda ou de scripts de manutenção de dados, a lógica de aplicação</p><p>frequentemente incluirá várias instruções SQL que precisam ser executadas em</p><p>conjunto como uma unidade de trabalho lógica. Este capítulo explora as</p><p>necessidades e a infraestrutura necessária para a execução de várias instruções SQL</p><p>de forma concorrente.</p><p>Bancos de dados multiusuários</p><p>Sistemas de gerenciamento de banco de dados permitem não apenas que um único</p><p>usuário consulte e modi�que dados, mas também que múltiplos usuários o façam</p><p>simultaneamente. Se todos os usuários estiverem executando consultas, como pode</p><p>ser o caso de um data warehouse durante o período normal de trabalho, o servidor</p><p>terá pouco para se preocupar. No entanto, se alguns dos usuários estiverem</p><p>adicionando e/ou modi�cando dados, o servidor terá um pouco mais de trabalho</p><p>pela frente.</p><p>Digamos, por exemplo, que você está executando um relatório que mostra o saldo</p><p>disponível de todas as contas correntes abertas em sua �lial. No entanto, ao mesmo</p><p>tempo em que você está executando o relatório, as seguintes atividades estão</p><p>acontecendo: • Um caixa em sua �lial está realizando um depósito para um de seus</p><p>clientes.</p><p>• Um cliente está �nalizando um saque no caixa eletrônico do salão de entrada.</p><p>• A aplicação de �m de mês do banco está aplicando os juros às contas.</p><p>Portanto, enquanto seu relatório está em execução, vários usuários estão</p><p>modi�cando os dados subjacentes – então quais números deverão aparecer no</p><p>relatório? A resposta depende, de certa forma, de como o servidor trata o locking,</p><p>que é descrito na próxima seção.</p><p>Locking</p><p>Os bloqueios (locks) são o mecanismo que o servidor de banco de dados usa para</p><p>controlar o uso simultâneo de recursos de dados. Quando alguma parte do banco de</p><p>dados é bloqueada, qualquer outro usuário que deseje modi�car (ou possivelmente</p><p>ler) aqueles dados deve esperar até que o bloqueio seja liberado. A maioria dos</p><p>servidores de banco de dados usa uma dessas duas estratégias de locking: •</p><p>Usuários que queiram escrever no banco de dados devem requisitar e receber do</p><p>servidor um bloqueio de escrita (write lock) para modi�car dados, e os usuários que</p><p>queiram ler o banco de dados devem requisitar e receber do servidor um bloqueio</p><p>de leitura (read lock) para consultar dados. Enquanto múltiplos usuários podem ler</p><p>dados simultaneamente, apenas um bloqueio de escrita é realizado por vez para</p><p>cada tabela (ou porções dela), e requisições de leitura são bloqueadas até que o</p><p>bloqueio de escrita seja liberado.</p><p>• Usuários que queiram escrever no banco de dados devem requisitar e receber do</p><p>servidor um bloqueio de escrita (write lock) para modi�car dados, mas os</p><p>usuários que queiram ler do banco de dados não precisam de um bloqueio para</p><p>consultar dados. Em vez disso, o servidor se garante que um usuário leitor veja</p><p>uma exibição consistente dos dados (os dados aparentam estar iguais, mesmo</p><p>que outros usuários estejam realizando modi�cações) do momento em que sua</p><p>consulta se inicia até o término da consulta. Essa abordagem é conhecida como</p><p>versioning (algo como “geração de versão”).</p><p>Existem prós e contras em ambas as abordagens. A primeira abordagem pode levar a</p><p>longas esperas se houver muitas leituras concorrentes e requisições de escrita, e a</p><p>segunda abordagem pode ser problemática se houver consultas longas sendo</p><p>executadas enquanto os dados são modi�cados. Dos três servidores discutidos neste</p><p>livro, o Microsoft SQL Server usa a primeira abordagem, o Oracle Database usa a</p><p>segunda e o MySQL usa ambas (dependendo de sua escolha de mecanismo de</p><p>armazenamento, que discutiremos mais tarde no capítulo).</p><p>Granularidade dos bloqueios</p><p>Existem várias estratégias diferentes que você pode empregar ao decidir como</p><p>bloquear um recurso. O servidor pode aplicar um bloqueio em três níveis diferentes,</p><p>ou granularidades: Bloqueios de tabela (table locks)</p><p>Impedem que múltiplos usuários modi�quem dados na mesma tabela</p><p>simultaneamente.</p><p>Bloqueios de página (page locks) Impedem que múltiplos usuários modi�quem dados</p><p>na mesma página (uma página é um segmento de memória, geralmente variando entre</p><p>2 e 16 KB) de uma tabela simultaneamente.</p><p>Bloqueios de linha (row locks)</p><p>Impedem que múltiplos usuários modi�quem a mesma linha de uma tabela</p><p>simultaneamente.</p><p>Mais uma vez, há prós e contras em cada uma dessas abordagens. Poucos recursos</p><p>são gastos para bloquear tabelas inteiras, mas essa abordagem rapidamente resulta</p><p>em tempos de espera inaceitáveis conforme o número de usuários aumenta. Por</p><p>outro lado, o bloqueio de linhas requer um pouco mais de recursos, mas permite</p><p>que muitos usuários modi�quem a mesma tabela, desde que estejam interessados</p><p>em linhas diferentes. Dos três servidores discutidos neste livro, o Microsoft SQL</p><p>Server usa bloqueios de página, linha e tabela, o Oracle Database usa apenas</p><p>bloqueios de linha, e o MySQL usa bloqueios de tabela, página ou linha</p><p>(dependendo, novamente, de sua escolha de mecanismo de armazenamento). O SQL</p><p>Server fará, sob certas circunstâncias, o escalonamento de linha para página, e de</p><p>página para tabela, enquanto o Oracle Database nunca escalona os bloqueios.</p><p>Voltando a seu relatório, os dados que aparecerem em suas páginas re�etirão o</p><p>estado do banco de dados no momento em que ele foi iniciado (se o seu servidor</p><p>usar uma abordagem de versioning) ou o estado do banco de dados no momento</p><p>em que o servidor concede um bloqueio de leitura à aplicação de relatório (se o</p><p>servidor usar tanto bloqueios de leitura quanto de escrita).</p><p>O que é uma transação?</p><p>Se os bancos de dados gozassem de 100% de operacionalidade, se os usuários</p><p>sempre permitissem que os programas terminassem de ser executados e se as</p><p>aplicações sempre fossem concluídas sem encontrar erros fatais que interrompessem</p><p>sua execução, não haveria mais o que discutir a respeito de acesso concorrente a</p><p>banco de dados. No entanto, não podemos contar com essas suposições, então mais</p><p>um elemento é necessário para permitir que múltiplos usuários acessem os mesmos</p><p>dados.</p><p>Essa peça extra do quebra-cabeça da concorrência é a transação, que é um</p><p>dispositivo que agrupa várias instruções SQL de forma que todas ou nenhuma das</p><p>instruções seja bem-sucedida (uma propriedade conhecida como atomicidade). Se</p><p>você tentar transferir U$ 500 de sua conta poupança para sua conta corrente, você</p><p>�caria um tanto bravo se o dinheiro fosse sacado com sucesso de sua conta</p><p>poupança, mas nunca chegasse à sua conta corrente. Independentemente da razão</p><p>da falha (o servidor foi desligado para manutenção, a requisição de um bloqueio de</p><p>página expirou etc.), você vai querer seus U$ 500 de volta.</p><p>Para proteger</p>
<p>o usuário desse tipo de erro, o programa que trataria sua requisição de</p><p>transferência primeiro iniciaria uma transação, para em seguida invocar as</p><p>instruções SQL necessárias à movimentação do dinheiro da conta poupança para a</p><p>conta corrente e, se tudo corresse bem, �nalizaria a transação invocando o comando</p><p>commit. No entanto, se algo inesperado acontecesse, o programa invocaria o</p><p>comando rollback, que instrui o servidor a desfazer todas as alterações feitas desde</p><p>o início da transação. O processo completo se pareceria com o seguinte: START</p><p>TRANSACTION;</p><p>/* saca dinheiro da primeira conta, certificando-se que o saldo é</p><p>suficiente */</p><p>UPDATE account SET avail_balance = avail_balance - 500</p><p>WHERE account_id = 9988</p><p>AND avail_balance > 500;</p><p>IF THEN</p><p>/* deposita o dinheiro na segunda conta */</p><p>UPDATE account SET avail_balance = avail_balance + 500</p><p>WHERE account_id = 9989;</p><p>IF THEN</p><p>/* tudo correu bem, torne as modificações permanentes */</p><p>COMMIT;</p><p>ELSE</p><p>/* algo deu errado, desfaça todas as mudanças dessa transação */</p><p>ROLLBACK;</p><p>END IF;</p><p>ELSE</p><p>/* fundos insuficientes, ou erro encontrado durante a atualização */</p><p>ROLLBACK;</p><p>END IF;</p><p>Apesar de o bloco de código anterior se parecer com algumas das linguagens</p><p>procedurais fornecidas pelas principais fabricantes de bancos de dados, como a PL/SQL</p><p>da Oracle ou a Transact-SQL da Microsoft, ele foi escrito em pseudocódigo e não tenta</p><p>imitar qualquer linguagem em particular.</p><p>O bloco de código anterior começa por iniciar uma transação e, então, tenta remover</p><p>U$ 500 da conta poupança e adicionar o valor à conta corrente. Se tudo correr bem,</p><p>a transação é cometida (committed). No entanto, se algo der errado, a transação é</p><p>retrocedida (rolled back), signi�cando que todas as modi�cações nos dados</p><p>ocorridas desde o início da transação serão desfeitas.</p><p>Usando uma transação, o programa garante que seus U$ 500 �carão na conta</p><p>poupança ou serão movidos para sua conta corrente, sem a possibilidade de o</p><p>dinheiro cair no limbo. Independentemente de a transação ser executada ou</p><p>desfeita, todos os recursos adquiridos durante a execução da transação (por</p><p>exemplo, bloqueios de escrita) são liberados quando a transação é concluída.</p><p>É claro que, caso o programa consiga completar ambas as instruções insert, mas o</p><p>servidor seja desligado antes de um commit ou rollback ser executado, a transação</p><p>será desfeita no momento em que o servidor voltar a �car on-line. (Uma das tarefas</p><p>que um servidor de banco de dados deve concluir antes de �car on-line é encontrar</p><p>quaisquer transações incompletas que estavam sendo executadas no momento em</p><p>que o servidor caiu e desfazê-las.) Além disso, se o seu programa termina uma</p><p>transação e invoca um commit, mas o servidor é desligado antes que as mudanças</p><p>sejam aplicadas ao armazenamento permanente (ou seja, os dados modi�cados</p><p>estão armazenados em memória, mas não foram descarregados para o disco), o</p><p>servidor de banco de dados deverá reaplicar as modi�cações de sua transação no</p><p>momento em que o servidor é reiniciado (uma propriedade conhecida como</p><p>durabilidade).</p><p>Iniciando uma transação</p><p>Os servidores de banco de dados podem lidar com a criação de transações de duas</p><p>maneiras diferentes: • Uma transação ativa é sempre associada a uma sessão de</p><p>banco de dados, então não há um método (nem há necessidade) de se iniciar uma</p><p>transação explicitamente. Quando a transação atual termina, o servidor</p><p>automaticamente inicia uma nova transação para sua sessão.</p><p>• A menos que você inicie uma transação explicitamente, instruções SQL</p><p>individuais são automaticamente cometidas, independentes umas das outras.</p><p>Para iniciar uma transação, você deve primeiro invocar um comando.</p><p>Dos três servidores, o Oracle Database usa a primeira abordagem, enquanto o</p><p>Microsoft SQL Server e o MySQL usam a segunda. Uma das vantagens da</p><p>abordagem de transações do Oracle é que, mesmo que você esteja invocando apenas</p><p>um comando SQL, você consegue desfazer as mudanças se não gostar do resultado</p><p>ou se mudar de ideia. Então, se você esquecer-se de adicionar uma cláusula where à</p><p>sua instrução delete, você terá a oportunidade de desfazer os danos (presumindo</p><p>que você acabou de tomar seu café matinal e percebeu que não tinha a intenção de</p><p>apagar todas as 125.000 linhas de sua tabela). No entanto, com o SQL Server e o</p><p>MySQL, uma vez pressionada a tecla Enter, as mudanças ocasionadas por sua</p><p>instrução SQL serão permanentes (a menos que seu DBA possa recuperar os dados</p><p>originais de um backup ou por qualquer outro meio).</p><p>O padrão SQL:2003 inclui uma comando start transaction que deve ser usado</p><p>quando você deseja iniciar uma transação explicitamente. Enquanto o MySQL se</p><p>conforma ao padrão, usuários do SQL Server devem, em vez disso, invocar o</p><p>comando begin transaction. Em ambos os servidores, enquanto não iniciar uma</p><p>transação explicitamente, você estará no que se chama de modo de autocomissão</p><p>(auto-commit), o que signi�ca que instruções individuais têm um commit realizado</p><p>automaticamente pelo servidor. Portanto, você pode decidir se quer invocar um</p><p>comando de início de transação ou se simplesmente deixará a cargo do servidor</p><p>realizar um commit das instruções individuais.</p><p>Tanto o MySQL quanto o SQL Server permitirão desativar o modo de autocomissão</p><p>em sessões individuais – nesse caso, os servidores agirão como o Oracle Database</p><p>no que diz respeito a transações. No SQL Server, você deve invocar o seguinte</p><p>comando para desativar o modo de autocomissão: SET</p><p>IMPLICIT_TRANSACTIONS ON</p><p>O MySQL permite desativar o modo de autocomissão da seguinte maneira: SET</p><p>AUTOCOMMIT=0</p><p>Uma vez que você tenha saído do modo de autocomissão, todos os comandos SQL</p><p>serão realizados dentro do escopo de uma transação e devem ter um commit</p><p>realizado ou devem ser desfeitos explicitamente.</p><p>Um conselho: desative o modo de autocomissão sempre que �zer o login e habitue-se a</p><p>executar todas as suas instruções dentro de uma transação. No mínimo, isso vai evitar a</p><p>situação embaraçosa de ter que pedir ao DBA para reconstruir os dados que você</p><p>apagou acidentalmente.</p><p>Finalizando uma transação</p><p>Uma vez que uma transação tenha iniciado, seja explicitamente por meio do</p><p>comando start transaction ou de forma implícita pelo servidor de banco de dados,</p><p>você deverá explicitamente �nalizar a transação para que as mudanças sejam</p><p>permanentes. Isso é possível por meio do comando commit, que instrui o servidor a</p><p>marcar as mudanças como permanentes e a liberar quaisquer recursos (ou seja,</p><p>bloqueios de página ou de linha) usados durante a transação.</p><p>Se decidir que deseja desfazer todas as mudanças realizadas desde o início da</p><p>transação, você pode invocar o command rollback, que instrui o servidor a retornar</p><p>os dados a seu estado de pré-transação. Após o término do retrocesso, qualquer</p><p>recurso usado por sua sessão é liberado.</p><p>Além de invocar o comando commit ou rollback, existem vários outros cenários de</p><p>término da transação, seja como o resultado indireto de suas ações ou como o</p><p>resultado de algo fora de seu controle: • O servidor é desligado – nesse caso, sua</p><p>transação será retrocedida automaticamente quando o servidor for reiniciado.</p><p>• Você invoca uma instrução de esquema SQL, como alter table, causando a</p><p>comissão da transação atual e o início de uma nova transação.</p><p>• Você invoca outro comando start transaction, provocando a comissão da</p><p>transação anterior.</p><p>• O servidor termina sua transação de forma prematura porque detectou um</p><p>deadlock (impasse) e decidiu que a sua transação é a responsável por isso. Nesse</p><p>caso, a transação será retrocedida e você receberá uma mensagem de erro.</p><p>Desses quatro cenários, o primeiro e o terceiro são simples, mas os outros dois</p><p>merecem ser discutidos. Em relação ao segundo cenário, alterações feitas em um</p><p>banco de dados, seja pela adição de uma nova tabela ou índice, ou a remoção de</p><p>uma coluna de uma tabela, não podem ser retrocedidas, então os comandos que</p><p>alteram o seu esquema deverão ser colocados</p>
<p>fora de uma transação. Portanto, se</p><p>uma transação estiver acontecendo, o servidor realizará um commit da sua transação</p><p>atual, executará o(s) comando(s) de instrução de esquema SQL e, então,</p><p>automaticamente iniciará uma nova transação para sua sessão. O servidor não</p><p>informará a você o que aconteceu, então você deve cuidar para que as instruções</p><p>que formam uma unidade de trabalho não sejam divididas, acidentalmente, em</p><p>múltiplas transações pelo servidor.</p><p>O quarto cenário lida com a detecção de deadlocks. Um deadlock ocorre quando</p><p>duas transações diferentes estão esperando por recursos que a outra transação esteja</p><p>usando. Por exemplo, a transação A pode ter acabado de atualizar a tabela account e</p><p>�ca esperando uma trava de escrita para a tabela transaction, enquanto a transação</p><p>B acabou de inserir uma linha na tabela transaction e �ca esperando por uma trava</p><p>de escrita na tabela account. Se acontecer de ambas as transações estarem</p><p>modi�cando a mesma página ou linha (dependendo da granularidade de bloqueio</p><p>em uso pelo servidor de banco de dados), elas esperarão para sempre pelo término</p><p>da outra transação e pela liberação dos recursos necessários. Servidores de banco de</p><p>dados devem estar sempre alertas para esse tipo de situação, para que seu</p><p>rendimento não despenque: quando um deadlock é detectado, uma das transações é</p><p>escolhida (arbitrariamente ou por meio de algum critério) para ser retrocedida a �m</p><p>de que a outra transação possa prosseguir. Na maior parte do tempo, a transação</p><p>�nalizada poderá ser reiniciada e será bem-sucedida, sem encontrar outra situação</p><p>de deadlock.</p><p>Diferentemente do segundo cenário discutido anteriormente, o servidor de banco de</p><p>dados sinalizará um erro para informar que sua transação foi retrocedida devido à</p><p>detecção de um deadlock. No MySQL, por exemplo, você receberá o erro #1213, que</p><p>apresenta a seguinte mensagem: Message: Deadlock found when trying to get lock;</p><p>try restarting transaction Como a mensagem de erro sugere, é uma prática razoável</p><p>reexecutar uma transação que tenha sido retrocedida devido à detecção de um</p><p>deadlock. No entanto, se os deadlocks começarem a se tornar comuns, você</p><p>precisará modi�car as aplicações que acessam o banco de dados com o objetivo de</p><p>diminuir a probabilidade de ocorrer deadlocks (uma estratégia comum é garantir</p><p>que os recursos de dados sejam sempre acessados na mesma ordem, como, por</p><p>exemplo, sempre modi�car os dados das contas bancárias antes de inserir dados de</p><p>transação).</p><p>Pontos de gravação de transações</p><p>Em alguns casos, você pode encontrar um problema em uma transação que</p><p>necessitará de um retrocesso, mas você não quer desfazer todo o seu suado trabalho.</p><p>Nessas situações, você pode estabelecer um ou mais pontos de gravação (savepoints)</p><p>dentro de uma transação e usá-los para retroceder a um local especí�co dentro da</p><p>transação, em vez de retroceder até o início dela.</p><p>Escolhendo um mecanismo de armazenamento</p><p>Ao usar o Oracle Database ou o Microsoft SQL Server, um único conjunto de</p><p>código �ca responsável pelas operações de banco de dados de baixo nível, como</p><p>a recuperação de uma linha particular de uma tabela baseada no valor da chave</p><p>primária. No entanto, o MySQL foi projetado de forma a permitir que múltiplos</p><p>mecanismos de armazenamento possam ser utilizados para fornecer</p><p>funcionalidades de banco de dados de baixo nível, incluindo o bloqueio de</p><p>recursos e o gerenciamento de transações. Na versão 5.1, o MySQL inclui os</p><p>seguintes mecanismos de armazenamento: MyISAM</p><p>Um mecanismo não-transacional que emprega bloqueio de tabelas.</p><p>MEMORY</p><p>Um mecanismo não-transacional usado para alocar tabelas em memória</p><p>volátil.</p><p>BDB</p><p>Um mecanismo transacional que emprega bloqueio de página.</p><p>InnoDB</p><p>Um mecanismo transacional que emprega bloqueio de linha.</p><p>Merge</p><p>Um mecanismo especial, usado para fazer com que múltiplas tabelas MyISAM</p><p>idênticas aparentem ser uma única tabela (também conhecido como</p><p>particionamento de tabela).</p><p>Maria</p><p>Um substituto do MyISAM incluído na versão 6.0.6 que adiciona recuperação</p><p>completa.</p><p>Falcon</p><p>Um mecanismo transacional novo (incluído na versão 6.0.4) de alto</p><p>desempenho que emprega bloqueio de linha.</p><p>Archive</p><p>Um mecanismo especial, usado para armazenar grandes quantidades de dados</p><p>não-indexados, principalmente com propósitos de arquivamento.</p><p>Apesar de você poder ter pensado que seria forçado a escolher um único</p><p>mecanismo de armazenamento para seu banco de dados, o MySQL é �exível o</p><p>su�ciente para permitir a seleção de um mecanismo de armazenamento para</p><p>cada tabela. No entanto, no caso de tabelas que farão parte de transações, você</p><p>deverá escolher o mecanismo de armazenamento InnoDB ou Falcon, que usa</p><p>bloqueio de linha e versioning para fornecer o nível mais alto de concorrência</p><p>entre todos os mecanismos de armazenamento.</p><p>Você pode especi�car explicitamente um mecanismo de armazenamento ao criar</p><p>uma tabela ou pode alterar uma tabela existente para que use um mecanismo</p><p>diferente. Se você não sabe qual mecanismo está associado a qual tabela, você</p><p>pode usar o comando show table, demonstrado a seguir: mysql> SHOW TABLE</p><p>STATUS LIKE ‘transaction’ \G</p><p>*************************** 1. row ***************************</p><p>Name: transaction</p><p>Engine: InnoDB</p><p>Version: 10</p><p>Row_format: Compact</p><p>Rows: 21</p><p>Avg_row_length: 780</p><p>Data_length: 16384</p><p>Max_data_length: 0</p><p>Index_length: 49152</p><p>Data_free: 0</p><p>Auto_increment: 22</p><p>Create_time: 2008-02-19 23:24:36</p><p>Update_time: NULL</p><p>Check_time: NULL</p><p>Collation: latin1_swedish_ci</p><p>Checksum: NULL</p><p>Create_options:</p><p>Comment:</p><p>1 row in set (1.46 sec)</p><p>Observando o segundo item, você pode ver que a tabela transaction já está</p><p>usando o mecanismo InnoDB. Se não estivesse, você poderia atribuir o</p><p>mecanismo InnoDB à tabela transaction por meio do seguinte comando:</p><p>ALTER TABLE transaction ENGINE = INNODB;</p><p>Todos os pontos de gravação devem ser nomeados, o que permite ter vários pontos</p><p>de gravação dentro de uma única transação. Para criar um ponto de gravação</p><p>nomeado my_savepoint, você pode fazer o seguinte: SAVEPOINT my_savepoint;</p><p>Para retroceder até um ponto de gravação especí�co, você simplesmente invoca o</p><p>comando rollback, seguido das palavras-chave to savepoint e do nome do ponto de</p><p>gravação, como em: ROLLBACK TO SAVEPOINT my_savepoint;</p><p>Veja um exemplo de como os pontos de gravação podem ser usados: START</p><p>TRANSACTION;</p><p>UPDATE product</p><p>SET date_retired = CURRENT_TIMESTAMP()</p><p>WHERE product_cd = 'XYZ';</p><p>SAVEPOINT before_close_accounts;</p><p>UPDATE account</p><p>SET status = 'CLOSED', close_date = CURRENT_TIMESTAMP(),</p><p>last_activity_date = CURRENT_TIMESTAMP()</p><p>WHERE product_cd = 'XYZ';</p><p>ROLLBACK TO SAVEPOINT before_close_accounts;</p><p>COMMIT;</p><p>O resultado �nal dessa transação é que o produto �ctício XYZ é aposentado, mas</p><p>nenhuma das contas é fechada.</p><p>Ao usar pontos de gravação, lembre-se do seguinte:</p><p>• Apesar de seu nome, nada é gravado quando você cria um ponto de gravação.</p><p>Eventualmente, você precisará invocar um commit se quiser que a transação</p><p>torne-se permanente.</p><p>• Se invocar um rollback sem nomear um ponto de gravação, todos os pontos de</p><p>gravação dentro da transação serão ignorados, e a transação inteira será desfeita.</p><p>Se estiver usando o SQL Server, você precisará usar o comando proprietário save</p><p>transaction para criar um ponto de gravação e rollback transaction para</p><p>retroceder a um ponto de gravação, com cada um desses comandos sendo seguido</p><p>do nome do ponto de gravação.</p><p>Teste seu conhecimento</p><p>Teste sua compreensão de transações resolvendo o exercício a seguir. Quando tiver</p><p>terminado, compare sua solução à apresentada no apêndice C.</p><p>Exercício 12.1</p><p>Gere uma transação que trans�ra U$ 50 da conta de aplicação (money market) de</p><p>Frank Tucker para a conta corrente do cliente. Você precisará inserir duas linhas na</p><p>tabela transaction e atualizar duas linhas na tabela account.</p><p>CAPÍTULO 13</p><p>Índices e restrições</p><p>Como o foco deste livro consiste nas técnicas de programação, os primeiros 12</p><p>capítulos se concentraram nos elementos da linguagem SQL que você pode usar</p><p>para construir instruções select, insert, update e delete poderosas.</p>
<p>uma nova tabela permanente pode ser</p><p>criada em um banco de dados relacional simplesmente armazenando o conjunto-</p><p>resultado de uma consulta. De maneira semelhante, uma consulta pode usar tanto</p><p>as tabelas permanentes quanto os conjuntos-resultados de outras consultas como</p><p>entradas (exploraremos esse fato em detalhes no capítulo 9).</p><p>Uma última nota: SQL não é um acrônimo de qualquer coisa (apesar de muitas</p><p>pessoas insistirem que signi�ca “Structured Query Language”). Ao se referir à</p><p>linguagem, é igualmente aceitável dizer as letras individualmente (ou seja, S. Q. L.)</p><p>ou usar a palavra sequel (pronuncia-se “síquel”).</p><p>Classes de instruções SQL</p><p>A linguagem SQL é dividida em várias partes distintas: as partes que exploraremos</p><p>neste livro incluem as instruções de esquema SQL, que são usadas para de�nir as</p><p>estruturas de dados armazenadas em um banco de dados; instruções de dados SQL,</p><p>que são usadas para manipular as estruturas de dados previamente de�nidas com as</p><p>instruções de esquema SQL; e as instruções de transação SQL, que são usadas para</p><p>iniciar, encerrar e desfazer transações (explicadas no capítulo 12). Por exemplo, para</p><p>criar uma nova tabela em seu banco de dados, você poderia usar a instrução de</p><p>esquema SQL create table, ao passo que o processo de população de sua nova</p><p>tabela com dados necessitaria da instrução de dados SQL insert.</p><p>Para lhe dar um aperitivo da aparência dessas instruções, aqui está uma instrução de</p><p>esquema SQL que cria uma tabela chamada corporation:</p><p>CREATE TABLE corporation</p><p>(corp_id SMALLINT,</p><p>name VARCHAR(30),</p><p>CONSTRAINT pk_corporation PRIMARY KEY (corp_id)</p><p>);</p><p>Essa instrução cria uma tabela com duas colunas, corp_id e name, com a coluna</p><p>corp_id identi�cada como a chave primária da tabela. Analisaremos os detalhes</p><p>dessa instrução, tais como os diferentes tipos de dados disponíveis no MySQL, no</p><p>capítulo 2. A seguir, veja uma instrução de dados SQL que insere uma linha na</p><p>tabela corporation para a empresa Acme Paper Corporation:</p><p>INSERT INTO corporation (corp_id, name)</p><p>VALUES (27, 'Acme Paper Corporation');</p><p>Essa instrução adiciona uma linha à tabela corporation com o valor 27 na coluna</p><p>corp_id e o valor Acme Paper Corporation na coluna name.</p><p>Finalmente, aqui está uma instrução select simples que retorna os dados recém-</p><p>criados:</p><p>mysql FROM corporation</p><p>-> WHERE corp_id = 27;</p><p>+------------------------+</p><p>| name |</p><p>+------------------------+</p><p>| Acme Paper Corporation |</p><p>+------------------------+</p><p>Todos os elementos do banco de dados criados por meio de instruções de esquema</p><p>SQL são armazenados em um conjunto especial de tabelas denominado dicionário</p><p>de dados. Esses “dados sobre o banco de dados” são conhecidos coletivamente como</p><p>metadados e serão explorados no capítulo 15. Assim como acontece com as tabelas</p><p>que você mesmo cria, as tabelas do dicionário de dados podem ser consultadas por</p><p>meio de uma instrução select, permitindo, assim, descobrir em tempo de execução</p><p>quais estruturas de dados existem atualmente no banco de dados. Por exemplo, se</p><p>pedirem a você que gere um relatório mostrando as novas contas bancárias criadas</p><p>no mês passado, você poderia codi�car na unha os nomes das colunas da tabela</p><p>account que eram conhecidos por você no momento em que gerou o relatório, ou</p><p>você poderia consultar o dicionário de dados para determinar o conjunto atual de</p><p>colunas e gerar dinamicamente o relatório cada vez em que a consulta fosse</p><p>executada.</p><p>A maior parte deste livro concentra-se nas instruções de dados da linguagem SQL,</p><p>que consistem nos comandos select, update, insert e delete. Instruções de</p><p>esquema SQL serão demonstradas no capítulo 2, no qual o banco de dados de</p><p>exemplo, usado ao longo de todo o livro, será gerado. Em geral, instruções de</p><p>esquema SQL não necessitam de muita discussão além de sua sintaxe, ao passo que</p><p>instruções de dados SQL, mesmo sendo em um número pequeno, oferecem</p><p>inúmeras oportunidades para estudos detalhados. Portanto, embora eu procure</p><p>apresentar várias instruções de esquema SQL, a maioria dos capítulos deste livro</p><p>concentra-se nas instruções de dados SQL.</p><p>SQL: uma linguagem não-procedural</p><p>Se você já trabalhou com linguagens de programação, deve estar acostumado a</p><p>de�nir variáveis e estruturas de dados, a usar lógica condicional (ou seja, if-then-</p><p>else) e construções de laço (ou seja, do while ... end) e a dividir seu código em</p><p>blocos pequenos e reutilizáveis (ou seja, objetos, funções, procedimentos). Seu</p><p>código é enviado a um compilador, e o executável que resulta disso faz exatamente</p><p>(bem, nem sempre exatamente) aquilo que você o programou para fazer.</p><p>Independentemente de você trabalhar com Java, C#, C, Visual Basic ou alguma</p><p>outra linguagem procedural, você tem total controle sobre o que o programa faz.</p><p>Uma linguagem procedural de�ne os resultados desejados e o mecanismo, ou processo,</p><p>pelo qual os resultados serão gerados. Linguagens não-procedurais também de�nem os</p><p>resultados desejados, mas o processo pelo qual os resultados são gerados é deixado nas</p><p>mãos de um agente externo.</p><p>Com SQL, no entanto, você precisará abrir mão de parte do controle ao qual está</p><p>acostumado, pois as instruções SQL de�nem as entradas e saídas necessárias, mas a</p><p>forma como uma instrução é executada é deixada para um componente do seu</p><p>mecanismo de banco de dados conhecido como otimizador. A função do otimizador</p><p>é olhar para suas instruções SQL e, considerando como suas tabelas estão</p><p>con�guradas e quais índices estão disponíveis, decidir o caminho de execução mais</p><p>e�ciente (bem, nem sempre o mais e�ciente). A maioria dos mecanismos de banco</p><p>de dados permitirá que você in�uencie as decisões do otimizador especi�cando</p><p>dicas, tal como sugerir que um índice em particular seja usado; a maioria dos</p><p>usuários de SQL, no entanto, nunca chegará a esse nível de so�sticação e deixará</p><p>tais ajustes �nos para seus administradores de banco de dados ou para especialistas</p><p>em desempenho.</p><p>Com SQL, portanto, você não será capaz de escrever aplicações completas. A menos</p><p>que você esteja escrevendo um script simples para manipular certos dados, você</p><p>precisará integrar a SQL com sua linguagem de programação favorita. Alguns</p><p>fabricantes de banco de dados já �zeram isso para você, tais como a linguagem</p><p>PL/SQL da Oracle, a linguagem de procedimentos armazenados (stored procedures)</p><p>do MySQL e a linguagem Transact-SQL, da Microsoft.</p><p>Tabela 1.2 – Kits de ferramentas de integração de SQL</p><p>Linguagem Kit de ferramentas</p><p>Java JDBC (Java Database Connectivity; JavaSoft)</p><p>C++</p><p>Rogue Wave SourcePro DB (ferramenta de terceiros para se conectar a bancos de dados Oracle, SQL Server,</p><p>MySQL, Informix, DB2, Sybase, e PostgreSQL)</p><p>C/C++ Pro*C (Oracle), MySQL C API (código aberto) e DB2 Call Level Interface (IBM)</p><p>C# ADO.NET (Microsoft)</p><p>Perl Perl DBI</p><p>Python Python DB</p><p>Visual</p><p>Basic</p><p>ADO.NET (Microsoft)</p><p>Se você precisa apenas executar comandos SQL de forma interativa, cada fabricante</p><p>de banco de dados fornece pelo menos uma ferramenta simples de linha de</p><p>comando para enviar comandos SQL ao mecanismo de banco de dados e para</p><p>inspecionar os resultados. A maioria dos fabricantes também fornece uma</p><p>ferramenta grá�ca que inclui duas janelas, uma mostrando seus comandos SQL e a</p><p>outra mostrando os resultados de seus comandos SQL. Como os exemplos deste</p><p>livro serão executados em um banco de dados MySQL, usarei a ferramenta de linha</p><p>de comando mysql, incluída como parte da instalação do MySQL, para executar os</p><p>exemplos e para formatar os resultados.</p><p>Exemplos em SQL</p><p>Anteriormente neste capítulo, prometi que mostraria a você uma instrução SQL que</p><p>retornaria todas as transações da conta corrente de George Blake. Sem mais</p><p>enrolação, aqui está ela:</p><p>Sem entrar muito nos detalhes por enquanto, essa consulta identi�ca a linha na</p><p>tabela individual relativa a George Blake e a linha na tabela product relativa ao</p><p>produto "CHK", encontra a linha na tabela account relativa a essa combinação</p><p>individual/product e retorna quatro colunas da tabela transaction relacionadas a</p><p>todas as transações ocorridas nessa conta.</p>
<p>No entanto,</p><p>outras características dos bancos de dados afetam indiretamente o código que você</p><p>escreve. Este capítulo concentra-se em duas dessas características: os índices e as</p><p>restrições.</p><p>Índices</p><p>Quando você insere uma linha em uma tabela, o servidor de banco de dados não</p><p>tenta colocar os dados em algum local especí�co dentro da tabela. Por exemplo, se</p><p>você adicionar uma linha à tabela department, o servidor não posiciona a linha em</p><p>ordem numérica por meio da coluna dept_id, nem em ordem alfabética por meio da</p><p>coluna name. Em vez disso, o servidor simplesmente coloca os dados na próxima</p><p>localização disponível dentro do arquivo (o servidor mantém uma lista de espaços</p><p>livres para cada tabela). Portanto, ao consultar a tabela department, o servidor</p><p>precisará inspecionar cada linha da tabela para responder à consulta. Por exemplo,</p><p>digamos que você faça a seguinte consulta: mysql> SELECT dept_id, name -></p><p>FROM department</p><p>-> WHERE name LIKE 'A%'; +---------+----------------+</p><p>| dept_id | name |</p><p>+---------+----------------+</p><p>| 3 | Administration |</p><p>+---------+----------------+</p><p>1 row in set (0.03 sec)</p><p>Para encontrar todos os departamentos cujos nomes iniciem com A, o servidor</p><p>precisa visitar cada linha da tabela department e inspecionar o conteúdo da coluna</p><p>name – se o departamento iniciar com A, a linha é adicionada ao conjunto-resultado.</p><p>Esse tipo de acesso é conhecido como uma varredura de tabela (table scan).</p><p>Apesar de esse método funcionar bem com uma tabela de apenas três linhas,</p><p>imagine quanto tempo pode levar para responder à consulta se a tabela tiver três</p><p>milhões de linhas. Para qualquer quantidade de linhas maior que três e menor que</p><p>três milhões, vai chegar um ponto em que o servidor não conseguirá responder à</p><p>consulta em uma quantidade de tempo razoável sem ajuda adicional. Essa ajuda</p><p>vem na forma de um ou mais índices na tabela department.</p><p>Mesmo que nunca tenha ouvido falar de um índice de banco de dados,</p><p>provavelmente você sabe o que é um índice (por exemplo, este livro tem um). Um</p><p>índice é nada mais do que um mecanismo para encontrar um item especí�co dentro</p><p>de um recurso. Todos os livros técnicos, por exemplo, incluem um índice no �nal</p><p>que permite localizar uma palavra ou frase especí�ca dentro da obra. O índice lista</p><p>essas palavras e frases em uma ordem alfabética, permitindo que o leitor mova-se</p><p>rapidamente para uma letra especí�ca no índice, encontre a entrada desejada e,</p><p>então, a página ou páginas nas quais a palavra ou frase pode ser encontrada.</p><p>Da mesma forma que uma pessoa usa um índice para encontrar palavras dentro de</p><p>uma publicação, um servidor de banco de dados usa índices para localizar linhas em</p><p>uma tabela. Índices são tabelas especiais que, diferentemente das tabelas normais,</p><p>são mantidas em uma ordem especí�ca. No entanto, em vez de conter todos os dados</p><p>de uma entidade, um índice contém apenas a coluna (ou colunas) usada para</p><p>localizar as linhas da tabela de dados, junto com informações que descrevem onde</p><p>as linhas estão �sicamente localizadas. Portanto, o papel dos índices é facilitar a</p><p>recuperação de um subconjunto de linhas e colunas de uma tabela sem a</p><p>necessidade de inspecionar cada linha da tabela.</p><p>Criação de índices</p><p>Voltando à tabela department, você pode decidir adicionar um índice na coluna name</p><p>para acelerar qualquer consulta que especi�que um nome de departamento</p><p>completo ou parcial, bem como qualquer operação update ou delete que especi�que</p><p>um nome de departamento. Veja como você pode adicionar tal índice em um banco</p><p>de dados MySQL: mysql> ALTER TABLE department -> ADD INDEX</p><p>dept_name_idx (name); Query OK, 3 rows a�ected (0.08 sec)</p><p>Records: 3 Duplicates: 0 Warnings: 0</p><p>Essa instrução cria um índice (um índice de árvore balanceada ou índice B-Tree,</p><p>para ser mais preciso, mas falaremos sobre isso mais adiante) à coluna</p><p>department.name; além disso, o índice recebe o nome de dept_name_idx. Com o</p><p>índice posicionado, o otimizador de consultas (discutido no capítulo 3) pode</p><p>escolher usar esse índice, caso julgue que isso bene�cie o processo (com apenas três</p><p>linhas na tabela department, por exemplo, o otimizador poderá muito bem escolher</p><p>ignorar o índice e ler a tabela completa). Se houver mais de um índice em uma</p><p>tabela, o otimizador deve decidir qual índice será o mais bené�co para uma</p><p>instrução SQL em particular.</p><p>O MySQL trata os índices como componentes opcionais de uma tabela, e é por isso que</p><p>você deve usar o comando alter table para adicionar ou remover um índice. Outros</p><p>bancos de dados, incluindo o SQL Server e o Oracle Database, tratam os índices como</p><p>objetos de esquema independentes. Portanto, tanto para o SQL Server como para o Oracle,</p><p>você geraria um índice usando o comando create index, como em: CREATE INDEX</p><p>dept_name_idx</p><p>ON department (name);</p><p>A partir da versão 5.0 do MySQL, um comando create index está disponível, apesar de</p><p>ser mapeado para o comando alter table.</p><p>Todos os servidores de banco de dados permitem observar os índices disponíveis.</p><p>Usuários do MySQL podem usar o comando show para ver todos os índices de uma</p><p>tabela especí�ca, como em: mysql> SHOW INDEX FROM department \G</p><p>*************************** 1. row *************************** 1. row</p><p>***************************</p><p>Table: department</p><p>Non_unique: 0</p><p>Key_name: PRIMARY</p><p>Seq_in_index: 1</p><p>Column_name: dept_id</p><p>Collation: A</p><p>Cardinality: 3</p><p>Sub_part: NULL</p><p>Packed: NULL</p><p>Null:</p><p>Index_type: BTREE</p><p>Comment:</p><p>Index_Comment:</p><p>*************************** 2. row ***************************</p><p>Table: department</p><p>Non_unique: 1</p><p>Key_name: dept_name_idx</p><p>Seq_in_index: 1</p><p>Column_name: name</p><p>Collation: A</p><p>Cardinality: 3</p><p>Sub_part: NULL</p><p>Packed: NULL</p><p>Null:</p><p>Index_type: BTREE</p><p>Comment:</p><p>Index_Comment:</p><p>2 rows in set (0.01 sec)</p><p>A saída indica que há dois índices da tabela department: um na coluna dept_id,</p><p>chamado PRIMARY, e o outro na coluna name, chamado dept_name_idx. Como criei</p><p>apenas um índice até agora (dept_name_idx), você pode estar se perguntando de</p><p>onde surgiu o outro: quando a tabela department foi criada, a instrução create</p><p>table incluiu uma restrição que nomeava a coluna dept_id como a chave primária</p><p>da tabela. Veja a instrução usada para criar a tabela: CREATE TABLE department</p><p>(dept_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,</p><p>name VARCHAR(20) NOT NULL,</p><p>CONSTRAINT pk_department PRIMARY KEY (dept_id) ); Quando a tabela foi</p><p>criada, o servidor MySQL automaticamente gerou um índice na coluna de chave</p><p>primária, que, nesse caso, é dept_id, e dei o nome PRIMARY ao índice. Falarei</p><p>sobre as restrições mais tarde neste capítulo.</p><p>Se, depois de criar um índice, você chegar à conclusão de que ele não está sendo útil,</p><p>você pode removê-lo da seguinte maneira:</p><p>mysql> ALTER TABLE department -> DROP INDEX dept_name_idx; Query OK, 3 rows</p><p>affected (0.02 sec)</p><p>Records: 3 Duplicates: 0 Warnings: 0</p><p>Os usuários do SQL Server e do Oracle Database podem usar o comando drop index</p><p>para remover um índice, como em: DROP INDEX dept_name_idx; (Oracle)</p><p>DROP INDEX dept_name_idx ON department (SQL Server) O MySQL agora</p><p>também suporta o comando drop index.</p><p>Índices exclusivos</p><p>Ao projetar um banco de dados, é importante considerar quais colunas têm</p><p>permissão para conter dados duplicados e quais não. Por exemplo, é permitido ter</p><p>dois clientes com o nome John Smith na tabela individual, já que cada linha terá</p><p>um identifcador (cust_id), uma data de nascimento e um número federal</p><p>(customer.fed_id) diferentes para diferenciar um do outro. No entanto, você não iria</p><p>querer ter dois departamentos com o mesmo nome na tabela department. Você pode</p><p>forçar uma regra contra nomes de departamento duplicados criando um índice</p><p>exclusivo na coluna department.name.</p><p>Um índice exclusivo exerce vários papéis, pois, além de fornecer todos os benefícios</p><p>de um índice comum, ele também serve como um mecanismo de desativação de</p><p>valores duplicados na coluna indexada. Sempre que uma linha é inserida ou quando</p><p>a coluna indexada é modi�cada, o servidor de banco de dados veri�ca o índice</p><p>exclusivo para ver se o valor já existe em alguma outra linha da tabela. Veja como</p>
<p>Se por acaso você souber que o ID de</p><p>George Blake é 8 e que a conta é designada pelo código ‘CHK’, você pode</p><p>simplesmente encontrar a conta corrente de George Blake na tabela account baseado</p><p>no ID de cliente e usar o ID de conta para encontrar as transações apropriadas:</p><p>SELECT t.txn_id, t.txn_type_cd, t.txn_date, t.amount</p><p>FROM account a</p><p>INNER JOIN transaction t ON t.account_id = a.account_id</p><p>WHERE a.cust_id = 8 AND a.product_cd = 'CHK';</p><p>Falarei sobre os conceitos nessas consultas (e muito mais) nos capítulos seguintes,</p><p>mas eu quis pelo menos mostrar qual é a aparência geral delas.</p><p>As consultas anteriores contêm três cláusulas diferentes: select, from e where. Quase</p><p>toda consulta que você encontrará inclui pelo menos essas três cláusulas, apesar de</p><p>haver muitas outras que podem ser usadas para propósitos mais especializados. O</p><p>papel de cada uma dessas três cláusulas é demonstrado a seguir:</p><p>SELECT /* selecione uma ou mais coisas */ ...</p><p>FROM /* de um ou mais lugares */ ...</p><p>WHERE /* onde uma ou mais condições se apliquem */ ...</p><p>A maioria das implementações de SQL trata qualquer texto entre as tags /* e */ como</p><p>comentários.</p><p>Ao construir sua consulta, sua primeira tarefa geralmente será determinar qual ou</p><p>quais tabelas serão necessárias e, então, adicioná-las à sua cláusula from. Em</p><p>seguida, você precisará adicionar condições à sua cláusula where para �ltrar os dados</p><p>dessas tabelas que não lhe interessam. Por �m, você decidirá quais colunas das</p><p>diferentes tabelas precisam ser recuperadas e irá adicioná-las à sua cláusula select.</p><p>Aqui está um exemplo simples que mostra como você encontraria todos os clientes</p><p>com o sobrenome “Smith”:</p><p>SELECT cust_id, fname</p><p>FROM individual</p><p>WHERE lname = 'Smith';</p><p>Essa consulta pesquisa a tabela individual por todas as linhas cuja coluna lname</p><p>corresponda à string ‘Smith’ e retorna as colunas cust_id e fname dessas linhas.</p><p>Além de consultar seu banco de dados, muito provavelmente você se verá envolvido</p><p>no processo de popular e modi�car seus dados. Aqui está um exemplo de como você</p><p>poderia inserir uma nova linha na tabela product:</p><p>INSERT INTO product (product_cd, name)</p><p>VALUES ('CD', 'Certificate of Depysit')</p><p>Opa, parece que você digitou a palavra “Deposit” errado. Sem problemas – você</p><p>pode arrumá-la com uma instrução update:</p><p>UPDATE product</p><p>SET name = 'Certificate of Deposit'</p><p>WHERE product_cd = 'CD';</p><p>Perceba que a instrução update também contém uma cláusula where, exatamente</p><p>como a instrução select. Isso acontece porque uma instrução update deve</p><p>identi�car as linhas que serão modi�cadas – nesse caso, você está especi�cando que</p><p>apenas as linhas cuja coluna product_cd corresponda à string ‘CD’ deverão ser</p><p>modi�cadas. Como a coluna product_cd é a chave primária da tabela product, você</p><p>deve esperar que sua instrução update modi�que exatamente uma linha (ou</p><p>nenhuma, caso o valor não exista na tabela). Sempre que você executar uma</p><p>instrução de dados SQL, você receberá um feedback do mecanismo de banco de</p><p>dados sobre quantas linhas foram afetadas por sua instrução. Se você estiver usando</p><p>uma ferramenta interativa, como a ferramenta de linha de comando do MySQL</p><p>mencionada anteriormente, você receberá um feedback relativo a quantas linhas</p><p>foram:</p><p>• Retornadas por sua instrução select</p><p>• Criadas por sua instrução insert</p><p>• Modi�cadas por sua instrução update</p><p>• Removidas por sua instrução delete</p><p>Se você estiver usando uma linguagem procedural com um dos kits de ferramentas</p><p>mencionados anteriormente, o kit de ferramentas incluirá uma chamada para obter</p><p>essa informação após a execução de sua instrução SQL. Em geral, é uma boa ideia</p><p>veri�car essa informação para ter certeza de que sua instrução não fez algo</p><p>inesperado (como quando você se esquece de colocar uma cláusula where na</p><p>instrução delete e acaba apagando todas as linhas da tabela!).</p><p>O que é MySQL?</p><p>Bancos de dados relacionais estão disponíveis comercialmente há pelo menos duas</p><p>décadas. Alguns dos produtos comerciais mais consolidados e difundidos incluem:</p><p>• Oracle Database, da Oracle Corporation</p><p>• SQL Server, da Microsoft</p><p>• DB2 Universal Database, da IBM</p><p>• Sybase Adaptive Server, da Sybase</p><p>Todos esses servidores de banco de dados fazem aproximadamente a mesma coisa,</p><p>apesar de alguns deles estarem melhores equipados para executar bancos de dados</p><p>muito grandes ou com taxas de transferência muito altas. Outros são melhores para</p><p>tratar objetos, arquivos muito grandes ou arquivos XML, e assim por diante.</p><p>Independentemente desses fatores, todos eles fazem um bom trabalho em se</p><p>conformarem ao padrão ANSI SQL mais recente. Isso é uma coisa boa, e cito isso</p><p>para mostrar a você que é possível escrever instruções SQL que serão executadas em</p><p>qualquer uma dessas plataformas com pouca ou nenhuma modi�cação.</p><p>Em paralelo aos servidores de banco de dados comerciais, tem havido muita</p><p>atividade na comunidade open source nos últimos cinco anos, com o objetivo de</p><p>criar alternativas viáveis aos servidores comerciais. Dois dos servidores open source</p><p>mais comumente utilizados são o PostgreSQL e o MySQL. O website do MySQL</p><p>(http://www.mysql.com) atualmente a�rma que ele já foi instalado em cerca de 10</p><p>milhões de computadores, é disponibilizado gratuitamente, e descobri que ele é</p><p>extremamente simples de baixar e instalar. Por essas razões, decidi que todos os</p><p>exemplos deste livro seriam executados em um banco de dados MySQL1 (versão</p><p>5.1), e que a ferramenta de linha de comando mysql seria usada para formatar os</p><p>resultados das consultas. Mesmo que você já esteja usando outro servidor e não</p><p>planeje usar o MySQL, eu o encorajo a instalar a última versão do servidor MySQl,</p><p>carregar o esquema e os dados de exemplo e a experimentar com os dados e os</p><p>exemplos deste livro.</p><p>No entanto, tenha em mente a seguinte advertência:</p><p>Este não é um livro sobre a implementação SQL do MySQL.</p><p>Em vez disso, este livro foi planejado para ensiná-lo a construir instruções SQL que</p><p>executarão no MySQL sem quaisquer modi�cações, e que executarão em versões</p><p>recentes do Oracle Database, Sybase Adaptive Server e SQL Server com pouca ou</p><p>nenhuma modi�cação.</p><p>Para manter o código deste livro o mais neutro possível, vou me abster de</p><p>demonstrar algumas das coisas interessantes que os implementadores da linguagem</p><p>SQL do MySQL decidiram fazer e que não podem ser feitas em outras</p><p>implementações de bancos de dados. Em vez disso, o apêndice B cobrirá algumas</p><p>dessas funções tendo em vista os leitores que planejam continuar usando o MySQL.</p><p>O que vem em seguida</p><p>O objetivo geral dos próximos quatro capítulos é introduzir as instruções de dados</p><p>SQL, com uma ênfase especial nas três cláusulas principais da instrução select.</p><p>Além disso, você verá muitos exemplos que usam o esquema de banco de dados</p><p>(apresentado no próximo capítulo), que será usado em todos os exemplos do livro.</p><p>Espero que a familiaridade com um único banco de dados permita a você captar a</p><p>essência de um exemplo sem ter que parar toda vez para examinar as tabelas que</p><p>estão sendo utilizadas. Se porventura o trabalho com um mesmo conjunto de</p><p>tabelas se tornar tedioso, sinta-se à vontade para ampliar o banco de dados com</p><p>tabelas adicionais, ou invente seu próprio banco de dados, com o qual possa fazer</p><p>experimentos.</p><p>Depois que você adquirir um bom traquejo das questões básicas, os capítulos</p><p>restantes irão mergulhar a fundo em conceitos adicionais, a maioria dos quais são</p><p>independentes uns dos outros. Portanto, se você se sentir confuso com um tópico,</p><p>você poderá seguir adiante e retornar mais tarde para revisar um capítulo. Após a</p><p>conclusão da leitura do livro e a exploração de todos os exemplos, você estará bem</p><p>encaminhado para se tornar um pro�ssional tarimbado em SQL.</p><p>Para os leitores interessados em aprender mais sobre bancos de dados relacionais,</p><p>sobre a história dos sistemas de banco de dados computadorizados ou sobre o tanto</p><p>da linguagem SQL que foi abordado nesta breve introdução, aqui estão alguns</p><p>recursos que vale a pena conferir:</p><p>• Database in Depth: Relational Theory for Practitioners</p><p>(http://oreilly.com/catalog/9780596100124/)</p>
<p>(O’Reilly), de C. J. Date.</p><p>• An Introduction to Database Systems, Eighth Edition (Addison-Wesley), de C. J.</p><p>Date.</p><p>• The Database Relational Model: A Retrospective Review and Analysis: A Historical</p><p>Account and Assessment of E. F. Codd’s Contribution to the Field of Database</p><p>Technology (Addison-Wesley), de C. J. Date.</p><p>• http://en.wikipedia.org/wiki/Database_management_system</p><p>• http://www.mcjones.org/System_R/</p><p>1 NOTA: Visando uma maior longevidade da Segunda Edição, a versão em inglês do livro utilizou a</p><p>versão 6.0 do MySQL, que, à época, encontrava-se no estágio Alpha de desenvolvimento. No</p><p>entanto, tal versão nunca passou do estágio Alpha e, devido a uma reestruturação no ciclo de</p><p>desenvolvimento, ela foi completamente removida do site o�cial do MySQL. Por isso, decidimos</p><p>utilizar a versão 5.1 no livro em português, por ser uma versão estável e que funciona</p><p>perfeitamente com todos os exemplos do livro.</p><p>CAPÍTULO 2</p><p>Criando e populando um banco de dados</p><p>Este capítulo fornecerá as informações necessárias para criar seu primeiro banco de</p><p>dados e para criar as tabelas e dados associados utilizados pelos exemplos deste</p><p>livro. Você também aprenderá sobre vários tipos de dados e verá como criar tabelas</p><p>com eles. Como os exemplos deste livro são executados em um banco de dados</p><p>MySQL, este capítulo é, de certa forma, direcionado às características e sintaxe do</p><p>MySQL, mas a maioria dos conceitos são aplicáveis a qualquer servidor.</p><p>Criando um banco de dados MySQL</p><p>Se você já conta com um banco de dados MySQL disponível para uso, você pode</p><p>pular as instruções de instalação e iniciar pelas instruções da tabela 2.1. Tenha em</p><p>mente, no entanto, que este livro presume que você esteja usando a versão 5.1 ou</p><p>posterior do MySQL – por isso, talvez seja necessário considerar a atualização de seu</p><p>servidor ou a instalação de outro servidor, caso você esteja usando uma versão mais</p><p>antiga.</p><p>As seguintes instruções mostram os passos mínimos necessários para instalar um</p><p>servidor MySQL 5.1 em um computador Windows: 1. Vá até a página de download</p><p>do MySQL Database Server em http://dev.mysql.com/downloads. Se desejar</p><p>especi�camente a versão 5.1, o endereço completo é</p><p>http://dev.mysql.com/downloads/mysql/5.1.html.</p><p>2. Baixe o pacote Windows Essentials (x86), que inclui apenas as ferramentas mais</p><p>comumente usadas.</p><p>3. Quando solicitado se “Deseja executar ou salvar esse arquivo?”, clique em</p><p>Executar.</p><p>4. A caixa de diálogo MySQL 5.1 – Setup Wizard aparecerá. Clique em Next.</p><p>5. Ative a opção Typical e clique em Next.</p><p>6. Clique em Install.</p><p>7. A caixa de diálogo MySQL Enterprise será exibida. Clique em Next duas vezes.</p><p>8. Quando a instalação for concluída, certi�que-se de que a opção “Con�gure the</p><p>MySQL Server now” esteja marcada e clique em Finish. Isso abrirá o</p><p>Con�guration Wizard.</p><p>9. Quando o Con�guration Wizard abrir, clique em Next, ative a opção Standard</p><p>Con�guration e clique em Next novamente. Selecione as opções “Install as</p><p>Windows Service” e “Include Bin Directory in Windows PATH”. Clique em Next.</p><p>10. Selecione a opção Modify Security Settings, insira uma senha para o usuário</p><p>root (certi�que-se de anotar a senha, pois você precisará dela em breve!) e clique</p><p>em Next.</p><p>11. Clique em Execute.</p><p>A essa altura, se tudo correu bem, o servidor MySQL está instalado e rodando. Se</p><p>não estiver, sugiro que você desinstale o servidor e leia o guia “Troubleshooting a</p><p>MySQL Installation Under Windows” (que você pode encontrar em</p><p>http://dev.mysql.com/doc/refman/5.1/en/windows-troubleshooting.html).</p><p>Se você desinstalou uma versão mais antiga do MySQL antes de carregar a versão 5.1,</p><p>talvez seja preciso fazer uma faxina extra (eu tive que limpar algumas entradas antigas</p><p>do Registro) antes de conseguir executar corretamente o Con�guration Wizard.</p><p>Em seguida, será necessário abrir uma janela de comando do Windows, executar a</p><p>ferramenta mysql e criar seu banco de dados e usuário do banco de dados. A tabela</p><p>2.1 descreve os passos necessários. No passo 5, sinta-se à vontade para escolher sua</p><p>própria senha para o usuário lrngsql, em vez de usar “xyz” (mas não se esqueça de</p><p>anotá-la!).</p><p>Tabela 2.1 – Criando o banco de dados de exemplo</p><p>Passo Descrição Ação</p><p>1</p><p>Abra a caixa de diálogo Executar a</p><p>partir do menu Iniciar</p><p>Clique em Iniciar e, em seguida, em</p><p>Executar.</p><p>2 Abra uma janela de comando Digite cmd e clique em OK</p><p>3 Faça o login no MySQL como root mysql -u root -p</p><p>4</p><p>Crie um banco de dados para os</p><p>dados de exemplo</p><p>create database bank;</p><p>5.</p><p>Crie o usuário do banco de dados</p><p>lrngsql com privilégios totais sobre o</p><p>banco de dados bank</p><p>grant all privileges on bank.* to</p><p>'lrngsql'@'localhost' identified by</p><p>'xyz';</p><p>6 Saia da ferramenta mysql quit;</p><p>7</p><p>Faça o login no MySQL como o</p><p>usuário lrngsql</p><p>mysql -u lrngsql -p;</p><p>8 Conecte-se ao banco de dados bank use bank;</p><p>Agora você tem um servidor MySQL, um banco de dados e um usuário do banco de</p><p>dados; – a única coisa que falta fazer é criar as tabelas do banco de dados e populá-</p><p>las com dados de exemplo. Para tanto, faça o download do script em</p><p>http://examples.oreilly.com/learningsql/ e execute-o a partir do utilitário mysql. Se</p><p>você salvou o arquivo como c:\temp\LearningSQLExample.sql, será preciso fazer o</p><p>seguinte: 1. Se você efetuou o logout na ferramenta mysql, repita os passos 7 e 8 da</p><p>tabela 2.1.</p><p>2. Digite source c:/temp/LearningSQLExample.sql; e pressione Enter.</p><p>Você deve ter agora um banco de dados funcional, populado com todos os dados</p><p>necessários para os exemplos deste livro.</p><p>Usando a ferramenta de linha de comando mysql</p><p>Sempre que você invocar a ferramenta de linha de comando mysql, você pode</p><p>especi�car o nome de usuário e o banco de dados que será usado, tal como no</p><p>exemplo seguinte: mysql -u lrngsql -p bank Isso irá poupá-lo de ter que digitar use</p><p>bank; toda vez que iniciar a ferramenta. A senha será solicitada, e então o prompt</p><p>mysql> surgirá, por meio do qual será possível escrever instruções SQL e veri�car os</p><p>resultados. Por exemplo, se você quiser saber a data e o horário atuais, é possível</p><p>escrever a seguinte consulta: mysql> SELECT now();</p><p>+---------------------+</p><p>| now() |</p><p>+---------------------+</p><p>| 2008-02-19 16:48:46 |</p><p>+---------------------+</p><p>1 row in set (0.01 sec)</p><p>A função now() é uma função nativa do MySQL que retorna a data e o horário</p><p>atuais. Como você pode ver, a ferramenta de linha de comando mysql formata os</p><p>resultados de suas consultas dentro de um retângulo limitado por caracteres +, - e |.</p><p>Após retornar todos os resultados (nesse caso, há apenas uma única linha de</p><p>resultado), a ferramenta de linha de comando mysql mostra quantas linhas foram</p><p>retornadas e quanto tempo a instrução SQL levou para ser executada.</p><p>Sobre a omissão da cláusula from</p><p>No caso de alguns servidores de banco de dados, não será possível executar uma</p><p>consulta sem uma cláusula from que nomeie pelo menos uma tabela. O Oracle</p><p>Database é um servidor comumente utilizado para o qual essa a�rmação é</p><p>verdadeira. Para os casos em que você queira apenas chamar uma função, o</p><p>Oracle fornece uma tabela chamada dual, que consiste em uma única coluna</p><p>denominada dummy e que contém uma única linha de dados. Para se manter</p><p>compatível com o Oracle Database, o MySQL também fornece uma tabela dual.</p><p>A consulta anterior, que determinava a data e o horário atuais, poderia,</p><p>portanto, ser escrita assim: mysql> SELECT now()</p><p>FROM dual;</p><p>+---------------------+</p><p>| now() |</p><p>+---------------------+</p><p>| 2005-05-06 16:48:46 |</p><p>+---------------------+</p><p>1 row in set (0.01 sec)</p><p>Se você não estiver usando o Oracle e não tiver a necessidade de manter seu</p><p>código compatível com ele, você poderá ignorar por completo a tabela dual e</p><p>apenas usar a cláusula select sem a cláusula from.</p><p>Quando você tiver terminado de usar a ferramenta de linha de comando mysql,</p><p>apenas digite quit; ou exit; para retornar ao prompt de comando do Windows.</p><p>Tipos de dados do MySQL</p><p>Em geral, todos os servidores de banco de dados mais difundidos têm a capacidade</p><p>de armazenar os mesmos tipos de dados, tais como strings, datas e números. Eles</p><p>costumam</p>
<p>diferir nos tipos de dados especializados, como documentos XML, textos</p><p>muito grandes ou documentos binários. Como este é um livro introdutório de SQL,</p><p>e como 98% das colunas que você encontrará serão de tipos de dados simples, este</p><p>livro aborda apenas os tipos de dados de caracteres, data e numéricos.</p><p>Dados do tipo caractere</p><p>Dados do tipo caractere podem ser armazenados como strings de tamanho �xo ou</p><p>variável: a diferença é que strings de tamanho �xo são preenchidas à direita com</p><p>espaços e sempre consomem o mesmo número de bytes, e strings de tamanho</p><p>variável não são preenchidas à direita com espaços e nem sempre consomem a</p><p>mesma quantidade de bytes. Ao de�nir uma coluna de caracteres, você deverá</p><p>especi�car o tamanho máximo que uma string a ser armazenada na coluna poderá</p><p>ter. Por exemplo, se você deseja armazenar strings que contenham até 20 caracteres</p><p>de extensão, você poderia usar uma das seguintes de�nições: char(20) /* tamanho</p><p>�xo */</p><p>varchar(20) /* tamanho variável */</p><p>O tamanho máximo de colunas do tipo char é atualmente de 255 bytes, enquanto</p><p>colunas varchar podem ter até 65.535 bytes. Se você precisa armazenar strings mais</p><p>longas (como e-mails, documentos XML etc.), você precisará usar um dos tipos de</p><p>dados de texto (mediumtext e longtext), que abordarei mais tarde nesta seção. Em</p><p>geral, você deve usar o tipo char quando todas as strings que serão armazenadas na</p><p>coluna têm o mesmo tamanho, como abreviações de estados, e o tipo varchar</p><p>quando as strings armazenadas na coluna têm tamanho variável. Tanto char quanto</p><p>varchar são usadas de forma semelhante em todos os principais servidores de banco</p><p>de dados.</p><p>O Oracle Database é uma exceção quando se fala em usar o tipo varchar. Usuários do</p><p>Oracle devem usar o tipo varchar2 quando forem de�nir colunas de caracteres com</p><p>tamanho variável.</p><p>Conjuntos de caracteres</p><p>Para as línguas que usam o alfabeto latino, como o inglês e o português, há um</p><p>número su�cientemente pequeno de caracteres usados de forma a se necessitar de</p><p>apenas um byte para armazenar cada um dos caracteres. Outras línguas, como o</p><p>japonês e o coreano, contêm um número muito grande de caracteres, necessitando,</p><p>assim, de múltiplos bytes de armazenamento para cada um deles. Tais conjuntos de</p><p>caracteres são, portanto, denominados conjuntos de caracteres multibytes.</p><p>O MySQL pode armazenar dados usando vários conjuntos de caracteres, tanto de</p><p>byte único quanto multibyte. Para exibir os conjuntos de caracteres suportados por</p><p>seu servidor, você pode usar o comando show da seguinte maneira:</p><p>Se o valor da quarta coluna, maxlen, for maior do que 1, o conjunto de caracteres é</p><p>um conjunto multibyte.</p><p>Quando instalei o servidor MySQL, o conjunto de caracteres latin1 foi</p><p>automaticamente selecionado como o conjunto de caracteres padrão. No entanto,</p><p>você pode optar por usar um conjunto de caracteres diferente para cada coluna de</p><p>caracteres em seu banco de dados, e pode até armazenar conjuntos de caracteres</p><p>diferentes dentro de uma mesma tabela. Para escolher um conjunto de caracteres</p><p>diferente do padrão ao de�nir uma coluna, basta nomear um dos conjuntos de</p><p>caracteres suportados após a de�nição do tipo, como em: varchar(20) character set</p><p>utf8</p><p>Com o MySQL, você também pode atribuir um conjunto de caracteres padrão para</p><p>todo o seu banco de dados: create database foreign_sales character set utf8;</p><p>Apesar de isso ser tudo que gostaria de discutir a respeito de conjuntos de caracteres</p><p>em um livro introdutório, há muito mais sobre o tópico de internacionalização do</p><p>que é mostrado aqui. Se você pretende trabalhar com vários conjuntos de caracteres</p><p>ou com conjuntos não-familiares, talvez você queira consultar um livro como Java</p><p>Internationalization, de Andy Deitsch e David Czarnecki</p><p>(http://oreilly.com/catalog/9780596000196/)(O’Reilly), ou Unicode Demysti�ed: A</p><p>Practical Programmer’s Guide to the Encoding Standard, de Richard Gillam (Addison-</p><p>Wesley).</p><p>Dados de texto</p><p>Se for necessário armazenar dados que possam exceder o limite de 64 KB das</p><p>colunas varchar, você precisará usar um dos tipos de texto.</p><p>A tabela 2.2 mostra os tipos de texto disponíveis e seus tamanhos máximos.</p><p>Tabela 2.2 – Tipos de texto do MySQL</p><p>Tipo de texto Número máximo de bytes</p><p>Tinytext 255</p><p>Text 65.535</p><p>Mediumtext 16.777.215</p><p>Longtext 4.294.967.295</p><p>Ao escolher utilizar um dos tipos de texto, tenha em mente o seguinte:</p><p>• Se os dados que estiverem sendo carregados em uma coluna de texto excederem</p><p>o tamanho máximo daquele tipo, os dados serão truncados.</p><p>• Espaços iniciais não serão removidos quando dados são carregados na coluna.</p><p>• Ao usar colunas de texto para ordenar ou agrupar, apenas os primeiros 1.024</p><p>bytes serão utilizados, apesar de esse limite poder ser aumentado, caso haja</p><p>necessidade.</p><p>• Os diferentes tipos de texto são exclusivos do MySQL. O SQL Server tem apenas</p><p>um tipo de texto para dados longos de caracteres, enquanto o DB2 e o Oracle</p><p>usam um tipo de dados chamado clob, um acrônimo de Character Large Object.</p><p>• Agora que o MySQL permite até 65.535 bytes em colunas varchar (elas eram</p><p>limitadas a 255 bytes na versão 4), não há nenhuma razão em particular para</p><p>usar o tipo de texto tinytext.</p><p>Se você estiver criando uma coluna para entrada de dados livres, como uma coluna</p><p>de notas que armazena dados sobre as interações dos clientes com o serviço de</p><p>atendimento ao cliente de sua empresa, o varchar provavelmente será o mais</p><p>adequado. No entanto, se você estiver armazenando documentos, você deve escolher</p><p>o tipo mediumtext ou longtext.</p><p>O Oracle Database permite até 2.000 bytes para colunas char e 4.000 bytes para</p><p>colunas varchar2. O SQL Server armazena até 8.000 bytes tanto para o tipo char</p><p>quanto para o tipo varchar.</p><p>Dados numéricos</p><p>Apesar de parecer razoável ter apenas um tipo de dados numérico denominado</p><p>“numérico”, existem, na verdade, vários tipos de dados numéricos que re�etem as</p><p>várias maneiras nas quais os números são utilizados, tal como ilustrado aqui: Uma</p><p>coluna indicando se o pedido de um cliente foi enviado</p><p>Esse tipo de coluna, referenciado como um Booleano, poderia conter um 0 para</p><p>indicar false e um 1 para indicar true.</p><p>Uma chave primária gerada pelo sistema para uma tabela de transações</p><p>Esse dado iniciaria geralmente em 1 e cresceria em incrementos de um até um</p><p>número potencialmente muito grande.</p><p>Um número de um item em uma cesta de compras eletrônica de um cliente</p><p>Os valores para esse tipo de coluna seriam números inteiros positivos entre 1 e,</p><p>no máximo, 200 (para compradores compulsivos).</p><p>Dados posicionais para uma máquina perfuradora de circuito impresso</p><p>Dados cientí�cos ou de manufatura de alta precisão normalmente necessitam de</p><p>uma precisão de oito casas decimais.</p><p>Para tratar esses tipos de dados (entre outros), o MySQL tem diferentes tipos de</p><p>dados numéricos. Os tipos de dados mais comuns são aqueles usados para</p><p>armazenar números inteiros. Ao especi�car um desses tipos, você também deve</p><p>especi�car que os dados são unsigned (sem sinal), indicando ao servidor que todos</p><p>os dados armazenados nessa coluna serão maiores ou iguais a zero. A tabela 2.3</p><p>mostra cinco tipos de dados diferentes utilizados no armazenamento de números</p><p>inteiros.</p><p>Tabela 2.3 – Tipos inteiros do MySQL1</p><p>Tipo Escopo com sinal Escopo sem sinal</p><p>Tinyint −128 a 127 0 a 255</p><p>Smallint −32.768 a 32.767 0 a 65.535</p><p>Mediumint −8.388.608 a 8.388.607 0 a 16.777.215</p><p>Int −2.147.483.648 a 2.147.483.647 0 a 4.294.967.295</p><p>Bigint −9.223.372.036.854.775.808 a 9.223.372.036.854,775.807 0 a 18.446.744.073.709.551.615</p><p>Quando você cria uma coluna usando um dos tipos inteiros, o MySQL alocará uma</p><p>quantidade de espaço apropriado para armazenar os dados, variando de um byte</p><p>para um tinyint até oito bytes para um bigint. Portanto, você deve tentar escolher</p><p>um tipo que seja grande o su�ciente para armazenar o maior número previsto para a</p><p>coluna, sem desperdiçar espaço de armazenamento de forma desnecessária.</p><p>Para números de ponto �utuante (como 3,1415927), você deve optar por um dos</p><p>tipos numéricos mostrados na tabela 2.4.</p><p>Tabela</p>
<p>2.4 – Tipos de ponto �utuante do MySQL</p><p>Tipo Escopo numérico</p><p>Float(p,e) −3,402823466E+38 a −1,175494351E-38 e de 1,175494351E-38 a 3,402823466E+38</p><p>Double(p,e) -1,7976931348623157E+308 a -2,2250738585072014E-308 e de 2,2250738585072014E-308 a</p><p>1,7976931348623157E+308</p><p>Ao usar um tipo de ponto �utuante, você pode especi�car uma precisão (o número</p><p>total de dígitos permitidos tanto à esquerda quanto à direita do ponto decimal) e</p><p>uma escala (o número de dígitos permitidos à direita do ponto decimal), mas isso</p><p>não é obrigatório. Esses valores estão representados na tabela 2.4 como p e e. Se você</p><p>especi�car uma precisão e uma escala para sua coluna de ponto �utuante, lembre-se</p><p>de que os dados armazenados na coluna serão arredondados se o número de dígitos</p><p>exceder a escala e/ou a precisão da coluna. Por exemplo, uma coluna de�nida como</p><p>float(4,2) armazenará um total de quatro dígitos, dois à esquerda e dois à direita</p><p>do ponto decimal. Portanto, uma coluna desse tipo trataria os números 27.44 e 8.19</p><p>sem problemas, mas o número 17.8675 seria arredondado para 17.87, e a tentativa de</p><p>armazenar o número 178.375 na sua coluna �oat(4, 2) geraria um erro.</p><p>Tais como os tipos inteiros, as colunas de ponto �utuante podem ser de�nidas como</p><p>unsigned, mas essa designação apenas evita que números negativos sejam</p><p>armazenados, não modi�cando o escopo dos dados que podem ser armazenados na</p><p>coluna.</p><p>Dados temporais</p><p>Além de strings e números, muito provavelmente você também se verá trabalhando</p><p>com datas e/ou horários. Esse tipo de dados é conhecido como temporal, e alguns</p><p>exemplos de dados temporais em um banco de dados incluem: • A data futura em</p><p>que se espera a ocorrência de um evento especí�co, como o envio do pedido de um</p><p>cliente.</p><p>• A data em que um pedido de um cliente foi enviado.</p><p>• A data e o horário em que um usuário modi�cou uma linha em particular em</p><p>uma tabela.</p><p>• A data de nascimento de um funcionário.</p><p>• O ano correspondente a uma linha de uma tabela de fatos vendas_anuais em um</p><p>data warehouse (depósito de dados).</p><p>• O tempo necessário para armar a �ação em uma linha de montagem de</p><p>automóveis.</p><p>O MySQL inclui tipos de dados que tratam todas essas situações. A tabela 2.5</p><p>mostra os tipos de dados temporais suportados pelo MySQL.</p><p>Tabela 2.5 – Tipos de dados temporais</p><p>Tipo Formato padrão Valores permitidos</p><p>Date AAAA-MM-DD 1000-01-01 a 9999-12-31</p><p>Datetime AAAA-MM-DD HH:MI:SS 1000-01-01 00:00:00 a 9999-12-31 23:59:59</p><p>Timestamp AAAA-MM-DD HH:MI:SS 1970-01-01 00:00:00 a 2037-12-31 23:59:59</p><p>Year AAAA 1901 a 2155</p><p>Time HHH:MI:SS −838:59:59 a 838:59:59</p><p>Apesar de os servidores de banco de dados armazenarem dados temporais de</p><p>diversas maneiras, o propósito da string de formato (segunda coluna da tabela 2.5) é</p><p>mostrar como os dados serão representados quando retornados, junto com a forma</p><p>em que uma string de data deve ser construída ao inserir ou atualizar uma coluna</p><p>temporal. Portanto, se você quisesse inserir a data 23 de março de 2005 em uma</p><p>coluna de data usando o formato AAAA-MM-DD, você usaria a string ‘2005-03-23’. O</p><p>capítulo 7 explora como os dados temporais são construídos e apresentados.</p><p>Cada servidor de banco de dados permite um escopo diferente de datas em colunas</p><p>temporais. O Oracle Database aceita datas de 4172 A.C. até 9999, enquanto o SQL</p><p>Server trabalha com datas que vão de 1753 até 9999 (a menos que você esteja usando o</p><p>novo tipo de dados datetime2 do SQL Server, que permite datas entre 1 e 9999). O</p><p>MySQL �ca entre o Oracle e o SQL Server, podendo armazenar datas entre 1000 A.C. e</p><p>9999. Apesar disso não fazer diferença para a maioria dos sistemas que registram</p><p>eventos atuais e futuros, é importante ter isso em mente caso você esteja armazenando</p><p>datas históricas.</p><p>A tabela 2.6 descreve os vários componentes dos formatos de data mostrados na</p><p>tabela 2.5.</p><p>Tabela 2.6 – Componentes dos formatos de data</p><p>Componente De�nição Escopo</p><p>AAAA Ano, incluindo século 1000 a 9999</p><p>MM Mês 01 (Janeiro) a 12 (Dezembro)</p><p>DD Dia 01 a 31</p><p>HH Hora 00 a 23</p><p>HHH Horas (transcorridas) −838 a 838</p><p>MI Minuto 00 a 59</p><p>SS Segundo 00 a 59</p><p>Aqui está como os vários tipos temporais seriam usados para implementar os</p><p>exemplos mostrados anteriormente: • Colunas que armazenam a data futura</p><p>esperada de envio do pedido de um cliente e a data de aniversário de um</p><p>funcionário usariam o tipo date, já que é desnecessário saber a que horas uma</p><p>pessoa nasceu e irreal agendar um envio futuro com precisão de segundos.</p><p>• Uma coluna que armazena informação sobre quando o pedido de um cliente foi</p><p>realmente enviado usaria o tipo datetime, pois ele é importante para registrar</p><p>não só a data em que o envio ocorreu, mas também o horário em que ele</p><p>ocorreu.</p><p>• Uma coluna que registra a última vez que um usuário modi�cou uma linha</p><p>especí�ca de uma tabela usaria o tipo timestamp. O tipo timestamp armazena a</p><p>mesma informação que o tipo datetime (ano, mês, dia, hora, minuto, segundo),</p><p>mas uma coluna timestamp será automaticamente povoada com a data e a hora</p><p>atuais pelo servidor MySQL no momento em que uma linha é adicionada a uma</p><p>tabela ou quando uma linha é modi�cada posteriormente.</p><p>• Uma coluna que armazene apenas dados de ano usaria o tipo year.</p><p>• Colunas que armazenam dados relativos a um período de tempo necessário para</p><p>se completar uma tarefa usariam o tipo time. Para esse tipo de dado, seria</p><p>desnecessário e confuso armazenar um componente de data, pois você está</p><p>interessado apenas no número de horas/minutos/segundos necessários para</p><p>completar a tarefa. Essa informação pode ser derivada utilizando-se duas</p><p>colunas datetime (uma para a data/hora inicial da tarefa e outra para a</p><p>data/hora de término) e subtraindo uma da outra, mas é mais simples utilizar</p><p>uma única coluna time.</p><p>O capítulo 7 explora como trabalhar com cada um dos tipos de dados temporais.</p><p>Criação de tabelas</p><p>Agora que temos um conhecimento sólido de quais tipos de dados podem ser</p><p>armazenados em um banco de dados SQL, chegou a hora de vermos como usar</p><p>esses tipos em de�nições de tabelas. Vamos começar de�nindo uma tabela que</p><p>armazenará informações sobre uma pessoa.</p><p>Passo 1: Projeto</p><p>Uma boa forma de começar a projetar uma tabela é fazer um brainstorming para</p><p>descobrir que tipo de informação seria interessante incluir. Aqui está o que marquei</p><p>depois de re�etir por instantes sobre os tipos de informações que de�nem uma</p><p>pessoa: • Nome</p><p>• Sexo</p><p>• Data de nascimento</p><p>• Endereço</p><p>• Comidas favoritas</p><p>Certamente isso não é uma lista completa, mas é boa o su�ciente por enquanto. O</p><p>próximo passo é atribuir nomes de colunas e tipos de dados. A tabela 2.7 mostra</p><p>minha tentativa inicial.</p><p>Tabela 2.7 – Tabela person, primeiro passo</p><p>Coluna Tipo Valores permitidos</p><p>Name Varchar(40)</p><p>Gender Char(1) M, F</p><p>Birth_date Date</p><p>Address Varchar(100)</p><p>Favorite_foods Varchar(200)</p><p>As colunas name, address e favorite_foods são do tipo varchar e permitem a entrada</p><p>de dados de forma livre. A coluna gender permite um único caractere, que deve ser</p><p>igual a M ou F. A coluna birth_date é do tipo date, já que um componente de</p><p>horário não é necessário.</p><p>Passo 2: Re�namento</p><p>No capítulo 1, você teve acesso ao conceito de normalização, que é o processo de</p><p>garantir que não haja colunas duplicadas (além das chaves estrangeiras) nem</p><p>colunas compostas em seu projeto de banco de dados. Ao olhar para as colunas da</p><p>tabela person uma segunda vez, as seguintes questões surgem: • A coluna name é, na</p><p>verdade, um objeto composto consistindo em um prenome e um sobrenome.</p><p>• Como várias pessoas podem ter o mesmo nome, sexo, data de nascimento e</p><p>assim por diante, não há colunas na tabela person que garantam unicidade.</p><p>• A coluna address também é um objeto composto, consistindo em rua, cidade,</p><p>estado/província, país e código postal.</p><p>• A coluna favorite_foods é uma lista, contendo 0, 1 ou mais itens independentes.</p><p>Seria melhor criar uma tabela separada para esse dado que inclua uma chave</p><p>estrangeira da tabela person, para você poder saber a qual pessoa uma comida</p><p>determinada deve ser atribuída.</p><p>Depois de levar todas essas</p>