深刻剖析linq的聯接

內聯接

代碼以下算法

from a in new List<string[]>{
                            new string[2]{"張三",""},
                            new string[2]{"李四",""},
                            new string[2]{"王五",""}
                            }
join b in new List<string[]>{
                new string[3]{"張三","英語","90"},
                new string[3]{"張三","語文","70"},
                new string[3]{"李四","數學","100"}
                }
    on a[0] equals b[0]
select new {User=a,Score=b}

 

結果的結構以下sql

注意結果裏沒有a表的「王五」數據,在內聯接查詢裏,內部聯接會生成一個結果集,在該結果集中,第一個集合的每一個元素對於第二個集合中的每一個匹配元素都會出現一次。 若是第一個集合中的元素沒有匹配元素,則它不會出如今結果集中c#

總結:內聯接用「join 數據源 on 條件"語法,會將左表(即寫在前面的表)的每一條記錄和右表(即寫在後面的表)的每一條記錄進行比較,若是左表有x條記錄,右表有y條記錄,比較會有x*y次比較,但最後的結果不會有x*y條,而是在x*y條裏過濾出符合on條件的記錄,有點相似「笛卡爾積+條件判斷」的操做。spa

上面的內聯接可徹底改爲兩個from操做(進行笛卡爾積求值),結果的結構是徹底同樣的code

from a in new List<string[]>{
                            new string[2]{"張三",""},
                            new string[2]{"李四",""},
                            new string[2]{"王五",""}
                            }
from b in new List<string[]>{
                new string[3]{"張三","英語","90"},
                new string[3]{"張三","語文","70"},
                new string[2]{"李四","100"}
                }
    where a[0]==b[0]
select new {User=a,Score=b}

 

寫到這,可能會問,那全部的內聯接操做都改爲幾個from表就行,還用得着join on的內聯接嗎?答案是內聯接比單純對幾個表進行笛卡爾積求值「效率高不少」,假設有a,b,c三個表,分別爲x,y,z條記錄,若是用笛卡爾積算法(linq代碼如:from a in tab_a from b in tab_b from c in tab_c where ...... select ....),一共會進行x*y*z次鏈接操做,並對x*y*z條記錄進行where過濾;但若是用內聯接(linq代碼如:from a in tab_a join b in tab_b on ... join c in tab_c on ... select ....),每一次的內聯接會基於上一次的結果來進行下一次的操做,即a表和b表進行x*y次操做後,最後可能只得出w條記錄(此時的w可能遠小於x*y),而後再對c表進行w*z次操做,二者比較x*y*z可能遠大於w*z。若是不是a,b,c三個表,而是更多的表進行聯接,效率就差距很大了。xml

 

組聯接

代碼以下:對象

from a in new List<string[]>{
                            new string[2]{"張三",""},
                            new string[2]{"李四",""},
                            new string[2]{"王五",""}
                            }
join b in new List<string[]>{
                new string[3]{"張三","英語","90"},
                new string[3]{"張三","語文","70"},
                new string[3]{"李四","數學","100"}
                }
    on a[0] equals b[0] into b_group
select new {User=a,Score=b_group}

 

結果的結構以下blog

 注意:此時「王五」出如今告終果裏,在組聯接裏,第一個集合的每一個元素都會出如今分組聯接的結果集中(不管是否在第二個集合中找到關聯元素)。 在未找到任何相關元素的狀況下,該元素的相關元素序列爲空。 所以,結果選擇器有權訪問第一個集合的每一個元素。 這與非分組聯接中的結果選擇器不一樣,後者沒法訪問第一個集合中在第二個集合中沒有匹配項的元素。數學

總結:內聯接用「join 數據源 on 條件 into 新數據源"語法,會以左表(即寫在前面的表)的每一條記錄爲一組,分別和右表(即寫在後面的表)的每一條記錄進行比較,若是左表有x條記錄,右表有條記錄,比較會有x*y次比較,但結果只有x組,而每一組可能有<=y條>=0條記錄。string

若是要對上面的代碼進行輸出操做,會有兩次循環操做

var query=from a in new List<string[]>{
                            new string[2]{"張三",""},
                            new string[2]{"李四",""},
                            new string[2]{"王五",""}
                            }
join b in new List<string[]>{
                new string[3]{"張三","英語","90"},
                new string[3]{"張三","語文","70"},
                new string[3]{"李四","數學","100"}
                }
    on a[0] equals b[0] into b_group
select new {User=a,Score=b_group};

foreach(var p1 in query){
    Console.WriteLine($@"{p1.User[0]}的成績以下:");
    foreach(var p2 in p1.Score){
        Console.Write($@"---{p2[1]}-{p2[2]}---");
    }
    Console.WriteLine();
}

 

結果輸出以下:

張三的成績以下:
---英語-90------語文-70---
李四的成績以下:
---數學-100---
王五的成績以下:

能夠發現,單是用組聯接其實返回的結果在有些狀況下是不方便進行處理的,由於要對每個組再進行循環才能取到咱們最終想要的值,下面介紹用「內聯接+組聯接」來方便的獲得咱們想要的值

 

內聯接+組聯接

代碼以下:

from a in new List<string[]>{
                            new string[2]{"張三",""},
                            new string[2]{"李四",""},
                            new string[2]{"王五",""}
                            }
join b in new List<string[]>{
                new string[3]{"張三","英語","90"},
                new string[3]{"張三","語文","70"},
                new string[3]{"李四","數學","100"}
                }
    on a[0] equals b[0] into b_group
from b2 in b_group
select new {User=a,Score=b2}

 即在組聯接後的新表b_group再次聯接:from b2 in b_group

結果的結構以下:

若是細心的朋友會注意到如今的結果和最前面「內聯接」一節的結果是同樣的。

這樣的結果結構相比上一節的組聯接的結構更容易獲取結果內容,再也不須要須要兩次循環,取值代碼以下

foreach(var p1 in query){
    Console.WriteLine($@"{p1.User[0]}-{p1.User[1]}-{p1.Score[1]}-{p1.Score[2]}");
}

 

輸出以下:

張三-男-英語-90
張三-男-語文-70
李四-女-數學-100

對代碼稍做修改

from a in new List<string[]>{
                            new string[2]{"張三",""},
                            new string[2]{"李四",""},
                            new string[2]{"王五",""}
                            }
join b in new List<string[]>{
                new string[3]{"張三","英語","90"},
                new string[3]{"張三","語文","70"},
                new string[3]{"李四","數學","100"}
                }
    on a[0] equals b[0] into b_group
from b2 in b_group
select new {User=a,Score=b_group}

 

只是將

select new {User=a,Score=b2}改爲了
select new {User=a,Score=b_group}

結果的結構變成以下

 每條結果的結構變成string[]和IGrouping<string,string[]>,無論結果的結構如何,記錄裏已經沒有「王五」的數據。

再改下代碼,看看b_group,b2和a的所有結構是怎樣的

代碼以下:

from a in new List<string[]>{
                            new string[2]{"張三",""},
                            new string[2]{"李四",""},
                            new string[2]{"王五",""}
                            }
join b in new List<string[]>{
                new string[3]{"張三","英語","90"},
                new string[3]{"張三","語文","70"},
                new string[2]{"李四","100"}
                }
    on a[0] equals b[0] into b_group
from b2 in b_group.DefaultIfEmpty()
select new{User=a, Scores=b_group,Score=b2}

 

結果的結構以下:

 

左外聯接

代碼以下

from a in new List<string[]>{
                            new string[2]{"張三",""},
                            new string[2]{"李四",""},
                            new string[2]{"王五",""}
                            }
join b in new List<string[]>{
                new string[3]{"張三","英語","90"},
                new string[3]{"張三","語文","70"},
                new string[2]{"李四","100"}
                }
    on a[0] equals b[0] into b_group
from b2 in b_group.DefaultIfEmpty()
select new{User=a, Score=b2}

 只是在「內聯接+組聯接」的代碼上作了一點改動,將from b2 in  b_group改爲了from b2 in b_group.DefaultIfEmpty()

結果以下

 

代碼稍做修改,看看內部全部結構

from a in new List<string[]>{
                            new string[2]{"張三",""},
                            new string[2]{"李四",""},
                            new string[2]{"王五",""}
                            }
join b in new List<string[]>{
                new string[3]{"張三","英語","90"},
                new string[3]{"張三","語文","70"},
                new string[2]{"李四","100"}
                }
    on a[0] equals b[0] into b_group
from b2 in b_group.DefaultIfEmpty()
select new{User=a, Scores=b_group,Score=b2}

 

結果的結構以下

 

結構和「內聯接+組聯接"的結構是完成同樣的,只是找不到成績的「王五」也出如今結果集裏。

不少資料只寫了怎麼用「左聯接」,但爲何要這麼「彆扭」寫的緣由卻沒有說明,特別是熟悉sql語句的對這種方式很不理解,以爲太繞了。要理解linq,先要拋開以前sql語句的影響,linq既然是c#裏對象的sql語句,那咱們就要以對象的方式去思考,微軟的目的是爲了保證linq to object、linq to sql、linq to xml的語法是同樣的。先理解linq to object,至於linq to sql最終生成的sql語句是由linq底層的算法來實現的。的下面用圖說明下「組聯接」--》「內聯接+組聯接」--》「左外聯接「是怎麼生成的

相關文章
相關標籤/搜索