Esta variação da instrução SELECT permite que uma consulta seja feita dentro do resultado de outra consulta, permitindo a construção de comandos SQL mais flexíveis, suportando o uso de funções agregadoras.

Visão geral

Consulta em resultado de outras consultas bem utilizada quando se deseja, por exemplo, trazer o valor máximo de uma coluna que foi gerada a patir de cálculos com funções de agregação.

Considere o seguinte cenário: temos duas tabelas no banco de dados, uma chamada vendas (Tabela 1), responsável por armazenar as informações de vendas da aplicação, e outra chamada vendas_parcelas (Tabela 2), essa responsável pelo detalhamento do parcelamento das vendas.


ID ID_CLIENTE DATA VALOR
1 1 2018-06-07 1200.00
2 5 2018-05-17 800.00
3 3 2018-07-17 620.00
4 1 2018-05-18 900.00
Tabela 4. Tabela vendas

'
ID ID_VENDA NUMERO_PARCELA VALOR_PARCELA VENCIMENTO DATA_PAGEMENTO
1 1 1 600.00 2018-06-10 NULL
2 1 2 600.00 2018-07-10 NULL
3 2 1 200.00 2018-05-10 2018-05-10
4 2 2 200.00 2018-06-10 2018-06-10
5 2 3 200.00 2018-07-10 2018-07-10
6 2 4 200.00 2018-08-10 NULL
7 3 1 310.00 2018-07-10 2018-07-10
8 3 2 310.00 2018-08-10 2018-07-10
9 4 1 300.00 2018-05-20 2018-07-17
10 4 2 300.00 2018-06-20 2018-07-17
11 4 3 300.00 2018-07-20 2018-07-17
Tabela 5. Tabela vendas_parcelas

Suponha que desejamos pagar uma comissão aos vendedores, e que para pagar essa comissão, é necessário que a primeira parcela da venda esteja paga. Foi nos solicitado uma consulta no banco de dados para saber quantas comissões serão pagas no mês de julho Para isso, primeiro precisamos pegar todas as vendas que tiveram a primeira parcela paga, e em seguida, verificar o total dessa consulta. Para isso podemos utilizar a query abaixo:

SELECT COUNT(ID) as TOTAL FROM
  (
  SELECT 
    V.ID,
    V.VALOR,
    P.DATA_PAGAMENTO,
    P.NUMERO_PARCELA
  FROM 
    VENDAS V, VENDAS_PARCELAS P
  WHERE (V.ID = P.ID_VENDA) 
    AND (P.NUMERO_PARCELA = 1) 
    AND (P.DATA_PAGAMENTO IS NOT NULL)
    AND (month(P.DATA_PAGAMENTO) = 7)
) as tabela

Com a query acima obtivemos o resultado ilustrado na Tabela 3.


TOTAL
2
Tabela 3. Resultado da consulta da quantidade de comissões que serão pagas em julho

Esse resultado foi encontrado porque no trecho de código acima, entre as linhas 3 a 7 selecionamos as colunas que virão no resultado da consulta.

Na linha 8 selecionamos as tabelas que serão utilizadas na consulta, VENDAS com o apelido V, e VENDAS_PARCELAS com o apelido P. Em seguida, na linha 9 realizamos uma condicional para garantir que o ID da venda será o mesmo nas duas tabelas.

Nas linhas 10, 11 e 12 é onde definimos que o número da parcela deve ser 1 (primeira parcela) , que a data de pagamento não deve ser nula, ou seja, estar paga, e que o mês do pagamento da parcela deve ser 7 (julho).

Com essa consulta pronta, podemos realizar um novo SELECT nesse resultado, o que é feito nas linhas 1 e 2, onde selecionamos o total de ID encontrado, dando um apelido como TOTAL.

Na linha 13 definimos um apelido para o select que está servindo como base de tabela para a subconsulta.


Sintaxe

SELECT [coluna1, coluna2, … ]
  FROM (
  SELECT [coluna1, coluna2, … ]
  FROM [tabela1, tabela2, … ]
)

Na prática

Exemplo 1

Como outro exemplo podemos aproveitar a consulta que foi feita no exemplo de SELECT AS FIELD, que era responsável não só por trazer todas as vendas, mas também trazer quantidade de parcelas abertas e pagas da tabela. Alterando um pouco o cenário, imagine que agora precisamos listar a partir dessa consulta anterior, somente as vendas que tenham alguma parcela paga. Para isso podemos utilizar a query abaixo e com o resultado da Tabela 4:

SELECT * FROM (
  SELECT
    V.ID, 
    V.VALOR,
    V.DATA,
    (SELECT 
        COUNT(P.ID_VENDA) 
     FROM 
        FN_VENDAS_PARCELAS P 
     WHERE
        (V.ID = P.ID_VENDA) AND
        (P.DATA_PAGAMENTO is NULL)
    ) AS QTD_PARCELAS_ABERTAS,
    (SELECT 
        COUNT(P.ID_VENDA) 
    FROM 
        FN_VENDAS_PARCELAS P 
    WHERE 
        (V.ID = P.ID_VENDA) AND
        (P.DATA_PAGAMENTO is NOT NULL)
    ) AS QTD_PARCELAS_PAGAS
  FROM 
    VENDAS V
  ) as TABELA
WHERE 
  QTD_PARCELAS_PAGAS > 0

ID VALOR DATA QTD_PARCELAS_ABERTAS QTD_PARCELAS_PAGAS
2 800.00 2018-05-17 1 3
3 620.00 2018-07-17 0 2
4 900.00 2018-05-18 0 3
Tabela 4. Resultado do select

Esse resultado foi possível porque fizemos um select dentro de outro select, ou seja, na linha 1 solicitamos todas as informações de uma outra consulta.

Naslinhas 2 a 5 selecionamos as colunas da tabela venda que irão vir na consulta e, logo em seguida, criamos nas linhas 6 a 17 duas colunas novas que estão vindo de uma subconsulta feita na tabela vendas_parcelas. Essas colunas foram nomeadas como QTD_PARCELAS_ABERTAS e QTD_PARCELAS_PAGAS, respectivamente.

Na linha 6 selecionamos o total vendas da tabela vendas_parcelas, através da função agregadora COUNT e, em seguida, nas linhas 9 e 10 definimos que o ID da venda deve ser o mesmo nas duas tabelas, e a data de pagamento deve ser nula, ou seja parcelas abertas. Na linha 11 nomeamos essa consulta como a coluna QTD_PARCELAS_ABERTAS.

Na linha 12 é feito outro select para trazer o total de vendas, através da função agregadora count e, em seguida, nas linhas 15 e 16 definimos que o ID da venda deve ser o mesmo nas duas tabelas, e que a data de pagamento não deve ser nula, ou seja, já estar pago. Na linha 17 nomeamos essa consulta como a coluna QTD_PARCELAS_PAGAS.

Na linha 21 definimos um apelido para a consulta, como TABELA e, logo em seguida, definimos a condição, que será exibir somente os dados que tiverem pelo menos uma parcela paga.

Exemplo 2

Para este exemplo, considere a estrutura de tabelas abaixo, onde temos uma tabela chamada produto (Tabela 5), responsável por armazenar todos os produtos do estoque do cliente, a tabela categoria_produto (Tabela 6), responsável por armazenar todas as categorias existentes no banco de dados, e a tabela venda_produto (Tabela 7), que relaciona os produtos que foram vendidos.

id nome preco Id_categoria
1 Bola 35.00 1
2 Patinete 120.00 1
3 Carrinho 15.00 1
4 Skate 296.00 1
5 Notebook 3500.00 2
6 Monitor LG 19 450.00 2
7 O Diário de Anne Frank 45.00 3
8 O dia do Curinga 65.00 3
9 O mundo de Sofia 48.00 3
10 Através do Espelho 38.00 3
Tabela 5. Tabela produto

id nome
1 Infantil
2 Informatica
3 Educacional
Tabela 6. Tabela categoria_produto

id id_produto valor data
1 1 35.00 2018-05-15
2 1 35.00 2018-06-15
3 1 35.00 2018-07-15
4 2 120.00 2018-07-15
5 2 120.00 2018-07-14
6 3 15.00 2018-07-15
7 7 45.00 2018-07-15
8 8 65.00 2018-07-15
9 8 65.00 2018-07-16
10 9 48.00 2018-07-16
11 5 3500.00 2018-07-16
12 5 3500.00 2018-07-16
13 6 450.00 2018-07-16
Tabela 7. Tabela venda_produto

Podemos fazer uma consulta de todos os produtos da tabela produto, e adicionando uma nova coluna que seria o valor da Média Geral de preços de todos os produtos. Em seguida, realizaremos uma consulta no resultado desta primeiro consulta, trazendo somente aqueles que possuem um preço acima da média de preços. Para isso podemos utilizar a query abaixo:

SELECT
    T.*
FROM
    (
        SELECT
          V.*,
          (
             SELECT
                AVG(P.preco)
             FROM
                produto P
          ) as MEDIA_GLOBAL
        FROM
          venda_produto V
) as T
WHERE
    T.valor > T.MEDIA_GLOBAL

No trecho de código acima, nas Linhas 1 e 2 selecionamos todos os dados da tabela nomeada T, através do *, e em seguida, na Linha 3 definimos que a consulta será feita com base no resultado de outro subquery, definido nas linhas de 5 a 14.

Observe que na linha 15 informamos uma nome para o resultado da subquery, que será trabalhado como uma tabela. Esse nome é obrigatório para que o SQL entenda de onde deve tirar as informações.

Exemplo 3

Para o exemplo a seguir, considere a estrutura de tabelas abaixo, onde temos a tabela projetos (Tabela 8), a tabela comentarios (Tabela 9), a tabela usuarios (Tabela 10), a tabela likes_por_projeto (Tabela 11) e a tabela likes_por_comentarios (Tabela12).

id titulo data
1 Aplicação C# 2018-04-01
2 Aplicação Ionic 2018-05-07
3 Aplicação Python 2018-08-05
Tabela 8. Estrutura da tabela projetos

id comentario id_projeto id_usuario
1 A microsoft acertou com essa linguagem! 1 1
2 Parabéns pelo projet! bem legal! 1 3
3 Super interessante! Fácil e rapido! 2 4
4 Cara, que simples fazer um app assim! 2 1
5 Linguagem muito diferente. 3 3
6 Adorei aprender Python! Parabéns! 3 2
7 Muito maneiro esse framework! 2 2
Tabela 9. Estrutura da tabela comentarios

id nome email senha
1 Bruna Luiza bruninha@gmail.com abc123.
2 Thiago Braga thiagobraga_1@hotmail.com pena093
3 Osvaldo Justino osvaltino@yahoo.com.br osvaldit1_s
4 Gabriel Fernando gabriel_fnd@gmail.com gabss34
Tabela 10. Estrutura da tabela usuarios

id_projeto id_usuario
1 1
1 3
2 1
2 2
2 3
2 4
3 2
Tabela 11. Estrutura da tabela likes_por_projeto

id_comentario id_usuario
7 1
7 2
7 4
Tabela 12. Estrutura da tabela likes_por_comentarios

Para este exemplo, realizaremos primeiro a query que servirá como fonte de dados para a consulta principal:

SELECT 
    P.id,
    P.titulo,
    (SELECT
        COUNT(C.id_projeto)
    FROM
        comentarios C
    WHERE
        C.id_projeto = P.id ) AS Quantidade_Comentarios
    FROM
        projetos P

Veja abaixo o resultado da query acima na Tabela 13.

titulo Quantidade_Comentarios
Aplicação C# 2
Apliocação Ionic 3
Aplicação Python 2
Tabela 13. Resultado da query

Com base no resultado acima, iremos selecionar apenas o projeto que teve a quantidade de comentarios maior de 2, dessa forma, utilizaremos a query acima como fonte de dados.

SELECT 
    F.titulo,
    F.Quantidade_Comentarios
FROM
    (SELECT 
        P.id,
        P.titulo,
        (SELECT
            COUNT(C.id_projeto)
        FROM
            comentarios C
        WHERE
            C.id_projeto = P.id ) AS Quantidade_Comentarios
FROM
    projetos P
) as F
WHERE
    F.Quantidade_Comentarios > 2

Observe que na query acima, a consulta principal solicita através do FROM as colunas titulo, Quantidade_Comentarios da fonte de dados baseada em uma outra consulta, e por fim, realiza um filtro no resultado através da cláusula WHERE para buscar somente aqueles projetos com a quantidade de comentarios maior que 2.

Sempre após a criação de uma subquery como fonte de dados de uma consulta principal será necessário definir um nome para esta fonte de dados, através da palavra reservada AS.

Saiba mais Veja a Série SQL nível Jedi: Subqueries