第 105 講:C# 3 之查詢(xún)表達(dá)式(九):LINQ 里的關(guān)系代數(shù)
(Relation Algebra)是數(shù)據(jù)庫(kù)里面需要用到的一個(gè)重要的理論概念。它針對(duì)于表進(jìn)行加減乘除等等的定義操作。下面我們就一一來(lái)看下,關(guān)系代數(shù)里都有些啥。
本文參照了數(shù)據(jù)庫(kù)的一些操作來(lái)進(jìn)行處理。如果你對(duì)數(shù)據(jù)庫(kù)并不了解的話(huà),本文可能難度就比較大了,建議你先學(xué)一下數(shù)據(jù)庫(kù)再來(lái)看這些內(nèi)容吧。當(dāng)然了,這些內(nèi)容也不是必須現(xiàn)在就要學(xué)會(huì)的東西。如果不會(huì)數(shù)據(jù)庫(kù)的同學(xué),可以暫時(shí)跳過(guò),也不影響后面的介紹內(nèi)容。
上一節(jié)內(nèi)容我們并未講完 LINQ 提供的自帶方法操作集,只是說(shuō)了一些聚合函數(shù)。LINQ 里還有一些不是聚合函數(shù)的函數(shù),它們執(zhí)行產(chǎn)生的結(jié)果并不一定都是我們想要的。下面我們繼續(xù)闡述 LINQ 提供的別的一類(lèi)的函數(shù)。
Part 1 加法(并集)
關(guān)系代數(shù)里,把兩個(gè)表加起來(lái)(在數(shù)學(xué)上一般記作 ),就好比是求得兩個(gè)表的并集。注意,在 LINQ 里,兩個(gè)列表要求獲取并集需要兩個(gè)列表是同元素類(lèi)型的。比如,我們只能針對(duì)于兩個(gè)
IEnumerable<int>
作運(yùn)算,嘗試把 IEnumerable<int>
和 IEnumerable<char>
作并集是沒(méi)有意義的。

好在,在 LINQ 里面提供了 Union
那么,如果 list1
里有 1、2、3,而 list2
里有 2、 3、 5 的話(huà),那么 result
里就有 1、2、3、5 了。
Part 2 減法(差集)
將兩個(gè)表求差(一般記作 ?或
),就好比取出被減集合里有,而減集合里沒(méi)有的元素。

在 LINQ 里,我們可以使用 Except
方法來(lái)獲取差集。
那么,如果 list1
里有 1、2、3,而 list2
里有 2、 3、 5 的話(huà),那么 result
里就是 1。
可見(jiàn),減法操作是要區(qū)分被減集合和減集合的。
Part 3 對(duì)稱(chēng)差集
對(duì)稱(chēng)差集又表示為 ,即取兩種差集的并集。

我們可以使用 SymmetricalExcept
方法獲取對(duì)稱(chēng)差集。
list2.SymmetricalExcept(list1)
也是一樣的。
Part 4 點(diǎn)乘法(交集)
為了區(qū)分這里的乘法和笛卡爾積的區(qū)別,我們把這里的乘法叫做點(diǎn)乘,而笛卡爾積我們表示為叉乘。
點(diǎn)乘兩個(gè)表(一般記作 ?或者
)相當(dāng)于為兩個(gè)表作交集處理,取出都有的部分。

Intersect
那么,如果 list1
里有 1、2、3,而 list2
里有 2、 3、 5 的話(huà),那么 result
里就是 2、3。
Part 5 叉乘法(笛卡爾積)
叉乘(一般記作 )就是笛卡爾積。不過(guò)笛卡爾積在前文已經(jīng)提到過(guò)了,它的模式是獲取兩個(gè)表的一一匹配的結(jié)果。因?yàn)榈芽柗e我們使用的是查詢(xún)表達(dá)式就可以實(shí)現(xiàn),所以叉乘法也就是這么做的,畢竟是等價(jià)的概念。
補(bǔ)集就是一個(gè)集合對(duì)于全局情況里不滿(mǎn)足的部分。如圖所示。

數(shù)學(xué)上一般把集合 A 的補(bǔ)集記作 ,而等價(jià)的公式是
,其中
?表示元素對(duì)應(yīng)的這種數(shù)據(jù)類(lèi)型里可以取到的全部情況(比如
int
類(lèi)型的
不過(guò)很遺憾的是,在 LINQ 的世界里是不好實(shí)現(xiàn)補(bǔ)集一說(shuō)的,因?yàn)檫@個(gè)所謂的“全集”是不清楚的概念。如果你要實(shí)現(xiàn)它,你可以嘗試給出一個(gè)全局集合,然后作出補(bǔ)集。而這種邏輯完全可以直接拿差集(減法運(yùn)算)來(lái)完成,所以又不需要給補(bǔ)集提供單獨(dú)的操作。
Part 7 選擇
選擇是橫向獲取所有滿(mǎn)足指定條件的對(duì)象。按照這種定義來(lái)看,它其實(shí)就等價(jià)于 where
從句的部分。
這樣就選擇出了所有大于等于 5 的元素。
Part 8 投影
投影是縱向獲取所有指定字段的信息。比如,我有一堆學(xué)生,學(xué)生包含姓名、學(xué)號(hào)等信息,我可以嘗試使用 select
從句來(lái)獲取所有學(xué)生的學(xué)號(hào)。
這樣篩選出來(lái)的就是所有學(xué)生的姓名、年齡和性別三大屬性值。
為啥叫縱向呢?因?yàn)槲覀儼堰@些學(xué)生的所有屬性構(gòu)成一張表的話(huà),那么學(xué)生的信息自然就是一排一排地列舉信息的。而我們要獲取的是這個(gè)表里指定“列”上的數(shù)據(jù),這就是縱向地取值的概念。
Part 9 連接
連接表在前面已經(jīng)講了很多了,所以這里我們就不再贅述了,不過(guò)需要注意的是,它還有很多操作,例如自然連接、非等值連接等等。
Part 10 除法
除法理解起來(lái)比較困難,所以我們把它放在最后講,而且我們得分四個(gè)例子給大家解釋。

第一個(gè)實(shí)例給出了 A 和 B 兩個(gè)表,現(xiàn)在要獲取兩個(gè)表的商。除法運(yùn)算的模式是,先找到第一個(gè)表在第二個(gè)表里沒(méi)有的字段,即這里的甲和乙,然后每一個(gè)部分都看一遍??纯此挟?dāng)前數(shù)值對(duì)應(yīng)行上的對(duì)應(yīng) B 表的丙字段的數(shù)值有沒(méi)有包含。如果包含就取出來(lái)。

第二個(gè)例子稍微難一點(diǎn)。B 表多了一個(gè)值,那么篩選 A 表數(shù)值的時(shí)候,就必須要看,是否篩選出來(lái)的集合,包含了全部 B 給出的信息值。舉個(gè)例子,B 表的丙字段是 3 和 6,我們就必須在 A 表里找到所有記錄,這些記錄的丙字段要包括所有 3 和 6。
我們嘗試整理 A 表,發(fā)現(xiàn) A 表記錄里甲乙字段值為 4 和 2 的所有記錄里,丙字段為 3 和 6,把 B 表給定的丙字段的結(jié)果全部包含在內(nèi),所以 4 和 2 是一個(gè)結(jié)果;同理,發(fā)現(xiàn) A 表滿(mǎn)足類(lèi)似要求的還有 1 和 5 的所有信息,它們素有記錄里也全部含有 3 和 6(只需要全部包含即可,可以允許有多出來(lái)的部分,比如 1 和 5 還包含了丙字段的 7、8、9 的信息)。

第三個(gè)例子和第二個(gè)例子類(lèi)似,發(fā)現(xiàn) A 表里甲乙字段記錄為 1 和 5 的信息的所有的丙字段分別為 3、6、7、8、9,完整包含了 3、6、7、8,所以甲乙字段 1 和 5 是這個(gè)結(jié)果的一個(gè)記錄。不過(guò)這個(gè)例子里,只有這一個(gè)結(jié)果滿(mǎn)足要求,所以只有它。

最后一個(gè)例子里,B 表是兩個(gè)記錄,所以我們只能去篩選 A 表里甲字段的信息。發(fā)現(xiàn),能夠完整包含 B 表信息的所有數(shù)據(jù)的,只有甲字段為 1 和 4 的時(shí)候,它們的記錄分別為 1、2、3 和 4、2、3,完整包含了 B 表乙丙字段的 2 和 3,所以 1 和 4 是這個(gè)除法的結(jié)果。
不過(guò)遺憾的是,LINQ 并不能夠很好地實(shí)現(xiàn)除法運(yùn)算,因?yàn)樗枰WC兩個(gè)表的信息能夠被正確投影,而在 LINQ 里,投影出來(lái)的結(jié)果一般用匿名類(lèi)型表示,但匿名類(lèi)型我們沒(méi)有基類(lèi)可以使用,所以我們不能夠簡(jiǎn)單地表達(dá)出這些字段或?qū)傩缘男畔?,所以即使是想要使用反射,也是非常困難的。而在 SQL 里,例如 R(X, Y)÷S(Y, Z)
的運(yùn)算用結(jié)構(gòu)化語(yǔ)言 SQL 語(yǔ)句可表達(dá)為下列形式:
即利用的是雙重否定表肯定的語(yǔ)義邏輯。