跨庫數據表的運算

1.    簡單合併(FROM)

 

       所謂跨庫數據表,是指邏輯上同一張數據表被分別存儲在不一樣數據庫中。其緣由有多是由於數據量太大,放在一個數據庫難以處理,也可能在業務上就須要將生產庫和歷史庫分開。而不一樣的數據庫,可能只是部署在不一樣的機器上的同種數據庫,也多是連類型都不一樣的數據庫系統。mysql

       在面對跨庫數據表,特別是數據庫類型都不相同的狀況時,數據庫自帶的工具每每就力所不及了,通常都須要尋找可以很好地支持多數據源類型的第三方工具,而集算器,能夠說是其中的佼佼者了。下面,咱們就針對幾種常見的跨庫混合運算狀況詳細討論一下:sql

       跨庫運算,簡單粗暴的思路就是把散佈在各個庫裏的邏輯上相同的數據表合併成一個表,而後在這一個表上進行運算。數據庫

       例如,在兩個數據庫 HSQL 和 MYSQL 中,分別存儲了一張學生成績表,二者各自保存了一部分學生信息,以下圖所示:緩存

       

       

       利用集算器,咱們能夠很容易地將這兩個結構相同的表合併爲一個表,集算器的 SPL 腳本以下:服務器

 

A函數

B工具

1性能

=connect("org.hsqldb.jdbcDriver","jdbc:hsqldb:hsql://127.0.0.1/demo?user=sa")fetch

=connect("com.mysql.jdbc.Driver","jdbc:mysql://127.0.0.1:3306/demo?user=root&password=password")大數據

2

=A1.query("select * from 學生成績表")

=B1.query("select   * from 學生成績表")

3

=A2 | B2

       A一、A2 和 B一、B2 分別讀取了兩個庫裏的學生成績表,而 A3 用一種簡單直觀的方式就把兩個表合併了。

       這種方式其實是把兩個表都讀入了內存,分別生成了集算器的序表對象,而後利用序表的運算「|」完成了合併。可能有的同窗會問:若是個人數據量比較大,沒法所有讀入內存怎麼辦?不要緊,專爲處理大數據而生的集算器,決不會被這麼簡單的小問題難住。咱們可使用遊標,一樣能夠實現表的快速拼接:

 

A

B

2

=A1.cursor("select * from 學生成績表")

=B1.cursor("select   * from 學生成績表")

3

=[A2, B2] .conjx()

       A二、B2 分別用遊標打開兩個庫裏的學生成績表,A3 則使用 conjx() 函數將這兩個遊標合併,造成了一個新的能夠同時訪問兩個表的遊標。

       對應於 SQL,這種簡單合併比如只是完成了 from 工做,讓結構相同的跨庫表的數據「縱向」拼接成了一個能夠訪問的序表或者遊標,而實際運算中,還會涉及過濾 (where/having)、分組聚合 (group+sum/count/avg/max/min)、鏈接 (join+on)、去重 (distinct)、排序 (order)、取部分數據 (limit+offset),等等操做,下面咱們就將對這些運算一一展開討論。

       固然,咱們在處理這些運算的需求時,不能只是簡單的實現功能,咱們還須要考慮實現的效率和性能,所以原則上,咱們會盡可能利用數據庫的計算能力,而集算器主要負責混合運算。不過,有時也須要由集算器負責幾乎全部的運算,數據庫僅僅負責存儲數據。

 

2.    WHERE

       where 過濾的本質是經過比較計算,去除比較的結果是 false 的記錄,所以 where 只做用於一條記錄,不涉及記錄之間的運算,也不須要考慮數據位於哪一個數據庫。好比,在前面的例子中,咱們要統計出「一班」全部同窗的「數學」成績,單庫中的 SQL 是這樣的:

       SELECT 學生 ID, 成績 FROM 學生成績表 WHERE 科目 =’數學’ AND 班級 =‘一班’

       多庫時,也只要將 where 子句直接寫在 SQL 中,讓各個數據庫去並行處理過濾就能夠了:

 

A

B

2

=A1.query("select 學生 ID, 成績 from 學生成績表   where 科目 =' 數學 'and 班級 =' 一班 ' ")

=B1.query("select   學生 ID, 成績 from 學生成績表 where 科目 =' 數學 'and   班級 =' 一班 ' ")

3

=A2 | B2

       咱們也可讓集算器負責全部過濾運算,數據庫僅存儲數據。這時可使用集算器的 select 函數(與 SQL 的 select 關鍵字不一樣)

 

A

B

2

=A1.query("select 學生 ID, 成績, 科目, 班級 from 學生成績表")

=B1.query("select   學生 ID, 成績, 科目, 班級 from 學生成績表")

3

=A2.select(科目 =="數學" && 班級 =="一班").new(學生 ID, 成績)

=B2.select(科目 =="數學" && 班級 =="一班").new(學生 ID, 成績)

4

=A3 | B3

       數據量較大時,一樣也能夠將序表換成遊標,使用 conjx 函數進行鏈接:

 

A

B

2

=A1.cursor("select 學生 ID, 成績 from 學生成績表   where 科目 =' 數學 'and 班級 =' 一班 ' ")

=B1.cursor("select   學生 ID, 成績 from 學生成績表 where 科目 =' 數學 'and   班級 =' 一班 ' ")

3

=[A2, B2].conjx()

 

3.    ORDER BY 和 LIMIT OFFSET

       order by 是在結果集產生後才進行的處理。在上面的例子中,若是咱們要按數學成績排序,對於單數據庫,只須要加上 order by 子句:

       SELECT 班級, 學生 ID, 成績 FROM 學生成績表 WHERE 科目 =’數學’ AND 班級 =‘一班’ ORDER BY 成績

       而對於多數據庫,可讓數據庫先分別排序,而後由集算器歸併有序數據。這樣能夠最大的發揮數據庫與並行服務器的性能。

 

A

B

2

=A1.query("select 班級, 學生 ID, 成績 from 學生成績表   where 科目 =' 數學 'and 班級 =' 一班 'order by 成績")

=B1.query("select   班級, 學生 ID, 成績 from 學生成績表 where 科目 =' 數學 'and 班級 =' 一班 'order by 成績")

3

=[A2, B2].merge(成績)

       也能夠倒序排序,歸併時在排序字段前加「-」(merge 函數能夠不加「-」,不過按標準寫法是加上的)

 

A

B

2

=A1.query("select 班級, 學生 ID, 成績 from 學生成績表   where 科目 =' 數學 'AND 班級 =' 一班 'order by 成績 desc")

=B1.query("select   班級, 學生 ID, 成績 from 學生成績表 where 科目 =' 數學 'AND 班級 =' 一班 'order by 成績 desc")

3

=[A2, B2].merge(- 成績)

       固然也能夠徹底由集算器來排序:

 

A

B

2

=A1.query("select 班級, 學生 ID, 成績 from 學生成績表 where 科目 =' 數學 'AND   班級 =' 一班 '")

=B1.query("select   班級, 學生 ID, 成績 from 學生成績表 where 科目 =' 數學 'AND 班級 =' 一班 '")

3

=[A2.sort( 成績),   B2.sort(成績)].merge(成績)

       由集算器實現倒序排序:

 

A

B

2

=A1.query("select 班級, 學生 ID, 成績 from 學生成績表 where 科目 =' 數學 'AND   班級 =' 一班 '")

=B1.query("select   班級, 學生 ID, 成績 from 學生成績表 where 科目 =' 數學 'AND 班級 =' 一班 '")

3

=[A2.sort(- 成績), B2.sort(- 成績)].merge(- 成績)

       而對於大數據量,須要使用遊標及 mergex 來完成有序歸併:

 

A

B

2

=A1.cursor("select 班級, 學生 ID, 成績 from 學生成績表 where 科目 =' 數學 'AND   班級 =' 一班 'order by 成績")

=B1.cursor("select   班級, 學生 ID, 成績 from 學生成績表 where 科目 =' 數學 'AND 班級 =' 一班 'order by 成績")

3

=[A2 , B2].mergex(成績)

       limit 和 offset 的執行又在 order 以後,例子中若是想取數學成績除了第一名以後的前十名(能夠少於但不能多於),單庫狀況下 SQL 是這樣的:

       SELECT 班級, 學生 ID, 成績 FROM 學生成績表 WHERE 科目 =’數學’ AND 班級 =‘一班’ ORDER BY 成績 DESC LIMIT 10 OFFSET 1

       多數據庫時,能夠用集算器的 to 函數實現 limit offset 的功能,to(n+1,n+m) 等同於 limit m offset n

 

A

B

2

=A1.query("select 班級, 學生 ID, 成績 from 學生成績表 where 科目 =' 數學 'AND   班級 =' 一班 'order by 成績 desc")

=B1.query("select   班級, 學生 ID, 成績 from 學生成績表 where 科目 =' 數學 'AND 班級 =' 一班 'order by 成績 desc")

3

=[A2, B2].merge(- 成績).to(2, 11)

       對於大數據量使用遊標的狀況,offset 功能可使用集算器函數 skip 實現,而 limit 的功能則可使用函數 fetch 實現

 

A

B

2

=A1.cursor("select 班級, 學生 ID, 成績 from 學生成績表 where 科目 =' 數學 'AND   班級 =' 一班 'order by 成績 desc")

=B1.cursor("select   班級, 學生 ID, 成績 from 學生成績表 where 科目 =' 數學 'AND 班級 =' 一班 'order by 成績 desc")

3

=[A2, B2].mergex(- 成績).

=A3.skip(1)

4

=A3.fetch(10)

 

 

4.    聚合運算

       咱們來討論五種常見的聚合運算:sum/count/avg/max/min。

       •   sum 的基礎是加法,根據加法結合律,各數據庫中內部數據先分別求和,而後拼接成一張表後再求總和,與先拼接成一張表而後一塊兒求和的結果,實際上是同樣的。

       •   count 的本質,是對每項非 null 數據計 1,null 數據計 0,而後進行累加計算。因此其本質還是加法運算,與 sum 同樣符合加法結合律。惟一不一樣的是對原始數據不是累加其自己的數值而是計 1(非 null)或計 0(爲 null)。

       •   avg 的本質,是當 count > 0 時 avg = sum/count,當 count = 0 時 avg = null。顯然 avg 不能像 sum 或 count 那樣先分別計算了。不過根據定義,咱們能夠先算出 sum 和 count,再經過 sum 和 count 計算出 avg。

       •   max 和 min 的基礎都是比較運算,而由於比較運算具備傳遞性,所以全部數據庫的最值,能夠經過比較各個數據庫的最值獲得。

       依舊是上面的例子,此次咱們要求兩個班全體學生的數學總分、人數、平均分、最高及最低分,對於單源數據:

       SELECT sum(成績) 總分數, count(成績) 考試人數, avg(成績) 平均分, max(成績) 最高分, min(成績) 最低分 FROM 學生成績表 WHERE 科目 ='數學'

       聚合運算的結果集很小,只有一行,所以不管源數據量的大小,均可以使用遊標,代碼以下:

 

A

B

2

=A1.cursor("select sum( 成績) 總分數, count(成績) 考試人數, max(成績) 最高分,   min(成績) 最低分 from 學生成績表 where 科目 =' 數學 ' ")

=B1.cursor("select   sum( 成績) 總分數, count(成績) 考試人數, max(成績) 最高分, min(成績) 最低分 from   學生成績表 where 科目 =' 數學 ' ")

3

=[A2, B2].conjx().total(sum( 總分數), sum(考試人數), max(最高分), min(最低分))

4

=create(總分數, 考試人數, 平均分, 最高分, 最低分).insert(0, A3(1), A3(2), if(A3(2)!=0,A3(1)/A3(2),null), A3(3),   A3(4))

       事實上,前面提到的 order by +limit offset 本質上也能夠當作是一種聚合運算:top。從這個角度進行優化,能夠得到更高的計算效率。畢竟數據量大時,全排序的成本很高,並且取前 N 個數據的操做也並不須要全排序。固然,這個方法對於數據量小的狀況也一樣適用。

       具體來講,對於 order by F limit m offset n 的狀況,只需先用 top(n+m, F, ~),再用 to(n+1,) 就好了。

       咱們仍以以前的含 order by+limit offset 的 SQL 語句爲例:

       SELECT 班級, 學生 ID, 成績 FROM 學生成績表 WHERE 科目 =’數學’ AND 班級 =‘一班’ ORDER BY 成績 DESC LIMIT 10 OFFSET 1

       對於多數據庫, 腳本以下,其中倒序排序只需在排序字段前加「-」:

 

A

B

2

=A1.cursor("select 班級, 學生 ID, 成績 from 學生成績表 where 科目 =' 數學 'AND   班級 =' 一班 '")

=B1.cursor("select   班級, 學生 ID, 成績 from 學生成績表 where 科目 =' 數學 'AND 班級 =' 一班 '")

3

=[A2, B2].conjx().top(11, - 成績, ~).to(2,)

 

 

5.    GROUP BY、DISTINCT 和 HAVING

       A、分組聚合運算

       對於 group by,由於最終所得結果與樣本個體的輸入順序無關,因此只要樣本的整體不變,最終結果也不會變。也就是說,只要在從分庫中提取數據和最終彙總所有數據時,都預先進行了分類運算便可。

       假設咱們想分別求1、二班的數學總分、人數、平均分、最高及最低分,單數據庫以下:

       SELECT 班級, sum(成績) 總分數, count(成績) 考試人數, avg(成績) 平均分, max(成績) 最高分, min(成績) 最低分 FROM 學生成績表 WHERE 科目 ='數學' GROUP BY 班級

       咱們分三種狀況討論:

       第一,對於小數據,聚合運算的結果集只會更小,這時推薦使用 query+groups:

 

A

B

2

=A1.query("select 班級, sum( 成績) 總分數,   count(成績) 考試人數, max(成績) 最高分, min(成績) 最低分 from 學生成績表 where 科目 =' 數學 'group by 班級")

=B1.query("select   班級, sum( 成績) 總分數, count(成績) 考試人數,   max(成績) 最高分, min(成績) 最低分 from 學生成績表   where 科目 =' 數學 'group by 班級")

3

=(A2 | B2) .groups(班級: 班級; sum( 總分數): 總分數, sum(考試人數): 考試人數,   max(最高分): 最高分, min(最低分): 最低分 )

4

=A3.new(班級, 總分數, 考試人數, if( 考試人數 ==0,   null, 總分數 / 考試人數): 平均分, 最高分, 最低分 )

       第二,對於大數據量,若是結果集也很大,那麼就應該使用 cursor+groupx。

       另外,因爲大結果集的分組計算較慢,須要在外存產生緩存數據。而若是咱們在數據庫中對數據先排序,則能夠避免這種緩存(此時計算壓力會轉到數據庫,所以須要根據實際狀況權衡,一般狀況下,數據庫服務器的計算能力會更強一些)。

       具體的辦法是對 SQL 的結果集使用 order by 排序,而後在集算器中使用 mergex 函數歸併後,再使用 groupx 的 @o 選項分組:

 

A

B

2

=A1.cursor("select   班級, sum( 成績) 總分數, count(成績) 考試人數,   max(成績) 最高分, min(成績) 最低分 from 學生成績表   where 科目 =' 數學 'group by 班級   order by 班級")

=B1.cursor("select 班級, sum( 成績) 總分數,   count(成績) 考試人數, max(成績) 最高分, min(成績) 最低分 from 學生成績表 where 科目 =' 數學 'group by 班級 order   by 班級")

3

=[A2   , B2].mergex(班級).groupx@o(班級: 班級; sum( 總分數): 總分數,   sum(考試人數): 考試人數, max(最高分): 最高分, min(最低分): 最低分 )

4

=A3.new(班級, 總分數, 考試人數, if( 考試人數 ==0, null, 總分數 / 考試人數): 平均分, 最高分, 最低分 )

       固然若是不但願加劇數據庫負擔,也可讓數據庫只作分組而不排序,此時集算器直接用 groupx,注意不能加 @o 選項。另外匯總數據時,也要把 mergex 換成 conjx:

 

A

B

2

=A1.cursor("select 班級, sum( 成績) 總分數,   count(成績) 考試人數, max(成績) 最高分, min(成績) 最低分 from 學生成績表 where 科目 =' 數學 'group by 班級")

=B1.cursor("select   班級, sum( 成績) 總分數, count(成績) 考試人數,   max(成績) 最高分, min(成績) 最低分 from 學生成績表   where 科目 =' 數學 'group by 班級")

3

=[A2, B2].conjx().groupx( 班級: 班級; sum( 總分數): 總分數, sum(考試人數): 考試人數,   max(最高分): 最高分, min(最低分): 最低分 )

4

=A3.new(班級, 總分數, 考試人數, if( 考試人數 ==0,   null, 總分數 / 考試人數): 平均分, 最高分, 最低分 )

       第三,若是已明確地知道結果集很小,那麼推薦用 cursor+groups

       此時 groups 比 groupx 有更好的性能,由於 groups 將運算數據都保存在內存中,比 groupx 節省了寫入外存文件的時間。

       另外用 groups 能夠不要求在數據庫中預先排序,由於數據庫 group by 的結果集自己不必定有序,再使用 orde by 排序也會增長成本。而對於小結果集,集算器用 groups@o 也並不必定比直接用 groups 更有效率。

       一般,彙總數據要用 conjx

 

A

B

2

=A1.cursor("select 班級, sum( 成績) 總分數,   count(成績) 考試人數, max(成績) 最高分, min(成績) 最低分 from 學生成績表 where 科目 =' 數學 'group by 班級")

=B1.cursor("select   班級, sum( 成績) 總分數, count(成績) 考試人數,   max(成績) 最高分, min(成績) 最低分 from 學生成績表   where 科目 =' 數學 'group by 班級")

3

=[A2, B2].conjx().groups( 班級: 班級; sum( 總分數): 總分數, sum(考試人數): 考試人數,   max(最高分): 最高分, min(最低分): 最低分 )

4

=A3.new(班級, 總分數, 考試人數, if( 考試人數 ==0,   null, 總分數 / 考試人數): 平均分, 最高分, 最低分 )

       B、去重後計數 (count distinct)

       在各個數據庫內去重,可使用 distinct 關鍵字。而數據庫之間的數據去重,則可使用集算器的 merge@u 函數。要注意的是使用前應該確保表內數據對主鍵字段(或者具備惟一性的一個或多個字段)有序。

       對於 distinct 來講, sum(distinct)、avg(distinct) 的計算方法與 count(distinct) 大同小異,並且業務中不經常使用到,而 max(distinct)、min(distinct) 與單純使用 max、min 沒有區別。所以,咱們只以 count(distinct) 爲例加以說明。

       好比,想要計算整年級(假設只有一班和二班)語數外三科至少有一科不及格須要補考的總人數,單數據庫的 SQL 是這樣的:

       SELECT count(distinct 學生 ID) 人數 FROM 學生成績表 WHERE 成績 <60

       對於多源數據,全分組聚合在使用遊標或序表方面沒有差異,爲了語法簡便起見以遊標爲例:

 

A

B

2

=A1.cursor("select distinct 班級, 學生 ID from 學生成績表   where 成績 <60 order by 班級, 學生 ID")

=B1.cursor("select   distinct 班級, 學生 ID from 學生成績表 where 成績 <60 order by 班級, 學生 ID")

3

=[A2, B2].mergex@u(班級, 學生 ID).total(count( 學生 ID))

       再如,想要分別計算每班語數外三科至少有一科不及格須要補考的總人數,單數據庫的 SQL 是這樣的:

       SELECT 班級, count(distinct 學生 ID) 人數 FROM 學生成績表 WHERE 成績 <60 GROUP BY 班級

       對於多數據庫,一樣須要先彙總去重,再進行分組聚合。彙總前須要數據有序,且彙總後數據仍然有序,因此分組函數 groups 和 groupx 均可以使用 @o 選項。

       對於小數據量,可使用 merge@u、groups@o 和 query:

 

A

B

2

=A1.query("select distinct 班級, 學生 ID from 學生成績表   where 成績 <60 order by 班級, 學生 ID")

=B1.query("select   distinct 班級, 學生 ID from 學生成績表 where 成績 <60 order by 班級, 學生 ID")

3

=[A2, B2].merge@u(班級, 學生 ID) .groups@o(班級: 班級; count( 學生 ID): 補習人數 )

       對於大數據量小結果集,可使用 mergex@u、groups@o 和 cursor:

 

A

B

2

=A1.cursor("select distinct 班級, 學生 ID from 學生成績表   where 成績 <60 order by 班級, 學生 ID")

=B1.cursor("select   distinct 班級, 學生 ID from 學生成績表 where 成績 <60 order by 班級, 學生 ID")

3

=[A2, B2].mergex@u(班級, 學生 ID) .groups@o(班級: 班級; count( 學生 ID): 補習人數 )

       對於大數據量大結果集,可使用 mergex@u、groupx@o 和 cursor:

 

A

B

2

=A1.cursor("select distinct 班級, 學生 ID from 學生成績表   where 成績 <60 order by 班級, 學生 ID")

=B1.cursor("select   distinct 班級, 學生 ID from 學生成績表 where 成績 <60 order by 班級, 學生 ID")

3

=[A2, B2].mergex@u(班級, 學生 ID) .groupx@o(班級: 班級; count( 學生 ID): 補習人數 )

       C、對聚合字段過濾(having)

       having 是對聚合 (分組) 後得出的結果集再作過濾。因此當語句中有 having 出現時,若是聚合 (分組) 操做沒有完全執行完畢,須要將 having 子句先提取出來。待數據完全完成聚合 (分組) 操做以後,再執行條件過濾。

       對於多源數據,若是聚合計算是在彙總以後才能最終完成,那麼 having 必須使用集算器的函數 select 來實現過濾。

       下面主要說明這種聚合計算在彙總以後才完成的狀況:好比,想要得到一班和二班的三個科目的考試中,有哪些平均分是低於 60 分的。對於單數據庫,SQL 能夠這樣寫:

SELECT 班級, 科目, avg(成績) 平均分 FROM 學生成績表 GROUP BY 班級, 科目 HAVING avg(成績)<60

       對於多數據庫,相關集算器執行代碼以下:

 

A

B

2

=A1.query("select 班級, 科目, sum( 成績) 總分, count(成績) 人數   from 學生成績表 group by 班級, 科目")

=B1.query("select   班級, 科目, sum( 成績) 總分, count(成績) 人數 from 學生成績表 group by 班級, 科目")

3

=(A2 | B2).groups(班級: 班級, 科目: 科目; sum( 總分): 總分, sum(人數): 人數 ).new( 班級, 科目, ( 總分 / 人數): 平均分 )

4

=A3.select(平均分 <60)

       對於大數據量,須要使用遊標 (select 函數一樣適用於遊標)

 

A

B

2

=A1.cursor("select 班級, 科目, sum( 成績) 總分, count(成績) 人數   from 學生成績表 group by 班級, 科目 order by 班級, 科目")

=B1.cursor("select   班級, 科目, sum( 成績) 總分, count(成績) 人數 from 學生成績表 group by 班級, 科目 order by 班級, 科目")

3

=[A2, B2].mergex(班級, 科目).groupx@o(班級: 班級, 科目: 科目; sum( 總分): 總分, sum(人數): 人數 ).new( 班級, 科目, ( 總分 / 人數): 平均分 )

4

=A3.select(平均分 <60)

 

6.    JOIN ON

       跨庫的 JOIN 實現起來很是困難,不過比較幸運的是,咱們能夠經過存儲設計避免不少跨庫 JOIN。咱們分三種狀況討論:

       1. 同維表分庫,須要從新拼接爲一個表

       2. 要鏈接的外鍵表在每一個庫中都有相同的一份

       3. 須要鏈接的外鍵表在另外一個庫中

       對於集算器來說,前兩種的處理狀況是同樣的:都不須要涉及跨庫 join,join 操做均可以在數據庫內完成。區別只在於第一種是分庫表,數據庫之間沒有重複數據;而第二種則要求把外鍵表的數據複製到每一個庫中。

       若是外鍵表沒有複製到每一個庫中,那就會涉及真正的跨庫 join,由於很複雜,這裏只舉一個內存外鍵表的例子,其它更復雜狀況會有專門的文章闡述。

       A、同維表或主子表同步分庫

       所謂同維表,簡單來說就是兩個表的主鍵字段徹底同樣,且其中一個表的主鍵與另外一個表的主鍵有邏輯意義上的外鍵約束(並不要求數據庫中必定有真正的外鍵,主鍵同理也是邏輯上的主鍵並不必定存在於數據庫中)。

       假設有兩個庫,每一個庫中有兩個表,分別記爲 A 庫中的 A1 表和 A2 表,B 庫中的 B1 表和 B2 表。從邏輯上看 1 表是 A1 表加上 B1 表,2 表是 A2 表加上 B2 表,咱們再假設 1 表與 2 表爲同維表,如今要作 1 表與 2 表的 join 鏈接運算。

       所謂同步分庫,就是在設計分庫存儲時,保證了 1 表和 2 表按主鍵進行了同步的分割。也就是必須保證分庫以後,A1 和 B2 的 join 等值鏈接的結果是空集,一樣 A2 和 B1 的 join 等值鏈接的結果也是空集,這樣也就沒必要有跨庫的 join 鏈接運算了。

       舉例說明,好比有兩張表:股票信息與公司信息,表的結構以下:

       公司信息

       

       股票信息

       

       兩個表的主鍵都是 (公司代碼, 股票代碼),且股票信息的主鍵與公司信息的主鍵有邏輯意義上的外鍵約束關係,兩者互爲同維表。

       如今假設我想將兩個表拼接在一塊兒,單數據庫時 SQL 是這樣的:

       SELECT * FROM 公司信息 T1 JOIN 股票信息 T2 ON T1. 公司代碼 =T2. 公司代碼 AND T1. 股票代碼 = T2. 股票代碼

       現假設公司信息分爲兩部分,分別存於 HSQL 和 MYSQL 數據庫中,股票信息一樣分爲兩部分,分別存於 HSQL 和 MYSQL 數據庫中,且兩者是同步分庫。

       join 鏈接公司信息與股票信息的集算器代碼:

 

A

B

2

=A1.query("SELECT * FROM 公司信息 T1 JOIN 股票信息 T2 ON T1. 公司代碼 =T2. 公司代碼 AND T1. 股票代碼 = T2. 股票代碼")

=B1.query("SELECT * FROM 公司信息 T1 JOIN 股票信息 T2 ON T1. 公司代碼 =T2. 公司代碼 AND T1. 股票代碼 = T2. 股票代碼")

3

=A2 | B2

       對於大數據:

 

A

B

2

=A1.cursor("SELECT * FROM 公司信息 T1 JOIN 股票信息 T2 ON T1. 公司代碼 =T2. 公司代碼 AND T1. 股票代碼 = T2. 股票代碼")

=B1.cursor("SELECT * FROM 公司信息 T1 JOIN 股票信息 T2 ON T1. 公司代碼 =T2. 公司代碼 AND T1. 股票代碼 = T2. 股票代碼")

3

=[A2,   B2].conjx()

       主子表的狀況與同維表相似,即一個表(主表)的主鍵字段被另外一個表(子表)的主鍵字段所包含,且子表中對應的主鍵字段與主表的主鍵有邏輯意義上的外鍵約束關係。

       舉例說明,好比有兩張表:訂單與訂單明細,表的結構以下:

       訂單

       

       訂單明細

       

       其中訂單是主表,主鍵爲 (訂單 ID);而訂單明細爲子表,主鍵爲 (訂單 ID, 產品 ID),且訂單明細的主鍵字段訂單 ID,與訂單的主鍵有邏輯意義上的外鍵約束關係,顯然兩者爲主子表的關係。

       如今假設我想將兩個表拼接在一塊兒,單數據庫的 SQL 是這樣的:

       SELECT * FROM 訂單 T1 JOIN 訂單明細 T2 ON T1. 訂單 ID=T2. 訂單 ID

       現假設訂單分爲兩部分,分別存於 HSQL 和 MYSQL 數據庫中,訂單明細一樣分爲兩部分,分別存於 HSQL 和 MYSQL 數據庫中,且兩者同步分庫。

       join 鏈接訂單與訂單明細的集算器代碼:

 

A

B

2

=A1.query("SELECT * FROM 訂單 T1 JOIN 訂單明細 T2 ON T1. 訂單 ID=T2. 訂單 ID")

=B1.query("SELECT * FROM 訂單 T1 JOIN 訂單明細 T2 ON T1. 訂單 ID=T2. 訂單 ID")

3

=A2 | B2

       對於大數據:

 

A

B

2

=A1.cursor("SELECT * FROM 訂單 T1 JOIN 訂單明細 T2 ON T1. 訂單 ID=T2. 訂單 ID")

=B1.cursor("SELECT * FROM 訂單 T1 JOIN 訂單明細 T2 ON T1. 訂單 ID=T2. 訂單 ID")

3

=[A2,   B2].conjx()

       B、外鍵表複製進每一個庫

       所謂外鍵表,便是指鏈接字段爲外鍵字段的狀況。這種外鍵表 join 也是業務上常見的一種狀況。由於要鏈接的外鍵表在每一個庫中都有同一份,那麼兩個外鍵表彙總並去重後,其實仍是任一數據庫中原來就有的那個外鍵表。

       而 join 的鏈接操做,本質上能夠視爲一種乘法,由於 join 鏈接等價於 cross join 後再用 on 中條件進行過濾。則根據乘法分配率能夠推導出:如果須要作鏈接操做的外鍵表(不妨設爲鏈接右側的表)在每一個庫中都有同一份,則鏈接左側的表(每一個數據庫中各有其一部分)在彙總後再鏈接,等同於各數據中的鏈接左側的表與外鍵表先作鏈接操做後,再彙總到一塊兒的結果。如圖所示:

       

       因此咱們在存儲設計時,只要在每一個數據庫中把外鍵表都重複一下,就能夠避免複雜的跨庫 join 操做。通常狀況下,外鍵表做爲維表的數據量相對較小,這樣重複的成本就不會很高,而事實表則會得很大,而後用分庫存儲的方法,來解決運算速度緩慢或存儲空間不足等問題。

       例如,有兩個表:客戶銷售表和客戶表,其中客戶銷售表的外鍵字段:客戶,與客戶表的主鍵字段:客戶 ID,有外鍵約束關係。如今咱們想查詢面向河北省各公司的銷售額記錄,對於單數據源,它的 SQL 是這樣寫的:

       SELECT T1. 公司名稱 公司名稱, T2. 訂購日期 訂購日期, T2. 銷售額 銷售額 FROM 客戶表 T1 JOIN 客戶銷售表 T2 ON T1. 客戶 ID=T2. 客戶 WHERE T1. 省份 ='河北'

       對於多數據源的狀況,咱們假設客戶銷售表分別存儲在兩個不一樣的數據庫中,而每一個數據庫中都有同一份的客戶表作爲外鍵表。則相關的集算器代碼以下:

 

A

B

2

=A1.query("select T1. 公司名稱 公司名稱, T2. 訂購日期 訂購日期, T2. 銷售額 銷售額 from 客戶表 T1 join 客戶銷售表 T2 on T1. 客戶 ID=T2. 客戶 where T1. 省份 =' 河北 ' ")

=B1.query("select   T1. 公司名稱 公司名稱, T2. 訂購日期 訂購日期,   T2. 銷售額 銷售額 from 客戶表 T1 join 客戶銷售表 T2 on T1. 客戶 ID=T2. 客戶 where T1. 省份 =' 河北 ' ")

3

=A2 | B2

       大數據量使用遊標時:

 

A

B

2

=A1.cursor("select T1. 公司名稱 公司名稱, T2. 訂購日期 訂購日期, T2. 銷售額 銷售額 from 客戶表 T1 join 客戶銷售表 T2 on T1. 客戶 ID=T2. 客戶 where T1. 省份 =' 河北 ' ")

=B1.cursor("select   T1. 公司名稱 公司名稱, T2. 訂購日期 訂購日期,   T2. 銷售額 銷售額 from 客戶表 T1 join 客戶銷售表 T2 on T1. 客戶 ID=T2. 客戶 where T1. 省份 =' 河北 ' ")

3

=[A2, B2].conjx()

       C、須要鏈接的外鍵表在另外一個庫中

       對於維表(外鍵表)也被分庫的狀況,咱們只考慮維表所有可內存化的狀況,不可內存化時,經常就不適合再將數據存在數據庫中了,須要專門針對性的的存儲和計算方案,這將在另外的文章中專門討論。在這裏咱們只經過例子來討論維表可內存化的狀況。

       對於這種狀況,當涉及的數據量比較大而須要使用遊標時,計算邏輯會變得比較複雜。因此咱們在這裏只講一下針對小數據量的使用序表的 join 處理方法。關於對大數據量的使用遊標的 join 處理,會另有一篇文章作專門的介紹。

       當要作 join 鏈接運算的外鍵表所有或部分存儲在另外一個庫中時,最直觀的辦法就是將兩個表都提取出來並各自彙總後,再計算 join 鏈接。

       下面仍以客戶銷售表和客戶表來舉例,假設外鍵表客戶表也分別存儲在兩個數據庫中,此時就不能在 SQL 中使用 join 關鍵字來實現鏈接運算了,但咱們能夠將其提取出來後,用集算器的 join 函數來實現目的,它的集算器代碼以下所示:

 

A

B

2

=A1.query("select 客戶 ID 客戶, 公司名稱 from 客戶表   where 省份 =' 河北 ' ")

=B1.query("select   客戶 ID 客戶, 公司名稱 from 客戶表 where 省份 =' 河北 ' ")

3

=A2 | B2

4

=A1.query("select 客戶, 訂購日期, 銷售額 from 客戶銷售表")

=B1.query("select 客戶, 訂購日期, 銷售額 from 客戶銷售表")

5

=A4 | B4

6

=join(A3: 客戶表, 客戶; A5: 客戶銷售表, 客戶)

7

=A4.new(~.field(1).field(1): 客戶, ~.field(1).field(2): 公司名稱, ~.field(2).field(2): 訂購日期,   ~.field(2).field(3): 銷售額 )

       當事實表數據量較大的時候,也可使用遊標處理事實表,只需將 join 換成 cs.join 便可:

 

A

B

2

=A1.query("select 客戶 ID 客戶, 公司名稱 from 客戶表   where 省份 =' 河北 ' ")

=B1.query("select   客戶 ID 客戶, 公司名稱 from 客戶表 where 省份 =' 河北 ' ")

3

=A2 | B2

4

=A1.cursor("select 客戶, 訂購日期, 銷售額 from 客戶銷售表")

=B1.cursor("select 客戶, 訂購日期, 銷售額 from 客戶銷售表")

5

=[A4, B4].conjx()

6

=A5.join(客戶, A3: 客戶)

 

7.    簡單 SQL

       前面咱們主要是從計算原理的角度出發,分析瞭如何使用集算器實現相似 SQL 效果的多數據源混合計算。除此以外,集算器還提供了一種更簡單、直觀的方法,那就是能夠在各個數據庫上經過 SQL 查詢獲取遊標,用全部這些遊標構建成一個多路遊標對象,再用簡單 SQL 對這個多路遊標作二次處理。若是簡單 SQL 中沒有涉及 join 的運算,甚至還可讓集算器直接將一句簡單 SQL 翻譯成各類數據庫的 SQL,從而實現更進一步的自動化。不過這種辦法屬於比較保守的作法,雖然簡單直接,但不能利用所瞭解的數據狀況進行優化(好比不會使用 groups),所以性能就會差一些。

       下面仍舊用學生成績的例子,咱們想要計算每一個班的數學成績的總分、考試人數、平均分、最高分和最低分,使用簡單 SQL 處理這個問題的集算器代碼以下:

 

A

B

2

=A1.cursor("select 班級, 成績 from 學生成績表   where 科目 =' 數學 ' ")

=B1.cursor("select   班級, 成績 from 學生成績表 where 科目 =' 數學 ' ")

3

=[A2, B2].mcursor()

4

=connect().query("select 班級, sum( 成績) 總分,   count(成績) 考生人數, avg(成績) 平均分, max(成績) 最高分, min(成績) 最低分 from   {A3} group by 班級")

       由於使用了遊標,因此這種寫法也能夠用於大數據量。另外再提一句,這個辦法甚至也能夠用於非數據庫的數據源(好比文件數據源)!簡單 SQL 的特性可參考相關文檔,這裏就再也不進一步舉例了。

更多精彩內容,詳情能夠瀏覽原文地址

相關文章
相關標籤/搜索