【问题标题】:Use inner join if record exists otherwise use left join如果记录存在则使用内连接,否则使用左连接
【发布时间】:2017-11-03 03:32:34
【问题描述】:

我的表结构如下:

dbo.Owner

OwnerID   OwnerName  
  1        John   
  2        Marie
  3        Alex

和 dbo.Pet

PetID PetTag Status OwnerID
  1    A341  Active    1  
  2    A342  Inactive  1  
  3    A343  Active    2
  4    A345  Active    2

我需要退回所有只有活跃宠物或没有宠物的主人。

所以在上面的这个例子中,我需要返回 Owner 2(所有宠物都处于活动状态)和 Owner 3(没有宠物)

我将使用实体框架在 C# 中提取数据,但纯 SQL 就足够了。

到目前为止,这是我想出的:

select mi.* from Owner o
join Pet p
on o.OwnerID= p.OwnerID
where o.Status='Active'
union select * from Owner
where OwnerID not in (select OwnerID from Pet)

现在,上面的查询有效,但它包含 OwnerID = 1。而且我想知道是否有一种方法可以在没有联合的情况下在 1 个查询中执行此操作。

【问题讨论】:

  • 听听你简单的英语查询:我需要返回所有只有活跃宠物或没有宠物的主人。你需要一个简单的联合那些有活跃宠物的人和那些没有宠物的人。

标签: c# sql-server entity-framework


【解决方案1】:

如果Status 的唯一值是“Active”和“Inactive”,您实际上可以简化查询。当你说:

我需要退回所有只有活跃宠物或没有宠物的主人。

这实际上会转化为:

我需要归还所有没有不活跃宠物的主人。

那么你的查询就变得容易多了。

在实体框架查询中:

owners = context.Owners
    .Where(o => !o.Pets.Any(p => p.Status == "Inactive"))
    .ToList();

由此产生的SQL查询是:

SELECT 
    [Extent1].[OwnerID] AS [OwnerID], 
    [Extent1].[OwnerName] AS [OwnerName]
    FROM [dbo].[Owners] AS [Extent1]
    WHERE  NOT EXISTS (SELECT 
        1 AS [C1]
        FROM [dbo].[Pets] AS [Extent2]
        WHERE ([Extent1].[OwnerID] = [Extent2].[OwnerID]) AND (N'Inactive' = [Extent2].[Status])
    )

或者去除杂乱:

SELECT 
    OwnerID,
    OwnerName
    FROM Owners o
    WHERE  NOT EXISTS (SELECT 
        1
        FROM Pets p
        WHERE (o.OwnerID = p.OwnerID AND p.Status = 'Inactive')
    )

如果您有更多的 Status 值,您可以使用(实体框架):

owners = context.Owners
    .Where(o => o.Pets.Any(p => p.Status == "Active") || !o.Pets.Any())
    .Where(o => !o.Pets.Any(p => p.Status == "Inactive" /* || p.Status == "Lost" and any other values */))
    .ToList();

将生成 SQL 查询:

SELECT 
    [Extent1].[OwnerID] AS [OwnerID], 
    [Extent1].[OwnerName] AS [OwnerName]
    FROM [dbo].[Owners] AS [Extent1]
    WHERE (( EXISTS (SELECT 
        1 AS [C1]
        FROM [dbo].[Pets] AS [Extent2]
        WHERE ([Extent1].[OwnerID] = [Extent2].[OwnerID]) AND (N'Active' = [Extent2].[Status])
    )) OR ( NOT EXISTS (SELECT 
        1 AS [C1]
        FROM [dbo].[Pets] AS [Extent3]
        WHERE [Extent1].[OwnerID] = [Extent3].[OwnerID]
    ))) AND ( NOT EXISTS (SELECT 
        1 AS [C1]
        FROM [dbo].[Pets] AS [Extent4]
        WHERE ([Extent1].[OwnerID] = [Extent4].[OwnerID]) AND (N'Inactive' = [Extent4].[Status])
    ))

您想要测试它的性能,并且可能有更好的方法,但它会产生预期的结果。它确实假设你有外键/导航属性。

【讨论】:

  • 如果一个人同时拥有一只 ACTIVE 宠物和一只 INACTIVE 宠物会发生什么。我认为您的查询在这种情况下不起作用
  • 两个查询都将省略这些所有者,这是问题所指定的。除非我误会你了?
【解决方案2】:

试试the following query:

select o.*
from dbo.owner o
where not exists(
  select *
  from dbo.pet p
  where p.ownerid=o.ownerid and
    p.status='Inactive'
);

【讨论】:

  • 谢谢安德烈,这应该可以。但是如果有更多的状态呢?我可以将 p.Status = 'Inactive' 更改为 p.Status 'Active' 吗?
【解决方案3】:
SELECT  OwnerID, OwnerName 
FROM Owner 
WHERE OwnerID NOT IN (
SELECT OwnerID from Pet
WHERE Status='Inactive'

这个简单的查询就可以解决问题。

OwnerId        OwnerName 
2              Marie
3              Alex

如果您想选择拥有至少一个 ACTIVE 或 NO PET 的所有者,请使用以下查询。

SELECT o.OwnerID o.OwnerName
FROM Owner o 
LEFT JOIN Pet p 
ON o.OwnerID= p.OwnerID 
AND (p.Status='Active' 
OR p.OwnerID is NULL)

OwnerId        OwnerName
1              John
2              Marie
3              Alex

此查询将返回 OWNER 名称,直到该 Owner 的所有宠物都处于 INACTIVE 状态

现在是另一个案例..

如果您的表有机会在 Pets 表中将 OwnerId 设置为 NULL。 请使用以下查询。 (Mysql)

SELECT OwnerID, OwnerName 
FROM Owner 
WHERE OwnerID NOT IN (
   SELECT IFNULL(OwnerID,0) from Pet
   WHERE Status='Inactive');

在子查询中添加了 IFNULL()。

SQLFIDDLE

【讨论】:

  • 作为一般规则,如果可以,请避免使用NOT IN。如果Pet.OwnerID 列中存在空值(即没有主人的宠物),您的查询将变成南瓜。 @AndreiOdegov 的not exists 版本不易受此影响。
  • SELECT DISTINCT OwnerName FROM Owner WHERE OwnerID NOT IN (SELECT IFNULL(OwnerID,0) from Pet WHERE Status='Inactive');我在 NOT IN 的选择中添加了 IFNULL 并且它正在工作。
  • 另外,因为 Owner 在 OwnerId 上是唯一的,所以不需要 DISTINCT,它可能会进行不必要的唯一排序。我不会删除 ID,因为多个人可以有相同的名字。
  • 移除 distinct 并添加 id
  • @FarazPV,MS SQL Server 中没有ifnull() 函数。另外,您是否将您的解决方案的执行计划与not exists 的执行计划进行了比较?我敢打赌,这样的包装器会让你失去 sargability。
【解决方案4】:

有趣的是,可以使用 LEFT JOIN 来做到这一点。我不知道这是否与其他答案建议的 NOT EXISTs 查询的执行方式不同。

CREATE TABLE [Owner] (
    OwnerID int PRIMARY KEY,
    OwnerName nvarchar(50)
);

INSERT INTO [Owner]
VALUES
  (1, 'John'), 
  (2, 'Marie'),
  (3, 'Alex');

CREATE TABLE Pet (
    PetID int PRIMARY KEY, 
    PetTag nvarchar(10), 
    Status nvarchar(30), 
    OwnerID int FOREIGN KEY REFERENCES [Owner](OwnerID)
);

INSERT INTO Pet
VALUES
  (1,'A341','Active', 1),  
  (2,'A342','Inactive', 1),  
  (3,'A343','Active', 2),
  (4,'A345','Active', 2);

SELECT * FROM [Owner];
SELECT * FROM Pet;

SELECT
    o.*
FROM
    [Owner] o
    LEFT JOIN Pet p
        ON o.OwnerID = p.OwnerID
        AND p.Status <> 'Active'
WHERE
    p.OwnerID IS NULL;

DROP TABLE Pet, [Owner];

【讨论】:

    【解决方案5】:
    select DISTINCT 
         o.Id 
    FROM Owner o
    LEFT JOIN Pet p ON o.OwnerID= p.OwnerID
    where p.Status='Active' OR p.OwnerID IS NULL
    

    【讨论】:

    • 不正确。这也将返回 id 为 1 的所有者。需要归还那些只有活跃宠物的主人。
    【解决方案6】:
    SELECT DISTINCT RESULT FROM (
                                SELECT    CASE WHEN POID is NULL 
                                               THEN OID
                                               WHEN OID NOT IN (SELECT DISTINCT 
                                                                OwnerID from Pet 
                                                                WHERE Status='Inactive')
                                               THEN OID
                                          END AS RESULT
                                FROM (
                                         SELECT O.OwnerID as OID, P.OwnerID as POID
                                         FROM Owner o 
                                         LEFT JOIN Pet p 
                                         ON o.OwnerID= p.OwnerID 
                                      ) T
                                )T2 WHERE  RESULT IS NOT NULL
    

    SQL Fiddle

    【讨论】:

      【解决方案7】:

      有趣的是,尽管您将其标记为实体框架,但大多数答案并未提供实体框架为您提供的简化。

      OwnersPets 之间存在一对多关系。每个Owner 有零个或多个Pets,每个Pet 恰好属于一个Owner

      如果您为 one-to-many relationship 正确配置了实体框架类,它们将类似于:

      class Owner
      {
          public int Id {get; set;}
          // every Owner has zero or more Pets:
          public virtual ICollection<Pet> Pets {get; set;}
      
          ... // other properties
      }
      class Pet
      {
          public int Id {get; set;}
          // every Pet belongs to exactly one Owner, using foreign key:
          public int OwnerId {get; set;}
          public Owner Owner {get; set;}
      }
      class MyDbConnection : DbConnection
      {
          public DbSet<Owner> Owners {get; set;}
          public DbSet<Pet> Pets {get; set;}
      }
      

      这足以让实体框架识别您设计了一对多关系。每当需要时,实体框架都会为您进行正确的连接。

      我需要退回所有只有活跃宠物或没有宠物的主人。

      这是相同的集合:

      我需要退回所有没有不活跃宠物的主人

      (自我提醒:没有宠物的主人,肯定没有不活跃的宠物!)

      如果您正确设置了类,查询将更具可读性。您可以考虑集合,而不是使用 ID 相互关联的表

      using (var dbConnection = new MyDbConnection())
      {
          var requestedOwners = dbConnection.Owners              // Give me all Owners
              .Where(owner => !owner.Pets.Any()                  // that have no Pets at all
                || owner.Pets.All(pet => pet.Status == Active)); // or have only active Pets
      }
      

      Entity Framework 将识别出这需要一个连接并将其转换为正确的内部连接。

      第二个查询更简单,可能更快,因为您可以在找到 Inactive Pet 后立即继续下一个 Owner

      var ownersWithoutInactivePets = dbContext.Owners   // give me all Owners
          .Where(owner => !owner.Pets                    // that don't have
              .Any(pet => pet.Status == Inactive);       // any inactive Pets
      

      再次,实体框架将为您完成连接

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-12-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-01-30
        • 1970-01-01
        相关资源
        最近更新 更多