2013-04-25 45 views
3

關係部門是Codd的原始關係運算符之一,通俗地說,它是供應所有零件的供應商。已經有各種翻譯成SQL的例子Celko使用the pilots who can fly all the planes in the hangar的例子討論了幾種方法。LINQ中的關係部門?

我更喜歡的是「與集合操作員的分工」,因爲它是「剩餘的」(即威爾遜也可以飛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這樣做是爲了對象。這裏有一個建議直接翻譯:

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 is supposedly based on the relational algebra然後直接翻譯似乎是合理的。但是有沒有更好的「原生」LINQ方法?


反思@Daniel Hilgarth的回答,在.NET土地的數據很可能是「組合」以開始:

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()); 
+0

你不需要'AsEnumerable()',數組的IEnumerable 2013-04-25 08:21:37

+0

Codd的劃分並不是原始的。非正式地,它返回「供應所有部件的供應商」和*供應至少一部分的供應商。 (雖然我同意你的非正式措辭可悲是常見的措辭),所以這實際上並不是那麼有用。 「方法」不是原文的「翻譯」,它們是原始*和不同但令人想起*的操作符的翻譯。 (關於關係子集運算符的最佳/最簡單的推理,然後轉換爲代數/演算。)PS「LINQ被認爲是基於關係代數」 - 哈哈。受到啓發,OK。 – philipxy 2017-12-14 09:04:44

+0

@philipxy引用來自我引用的文章:「本文將單子和LINQ描述爲關係代數的泛化」。如果你有反駁的引用,我會非常感興趣。同樣,我不是故意暗示分裂是原始的,也不認爲我有;我所鏈接的文章指的是(有點非正式的)託德和羅姆利的。但是如果你有什麼需要了解爲什麼師不是一個原始的操作員,那麼它會受到感謝。 – onedaywhen 2018-01-09 08:32:36

回答

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是機庫中的飛機數量。

+0

* $(*%£「只是打敗了我的答案,但是你確實需要一個選擇,以達到完全相同的結果作爲OP ... – 2013-04-25 08:33:28

+1

@BobVale:是的,我打敗了你10分鐘: p是的,我已經添加了選擇,對我來說,更重要的是要證明所有的飛機都返回了,而不僅僅是機庫中的飛機。 – 2013-04-25 08:34:30

+0

是的,它就像我剛剛提交時彈出一樣。 .. – 2013-04-25 08:35:08

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 }); 
+0

您使用'Except'的解決方案可能更好,因爲它僅迭代'g'一次。我用'Contains'解決方案將其重複N次,其中N是機庫中的飛機數量。 – 2013-04-25 10:17:36