4

关系划分是Codd的原始关系运算符之一,俗称提供所有零件的供应商。已经有各种 SQL 翻译,例如 Celko 使用可以驾驶机库中所有飞机的飞行员的例子讨论了几种方法。

我更喜欢“与集合操作员的划分”,因为它是“有余数”(即威尔逊也可以驾驶 F-17 战斗机,但机库中没有)以及当除数是空集(即当机库为空时,所有飞行员都返回):

WITH PilotSkills
     AS
     (
      SELECT * 
        FROM (
              VALUES ( 'Celko', 'Piper Cub' ), 
                     ( 'Higgins', 'B-52 Bomber' ), ( 'Higgins', 'F-14 Fighter' ),
                     ( 'Higgins', 'Piper Cub' ), 
                     ( 'Jones', 'B-52 Bomber' ), ( 'Jones', 'F-14 Fighter' ),
                     ( 'Smith', 'B-1 Bomber' ), ( 'Smith', 'B-52 Bomber' ),
                     ( 'Smith', 'F-14 Fighter' ),
                     ( 'Wilson', 'B-1 Bomber' ), ( 'Wilson', 'B-52 Bomber' ),
                     ( 'Wilson', 'F-14 Fighter' ), ( 'Wilson', 'F-17 Fighter' )
             ) AS T ( pilot_name, plane_name )
     ), 
     Hangar
     AS
     (
      SELECT * 
        FROM (
              VALUES ( 'B-1 Bomber' ), 
                     ( 'B-52 Bomber' ), 
                     ( 'F-14 Fighter' )
             ) AS T ( plane_name )
     )
SELECT DISTINCT pilot_name 
  FROM PilotSkills AS P1
 WHERE NOT EXISTS (
                   SELECT plane_name 
                     FROM Hangar
                   EXCEPT
                   SELECT plane_name
                     FROM PilotSkills AS P2
                    WHERE P1.pilot_name = P2.pilot_name
                  );

现在我需要在 LINQ to Objects 中执行此操作。这是一个建议的直接翻译:

var hangar = new [] 
{ 
    new { PlaneName = "B-1 Bomber" },
    new { PlaneName = "F-14 Fighter" },
    new { PlaneName = "B-52 Bomber" }
}.AsEnumerable();

var pilotSkills = new [] 
{ 
    new { PilotName = "Celko", PlaneName = "Piper Cub" },
    new { PilotName = "Higgins", PlaneName = "B-52 Bomber" },
    new { PilotName = "Higgins", PlaneName = "F-14 Fighter" },
    new { PilotName = "Higgins", PlaneName = "Piper Cub" },
    new { PilotName = "Jones", PlaneName = "B-52 Bomber" },
    new { PilotName = "Jones", PlaneName = "F-14 Fighter" },
    new { PilotName = "Smith", PlaneName = "B-1 Bomber" },
    new { PilotName = "Smith", PlaneName = "B-52 Bomber" },
    new { PilotName = "Smith", PlaneName = "F-14 Fighter" },
    new { PilotName = "Wilson", PlaneName = "B-1 Bomber" },
    new { PilotName = "Wilson", PlaneName = "B-52 Bomber" },
    new { PilotName = "Wilson", PlaneName = "F-14 Fighter" },
    new { PilotName = "Wilson", PlaneName = "F-17 Fighter" }
}.AsEnumerable();

var actual = pilotSkills.Where
(
    p1 => hangar.Except
    ( 
        pilotSkills.Where( p2 => p2.PilotName == p1.PilotName )
                    .Select( p2 => new { p2.PlaneName } )
    ).Any() == false 
).Select( p1 => new { p1.PilotName } ).Distinct();

var expected = new [] 
{
    new { PilotName = "Smith" },
    new { PilotName = "Wilson" }
};

Assert.That( actual, Is.EquivalentTo( expected ) );

由于LINQ 据说是基于关系代数的,因此直接翻译似乎是合理的。但是有更好的“本机”LINQ 方法吗?


回顾@Daniel Hilgarth 的回答,在 .NET Land 中,数据很可能首先被“分组”:

var pilotSkills = new [] 
{ 
    new { PilotName = "Celko", 
            Planes = new [] 
            { new { PlaneName = "Piper Cub" }, } },
    new { PilotName = "Higgins", 
            Planes = new [] 
            { new { PlaneName = "B-52 Bomber" }, 
              new { PlaneName = "F-14 Fighter" }, 
              new { PlaneName = "Piper Cub" }, } },
    new { PilotName = "Jones", 
            Planes = new [] 
            { new { PlaneName = "B-52 Bomber" }, 
              new { PlaneName = "F-14 Fighter" }, } },
    new { PilotName = "Smith", 
            Planes = new [] 
            { new { PlaneName = "B-1 Bomber" }, 
              new { PlaneName = "B-52 Bomber" }, 
              new { PlaneName = "F-14 Fighter" }, } },
    new { PilotName = "Wilson", 
            Planes = new [] 
            { new { PlaneName = "B-1 Bomber" }, 
              new { PlaneName = "B-52 Bomber" }, 
              new { PlaneName = "F-14 Fighter" }, 
              new { PlaneName = "F-17 Fighter" }, } },
};

...并且仅投影名称是任意的,从而使潜在的解决方案更加直接:

 // Easy to understand at a glance:
var actual1 = pilotSkills.Where( x => hangar.All( y => x.Planes.Contains(y) ));

// Potentially more efficient:
var actual = pilotSkills.Where( x => !hangar.Except( x.Planes ).Any() );
4

2 回答 2

5

以下应该产生相同的结果:

pilotSkills.GroupBy(x => x.PilotName, x => x.PlaneName)
           .Where(g => hangar.All(y => g.Contains(y.PlaneName)))

这将为每位飞行员返回一组,他们可以驾驶机库中的所有飞机。
组的关键是飞行员的名字,组的内容是飞行员能飞的所有飞机,包括那些不在机库的飞机。

如果您现在只想要飞行员,您可以.Select(g => new { PilotName = g.Key })在查询末尾添加一个。


将上述方法与 一起使用Except,使其更接近 OP 的原版:

pilotSkills.GroupBy(x => x.PilotName, x => new { x.PlaneName } )
           .Where(g => !hangar.Except(g).Any());

第二个查询可能更好,因为它只迭代g一次;第一个查询将其Contains迭代 N 次,其中 N 是机库中的飞机数量。

于 2013-04-25T08:24:26.853 回答
1

使用@Daniel Hilgarth 的答案中的方法,但更接近我的原作:

var actual = pilotSkills
                 .GroupBy(x => x.PilotName, x => new { x.PlaneName })
                 .Where(g => !hangar.Except(g).Any())
                 .Select(x => new { PilotName = x.Key });
于 2013-04-25T10:04:04.500 回答