第三篇 - 外键维表的关联

SQL 对关联的定义过于简单,就是两个表做笛卡尔积后再过滤,在语法上写成 A JOIN B ON …的形式。这样笼统处理多种关联,不体现关联运算本质,书写和优化都困难。

SPL 重新定义了关联。关联不再和笛卡尔积有关,而是分成两种情况:外键关联和主键关联。SPL 用不同的函数来计算不同的关联,体现关联运算本质。这样就可以利用不同关联的特征,采用不同的手段甚至不同的存储方式有效提速。

关联的一种情况是外键关联。一个表的普通字段(外键)和另一个表的主键关联。比如订单表与客户表、运货商表都是外键关联。

..

外键在关联表中是主键,具有唯一性,SPL 把外键理解成一个对象。

要注意,这里说的是逻辑上的主键,是在表中取值唯一的字段,不一定是在物理表上建立的 primary key。外键也不一定是物理上的 foreign key。

另一种关联是主键关联。用一个表的主键,关联另一个表的主键部分主键。比如订单表的主键订单号,关联明细表的部分主键订单号。

..

SPL 把主键关联看作记录对象或记录集合之间的连接。

这里要“敲黑板、划重点”啦:使用 SPL 编程实现关联,最重要的是要先识别关联类型!只有识别了关联的类型,才能选择相应的方法实现。识别的时候,重点要看主键在连接中的作用!

下面,我们以订单表、客户表、城市表、州表和运货商表为例,介绍 esProc SPL 外置数据提速外键关联的方法。

使用 ETL 工具定义 Q3.etl,拖拽需要的表:

..

比较大的 orders 是事实表,转储成 CTX。较小的维表转成 BTX,编程更方便。

定义关联字段序号化,比如 customer 中的 city_id,转维表 city 中的序号。

..

双击表达式 / 序号化表空白处—选择 city 表。city 表的 state_id,要类似的设置成 state 表。

修改 orders 表名为 ordersQ3(区别前面的表 ):

..

employee_name 没有维表,要在 ordersQ3 表中找全所有值,新建维表 employee:

..

上图的操作顺序:1、点击序号化枚举列。2、双击 employee_name。3、定义维表名 employee。4、定义维表序号列名称 id。5、定义维表值列名称 name。

还要设置 ordersQ3 中的序号化字段:

..

生成 SPL 代码 14:导出数据,转储成 BTX 或 CTX。


A

1

=connect("speed")

2

="d:\\speed\\etl\\"

3

=A1.query("SELECT Distinct employee_name FROM orders")

4

=A3.(employee_name).new(#:id,~:name).keys@i(name)

5

=file(A2+"employee.btx").export@b(A4)

6

=A1.query("SELECT state_id,state_name FROM state ORDER BY state_id")

7

=A6.keys@i(state_id)

8

=file(A2+"state.btx").export@b(A6)

9

=A1.query("SELECT city_id,city_name,state_id FROM city ORDER BY city_id")

10

=A9.new(city_id,city_name,A7.pfind(state_id):state_id).keys@i(city_id)

11

=file(A2+"city.btx").export@b(A9)

12

=A1.query("SELECT customer_id,customer_name,city_id FROM customer ORDER BY customer_id")

13

=A12.new(customer_id,customer_name,A10.pfind(city_id):city_id).keys@i(customer_id)

14

=file(A2+"customer.btx").export@b(A12)

15

=A1.query("SELECT shipper_id,shipper_name FROM shipper ORDER BY shipper_id")

16

=A15.keys@i(shipper_id)

17

=file(A2+"shipper.btx").export@b(A15)

18

=A1.cursor("SELECT order_id,customer_id,employee_id,employee_name,order_date,shipper_id,shipping_fee,shipping_address FROM orders ORDER BY order_id")

19

=A18.new(order_id,A13.pfind(customer_id):customer_id,employee_id,A4.pfind(employee_name):employee_name,order_date,A16.pfind(shipper_id):shipper_id,shipping_fee,shipping_address)

20

=file(A2+"ordersQ3.ctx").create@y(#order_id,customer_id,employee_id,employee_name,order_date,shipper_id,shipping_fee,shipping_address).append(A19).close()

21

=A1.close()

A13 中,customer 表的 city_id,用 pfind 函数转为维表 city 中的序号:

..

SPL 代码 15:在系统启动,或数据有变动时执行初始化。


A

B

1

=file("state.btx").import@b()

=env(state,A1)

2

=file("city.btx").import@b()

=env(city,A2)

3

=file("customer.btx").import@b()

=env(customer,A3)

4

=file("shipper.btx").import@b()

=env(shipper,A4)

5

=city.run(state_id=state(state_id))

6

=customer.run(city_id=city(city_id))

初始化完成预加载,预先把维表读入内存,用 env 存成全局变量。

还要完成预关联,预先把外键用 run 转换成对应维表的记录,比如 A6:

..

序表 customer 的普通字段 city_id 被转换为 city 表的记录。

数据转储、初始化完成后,可以实际计算,比如下面的例子:

例 3.1 某州订单,按运货商分组统计运费,结果包括运货商名。

SQL 这样写:

select shipper.shipper_id, shipper.shipper_name,sum(o.shipping_fee)
from 
    orders o
join 
    customer c on o.customer_id = c.customer_id
join 
    city on city.city_id = c.city_id
join 
    state on state.state_id = city.state_id
join 
    shipper on o.shipper_id = shipper.shipper_id
where 
    state.state_name = 'California'
group by 
    shipper.shipper_id, shipper.shipper_name;

执行时间是 20 秒。

SPL 代码 16:


A

1

=now()

2

=file("ordersQ3.ctx").open()

3

=A2.cursor@m(order_id, customer_id,shipper_id,shipping_fee;customer_id:customer:#,shipper_id:shipper:#)

4

=A3.select(customer_id.city_id.state_id.state_name=="California").groups(shipper_id;shipper_id.shipper_name,sum(shipping_fee))

5

>output("query cost:"/interval@ms(A1,now())/"ms")

A2 中 customer_id:customer:# 的写法,是利用序号 #,在游标上完成订单记录与客户表的关联。关联后, customer_id 和 shipper_id 字段都被转换为对应表的记录对象。

由于维表已经在初始化时预先关联好了,A3 就可以采用对象属性的方式写代码: customer_id.city_id.state_id.state_name。

执行时间是 0.4 秒

例 3.2 按客户所在城市分组统计订单数量,结果要包含州名称、城市名称。

select city.city_id,city.city_name,state.state_name,count(o.order_id)
from 
    orders o
join 
    customer c on o.customer_id = c.customer_id
join 
    city  on c.city_id = city.city_id
join 
    state on city.state_id = state.state_id
group by 
    city.city_id,city.city_name,state.state_name;

执行时间:22 秒。

SPL 代码 17:在前面代码 SPL 代码 16 基础上,改一下 A4 就可以了。


A

1

=now()

2

=file("ordersQ3.ctx").open()

3

=A2.cursor@m(order_id, customer_id,shipper_id,shipping_fee;customer_id:customer:#,shipper_id:shipper:#)

4

=A3.groups(customer_id.city_id.city_id;customer_id.city_id.city_name,customer_id.city_id.state_id.state_name, count(1))

5

>output("query cost:"/interval@ms(A1,now())/"ms")

执行时间是 0.3 秒。

小结一下性能(单位 - 秒):


MYSQL

SPL

例 3.1

20

0.4

例 3.2

22

0.3

请动手练习一下:

1、找出运货商是 Elite Shipping Co. 的订单,按客户所在州分组统计运货费。结果要包括州名称。

2、思考:在自己熟悉的数据库中有没有通过外键关联的多个表?是否可以用序号关联的方法来提速?