Sql在连接表中找到与过滤器完全匹配的记录

时间:2018-03-11 19:55:27

标签: sql-server tsql

最好用一个例子来解释:)

假设我有一张桌子

CREATE TABLE dbo.Customer (
    CustomerId INT PRIMARY KEY,
    Name NVARCHAR(50)
)

CREATE TABLE dbo.ShoppingBasket (
    ShoppingBasketId INT PRIMARY KEY,
    CustomerId INT NOT NULL FOREIGN KEY dbo.Customer(CustomerId),
    ItemName NVARCHAR(50)
)

示例数据

INSERT INTO dbo.Customer
VALUES (1, 'Steve'), (2, 'Bucky')

INSERT INTO dbo.ShoppingBasket 
VALUES (1, 1, 'Banana'), (2, 1, 'Orange'), (3, 2, 'Orange')

现在,我想找到所有客户,他们的购物篮中都有香蕉和橙子。所以在上面的情况下,它应该只返回史蒂夫。因为Bucky只有香蕉。

以下查询适用于此

SELECT *
FROM dbo.Customer AS c
WHERE EXISTS (
    SELECT 1
    FROM dbo.ShoppingBasket AS b
    WHERE b.CustomerId = c.CustomerId
       AND b.ItemName IN ('Banana', 'Orange')
    GROUP BY CustomerId
    HAVING COUNT(CustomerId) = 2
)

没关系。现在,如果我想要所有只有Orange的客户,则上述查询自

后失败
SELECT *
FROM dbo.Customer AS c
WHERE EXISTS (
    SELECT 1
    FROM dbo.ShoppingBasket AS b
    WHERE b.CustomerId = c.CustomerId
       AND b.ItemName = 'Orange'
    GROUP BY CustomerId
    HAVING COUNT(CustomerId) = 1
)

过滤掉购物篮,然后应用group and having子句。因此史蒂夫和巴基都回来了,而只有巴基应该归还。

有人能指出我正确的方向找到这样的查询,我想我总是可以在存在的子查询中做另一个NOT EXIST,以确保找不到其他项目。 E.g。

SELECT *
FROM dbo.Customer AS c
WHERE EXISTS (
    SELECT 1
    FROM dbo.ShoppingBasket AS b
    WHERE b.CustomerId = c.CustomerId
       AND b.ItemName = 'Orange'
       AND NOT EXISTS (
           SELECT 1
           FROM dbo.ShoppingBasket AS b2
           WHERE b2.CustomerId = b.CustomerId
              AND b.ItemName <> 'Orange'
       )
)

但是,如果有一种更优雅的方式来处理它,那就是徘徊。最好不要在同一张桌子上进行额外的,否定的连接。

2 个答案:

答案 0 :(得分:1)

你应该检查不同的ItemName而不是customerId,例如:

  select c.*
  from dbo.Customer
  inner join(
    select CustomerId, count(distinct ItemName) count_name
    from ShoppingBasket
    where ItemName IN ('Banana', 'Orange')
    group by CustomerId
    having count_name = 2
  )  t on t.CustomerId = c.CustomerId

如果你需要两次检查项目名称的数量,你可以用两部分组成内部联接

select c.*
from dbo.Customer
inner join(
  select CustomerId
  from ShoppingBasket b
  where ItemName IN ('Banana', 'Orange')
  INNER JOIN (
  Select CustomerId, count(distinct ItemName) count_name
  from ShoppingBasket
  group by CustomerId
  having count_name = 2
  ) t2 ON t2.CustomerId = b.CustomerId
)  t on t.CustomerId = c.CustomerId

和Orange ..

select c.*
from dbo.Customer
inner join(
  select CustomerId
  from ShoppingBasket b
  where ItemName IN ('Orange')
  INNER JOIN (
  Select CustomerId, count(distinct ItemName) count_name
  from ShoppingBasket
  group by CustomerId
  having count_name = 1
  ) t2 ON t2.CustomerId = b.CustomerId
)  t on t.CustomerId = c.CustomerId

问题在于ambiguos中的in子句,因为对于ShoppingBasket CustomerId也返回true,并且有一个正面检查 那么你应该处理的是一个in子句(相当于OR),并为所有客户提供条款,这些客户在你正在寻找的那个号码上有许多不同的名字等价

  Select CustomerId
  from ShoppingBasket a
  inner join ShoppingBasket b a.ItemName = 'Orange' and b.ItemName = 'Banana'
  and customerId IN (
      Select CustomerId 
      from ShoppingBasket
      group by CustomerId
      having count(distinct ItemName) = 2
  )

答案 1 :(得分:0)

我喜欢使用group byhaving执行此操作。如果你想要“香蕉”和“橙色”:

select sb.customerId
from dbo.ShoppingBasket sb
where sb.itemName in ('banana', 'orange')
group by sb.customerId
having count(distinct itemName) = 2;  -- has both

如果你想要这两个项目,那么请使用这个更通用的形式:

select sb.customerId
from dbo.ShoppingBasket sb
where sb.itemName in ('banana', 'orange')
group by sb.customerId
having sum(case when sb.itemName = 'banana' then 1 else 0 end) > 0 and
       sum(case when sb.itemName = 'orange' then 1 else 0 end) > 0 and
       sum(case when sb.itemName not in ('orange, 'banana') then 1 else 0 end) = 0 ;

您可以轻松扩展此版本。每个项目都有自己的sum()。您还可以包含多个项目,例如“香蕉和(橙色或克莱门汀)”。