Skip to content

6. Aprofundamento na linguagem SQL

6.1. Introduction

Neste capítulo, apresentamos

  • outras sintaxes do comando SELECT, que o tornam um comando de consulta muito poderoso, especialmente para consultar várias tabelas ao mesmo tempo.
  • sintaxes ampliadas de comandos já estudados

Para ilustrar os diversos comandos, trabalharemos com as seguintes tabelas utilizadas para o gerenciamento de pedidos em um sistema de distribuição de livros PME:

6.1.1. a tabela CLIENTS

Ela armazena informações sobre os clientes do PME:

 

Image

ID
número que identifica o cliente de forma exclusiva — chave primária
NOM
nome do cliente
STATUT
I = Pessoa física, E = Empresa, A = Órgão público
PRENOM
nome, no caso de pessoa física
CONTACT
Nome da pessoa de contato no cliente (no caso de uma empresa ou órgão público)
RUE
Endereço do cliente – rua
VILLE
cidade
CPOSTAL
CEP
TELEPH
Telefone
DEPUIS
Desde quando é cliente?
DEBITEUR
O (Sim) se o cliente deve dinheiro à empresa e N (Não) caso contrário.

6.1.2. a tabela ARTICLES

Ela armazena informações sobre os produtos vendidos, neste caso, livros. Sua estrutura é a seguinte:

Image

ISBN
número que identifica um livro de forma exclusiva (ISBN = International Standard Book Number) — chave primária
TITRE
Título do livro
CODEDITEUR
Código que identifica uma editora de forma exclusiva
AUTEUR
Nome do autor
RESUME
Resumo do livro
QTEANCOUR
Quantidade vendida no ano
QTEANPREC
Quantidade vendida no ano anterior
DERNVENTE
Data da última venda
QTERECUE
Quantidade da última entrega
DERNLIV
Data da última entrega
PRIXVENTE
Preço de venda
COUT
Custo de aquisição
MINCDE
Quantidade mínima a ser encomendada
MINSTOCK
Nível mínimo de estoque
QTESTOCK
Quantidade em estoque

Seu conteúdo poderia ser o seguinte:

Image

6.1.3. a tabela COMMANDES

Ela armazena informações sobre os pedidos feitos pelos clientes. Sua estrutura é a seguinte:

Image

NOCMD
Número que identifica um pedido de forma exclusiva — chave primária
IDCLI
Número do cliente que fez este pedido - chave estrangeira - referência CLIENTS(ID)
DATE_CMD
Data de registro deste pedido
ANNULE
O (Sim) se o pedido tiver sido cancelado e N (Não) caso contrário.

Image

6.1.4. a tabela DETAILS

Ela contém os detalhes de um pedido, ou seja, as referências e quantidades dos livros encomendados. Sua estrutura é a seguinte:

Image

NOCMD
Número do pedido — chave estrangeira que faz referência à coluna NOCMD da tabela COMMANDES
ISBN
Nº do livro encomendado — chave estrangeira que faz referência à coluna ISBN da tabela LIVRES
QTE
Quantidade encomendada

Seu conteúdo poderia ser o seguinte:

Image

Acima, vemos que o pedido nº 3 (NOCMD) refere-se a três livros. Isso significa que o cliente encomendou três livros ao mesmo tempo. As referências desse cliente podem ser encontradas na tabela [COMMANDES], onde se observa que o pedido nº 3 foi feito pelo cliente nº 5. A tabela [CLIENTS] nos informa que o cliente nº 5 é a empresa NetLogos, de Segré.

6.2. O pedido SELECT

Propomos aqui aprofundar nosso conhecimento sobre o pedido SELECT, apresentando novas sintaxes do mesmo.

6.2.1. Sintaxe de uma consulta em várias tabelas

syntaxe
SELECT coluna1, coluna2, ...
FROM tabela1, tabela2, ..., tabela p
WHERE condition
ORDER BY ...
action
A novidade aqui reside no fato de que as colunas coluna1, coluna2, ... provêm de várias tabelas tabela1, tabela2, ... Se duas tabelas tiverem colunas com o mesmo nome, a ambiguidade é resolvida por meio da notação tablei.colonnej. O código condition pode se referir às colunas de diferentes tabelas.

Funcionamento

1
É gerada a tabela cartesiana de table1, table2, ..., tablep. Se ni for o número de linhas de tablei, a tabela construída terá, portanto, n1*n2*...*np linhas, contendo o conjunto das colunas das diferentes tabelas.
2
A condition da WHERE é aplicada a essa tabela. Assim, é gerada uma nova tabela
3
Esta é ordenada de acordo com o modo indicado em ORDER.
4
As colunas solicitadas no SELECT são exibidas.

Exemplos

Utilizamos as tabelas apresentadas anteriormente. Queremos conhecer os detalhes dos pedidos feitos após 25 de setembro:

SQL>select details.nocmd,isbn,qte from commandes,details
  where commandes.datecmd>'25-sep-91'
  and details.nocmd=commandes.nocmd

Image

Observe-se que, após FROM, é inserido o nome de todas as tabelas cujas colunas são referenciadas. No exemplo anterior, todas as colunas selecionadas pertencem à tabela DETAILS. No entanto, a condição faz referência à tabela COMMANDES. Daí a necessidade de nomear esta última após o FROM. A operação que verifica a igualdade entre colunas de duas tabelas diferentes é frequentemente chamada de equijunção.

A consulta SELECT também poderia ter sido escrita da seguinte maneira:

SQL> select details.nocmd,isbn,qte from commandes
    inner join details on details.nocmd=commandes.nocmd
    where commandes.datecmd>'25-sep-91'

Vamos continuar com nossos exemplos. Queremos o mesmo resultado de antes, mas com o título do livro encomendado, em vez de seu número: ISBN:

SQL>select commandes.nocmd, articles.titre, details.qte
  from commandes,articles,details
  where commandes.datecmd>'25-sep-91'
  and details.nocmd=commandes.nocmd
  and details.isbn=articles.isbn

Image

O mesmo resultado é obtido com a seguinte consulta SQL, que é menos legível:

SQL> select details.nocmd,articles.titre,details.qte from details
    inner join commandes on details.nocmd=commandes.nocmd
    inner join articles on  details.isbn=articles.isbn
    where commandes.datecmd>'25-sep-91'

Acima, são realizadas duas junções internas com a tabela [DETAILS]:

  • uma com a tabela [COMMANDES] para acessar a data do pedido de um livro
  • uma delas com a tabela [ARTICLES] para ter acesso ao título do livro encomendado

Além disso, queremos o nome do cliente que fez o pedido:

SQL>select commandes.nocmd, articles.titre, qte ,clients.nom
  from commandes,details,articles,clients
  where commandes.datecmd>'25-sep-91'
  and details.nocmd=commandes.nocmd
  and details.isbn=articles.isbn
  and commandes.idcli=clients.id

Image

Além disso, queremos as datas dos pedidos e que elas sejam exibidas em ordem decrescente:

SQL>select commandes.nocmd, commandes.datecmd, articles.titre, qte ,clients.nom
    from commandes,details,articles,clients
    where commandes.datecmd>'25-sep-91'
    and details.nocmd=commandes.nocmd
    and details.isbn=articles.isbn
    and commandes.idcli=clients.id
    order by commandes.datecmd descending

Image

Aqui estão algumas regras a serem observadas nas junções:

  1. Após SELECT, colocam-se as colunas que se deseja exibir. Se a coluna existir em várias tabelas, ela deve ser precedida pelo nome da tabela.
  2. Após FROM, colocam-se todas as tabelas que serão exploradas pelo SELECT, ou seja, as tabelas que contêm as colunas que se encontram após SELECT e WHERE.

6.2.2. A auto-junção

Queremos saber quais livros têm um preço de venda superior ao do livro 'Using SQL':

SQL>select a.titre from articles a, articles b
  where b.titre='Using SQL'
  and a.prixvente>b.prixvente

Image

As duas tabelas da junção são, neste caso, idênticas: a tabela articles. Para diferenciá-las, atribui-se a elas um alias: from articles a, articles b. O alias da primeira tabela é a e o da segunda, b. Essa sintaxe pode ser usada mesmo que as tabelas sejam diferentes. Ao utilizar um alias, ele deve ser usado em todo o comando SELECT no lugar da tabela a que se refere.

6.2.3. Junção externa

Queremos identificar os clientes que compraram algo em setembro, incluindo a data do pedido. Os demais clientes são exibidos sem essa data:

SQL>select clients.nom,commandes.datecmd from clients
    left outer join commandes  on clients.id=commandes.idcli
    where datecmd between '01-sep-91' and '30-sep-91'

Image

Ficamos surpresos por não obter o resultado esperado. Deveríamos ter todos os clientes presentes na tabela [CLIENTS], o que não é o caso. Ao refletir sobre o funcionamento da junção externa, percebemos que os clientes que não compraram foram associados a uma linha vazia da tabela COMMANDES e, portanto, a uma data vazia (valor NULL na terminologia SQL). Essa data, portanto, não atende à condição definida para a data, e o cliente correspondente não é exibido. Vamos tentar outra coisa:

SQL>select clients.nom,commandes.datecmd from clients
    left outer join commandes  on clients.id=commandes.idcli
    where (commandes.datecmd between '01-sep-91' and '30-sep-91')
        or (commandes.datecmd is null)

Image

Desta vez, obtemos a resposta correta para nossa pergunta.

6.2.4. Consultas aninhadas

syntaxe
SELECT coluna[s] FROM tabela[s]
WHERE expressão operador consulta
ORDER BY ...
fonctionnement
requête é um comando SELECT que retorna um grupo de 0, 1 ou mais valores. Temos, então, uma condição WHERE do tipo
expressão operador (val1, val2, ..., vali)
expression e vali devem ser do mesmo tipo. Se a consulta retornar um único valor, voltamos a uma condição do tipo
expressão operador valor
que já conhecemos bem. Se a consulta retornar uma lista de valores, poderemos utilizar os seguintes operadores:
IN
expression IN (val1, val2, ..., vali): verdadeiro se expression tiver como valor um dos elementos da lista vali.
NOT IN
inverso de IN
ANY
deve ser precedido por =, !=, >, >=, <, <=
expression >= ANY (val1, val2, .., valn): verdadeiro se expression for >= a um dos valores vali da lista
ALL
deve ser precedido por =, !=, >, >=, <, <=
expression >= ALL (val1, val2, .., valn): verdadeiro se a expressão for >= a todos os valores válidos da lista
EXISTS 
consulta: verdadeira se a função requête retornar pelo menos uma linha.

Exemplos

Retomamos a questão já resolvida por uma equijunção: exibir os títulos com preço de venda superior ao do livro “Using SQL”.

SQL>select titre from ARTICLES
    where prixvente > (select prixvente from ARTICLES where titre='Using SQL')

Image

Essa solução parece mais intuitiva do que a da junção equi. Faz-se uma primeira filtragem com um SELECT e, em seguida, uma segunda sobre o resultado obtido. É possível realizar várias filtragens em série dessa maneira.

Queremos identificar os títulos cujo preço de venda seja superior ao preço médio de venda:

SQL> select titre from ARTICLES
    where prixvente > (select avg(prixvente) from ARTICLES)

Image

Quais são os clientes que encomendaram os títulos resultantes da consulta anterior?

SQL>select distinct idcli from COMMANDES,DETAILS
    where DETAILS.isbn in
    (select isbn from ARTICLES where prixvente
        > (select avg(prixvente) from ARTICLES))
    and COMMANDES.nocmd=DETAILS.nocmd

Image

Explicações

  1. seleciona-se na tabela DETAILS os códigos ISBN que estão entre os livros com preço superior ao preço médio dos livros.
  2. Nas linhas selecionadas na etapa anterior, não consta o código de cliente IDCLI. Ele se encontra na tabela COMMANDES. A ligação entre as duas tabelas é feita pelo número do pedido NOCMD, daí a equijunção COMMANDES.nocmd=DETAILS.nocmd.
  3. Um mesmo cliente pode ter comprado várias vezes um dos livros em questão; nesse caso, seu código IDCLI aparecerá várias vezes. Para evitar isso, colocamos a chave DISTINCT após SELECT. DISTINCT, de maneira geral, elimina as duplicatas nas linhas resultantes de um SELECT.
  4. Para obter o nome do cliente, precisaríamos realizar uma junção equi entre as tabelas COMMANDES e CLIENTS, conforme mostra a consulta a seguir.
SQL> select distinct CLIENTS.nom from COMMANDES,DETAILS,CLIENTS
    where DETAILS.isbn in
    (select isbn from ARTICLES where prixvente
        > (select avg(prixvente) from ARTICLES))
    and COMMANDES.nocmd=DETAILS.nocmd
    and COMMANDES.IDCLI=CLIENTS.ID

Image

Encontrar os clientes que não fizeram nenhum pedido desde 24 de setembro:

SQL>select nom from CLIENTS
    where clients.id not in
    (select distinct commandes.idcli from commandes where datecmd>='24-sep-91')

Image

Vimos que é possível filtrar linhas de outra forma além da cláusula WHERE: utilizando a cláusula HAVING em conjunto com as cláusulas GROUP e BY. A cláusula HAVING filtra grupos de linhas.

Da mesma forma que na cláusula WHERE, a sintaxe


     HAVING expression opérateur requête 

é válida, com a restrição já apresentada de que expression deve ser uma das expressões expri da cláusula


     GROUP BY expr1, expr2, ...

Exemplos

Quais são as quantidades vendidas dos livros com mais de 200F?

Vamos primeiro exibir as quantidades vendidas por título:

SQL>select ARTICLES.titre,sum(qte) QTE from ARTICLES, DETAILS
    where DETAILS.isbn=ARTICLES.isbn
    group by titre

Image

Agora, vamos filtrar os títulos:

SQL> select ARTICLES.titre,sum(qte) QTE from ARTICLES, DETAILS
    where DETAILS.isbn=ARTICLES.isbn
    group by titre
    having titre in (select titre from ARTICLES where prixvente>200)

Image

De uma forma talvez mais clara, poderíamos ter escrito:

SQL>select ARTICLES.titre,sum(qte) QTE from ARTICLES, DETAILS
    where DETAILS.isbn=ARTICLES.isbn
    and ARTICLES.prixvente>200
    group by titre

Image

6.2.5. Consultas correlacionadas

No caso de consultas aninhadas, há uma consulta pai (a consulta mais externa) e uma consulta filha (a consulta mais interna). A consulta pai só é avaliada depois que a consulta filha tiver sido completamente avaliada.

As consultas correlacionadas têm a mesma sintaxe, com a seguinte diferença: a consulta filha realiza uma junção com a tabela da consulta mãe. Nesse caso, o conjunto consulta-mãe/consulta-filha é avaliado repetidamente para cada linha da tabela mãe.

Exemplo

Retomamos o exemplo em que queremos os nomes dos clientes que não fizeram nenhum pedido desde 24 de setembro:

SQL> 
select nom from clients
    where not exists
        (select idcli from commandes
            where datecmd>='24-sep-91'
                and commandes.idcli=clients.id)

Image

A consulta principal é executada na tabela clients. A consulta secundária realiza uma junção entre as tabelas clients e commandes. Portanto, temos uma consulta correlacionada. Para cada linha da tabela clients, a consulta filha é executada: ela procura o código id do cliente nos pedidos feitos após 24 de setembro. Se não encontrar (not exists), o nome do cliente é exibido. Em seguida, passa-se para a próxima linha da tabela clients.

6.2.6. Critérios de seleção para a consulta SELECT

Vimos, em várias ocasiões, que era possível obter o mesmo resultado por meio de diferentes gravações na tabela SELECT. Vejamos um exemplo: exibir os clientes que fizeram algum pedido:

Junção

SQL>
select distinct nom from clients,commandes
    where clients.id=commandes.idcli

Image

Consultas aninhadas

SQL> 
select nom from clients
    where id in (select idcli from commandes)

dá o mesmo resultado.

Consultas correlacionadas

SQL>
select nom from clients
    where exists (select * from commandes where commandes.idcli=clients.id)

dá o mesmo resultado.

Os autores Christian MAREE e Guy LEDANT, em seu livro ‘SQL, Introdução, Programação e Domínio’, propõem alguns critérios de escolha:

Desempenho

O usuário não sabe como o SGBD “consegue” encontrar os resultados que ele solicita. Portanto, somente por meio da experiência ele descobrirá que determinada sintaxe é mais eficiente do que outra. MAREE e LEDANT afirmam, com base na experiência, que as consultas correlacionadas geralmente parecem mais lentas do que as consultas aninhadas ou as junções.

Formulação

A formulação por consultas aninhadas costuma ser mais legível e intuitiva do que a junção. No entanto, nem sempre é viável. Há dois pontos que merecem atenção especial:

  • As tabelas que contêm as colunas argumentos do SELECT (SELECT col1, col2, ...) devem ser nomeadas após a palavra-chave FROM. O produto cartesiano dessas tabelas é então realizado, o que se denomina junção.
  • Quando a consulta exibe resultados provenientes de uma única tabela e a filtragem das linhas dessa tabela exige a consulta a outra tabela, podem ser utilizadas consultas aninhadas.

6.3. Extensões de sintaxe

Por uma questão de conveniência, apresentamos na maioria das vezes sintaxes reduzidas dos diversos comandos. Nesta seção, apresentamos sintaxes ampliadas. Elas são intuitivas, pois são análogas às do comando SELECT, amplamente estudado.

INSERT

syntaxe1
INSERT INTO table (col1, col2, ..) VALUES (val1, val2, ...)
syntaxe2
INSERT INTO table (col1, col2, ...) (requête)
explication
Essas duas sintaxes foram apresentadas

DELETE

syntaxe1
DELETE FROM table WHERE condition
explication
Essa sintaxe é conhecida. Acrescentemos que a condição pode conter uma consulta com a sintaxe WHERE expressão operador (consulta)

UPDATE

syntaxe1
UPDATE table
SET col1=expr1, col2=expr2, ...
WHERE condition
explication
Essa sintaxe já foi apresentada. Acrescentemos que a condição pode conter uma consulta com a sintaxe WHERE expressão operador (consulta)
syntaxe2
UPDATE table
SET (col1, col2, ..) = consulta1, (cola, colb, ..) = consulta2, ...
WHERE condition
explication
Os valores atribuídos às diferentes colunas podem ser provenientes de uma consulta.