Em meu artigo anterior, expliquei o básico dos operadores de conjunto, seus tipos e pré-requisitos para seu uso. Também falei sobre os operadores UNION e UNION ALL, seus usos e diferenças.
Neste artigo, vamos aprender o seguinte:
- Operadores EXCEPT e INTERSECT.
- Diferença entre INTERSECT e INNER JOIN.
- A explicação detalhada de INTERSECT e EXCEPT com um exemplo.
Os operadores EXCEPT e INTERSECT foram introduzidos no SQL Server 2005. Ambos são operadores de conjunto usados para combinar os conjuntos de resultados gerados por duas consultas e recuperar a saída desejada.
O que é o operador INTERSECT
INTERSECT é usado para obter registros comuns a todos os conjuntos de dados recuperados de várias consultas ou tabelas. Aqui está uma visualização disso:
A sintaxe do operador INTERSECT é a seguinte:
SELECT COLUMN1, COLUMN2, COLUMN3, COLUMN4..FROM TABLE1 INTERSECT SELECT COLUMN1, COLUMN2, COLUMN3, COLUMN4..FROM TABLE2
O que é o operador EXCEPT
EXCEPT é usado para recuperar registros encontrados em uma consulta, mas não em outra consulta. Em outras palavras, ele retorna registros que são exclusivos para um conjunto de resultados. Isto é o que parece visualizado:
A sintaxe do operador EXCEPT é a seguinte:
SELECT COLUMN1, COLUMN2, COLUMN3, COLUMN4..FROM TABLE1 EXCEPT SELECT COLUMN1, COLUMN2, COLUMN3, COLUMN4..FROM TABLE2
Vamos criar uma configuração de demonstração para demonstrar como esses operadores podem ser usados.
Configuração de demonstração
Para demonstrar INTERSECT e EXCEPT, criei duas tabelas chamadas Employee e Estagiário .
Execute a seguinte consulta para criar estas tabelas:
CREATE TABLE [DBO].[EMPLOYEE] ( [NAME] [NVARCHAR](250) NOT NULL, [BUSINESSENTITYID] [INT] NOT NULL, [NATIONALIDNUMBER] [NVARCHAR](15) NOT NULL, [LOGINID] [NVARCHAR](256) NOT NULL, [BIRTHDATE] [DATE] NOT NULL, [MARITALSTATUS] [NCHAR](1) NOT NULL, [GENDER] [NCHAR](1) NOT NULL ) ON [PRIMARY] CREATE TABLE [DBO].[TRAINEE] ( [NAME] [NVARCHAR](250) NOT NULL, [BUSINESSENTITYID] [INT] NOT NULL, [NATIONALIDNUMBER] [NVARCHAR](15) NOT NULL, [BIRTHDATE] [DATE] NOT NULL, [GENDER] [NCHAR](1) NOT NULL ) ON [PRIMARY]
Agora, vamos inserir alguns dados fictícios no campo Funcionário tabela executando a seguinte consulta:
INSERT [DBO].[EMPLOYEE] ([NAME], [BUSINESSENTITYID], [NATIONALIDNUMBER], [LOGINID], [BIRTHDATE], [MARITALSTATUS], [GENDER]) VALUES (N'KEN SÁNCHEZ', 1, N'295847284', N'ADVENTURE-WORKS\KEN0', CAST(N'1969-01-29' AS DATE), N'S', N'M') GO INSERT [DBO].[EMPLOYEE] ([NAME], [BUSINESSENTITYID], [NATIONALIDNUMBER], [LOGINID], [BIRTHDATE], [MARITALSTATUS], [GENDER]) VALUES (N'TERRI DUFFY', 2, N'245797967', N'ADVENTURE-WORKS\TERRI0', CAST(N'1971-08-01' AS DATE), N'S', N'F') GO INSERT [DBO].[EMPLOYEE] ([NAME], [BUSINESSENTITYID], [NATIONALIDNUMBER], [LOGINID], [BIRTHDATE], [MARITALSTATUS], [GENDER]) VALUES (N'ROBERTO TAMBURELLO', 3, N'509647174', N'ADVENTURE-WORKS\ROBERTO0', CAST(N'1974-11-12' AS DATE), N'M', N'M') GO INSERT [DBO].[EMPLOYEE] ([NAME], [BUSINESSENTITYID], [NATIONALIDNUMBER], [LOGINID], [BIRTHDATE], [MARITALSTATUS], [GENDER]) VALUES (N'ROB WALTERS', 4, N'112457891', N'ADVENTURE-WORKS\ROB0', CAST(N'1974-12-23' AS DATE), N'S', N'M') GO INSERT [DBO].[EMPLOYEE] ([NAME], [BUSINESSENTITYID], [NATIONALIDNUMBER], [LOGINID], [BIRTHDATE], [MARITALSTATUS], [GENDER]) VALUES (N'GAIL ERICKSON', 5, N'695256908', N'ADVENTURE-WORKS\GAIL0', CAST(N'1952-09-27' AS DATE), N'M', N'F') GO INSERT [DBO].[EMPLOYEE] ([NAME], [BUSINESSENTITYID], [NATIONALIDNUMBER], [LOGINID], [BIRTHDATE], [MARITALSTATUS], [GENDER]) VALUES (N'JOSSEF GOLDBERG', 6, N'998320692', N'ADVENTURE-WORKS\JOSSEF0', CAST(N'1959-03-11' AS DATE), N'M', N'M')
Em seguida, faremos o mesmo para o Trainee tabela:
INSERT [DBO].[TRAINEE] ([NAME], [BUSINESSENTITYID], [NATIONALIDNUMBER], [BIRTHDATE], [GENDER]) VALUES (N'JOHN WOOD', 18, N'222969461', CAST(N'1978-03-06' AS DATE), N'M') GO INSERT [DBO].[TRAINEE] ([NAME], [BUSINESSENTITYID], [NATIONALIDNUMBER], [BIRTHDATE], [GENDER]) VALUES (N'MARY DEMPSEY', 19, N'52541318', CAST(N'1978-01-29' AS DATE), N'F') GO INSERT [DBO].[TRAINEE] ([NAME], [BUSINESSENTITYID], [NATIONALIDNUMBER], [BIRTHDATE], [GENDER]) VALUES (N'WANIDA BENSHOOF', 20, N'323403273', CAST(N'1975-03-17' AS DATE), N'F') GO INSERT [DBO].[TRAINEE] ([NAME], [BUSINESSENTITYID], [NATIONALIDNUMBER], [BIRTHDATE], [GENDER]) VALUES (N'KEN SÁNCHEZ', 1, N'295847284', CAST(N'1969-01-29' AS DATE), N'M') GO INSERT [DBO].[TRAINEE] ([NAME], [BUSINESSENTITYID], [NATIONALIDNUMBER], [BIRTHDATE], [GENDER]) VALUES (N'TERRI DUFFY', 2, N'245797967', CAST(N'1971-08-01' AS DATE), N'F') GO INSERT [DBO].[TRAINEE] ([NAME], [BUSINESSENTITYID], [NATIONALIDNUMBER], [BIRTHDATE], [GENDER]) VALUES (N'ROBERTO TAMBURELLO', 3, N'509647174', CAST(N'1974-11-12' AS DATE), N'M') GO
Agora, vamos usar INTERSECT para recuperar a lista de funcionários que são comuns a ambas as tabelas. Para fazer isso, execute a seguinte consulta:
SELECT NAME, BUSINESSENTITYID, NATIONALIDNUMBER, BIRTHDATE, GENDER FROM EMPLOYEE INTERSECT SELECT NAME, BUSINESSENTITYID, NATIONALIDNUMBER, BIRTHDATE, GENDER FROM TRAINEE
A saída desta consulta deve ser a seguinte:
Como você pode ver na captura de tela acima, a consulta retornou apenas registros comuns a ambas as tabelas.
INNER JOIN vs. INTERSECT
Na maioria dos casos, INTERSECT e INNER JOIN retornam a mesma saída, mas há algumas exceções. Um exemplo simples nos ajudará a entender isso.
Vamos adicionar alguns registros duplicados à tabela Trainee. Execute a seguinte consulta:
INSERT [DBO].[TRAINEE] ([NAME], [BUSINESSENTITYID], [NATIONALIDNUMBER], [BIRTHDATE], [GENDER]) VALUES (N'TERRI DUFFY', 2, N'245797967', CAST(N'1971-08-01' AS DATE), N'F') GO INSERT [DBO].[TRAINEE] ([NAME], [BUSINESSENTITYID], [NATIONALIDNUMBER], [BIRTHDATE], [GENDER]) VALUES (N'ROBERTO TAMBURELLO', 3, N'509647174', CAST(N'1974-11-12' AS DATE), N'M') GO
Agora, vamos tentar gerar a saída desejada usando INTERSECT.
SELECT NAME,BUSINESSENTITYID,NATIONALIDNUMBER,BIRTHDATE,GENDER FROM EMPLOYEE INTERSECT SELECT NAME,BUSINESSENTITYID,NATIONALIDNUMBER,BIRTHDATE,GENDER FROM TRAINEE
Esta é a saída que obtemos:
Agora, vamos tentar usar INNER JOIN.
SELECT A.NAME, A.BUSINESSENTITYID, A.NATIONALIDNUMBER, A.BIRTHDATE, A.GENDER FROM EMPLOYEE A INNER JOIN TRAINEE B ON A.NAME = B.NAME
A saída que obtemos neste caso é a seguinte:
Agora, como você pode ver na captura de tela acima, INNER JOIN recupera registros comuns a ambas as tabelas. Ele preenche todos os registros da tabela correta. Portanto, você pode ver registros duplicados.
Agora, vamos adicionar a palavra-chave DISTINCT à consulta INNER JOIN e ver o que isso faz:
SELECT DISTINCT A.NAME, A.BUSINESSENTITYID, A.NATIONALIDNUMBER, A.BIRTHDATE, A.GENDER FROM EMPLOYEE A INNER JOIN TRAINEE B ON A.NAME = B.NAME
A saída deve ficar assim:
Como você pode ver na imagem acima, os registros duplicados foram eliminados.
INTERSECT e INNER JOIN tratam valores NULL de forma diferente. Para INNER JOIN, dois valores NULL são diferentes, portanto, há chances de ignorá-los ao unir duas tabelas.
Por outro lado, INTERSECT trata dois valores NULL como sendo iguais, portanto, os registros que possuem valores NULL não serão eliminados. Para entender melhor, vejamos um exemplo.
Primeiro, vamos adicionar alguns valores NULL ao Trainee e Funcionário tabelas executando a seguinte consulta:
INSERT [DBO].[TRAINEE] ([NAME], [BUSINESSENTITYID], [NATIONALIDNUMBER], [BIRTHDATE], [GENDER]) VALUES (NULL, 3, N'509647174', CAST(N'1974-11-12' AS DATE), N'M') GO INSERT [DBO].[Employee] ([NAME], [BUSINESSENTITYID], [NATIONALIDNUMBER],[LOGINID], [BIRTHDATE],[MARITALSTATUS], [GENDER]) VALUES (NULL, 3, N'509647174','ADVENTURE-WORKS\TERRI0', CAST(N'1974-11-12' AS DATE), N'M',N'M') GO
Agora vamos tentar recuperar registros comuns às duas tabelas usando INTERSECT e INNER JOIN. Você precisará executar a seguinte consulta:
/*QUERY WITH INTERSECT*/ SELECT NAME, BUSINESSENTITYID, NATIONALIDNUMBER, BIRTHDATE, GENDER FROM EMPLOYEE INTERSECT SELECT NAME, BUSINESSENTITYID, NATIONALIDNUMBER, BIRTHDATE, GENDER FROM TRAINEE /*QUERY WITH INNER JOIN*/ SELECT A.NAME, A.BUSINESSENTITYID, A.NATIONALIDNUMBER, A.BIRTHDATE, A.GENDER FROM EMPLOYEE A INNER JOIN TRAINEE B ON A.NAME = B.NAME
Esta é a saída que devemos obter como resultado:
Como você pode ver acima, o conjunto de resultados gerado por INTERSECT contém valores NULL, enquanto INNER JOIN ignorou os registros que possuem valores NULL.
O operador EXCETO
Para demonstrar o operador EXCEPT em ação, vejamos um caso de uso. Por exemplo, quero preencher os detalhes das funcionárias da tabela Employee. A consulta a seguir nos ajudará a fazer exatamente isso:
SELECT NAME, BUSINESSENTITYID, NATIONALIDNUMBER, BIRTHDATE, GENDER FROM EMPLOYEE WHERE GENDER = 'F' EXCEPT SELECT NAME, BUSINESSENTITYID, NATIONALIDNUMBER, BIRTHDATE, GENDER FROM EMPLOYEE WHERE GENDER = 'M'
Esta é a saída que obtemos:
Como você pode ver acima, a consulta preencheu apenas os detalhes das funcionárias.
Você também pode preencher o conjunto de resultados usando uma subconsulta:
SELECT NAME, BUSINESSENTITYID, NATIONALIDNUMBER, BIRTHDATE, GENDER FROM EMPLOYEE AS M WHERE GENDER = 'F' AND GENDER NOT IN (SELECT GENDER FROM EMPLOYEE AS F WHERE GENDER = 'M')
Limitações de INTERSECT e EXCEPT
- Não podemos usar EXCEPT e INTERSECT em definições de exibição particionada distribuída com cláusulas COMPUTE e COMPUTE BY.
- EXCEPT e INTERSECT podem ser usados em cursores apenas de avanço rápido e estáticos.
- EXCEPT e INTERSECT podem ser usados em consultas distribuídas, mas só podem ser executados no servidor local. Você não pode executá-los em um servidor remoto.
Resumo
Neste artigo, abordei:
- Os operadores EXCEPT e INTERSECT.
- A diferença entre INTERSECT e INNER JOIN.
- Uma explicação detalhada dos operadores INTERSECT e EXCEPT com um exemplo.