SPL:多对一连接

连接(JOIN)用于把来自两个或多个表的记录结合起来。本文将探讨对于连接问题,SPL 的解决方案和基本原理。

表之间存在的数据相互依赖关系,就叫做表间关联关系。表间关联关系可以分为以下几种:一对一、多对一、一对多和多对多。我们可以通过表间关联关系,把两个或多个表连接起来,从而实现多表关联查询的目的。

多对一关联,是指一张表的任意条记录能与另外一张表的一条记录进行对应。多对一关联关系,常见于有外键关系的两个表。表 A 的某些字段与表 B 的主键关联。A 表中与 B 表主键关联的字段称为指向 B 表的外键,B 也称为 A 的外键表

例如有员工表和部门表,其中员工表的部门 ID 字段,指向了部门表的 ID 字段。每个员工都有一个部门,但是每个部门可以有多个员工,员工表和部门表是多对一的关系。

我们经常把一对多和多对一混在一起来讲,其实是受了 SQL 的误导。在 SQL 中对这两种关联关系并没有进行区分,也没有单独提供各自的解决方案。

解决多对一关联的思路与一对多完全不同,我们可以在连接时将外键值转换成对应记录,或者将需要的字段值附加到“多”表上,然后就可以当作是一张表进行查询运算了。SPL 提供了 A.switch(),A.join() 等函数,针对外键表连接(多对一)进行优化和提速。

一.外键对象化

【例 1】 根据员工表和部门表,查询所有员工的姓名及其所在部门名称。部分数据如下:

EMPLOYEE:

ID

NAME

BIRTHDAY

DEPARTMENTID

SALARY

1

Rebecca

1974/11/20

6

7000

2

Ashley

1980/07/19

2

11000

3

Rachel

1970/12/17

7

9000

4

Emily

1985/03/07

3

7000

5

Ashley

1975/05/13

6

16000

DEPARTMENT:

ID

NAME

MANAGER

1

Administration

18

2

Finance

2

3

HR

4

4

Marketing

6

5

Production

7

SPL提供了函数 A.switch(),将编码字段的键值转换为对应的记录。

SPL脚本如下:


A

1

=T("Employee.txt")

2

=T("Department.txt")

3

=A1.switch(DEPARTMENTID, A2:ID)

4

=A3.new(NAME, DEPARTMENTID.NAME:DEPT_NAME)

A1:导入员工表。

A2:导入部门表。

A3:使用函数 A.switch() 将部门 ID 进行外键对象化,即将部门 ID 的字段值转换成相应的部门记录。

A4:返回员工名称和部门名称,其中部门名称可以通过部门记录的 NAME 字段获得。

经过外键对象化以后,员工表的部门字段的值,就是员工在部门表中的对应记录。在员工表中可以直接用”部门. 名称”来获取部门名称,也可以用”部门. 经理”来获取部门经理名称等等。

由于这个例子比较简单,可能有人会有疑问,在连接时将需要的外键字段引入就可以了,为什么还要进行外键对象化呢?接下来我们用一个稍微复杂一点的例子来说明。

【例 2】 根据员工表和部门表,查询哪些美国籍员工有一个中国籍经理。部分数据如下:

EMPLOYEE:

ID

NAME

BIRTHDAY

DEPARTMENTID

SALARY

1

Rebecca

1974/11/20

6

7000

2

Ashley

1980/07/19

2

11000

3

Rachel

1970/12/17

7

9000

4

Emily

1985/03/07

3

7000

5

Ashley

1975/05/13

6

16000

DEPARTMENT:

ID

NAME

MANAGER

1

Administration

18

2

Finance

2

3

HR

4

4

Marketing

6

5

Production

7

我们还是可以通过函数 A.switch() 进行外键的对象化来解决问题。

SPL脚本如下:


A

1

=T("Employee.txt").keys(ID)

2

=T("Department.txt").keys(ID)

3

=A2.switch(MANAGER, A1)

4

=A1.switch(DEPARTMENTID, A2)

5

=A4.select(NATION=="American" && DEPARTMENTID.MANAGER.NATION=="Chinese")

A1:导入员工表,以 ID 为主键。

A2:导入部门表,以 ID 为主键。

A3:使用函数 A.switch() 外键对象化,将部门表的经理字段,转换成相应的员工记录。

A4:使用函数 A.switch() 外键对象化,将员工表的部门字段,转换成相应的部门记录。

A5:选出经理是中国籍的美国籍员工。

这个例子中多了一层关联关系,但是 SPL 语句并没有变得复杂,只是按照逻辑增加了一次外键对象化。我们不需要每次外键连接都提前想好需要哪些字段,可以直接从记录对象中取得,在选出时也非常符合自然逻辑,部门的经理的国籍(DEPARTMENTID.MANAGER.NATION)是中国籍。

【例 3】 根据课程表和选课表,查询有多少学生选修了“Matlab”课程。部分数据如下:

COURSE

ID

NAME

TEACHERID

1

Environmental protection and sustainable development

5

2

Mental health of College Students

1

3

Matlab

8

4

Electromechanical basic practice

7

5

Introduction to modern life science

3

SELECT_COURSE

ID

COURSEID

STUDENTID

1

6

59

2

6

43

3

5

52

4

5

44

5

5

37

我们想要在连接时,删除不匹配的记录(选出课程不是 Matlab 的),可以使用内连接。在函数 A.switch() 中,选项 @i 用于在连接时删除不匹配的记录。

SPL脚本如下:


A

1

=T("Course.csv")

2

=T("SelectCourse.csv")

3

=A1.select(NAME:"Matlab")

4

=A2.switch@i(COURSEID, A3:ID).count()

A1:导入课程表。

A2:导入选课表。

A3:在课程表中选出名为 Matlab 的记录。

A4:使用函数 A.switch() 的选项 @i,在连接时删除不匹配的记录,即剩下的都是选择了 Matlab 课程的记录,再统计数量。

【例 4】 根据销售表和客户表,查询 2014 年新增客户的销售情况(即销售表的客户 ID 不在客户表中的记录)。部分数据如下:

SALES

ID

CUSTOMERID

ORDERDATE

SELLERID

PRODUCTID

AMOUNT

10248

VINET

2013/7/4

5

59

2440

10249

TOMSP

2013/7/5

6

38

1863.4

10250

HANAR

2013/7/8

4

65

1813

10251

VICTE

2013/7/8

3

66

670.8

10252

SUPRD

2013/7/9

4

46

3730

CUSTOMER

ID

NAME

CITY

POSTCODE

TEL

ALFKI

Sanchuan Industrial Co., Ltd

Tianjin

343567

(030) 30074321

ANATR

Southeast industries

Tianjin

234575

(030) 35554729

ANTON

Tanson trade

Shijiazhuang

985060

(0321) 5553932

AROUT

Guoding Co., Ltd

Shenzhen

890879

(0571) 45557788

BERGS

Tongheng machinery

Nanjing

798089

(0921) 9123465

我们想要在连接时,只保留不匹配的记录(销售表的客户 ID 不在客户表中的)。在函数 A.switch() 中,选项 @d 时只保留不匹配的记录。

SPL脚本如下:


A

1

=T("Sales.csv")

2

=T("Customer.txt")

3

=A1.select(year(ORDERDATE)==2014)

4

=A3.switch@d(CUSTOMERID, A2:ID)

A1:导入销售表。

A2:导入客户表。

A3:在销售表中选出 2014 的记录。

A4:使用函数 A.switch() 的选项 @d,在连接时只保留不匹配的记录,即 2014 年新增客户的销售记录。

【例 5】 根据组织机构表,查询每个机构的上级机构名称。部分数据如下:

ID

ORG_NAME

PARENT_ID

1

Head Office

0

2

Beijing Branch Office

1

3

Shanghai Branch Office

1

4

Chengdu Branch Office

1

5

Beijing R&D Center

2

在组织机构表中,上级机构 ID(PARENT_ID)指向的还是本表,这是一个自连接的例子。我们还是可以使用函数 A.switch() 进行外键对象化,外键表选择自身即可。

SPL脚本如下:


A

1

=T("Organization.txt")

2

=A1.switch(PARENT_ID, A1:ID)

3

=A2.new(ID, ORG_NAME, PARENT_ID.ORG_NAME:PARENT_NAME)

A1:导入组织机构表。

A2:使用函数 A.switch() 进行外键对象化,将父机构 ID 字段值转换为相应的父机构记录。

A3:返回所有机构的名称和父机构的名称。

二.外键对象化不支持的情况

多字段外键时,外键对象化就不支持了,这种情况可以通过增加外键表的字段或者记录的方式来解决。

【例 6】 根据组织机构表,选出 2014 年回款的价格超过 500 的产品名称。表间关系如下:

..

上图中,订单明细表和产品表通过单字段外键产品序号进行关联,订单回款表与订单明细表通过订单编号、订单序号这两个外键字段进行关联。多字段外键时,SPL 提供了函数 A.join() 用于连接外键表,将外键表的字段值或者记录添加到新增的字段中。解决方案:第一步,订单明细表的订购产品字段进行外键对象化,替换为产品表的相应记录。第二步,订单回款表与订单明细表进行连接,相应的记录存储在新增的字段 DETAIL 中。

SPL脚本如下:


A

1

=T("OrderPayment.txt")

2

=T("OrderDetail.txt")

3

=T("Product.txt")

4

=A2.switch(PRODUCTID, A3:ID)

5

=A1.join(ORDER:ORDER_NUMBER,A2:ID:ORDER_NUMBER,~:DETAIL)

6

=A5.select(year(PAY_DATE)==2014 && DETAIL.PRICE>500)

7

=A6.new(ORDER,PAY_DATE,DETAIL.PRODUCTID.NAME:PRODUCT_NAME,DETAIL.PRICE:PRICE)

A1:导入订单回款表。

A2:导入订单明细表。

A3:导入产品表。

A4:使用函数 A.switch() 进行外键对象化,将产品 ID 字段值转换为相应的产品记录。

A5:使用函数 A.join() 通过双字段连接订单回款表和订单明细表,将订单明细表的相应记录添加到 DETAIL 字段。

A6:选出 2014 年汇款价格超过 500 的记录。

A7:产生字段为订单编号、回款日期、产品名称、产品价格的表。

当引用的外键表字段可能为空的情况下,我们又想要保留外键字段的原值时,就不能使用外键对象化了。可以使用函数 A.join() 连接外键表,将外键表的字段值添加到新增的字段中。

【例 7】 根据销售表和客户表,查询 2014 年每个客户的总销售额(有的客户可能还没有登记在客户表中)。部分数据如下:

SALES

ID

CUSTOMERID

ORDERDATE

SELLERID

PRODUCTID

AMOUNT

10248

VINET

2013/7/4

5

59

2440

10249

TOMSP

2013/7/5

6

38

1863.4

10250

HANAR

2013/7/8

4

65

1813

10251

VICTE

2013/7/8

3

66

670.8

10252

SUPRD

2013/7/9

4

46

3730

CUSTOMER

ID

NAME

CITY

POSTCODE

TEL

ALFKI

Sanchuan Industrial Co., Ltd

Tianjin

343567

(030) 30074321

ANATR

Southeast industries

Tianjin

234575

(030) 35554729

ANTON

Tanson trade

Shijiazhuang

985060

(0321) 5553932

AROUT

Guoding Co., Ltd

Shenzhen

890879

(0571) 45557788

BERGS

Tongheng machinery

Nanjing

798089

(0921) 9123465

SPL脚本如下:


A

1

=T("Sales.csv")

2

=T("Customer.txt")

3

=A1.select(year(ORDERDATE)==2014)

4

=A3.join(CUSTOMERID, A2:ID, NAME:CUSTOMERNAME)

5

=A4.groups(if (CUSTOMERNAME,CUSTOMERNAME,CUSTOMERID):CUSTOMER; sum(AMOUNT):AMOUNT)

A1:导入销售表。

A2:导入客户表。

A3:选出 2014 年的销售记录。

A4:使用函数 A.join() 连接销售表和客户表,将客户表的客户名称字段添加到销售表的 CUSTOMERNAME 字段。

A5:按照客户名称分组汇总每个客户的总销售额,当客户名称不存在时使用客户 ID。

通过以上的例子我们可以看到,多对一与一对多关系是有很大的区别的。在 SPL 中,明确的区分了两者的不同,分别提供了不同的解决方案来支持多对一和一对多两种关联关系。


Employee.txt

Department.txt

Course.csv

SelectCourse.csv

Sales.csv

Customer.txt

Organization.txt

OrderPayment.txt

Product.txt

OrderDetail.txt