Como funcionam as instruções SQL EXISTS?

88

Estou tentando aprender SQL e tenho dificuldade em entender as instruções EXISTS. Me deparei com esta citação sobre "existe" e não entendi algo:

Usando o operador exists, sua subconsulta pode retornar zero, uma ou várias linhas, e a condição simplesmente verifica se a subconsulta retornou alguma linha. Se você olhar a cláusula select da subconsulta, verá que ela consiste em um único literal (1); uma vez que a condição na consulta contida precisa apenas saber quantas linhas foram retornadas, os dados reais que a subconsulta retornou são irrelevantes.

O que não entendo é como a consulta externa sabe qual linha a subconsulta está verificando? Por exemplo:

SELECT *
  FROM suppliers
 WHERE EXISTS (select *
                 from orders
                where suppliers.supplier_id = orders.supplier_id);

Eu entendo que se o id do fornecedor e da tabela de pedidos corresponderem, a subconsulta retornará verdadeiro e todas as colunas da linha correspondente na tabela de fornecedores serão geradas. O que não entendo é como a subconsulta comunica qual linha específica (digamos, a linha com o id do fornecedor 25) deve ser impressa se apenas um verdadeiro ou falso estiver sendo retornado.

Parece-me que não há relação entre a consulta externa e a subconsulta.

Dan
fonte

Respostas:

98

Pense desta forma:

Para 'cada' linha de Suppliers, verifique se 'existe' uma linha na Ordertabela que atenda à condição Suppliers.supplier_id(isso vem da 'linha' atual da consulta externa) = Orders.supplier_id. Quando você encontrar a primeira linha correspondente, pare aí - o WHERE EXISTSfoi satisfeito.

O elo mágico entre a consulta externa e a subconsulta reside no fato de que Supplier_idé passado da consulta externa para a subconsulta para cada linha avaliada.

Ou, dito de outra forma, a subconsulta é executada para cada linha da tabela da consulta externa.

NÃO é como se a subconsulta fosse executada como um todo e obtivesse o 'verdadeiro / falso' e então tentasse combinar esta condição 'verdadeiro / falso' com a consulta externa.

sojin
fonte
7
Obrigado! "NÃO é como uma subconsulta executada no todo e obtém o 'verdadeiro / falso', e então tenta combinar esta condição 'verdadeiro / falso' com a consulta externa. foi o que realmente esclareceu para mim, continuo pensando que é assim que as subconsultas funcionam (e muitas vezes funcionam), mas o que você disse faz sentido porque a subconsulta depende da consulta externa e, portanto, deve ser executada uma vez por linha
Clarence Liu
32

Parece-me que não há relação entre a consulta externa e a subconsulta.

O que você acha que a cláusula WHERE dentro do exemplo EXISTS está fazendo? Como você chega a essa conclusão quando a referência SUPPLIERS não está nas cláusulas FROM ou JOIN dentro da cláusula EXISTS?

EXISTS é avaliado como TRUE / FALSE e sai como TRUE na primeira correspondência dos critérios - é por isso que pode ser mais rápido do que IN. Também esteja ciente de que a cláusula SELECT em um EXISTS é ignorada - IE:

SELECT s.*
  FROM SUPPLIERS s
 WHERE EXISTS (SELECT 1/0
                 FROM ORDERS o
                WHERE o.supplier_id = s.supplier_id)

... deve atingir uma divisão por erro zero, mas não vai. A cláusula WHERE é a parte mais importante de uma cláusula EXISTS.

Esteja ciente também de que um JOIN não é uma substituição direta de EXISTS, porque haverá registros pai duplicados se houver mais de um registro filho associado ao pai.

Pôneis OMG
fonte
1
Ainda estou faltando alguma coisa. Se ele sair na primeira correspondência, como a saída acabará sendo todos os resultados onde o.supplierid = s.supplierid? Não seria apenas o primeiro resultado?
Dan
3
@Dan: A EXISTSsaída, retornando TRUE na primeira correspondência - porque o fornecedor existe pelo menos uma vez na tabela ORDERS. Se você quiser ver a duplicação dos dados do FORNECEDOR por ter mais de um relacionamento filho em PEDIDOS, deverá usar JOIN. Mas a maioria não quer essa duplicação, e a execução de GROUP BY / DISTINCT pode adicionar sobrecarga à consulta. EXISTSé mais eficiente do que SELECT DISTINCT ... FROM SUPPLIERS JOIN ORDERS ...no SQL Server, não testei no Oracle ou MySQL recentemente.
OMG Ponies
Eu tinha uma dúvida, a correspondência é feita para cada registro SELECIONADO na consulta externa. Como em, buscamos 5 vezes de Pedidos se houver 5 linhas selecionadas de Fornecedores.
Rahul Kadukar
24

Você pode produzir resultados idênticos usando JOIN, EXISTS, INou INTERSECT:

SELECT s.supplier_id
FROM suppliers s
INNER JOIN (SELECT DISTINCT o.supplier_id FROM orders o) o
    ON o.supplier_id = s.supplier_id

SELECT s.supplier_id
FROM suppliers s
WHERE EXISTS (SELECT * FROM orders o WHERE o.supplier_id = s.supplier_id)

SELECT s.supplier_id 
FROM suppliers s 
WHERE s.supplier_id IN (SELECT o.supplier_id FROM orders o)

SELECT s.supplier_id
FROM suppliers s
INTERSECT
SELECT o.supplier_id
FROM orders o
Anthony Faull
fonte
1
ótima resposta, mas também lembre-se que é melhor não usar existe para evitar correlação
Florian Fröhlich
1
Qual consulta você acha que será executada mais rapidamente se os fornecedores tiverem 10 milhões de linhas e os pedidos tiverem 100 milhões de linhas e por quê?
Teja
7

Se você tivesse uma cláusula where parecida com esta:

WHERE id in (25,26,27) -- and so on

você pode entender facilmente por que algumas linhas são retornadas e outras não.

Quando a cláusula where é assim:

WHERE EXISTS (select * from orders where suppliers.supplier_id = orders.supplier_id);

significa apenas: retornar linhas que possuem um registro existente na tabela de pedidos com o mesmo id.

Menahem
fonte
2

Essa é uma pergunta muito boa, então decidi escrever um artigo muito detalhado sobre esse assunto no meu blog.

Modelo de tabela de banco de dados

Vamos supor que temos as duas tabelas a seguir em nosso banco de dados, que formam uma relação de tabela um para muitos.

Tabelas SQL EXISTS

A studenttabela é o pai e o student_gradeé a tabela filho, pois tem uma coluna de chave estrangeira student_id referenciando a coluna de chave primária id na tabela de alunos.

O student tablecontém os dois registros a seguir:

| id | first_name | last_name | admission_score |
|----|------------|-----------|-----------------|
| 1  | Alice      | Smith     | 8.95            |
| 2  | Bob        | Johnson   | 8.75            |

E a student_gradetabela armazena as notas que os alunos receberam:

| id | class_name | grade | student_id |
|----|------------|-------|------------|
| 1  | Math       | 10    | 1          |
| 2  | Math       | 9.5   | 1          |
| 3  | Math       | 9.75  | 1          |
| 4  | Science    | 9.5   | 1          |
| 5  | Science    | 9     | 1          |
| 6  | Science    | 9.25  | 1          |
| 7  | Math       | 8.5   | 2          |
| 8  | Math       | 9.5   | 2          |
| 9  | Math       | 9     | 2          |
| 10 | Science    | 10    | 2          |
| 11 | Science    | 9.4   | 2          |

SQL EXISTS

Digamos que queremos todos os alunos que receberam nota 10 na aula de matemática.

Se estivermos interessados ​​apenas no identificador do aluno, podemos executar uma consulta como esta:

SELECT
    student_grade.student_id
FROM
    student_grade
WHERE
    student_grade.grade = 10 AND
    student_grade.class_name = 'Math'
ORDER BY
    student_grade.student_id

Mas, o aplicativo está interessado em exibir o nome completo de a student, não apenas o identificador, portanto, também precisamos das informações da studenttabela.

Para filtrar os studentregistros que têm nota 10 em matemática, podemos usar o operador EXISTS SQL, assim:

SELECT
    id, first_name, last_name
FROM
    student
WHERE EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade = 10 AND
        student_grade.class_name = 'Math'
)
ORDER BY id

Ao executar a consulta acima, podemos ver que apenas a linha Alice está selecionada:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

A consulta externa seleciona as studentcolunas de linha que estamos interessados ​​em devolver ao cliente. No entanto, a cláusula WHERE está usando o operador EXISTS com uma subconsulta interna associada.

O operador EXISTS retorna verdadeiro se a subconsulta retornar pelo menos um registro e falso se nenhuma linha for selecionada. O mecanismo de banco de dados não precisa executar a subconsulta inteiramente. Se um único registro for correspondido, o operador EXISTS retornará verdadeiro e a outra linha de consulta associada será selecionada.

A subconsulta interna é correlacionada porque a coluna student_id da student_gradetabela é comparada com a coluna id da tabela externa de aluno.

Vlad Mihalcea
fonte
Que ótima resposta. Acho que não entendi o conceito porque estava usando um exemplo errado. Funciona EXISTapenas com subconsulta correlacionada? Eu estava brincando com uma consulta contendo apenas 1 tabela, tipo SELECT id FROM student WHERE EXISTS (SELECT 1 FROM student WHERE student.id > 1). Eu sei que o que escrevi poderia ser alcançado por uma simples consulta WHERE, mas eu estava apenas usando para entender EXISTS. Eu tenho todas as linhas. É realmente devido ao fato de que eu não usei subconsulta correlacionada? Obrigado.
Bowen Liu
Faz sentido apenas para subconsultas correlacionadas, pois você deseja filtrar os registros da consulta externa. No seu caso, a consulta interna pode ser substituída por ONDE VERDADEIRO
Vlad Mihalcea
Obrigado Vlad. Isso foi o que eu pensei. É apenas uma ideia estranha que ocorreu quando eu estava brincando com isso. Sinceramente, não conhecia o conceito de subconsulta correlacionada. E agora faz muito mais sentido filtrar as linhas da consulta externa com a consulta interna.
Bowen Liu
0

EXISTS significa que a subconsulta retorna pelo menos uma linha, só isso. Nesse caso, é uma subconsulta correlacionada porque verifica o fornecedor_id da tabela externa para o fornecedor_id da tabela interna. Esta consulta diz, com efeito:

SELECIONE todos os fornecedores Para cada ID de fornecedor, veja se existe um pedido para este fornecedor Se o fornecedor não estiver presente na tabela de pedidos, remova o fornecedor dos resultados RETORNAR todos os fornecedores que possuem linhas correspondentes na tabela de pedidos

Você poderia fazer a mesma coisa neste caso com um INNER JOIN.

SELECT suppliers.* 
  FROM suppliers 
 INNER 
  JOIN orders 
    ON suppliers.supplier_id = orders.supplier_id;

O comentário dos pôneis está correto. Você precisaria fazer o agrupamento com essa junção ou selecionar distinto, dependendo dos dados de que precisa.

David Fells
fonte
4
A junção interna produzirá resultados diferentes de EXISTS se mais de um registro filho estiver associado a um pai - eles não são idênticos.
OMG Ponies
Acho que minha confusão pode ser que li que a subconsulta com EXISTS retorna verdadeiro ou falso; mas isso não pode ser a única coisa que ele retorna, certo? A subconsulta também está retornando todos os "fornecedores que possuem linhas correspondentes na tabela de pedidos"? Mas se for, como a instrução EXISTS está retornando um resultado booleano? Tudo o que estou lendo nos livros de texto está dizendo que ele retorna apenas um resultado booleano, então estou tendo dificuldade em conciliar o resultado do código com o que estou sendo informado de que ele retorna.
Dan
Leia EXISTS como uma função ... EXISTS (conjunto de resultados). A função EXISTS retornaria true se o conjunto de resultados tiver linhas, false se estiver vazio. É basicamente isso.
David Fells
3
@Dan, considere que EXISTS () é avaliado logicamente para cada linha de origem de forma independente - não é um valor único para a consulta inteira.
Arvo
-1

O que você descreve é ​​uma chamada consulta com uma subconsulta correlacionada .

(Em geral) é algo que você deve tentar evitar escrevendo a consulta usando uma junção:

SELECT suppliers.* 
FROM suppliers 
JOIN orders USING supplier_id
GROUP BY suppliers.supplier_id

Caso contrário, a subconsulta será executada para cada linha na consulta externa.

Wouter van Nifterick
fonte
2
Essas duas soluções não são equivalentes. O JOIN fornece um resultado diferente do que a subconsulta EXISTS se houver mais de uma linha ordersque corresponda à condição de junção.
a_horse_with_no_name
1
obrigado pela solução alternativa. mas você sugere que, se houver uma opção entre subconsulta correlacionada e junção, eu deveria ir com junção porque é mais eficiente?
sunny_dev