Skip to content

6. SQL avançado

6.1. Introdução

Neste capítulo, apresentamos

  • sintaxes adicionais da instrução SELECT que a tornam um comando de consulta muito poderoso, especialmente para consultar várias tabelas de uma só vez.
  • sintaxes estendidas de comandos já abordados

Para ilustrar as várias ordens, iremos trabalhar com as seguintes tabelas utilizadas para a gestão de encomendas numa empresa de distribuição de livros de pequena a média dimensão:

6.1.1. a tabela CLIENTS

Armazena informações sobre os clientes da empresa:

 

Image

ID
Um identificador único para o cliente - chave primária
NOME
nome do cliente
STATUS
I=Particular, E=Empresa, A=Governo
NOME
nome próprio para um indivíduo
CONTACTO
Nome da pessoa de contacto nas instalações do cliente (no caso de uma empresa ou entidade governamental)
RUA
Endereço do cliente - rua
CIDADE
Cidade
CÓDIGO POSTAL
Código postal
Telefone
Telefone
DESDE
Desde quando é cliente?
DEVEDOR
Y (Sim) se o cliente tiver dívidas para com a empresa e N (Não) caso contrário.

6.1.2. A tabela ARTIGOS

Armazena informações sobre os produtos vendidos, neste caso livros. A sua estrutura é a seguinte:

Image

ISBN
Um identificador único para um livro (ISBN = International Standard Book Number) - chave primária
TÍTULO
Título do livro
CÓDIGO_DA_EDITORA
Código que identifica de forma única uma editora
AUTOR
Nome do autor
RESUMO
Resumo do livro
QTD
Quantidade vendida este ano
QTEANPREC
Quantidade vendida no ano anterior
ÚLTIMA VENDA
Data da última venda
QTERECUE
Quantidade da última entrega
ÚLTIMA ENTREGA
Data da última entrega
PREÇO DE VENDA
Preço de venda
CUSTO
Custo de aquisição
MINCDE
Quantidade mínima de encomenda
MINSTOCK
Nível mínimo de stock
QTESTOCK
Quantidade em stock

O seu conteúdo pode ser o seguinte:

Image

6.1.3. a tabela ORDERS

Armazena informações sobre as encomendas feitas pelos clientes. A sua estrutura é a seguinte:

Image

NOCMD
Um identificador único para uma encomenda - chave primária
CUSTID
ID do cliente para esta encomenda - chave estrangeira - referência a CUSTOMERS(ID)
ORDER_DATE
Data em que esta encomenda foi registada
CANCELADO
O (Sim) se a encomenda foi cancelada e N (Não) caso contrário.

Image

6.1.4. A tabela DETAILS

Contém os detalhes de uma encomenda, ou seja, os títulos e as quantidades dos livros encomendados. A sua estrutura é a seguinte:

Image

NOCMD
Número da encomenda - chave estrangeira que faz referência à coluna NOCMD da tabela COMMANDES
ISBN
Número do livro encomendado - chave estrangeira que faz referência à coluna ISBN na tabela BOOKS
QTE
Quantidade encomendada

O seu conteúdo pode ser o seguinte:

Image

Acima, vemos que a encomenda n.º 3 (NOCMD) envolve três livros. Isto significa que o cliente encomendou três livros ao mesmo tempo. Os registos deste cliente podem ser encontrados na tabela [ORDERS], onde vemos que a encomenda n.º 3 foi feita pelo cliente n.º 5. A tabela [CUSTOMERS] indica-nos que o cliente n.º 5 é a empresa NetLogos, em Segré.

6.2. A instrução SELECT

Aqui, pretendemos aprofundar a nossa compreensão da instrução SELECT, introduzindo novas sintaxes para a mesma.

6.2.1. Sintaxe de uma consulta com várias tabelas

sintaxe
SELECT coluna1, coluna2, ...
FROM tabela1, tabela2, ..., tabelaP
WHERE condição
ORDER BY ...
ação
A novidade aqui é 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 utilizando a notação tabelai.coluna. A condição pode aplicar-se a colunas de tabelas diferentes.

Como funciona

1
É construído o produto cartesiano das tabelas table1, table2, ..., tablep. Se n_i for o número de linhas na tabela_i, a tabela resultante terá n₁*n₂*...*n_p linhas contendo todas as colunas das diferentes tabelas.
2
A condição WHERE é aplicada a esta tabela. É assim produzida uma nova tabela
3
Esta tabela é ordenada de acordo com o método especificado em ORDER.
4
São apresentadas as colunas especificadas após SELECT.

Exemplos

Utilizamos as tabelas apresentadas anteriormente. Pretendemos ver os detalhes das encomendas efetuadas 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

Note que, após FROM, listamos os nomes de todas as tabelas cujas colunas estamos a referenciar. No exemplo anterior, todas as colunas selecionadas pertencem à tabela DETAILS. No entanto, a condição faz referência à tabela ORDERS. Daí a necessidade de listar esta última após FROM. A operação que verifica a igualdade entre colunas em duas tabelas diferentes é frequentemente chamada de equijoin.

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

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 os nossos exemplos. Queremos o mesmo resultado de antes, mas com o título do livro encomendado, em vez do seu 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 [ORDERS] para aceder à data de encomenda de um livro
  • uma com a tabela [ARTICLES] para aceder ao título do livro encomendado

Também queremos o nome do cliente que efetuou a encomenda:

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

Também queremos as datas dos pedidos e que estas sejam apresentadas por 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 seguir ao criar junções:

  1. Após SELECT, liste as colunas que pretende apresentar. Se a coluna existir em várias tabelas, preceda-a com o nome da tabela.
  2. Após FROM, liste todas as tabelas que serão consultadas pela instrução SELECT, ou seja, as tabelas que contêm as colunas listadas após SELECT e WHERE.

6.2.2. Autojuntão

Queremos encontrar os livros que têm um preço de retalho 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 envolvidas na junção são idênticas neste caso: a tabela articles. Para as distinguir, atribuímos-lhes aliases: a partir de articles a, articles b. O alias da primeira tabela é a, e o da segunda é b. Esta sintaxe pode ser utilizada mesmo que as tabelas sejam diferentes. Quando se utiliza um alias, este deve ser utilizado ao longo de toda a instrução SELECT, em substituição da tabela a que se refere.

6.2.3. Juntar Externa

Queremos identificar os clientes que fizeram uma compra em setembro, juntamente com a data da encomenda. Os outros clientes são apresentados sem esta 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

É surpreendente que, neste caso, não obtenhamos o resultado correto. Deveríamos ter todos os clientes presentes na tabela [CLIENTS], o que não é o caso. Quando pensamos em como funciona uma junção externa, percebemos que os clientes que não fizeram uma compra foram associados a uma linha vazia na tabela ORDERS e, portanto, a uma data vazia (um valor NULL na terminologia SQL). Esta data não cumpre a condição definida para a data, pelo que o cliente correspondente não é apresentado. 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 à nossa pergunta.

6.2.4. Consultas aninhadas

sintaxe
SELECT coluna[s] FROM tabela[s]
WHERE expressão operador de consulta
ORDER BY ...
Como funciona
Uma consulta é uma instrução SELECT que devolve um conjunto de 0, 1 ou mais valores. Temos então uma condição WHERE do tipo
expressão operador (val1, val2, ..., vali)
expressão e vali devem ser do mesmo tipo. Se a consulta devolver um único valor, ficamos reduzidos a uma condição do tipo
expressão operador valor
, com a qual estamos familiarizados. Se a consulta devolver uma lista de valores, podemos utilizar os seguintes operadores:
IN
expressão IN (val1, val2, ..., vali): verdadeiro se a expressão for avaliada como um dos elementos da lista vali.
NOT IN
oposto de IN
ANY
deve ser precedida por =, !=, >, >=, <, <=
expressão >= ANY (val1, val2, .., valn): verdadeiro se a expressão for >= um dos valores vali da lista
ALL
deve ser precedida por =, !=, >, >=, <, <=
expressão >= ALL (val1, val2, .., valn): verdadeiro se a expressão for >= todos os valores válidos na lista
EXISTS
consulta: verdadeiro se a consulta devolver pelo menos uma linha.

Exemplos

Vamos revisitar a questão já resolvida por uma equijoin: exibir os títulos com um 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

Esta solução parece mais intuitiva do que a equijoin. Realizamos um filtro inicial com um SELECT e, em seguida, um segundo filtro no conjunto resultante. Desta forma, podemos realizar vários filtros em sequência.

Queremos encontrar os títulos com um preço de venda superior ao preço médio de venda:

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

Image

Quais os clientes que encomendaram os títulos devolvidos pela 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ção

  1. Selecionamos da tabela DETAILS os códigos ISBN encontrados entre os livros com um preço superior ao preço médio dos livros.
  2. Nas linhas selecionadas no passo anterior, o ID do cliente (IDCLI) não está presente. Este encontra-se na tabela ORDERS. A ligação entre as duas tabelas é estabelecida através do número da encomenda (NOCMD), daí a condição de junção ORDERS.nocmd=DETAILS.nocmd.
  3. Um único cliente pode ter comprado um dos livros relevantes várias vezes, caso em que o seu código IDCLI apareceria várias vezes. Para evitar isso, colocamos a palavra-chave DISTINCT após SELECT. DISTINCT geralmente elimina duplicados das linhas devolvidas por uma consulta SELECT.
  4. Para recuperar o nome do cliente, teríamos de realizar uma junção adicional entre as tabelas ORDERS e CUSTOMERS, conforme mostrado na consulta seguinte.
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

Encontre os clientes que não fizeram uma encomenda 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 as linhas podem ser filtradas de outras formas além da cláusula WHERE: utilizando a cláusula HAVING em conjunto com a cláusula GROUP BY. A cláusula HAVING filtra grupos de linhas.

Tal como na cláusula WHERE, a sintaxe


     HAVING expression opérateur requête 

é possível, com a restrição já mencionada de que a expressão deve ser uma das expressões expri na cláusula


     GROUP BY expr1, expr2, ...

Exemplos

Quais são os números de vendas dos livros com preço superior a 200F?

Primeiro, vamos apresentar 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

Talvez de forma mais óbvia, 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 aninhadas

No caso de consultas aninhadas, existe uma consulta pai (a consulta mais externa) e uma consulta filho (a consulta mais interna). A consulta pai só é avaliada depois de a consulta filho ter sido totalmente avaliada.

As consultas correlacionadas têm a mesma sintaxe, com a seguinte diferença menor: a consulta filha realiza uma junção na tabela da consulta pai. Neste caso, o par de consultas pai-filha é avaliado repetidamente para cada linha da tabela pai.

Exemplo

Vamos revisitar o exemplo em que queremos os nomes dos clientes que não fizeram uma encomenda 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 opera na tabela de clientes. A consulta secundária realiza uma junção entre as tabelas de clientes e de encomendas. Trata-se, portanto, de uma consulta correlacionada. Para cada linha da tabela de clientes, a consulta secundária é executada: procura o ID do cliente nas encomendas feitas após 24 de setembro. Se não encontrar nenhuma (not exists), o nome do cliente é exibido. Em seguida, passa para a linha seguinte da tabela de clientes.

6.2.6. Critérios para escrever a instrução SELECT

Vimos, em várias ocasiões, que é possível obter o mesmo resultado utilizando diferentes instruções SELECT. Vejamos um exemplo: Apresentar os clientes que fizeram uma encomenda:

Juntar

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)

retorna o mesmo resultado.

Consultas correlacionadas

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

retorna o mesmo resultado.

Os autores Christian MAREE e Guy LEDANT, no seu livro «SQL: Introdução, Programação e Domínio», sugerem alguns critérios de seleção:

Desempenho

O utilizador não sabe como o SGBD «consegue» encontrar os resultados que solicita. Por isso, só através da experiência é que descobrirá que uma consulta é mais eficiente do que outra. MAREE e LEDANT afirmam, com base na experiência, que as consultas correlacionadas parecem geralmente mais lentas do que as consultas aninhadas ou as junções.

Formulação

A formulação utilizando consultas aninhadas é frequentemente mais legível e intuitiva do que as junções. No entanto, nem sempre é utilizável. Devem ser destacados dois pontos em particular:

  • As tabelas que contêm as colunas especificadas na cláusula SELECT (SELECT col1, col2, ...) devem ser listadas após a palavra-chave FROM. É então realizado o produto cartesiano destas tabelas, o que é conhecido como uma junção.
  • Quando a consulta apresenta resultados de uma única tabela e a filtragem das linhas dessa tabela requer a consulta a outra tabela, podem ser utilizadas consultas aninhadas.

6.3. Extensões de sintaxe

Por conveniência, apresentámos principalmente sintaxes abreviadas para os vários comandos. Nesta secção, apresentamos as suas sintaxes expandidas. Estas são autoexplicativas, pois são análogas às do comando SELECT, amplamente estudado.

INSERT

sintaxe1
INSERT INTO tabela (col1, col2, ...) VALUES (val1, val2, ...)
sintaxe2
INSERT INTO tabela (col1, col2, ..) (consulta)
explicação
Estas duas sintaxes foram apresentadas

DELETE

sintaxe1
DELETE FROM tabela WHERE condição
explicação
Esta sintaxe é bem conhecida. Note-se que a condição pode conter uma consulta utilizando a sintaxe WHERE expressão operador (consulta)

UPDATE

sintaxe1
ATUALIZAR tabela
SET col1=expr1, col2=expr2, ...
WHERE condição
explicação
Esta sintaxe já foi apresentada. Note que a condição pode conter uma consulta utilizando a sintaxe WHERE expressão operador (consulta)
sintaxe2
UPDATE tabela
SET (col1, col2, ..) = consulta1, (cola, colb, ..) = consulta2, ...
WHERE condição
explicação
Os valores atribuídos às várias colunas podem provir de uma consulta.