跨库数据表的运算,一贯都是一个说难不算太难,说大略却又不是很大略的、总之是一个麻烦的事。大量的、散布在不同数据库中的数据表们,明明觉得要把它们合并起来,再来个小小的打算,彷佛也就那么回事……但真要做起来,须要这又忘了那的,却又不像仅仅就那么回事?
想要给这些小麻烦们,来一个快刀斩乱麻式的、嘁嚓咔嚓地一劳永逸的办理方案么?首先,你须要一把叫做集算器的宝刀(重点);然后,你可以再看看这篇算是买一赠一的秘传刀法(免费);末了,面向仇敌们手起刀落……你就可以轻松愉快地去睡一个好觉了:跨库数据表的运算!
1. 大略合并(FROM)

所谓跨库数据表,是指逻辑上同一张数据表被分别存储在不同数据库中。其缘故原由有可能是由于数据量太大,放在一个数据库难以处理,也可能在业务上就须要将生产库和历史库分开。而不同的数据库,可能只是支配在不同的机器上的同种数据库,也可能是连类型都不同的数据库系统。
在面对跨库数据表,特殊是数据库类型都不相同的情形时,数据库自带的工具每每就无能为力了,一样平常都须要探求能够很好地支持多数据源类型的第三方工具,而集算器,可以说是个中的佼佼者了。下面,我们就针对几种常见的跨库稠浊运算情形详细谈论一下:
跨库运算,大略粗暴的思路便是把散布在各个库里的逻辑上相同的数据表合并成一个表,然后在这一个表上进走运算。
例如,在两个数据库 HSQL 和 MYSQL 中,分别存储了一张学天生就表,两者各自保存了一部分学生信息,如下图所示:
利用集算器,我们可以很随意马虎地将这两个构造相同的表合并为一个表,集算器的 SPL 脚本如下:
A1、A2 和 B1、B2 分别读取了两个库里的学天生就表,而 A3 用一种大略直不雅观的办法就把两个表合并了。
这种办法实际上是把两个表都读入了内存,分别天生了集算器的序表工具,然后利用序表的运算“|”完成了合并。可能有的同学会问:如果我的数据量比较大,无法全部读入内存怎么办?没紧要,专为处理大数据而生的集算器,决不会被这么大略的小问题难住。我们可以利用游标,同样可以实现表的快速拼接:
A2、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 中,让各个数据库去并行处理过滤就可以了:
我们也可以让集算器卖力所有过滤运算,数据库仅存储数据。这时可以利用集算器的 select 函数(与 SQL 的 select 关键字不同)
数据量较大时,同样也可以将序表换成游标,利用 conjx 函数进行连接:
3. ORDER BY 和 LIMIT OFFSET
order by 是在结果集产生后才进行的处理。在上面的例子中,如果我们要按数学成绩排序,对付单数据库,只须要加上 order by 子句:
SELECT 班级, 学生 ID, 成绩 FROM 学天生就表 WHERE 科目 =’数学’ AND 班级 =‘一班’ ORDER BY 成绩
而对付多数据库,可以让数据库先分别排序,然后由集算器归并有序数据。这样可以最大的发挥数据库与并行做事器的性能。
也可以倒序排序,归并时在排序字段前加“-”(merge 函数可以不加“-”,不过按标准写法是加上的)
当然也可以完备由集算器来排序:
由集算器实现倒序排序:
而对付大数据量,须要利用游标及 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
对付大数据量利用游标的情形,offset 功能可以利用集算器函数 skip 实现,而 limit 的功能则可以利用函数 fetch 实现
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 科目 ='数学'
聚合运算的结果集很小,只有一行,因此无论源数据量的大小,都可以利用游标,代码如下:
事实上,前面提到的 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
对付多数据库, 脚本如下,个中倒序排序只需在排序字段前加“-”:
5. GROUP BY、DISTINCT 和 HAVING
A、分组聚合运算
对付 group by,由于终极所得结果与样本个体的输入顺序无关,以是只要样本的总体不变,终极结果也不会变。也便是说,只要在从分库中提取数据和终极汇总全部数据时,都预前辈行了分类运算即可。
假设我们想分别求一、二班的数学总分、人数、均匀分、最高及最低分,单数据库如下:
SELECT 班级, sum(成绩) 总分数, count(成绩) 考试人数, avg(成绩) 均匀分, max(成绩) 最高分, min(成绩) 最低分 FROM 学天生就表 WHERE 科目 ='数学' GROUP BY 班级
我们分三种情形谈论:
第一,对付小数据,聚合运算的结果集只会更小,这时推举利用 query+groups:
第二,对付大数据量,如果结果集也很大,那么就该当利用 cursor+groupx。
其余,由于大结果集的分组打算较慢,须要在外存产生缓存数据。而如果我们在数据库中对数据先排序,则可以避免这种缓存(此时打算压力会转到数据库,因此须要根据实际情形权衡,常日情形下,数据库做事器的打算能力会更强一些)。
详细的办法是对 SQL 的结果集利用 order by 排序,然后在集算器中利用 mergex 函数归并后,再利用 groupx 的 @o 选项分组:
当然如果不肯望加重数据库包袱,也可以让数据库只做分组而不排序,此时集算器直接用 groupx,把稳不能加 @o 选项。其余汇总数据时,也要把 mergex 换成 conjx:
第三,如果已明确地知道结果集很小,那么推举用 cursor+groups
此时 groups 比 groupx 有更好的性能,由于 groups 将运算数据都保存在内存中,比 groupx 节省了写入外存文件的韶光。
其余用 groups 可以不哀求在数据库中预先排序,由于数据库 group by 的结果集本身不一定有序,再利用 orde by 排序也会增加本钱。而对付小结果集,集算器用 groups@o 也并不一定比直接用 groups 更有效率。
常日,汇总数据要用 conjx
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
对付多源数据,全分组聚合在利用游标或序表方面没有差别,为了语法简便起见以游标为例:
再如,想要分别打算每班语数外三科至少有一科不及格须要补考的总人数,单数据库的 SQL 是这样的:
SELECT 班级, count(distinct 学生 ID) 人数 FROM 学天生就表 WHERE 成绩 <60 GROUP BY 班级
对付多数据库,同样须要先汇总去重,再进行分组聚合。汇总前须要数据有序,且汇总后数据仍旧有序,以是分组函数 groups 和 groupx 都可以利用 @o 选项。
对付小数据量,可以利用 merge@u、groups@o 和 query:
对付大数据量小结果集,可以利用 mergex@u、groups@o 和 cursor:
对付大数据量大结果集,可以利用 mergex@u、groupx@o 和 cursor:
C、对聚合字段过滤(having)
having 是对聚合 (分组) 后得出的结果集再做过滤。以是当语句中有 having 涌现时,如果聚合 (分组) 操作没有彻底实行完毕,须要将 having 子句先提取出来。待数据彻底完成聚合 (分组) 操作之后,再实行条件过滤。
对付多源数据,如果聚合打算是在汇总之后才能终极完成,那么 having 必须利用集算器的函数 select 来实现过滤。
下面紧张解释这种聚合打算在汇总之后才完成的情形:比如,想要得到一班和二班的三个科目的考试中,有哪些均匀分是低于 60 分的。对付单数据库,SQL 可以这样写:
SELECT 班级, 科目, avg(成绩) 均匀分 FROM 学天生就表 GROUP BY 班级, 科目 HAVING avg(成绩)<60
对付多数据库,干系集算器实行代码如下:
对付大数据量,须要利用游标 (select 函数同样适用于游标)
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 连接公司信息与股票信息的集算器代码:
对付大数据:
主子表的情形与同维表类似,即一个表(主表)的主键字段被另一个表(子表)的主键字段所包含,且子表中对应的主键字段与主表的主键有逻辑意义上的外键约束关系。
举例解释,比如有两张表:订单与订单明细,表的构造如下:
订单
订单明细
个中订单是主表,主键为 (订单 ID);而订单明细为子表,主键为 (订单 ID, 产品 ID),且订单明细的主键字段订单 ID,与订单的主键有逻辑意义上的外键约束关系,显然二者为主子表的关系。
现在假设我想将两个表拼接在一起,单数据库的 SQL 是这样的:
SELECT FROM 订单 T1 JOIN 订单明细 T2 ON T1. 订单 ID=T2. 订单 ID
现假设订单分为两部分,分别存于 HSQL 和 MYSQL 数据库中,订单明细同样分为两部分,分别存于 HSQL 和 MYSQL 数据库中,且二者同步分库。
join 连接订单与订单明细的集算器代码:
对付大数据:
B、外键表复制进每个库
所谓外键表,即是指连接字段为外键字段的情形。这种外键表 join 也是业务上常见的一种情形。由于要连接的外键表在每个库中都有同一份,那么两个外键表汇总并去重后,实在还是任一数据库中原来就有的那个外键表。
而 join 的连接操作,实质上可以视为一种乘法,由于 join 连接等价于 cross join 后再用 on 中条件进行过滤。则根据乘法分配率可以推导出:若是须要做连接操作的外键表(不妨设为连接右侧的表)在每个库中都有同一份,则连接左侧的表(每个数据库中各有其一部分)在汇总后再连接,等同于各数据中的连接左侧的表与外键表先做连接操作后,再汇总到一起的结果。如图所示:
以是我们在存储设计时,只要在每个数据库中把外键表都重复一下,就可以避免繁芜的跨库 join 操作。一样平常情形下,外键表作为维表的数据量相对较小,这样重复的本钱就不会很高,而事实表则会得很大,然后用分库存储的方法,来办理运算速率缓慢或存储空间不敷等问题。
例如,有两个表:客户发卖表和客户表,个中客户发卖表的外键字段:客户,与客户表的主键字段:客户 ID,有外键约束关系。现在我们想查询面向河北省各公司的发卖额记录,对付单数据源,它的 SQL 是这样写的:
SELECT T1. 公司名称 公司名称, T2. 订购日期 订购日期, T2. 发卖额 发卖额 FROM 客户表 T1 JOIN 客户发卖表 T2 ON T1. 客户 ID=T2. 客户 WHERE T1. 省份 ='河北'
对付多数据源的情形,我们假设客户发卖表分别存储在两个不同的数据库中,而每个数据库中都有同一份的客户表做为外键表。则干系的集算器代码如下:
大数据量利用游标时:
C、须要连接的外键表在另一个库中
对付维表(外键表)也被分库的情形,我们只考虑维表全部可内存化的情形,不可内存化时,常常就不适宜再将数据存在数据库中了,须要专门针对性的的存储和打算方案,这将在其余的文章中专门谈论。在这里我们只通过例子来谈论维表可内存化的情形。
对付这种情形,当涉及的数据量比较大而须要利用游标时,打算逻辑会变得比较繁芜。以是我们在这里只讲一下针对小数据量的利用序表的 join 处理方法。关于对大数据量的利用游标的 join 处理,会另有一篇文章做专门的先容。
当要做 join 连接运算的外键表全部或部分存储在另一个库中时,最直不雅观的办法便是将两个表都提取出来并各自汇总后,再打算 join 连接。
下面仍以客户发卖表和客户表来举例,假设外键表客户表也分别存储在两个数据库中,此时就不能在 SQL 中利用 join 关键字来实现连接运算了,但我们可以将其提取出来后,用集算器的 join 函数来实现目的,它的集算器代码如下所示:
当事实表数据量较大的时候,也可以利用游标处理事实表,只需将 join 换成 cs.join 即可:
7. 大略 SQL
前面我们紧张是从打算事理的角度出发,剖析了如何利用集算器实现类似 SQL 效果的多数据源稠浊打算。除此之外,集算器还供应了一种更大略、直不雅观的方法,那便是可以在各个数据库上通过 SQL 查询获取游标,用所有这些游标构建成一个多路游标工具,再用大略 SQL 对这个多路游标做二次处理。如果大略 SQL 中没有涉及 join 的运算,乃至还可以让集算器直接将一句大略 SQL 翻译成各种数据库的 SQL,从而实现更进一步的自动化。不过这种办法属于比较守旧的做法,虽然大略直接,但不能利用所理解的数据情形进行优化(比如不会利用 groups),因此性能就会差一些。
下面仍旧用学天生就的例子,我们想要打算每个班的数学成绩的总分、考试人数、均匀分、最高分和最低分,利用大略 SQL 处理这个问题的集算器代码如下:
由于利用了游标,以是这种写法也可以用于大数据量。其余再提一句,这个办法乃至也可以用于非数据库的数据源(比如文件数据源)!
大略 SQL 的特性可参考干系文档,这里就不再进一步举例了。