T-sql,查找与键列表不匹配的元素

时间:2017-12-02 14:36:34

标签: sql-server tsql

SQL小提琴:http://sqlfiddle.com/#!6/52c67/1

CREATE TABLE MailingList (EmployeeId INT, Email VARCHAR(50))
INSERT INTO MailingList VALUES (1, 'bob@co.com')
INSERT INTO MailingList VALUES (2, 'jill@co.com')
INSERT INTO MailingList VALUES (3, 'frank@co.com')
INSERT INTO MailingList VALUES (4, 'fred@co.com')

现在我从某处获得了EmployeeIds列表:1,2,3,4,5

我需要检查哪些employeeId不在Mailinglist表中。我希望得到结果" 5"在这种情况下,因为它不在邮件列表表中。

最简单的方法是什么?

是否有一种比生成临时表更简单的方法,插入值1,2,3,4,5然后执行选择...不在(选择...) - 或获取相同的加入。所以基本上没有创建临时表并插入数据,只是使用列表1,2,3,4,5。

3 个答案:

答案 0 :(得分:1)

您可以使用EXCEPT命令。 例如:

SELECT *
FROM 
(
    SELECT 1 AS Id
    UNION ALL SELECT 2
    UNION ALL SELECT 3
    UNION ALL SELECT 4
    UNION ALL SELECT 5
) AS t
EXCEPT
SELECT Id FROM MailingList

答案 1 :(得分:1)

您似乎没有询问逻辑,只是关于如何“最佳”代表集合{1,2,3,4,5}

正如你所提到的,一个答案就是临时表。

另一个是带有一堆UNION ALL语句的子查询或CTE。

另一种方法是在CTE或子查询中使用VALUES (1), (2), (3), (4), (5)

但这里有一个明显的观点。如果您的表格中包含EmployeeID字段,那么 肯定 您有一个Employee表格?既然如此,您应该能够从那里“衍生”您的5名员工?

(SELECT id FROM employee WHERE manager_id = 666)

or...

(SELECT id FROM employee WHERE staff_ref IN ('111', '222', '333', '444', '555'))

etc, etc...

<强> 编辑:

至于实际逻辑,一旦你的集合代表你的5名员工,你可以使用LEFT JOINIS NULL进行“反加入”......

SELECT
    Employee.*
FROM
    Employee
LEFT JOIN
    MailingList
        ON  MailingList.list_id     = 789
        AND MailingList.employee_id = Employee.id
WHERE
    Employee.manager_id = 666
    AND MailingList.employee_id IS NULL

=&GT;经理#666但不在邮件列表#789

的员工

答案 2 :(得分:1)

每个人都在正确的轨道上,想到ANTI JOIN。然而值得注意的是,所提出的答案并不总能产生完全相同的结果,并且每种解决方案都有不同的性能影响。 MatBailie提出的建议是如何进行ANTI JOIN,亚历山大建议的是如何进行ANTI SEMI JOIN

Alexander正在寻找正确的IMO,我们正在寻找的是ANTI SEMI JOIN;一个 LEFT ANTI SEMI JOIN,具体来说,您将“somewhere”中的employeeId列表作为 Left 表,将MailingList作为 Right 表。

ANTI JOIN返回 设置中 通过set我指的是一个表,视图,子查询等。通过“this”集我指的是LEFT表,而“that”集我指的是RIGHT表。 SEMI JOIN是仅返回LEFT表中一个匹配行的位置。换句话说,A SEMI连接返回 distinct 集。

  

现在我从某个地方获得了EmployeeIds列表

使用提供的样本数据。让我们说,通过“某处”,你在谈论一张桌子。 (我将两次数字包括在内,以证明ANTI JOIN和ANTI SEMI JOIN之间的区别)

CREATE TABLE dbo.somewhere (employeeId int);
INSERT dbo.somewhere VALUES (1),(2),(3),(4),(5),(5);

您可以使用NOT INNOT EXISTS

进行左反联接
-- ANTI JOIN USING NOT IN
SELECT somewhere.EmployeeId--, <other columns>
FROM dbo.somewhere
WHERE somewhere.EmployeeId NOT IN (SELECT EmployeeId FROM dbo.MailingList); -- EXLCLUDE IDs NOT IN MailingList

-- ANTI JOIN USING NOT EXISTS
SELECT somewhere.EmployeeId--, <other columns>
FROM dbo.somewhere
WHERE NOT EXISTS 
(
  SELECT EmployeeId 
  FROM dbo.MailingList ML
  WHERE ML.EmployeeId = somewhere.employeeId
);

请注意,其中每个都返回数字5两次。如果您只需要使用EXCEPT来执行ANTI SEMI JOIN,那么:

SELECT somewhere.EmployeeId
FROM dbo.somewhere
EXCEPT -- SET OPERATOR (SET OPERATORS INCLUDE: UNION, UNION ALL, EXCEPT, INTERSECT)
SELECT EmployeeId 
FROM dbo.MailingList; -- EXLCLUDE IDs NOT IN MailingList

Set Operator UNIONINTERSECT外,我是string_split。集合运算符返回唯一的结果集。 (这是UNION ALL的一个例外)。如果您想使用NOT IN或NOT EXISTS获得唯一的结果集,则还需要包含DISTINCT或GROUP BY所有您想要唯一的列。

如果你在“某处”谈论逗号分隔的列表或XML或JSON文件/片段,那么你首先需要将该列表,XML,JSON或其他内容转换为LEFT表。使用SQL Server 2016的-- "somewhere" is a csv, list or array DECLARE @somewhere varchar(1000) = '1,2,3,4,5'; -- ANTI JOIN WITH NOT IN SELECT EmployeeId = [value] FROM string_split(@somewhere, ',') WHERE [value] NOT IN (SELECT EmployeeId FROM dbo.MailingList); -- ANTI SEMI JOIN WITH NOT IN SELECT DISTINCT EmployeeId = [value] FROM string_split(@somewhere, ',') WHERE [value] NOT IN (SELECT EmployeeId FROM dbo.MailingList); -- ANTI SEMI JOIN WITH EXCEPT SELECT EmployeeId = [value] FROM string_split(@somewhere, ',') EXCEPT SELECT EmployeeId FROM dbo.MailingList; GO (或其他“拆分器”功能),您可以这样做:

-- "somewhere" is XML
DECLARE @somewhere XML =
'<employees>
 <employee>1</employee>
 <employee>2</employee>
 <employee>3</employee>
 <employee>4</employee>
 <employee>5</employee>
 </employees>'

-- ANTI SEMI JOIN using EXCEPT    
SELECT employeeId = emp.id.value('.', 'int')
FROM (VALUES (@somewhere)) s(empid)
CROSS APPLY empid.nodes('/employees/employee') emp(id)
EXCEPT 
SELECT employeeId 
FROM dbo.MailingList;

..或者如果它是XML,一个选项看起来像这样:

RewriteEngine on
ErrorDocument 404 /404.php

RewriteRule ^signup$ signup.php [QSA,L]
RewriteRule ^signin$ signin.php [QSA,L]
RewriteRule ^home$ home.php [QSA,L]
RewriteRule ^messages$ messages.php [QSA,L]
RewriteRule ^signout$ signout.php [QSA,L]
RewriteRule ^settings$ settings.php [QSA,L]
RewriteRule ^settings/ChangePassword$ change_password.php [QSA,L]
RewriteRule ^settings/RemoveAccount$ remove_account.php [QSA,L]

RewriteRule ^message/([0-9]+)$ show_message.php?id=$1

最后。您想在邮件列表表中找到EmployeeId的索引。在我的示例中,您还需要dbo.somewhere上的索引。如果您正在进行SEMI连接,那么您希望这些索引是唯一的。