SQL 和 SPL 的一对一和一对多连接对比

【摘要】
    连接(JOIN)用于把来自两个或多个表的记录结合起来。SQL 和 SPL 是大家比较熟悉的程序语言,本文将探讨对于连接问题,这两种语言的解决方案和基本原理。如何简便快捷的处理连接运算,这里为你全程解析,并提供 SQL 和 SPL 示例代码。SQL 和 SPL 的一对一和一对多连接对比

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

 

一. 一对一

一对一关联,是指一张表的一条记录只能与另外一张表的一条记录进行对应;反之亦然。一对一关联关系常见于两张表都使用相同的主键字段,这样两张表的记录就是一一对应的关系。

例如有学生表和学生联系表,两个表的主键都是学生 ID。第一张表中存储了学生的姓名、性别、生日、系和班级等等基本信息,而学生联系表中存储了学生的联系人和地址等联系方式。每一个学生信息都对应了唯一一条联系方式,每一个联系方式也对应了唯一的学生信息。

 

【例 1】 根据学生表和学生联系表,查询有联系方式的学生姓名、联系人和住址。部分数据如下:

STUDENT:

ID

NAME

DEPARTMENTID

CLASSID

GENDER

BIRTHDAY

1

Rebecca

1

1

F

2010/09/08

2

Ashley

1

1

F

2010/10/09

3

Rachel

1

1

F

2011/04/29

4

Emily

1

1

F

2010/11/24

5

Ashley

1

1

F

2011/03/03

STUDENT_CONTACTS:

ID

CONTACTS

ADDRESS

1

Mrs. Moore

124 Guangming North Road

2

Mrs. Wilson

116 Baishi Road

3

Mr. Johnson

No.8, Mingcheng Road, Haidian District

4

Mr. Smith

12 Fuxing Road

5

Mr. Smith

462 Shijingshan Road

 

 

 

SQL的解决方案:

    根据题意,有的学生可能没有登记联系方式。要查询满足条件的学生姓名和联系方式,我们需要使用内连接,只有学生 ID 在两个表中都存在的记录才会被选出。内连接也叫连接,会从结果表中删除与其他被连接表中没有匹配的所有行。在 SQL 语句中,INNER JOIN 用于内连接前后两张表,ON 语句定义了连接的条件。具体语句如下:

 

    SELECT

      S.NAME,C.CONTACTS,C.ADDRESS

   FROM

      STUDENT S

   INNER JOIN

   STUDENT_CONTACTS C

   ON S.ID=C.ID

 

SPL的解决方案:

    在 SPL 中称这种一对一关联关系的两个(或多个)表互为同维表。SPL 提供了函数 join() 用于连接,默认为内连接。


A

1

=T("Student.txt")

2

=T("StudentContacts.txt")

3

=join(A1:S,ID;A2:C,ID)

4

=A3.new(S.NAME,C.CONTACTS,C.ADDRESS)

A1:从文件中导入学生表。

A2:从文件中导入学生联系表。

A3:学生表和学生联系表通过学生 ID 进行内连接。

A4:创建以学生姓名、联系人、联系地址为字段的序表。

 

SPL同样也支持从数据库中读取数据表,比如数据来源于数据库"db"中 "STUDENT" 表时, A1可以改为:


A

1

=connect("db").query("select * from STUDENT")

 

 

【例 2】 根据员工表和经理表,查询所有员工(包括经理)的收入(加上津贴)。部分数据如下:

EMPLOYEE:

ID

NAME

BIRTHDAY

STATE

DEPT

SALARY

1

Rebecca

1974/11/20

California

R&D

7000

2

Ashley

1980/07/19

New   York

Finance

11000

3

Rachel

1970/12/17

New   Mexico

Sales

9000

4

Emily

1985/03/07

Texas

HR

7000

5

Ashley

1975/05/13

Texas

R&D

16000

MANAGER:

ID

ALLOWANCE

18

7000

2

11000

4

7000

6

10000

7

9000

 

 

 

SQL的解决方案:

    经理也是员工,只不过经理表中额外存储了一些信息,例如津贴。要查询所有员工的工资,需要使用左连接,所有的员工不管是否经理都会被选出。左连接也叫左外连接,以左表为基础将两表连接起来。结果会将左表所有的查询信息列出,而右表只列出条件与左表满足的部分。在 SQL 语句中,LEFT JOIN 用于左连接。具体语句如下:

 

    SELECT

      E.ID,E.NAME,E.SALARY+NVL(M.ALLOWANCE,0) INCOME

   FROM EMPLOYEE E

   LEFT JOIN

   MANAGER M

   ON E.ID=M.ID

 

SPL的解决方案:

    SPL提供了函数 join() 用于连接,选项 @1 表示左连接。


A

1

=T("Employee.csv")

2

=T("Manager.txt")

3

=join@1(A1:E, ID; A2:M, ID)

4

=A3.new(E.ID, E.NAME, E.SALARY+M.ALLOWANCE:INCOME)

A1:从文件中导入员工表。

A2:从文件中导入经理表。

A3:员工表和经理表根据员工 ID,以第一个表(员工表)为准进行左连接。

A4:创建以员工 ID、姓名、总收入为字段的序表。

 

    一对一关联关系,在所有关联关系中是最简单的,两个表连接时直接按照主键进行连接即可。无论是 SQL 还是 SPL 都可以很好的解决一对一关联关系。

 

 

二. 一对多

一对多关联,是指一张表的一条记录能与另外一张表的任意记录进行对应。在一对多的关系中,我们把“一”的这端表叫做主表,“多”的这端表叫子表(从表)。例如有订单表和订单明细表,每笔订单对应唯一的订单 ID,但是每个订单 ID 可能对应多条订单明细信息。我们称订单表是主表,订单明细表是子表。

 

【例 3】 根据订单表和订单明细表,统计每个订单的总金额。部分数据如下:

ORDERS:

ID

CUSTOMERID

EMPLOYEEID

ORDER_DATE

ARRIVAL_DATE

10248

VINET

5

2012/07/04

2012/08/01

10249

TOMSP

6

2012/07/05

2012/08/16

10250

HANAR

4

2012/07/08

2012/08/05

10251

VICTE

3

2012/07/08

2012/08/05

10252

SUPRD

4

2012/07/09

2012/08/06

ORDER_DETAIL:

ID

ORDER_NUMBER

PRODUCTID

PRICE

COUNT

DISCOUNT

10814

1

48

102.0

8

0.15

10814

2

48

102.0

8

0.15

10814

3

48

306.0

24

0.15

10814

4

48

102.0

8

0.15

10814

5

48

204.0

16

0.15

 

SQL的解决方案:

    在 SQL 中处理一对多关联关系时,还是使用 JOIN 语句。SQL 语句如下:

 

    SELECT

      ID, SUM(PRICE*COUNT) AMOUNT

   FROM (

      SELECT

         Orders.ID, Detail.PRICE, Detail.COUNT

      FROM ORDERS Orders

      INNER JOIN

      ORDER_DETAIL Detail

      ON Orders.ID=Detail.ID

   )

   GROUP BY ID

   ORDER BY ID

 

SPL的解决方案:

    单个主子表时,可以使用函数 join(), 通过子表的部分主键与主表主键进行连接。


A

1

=T("Orders.txt")

2

=T("OrderDetail.txt")

3

=join(A1:Orders,ID; A2:Detail,ID)

4

=A3.groups(Orders.ID; sum(Detail.PRICE*Detail.COUNT):AMOUNT)

A1:导入订单表。

A2:导入订单明细表。

A3:订单表和订单明细表通过订单 ID 进行连接。

A4:分组汇总每个订单的总金额。

 

【例 4】 订单表还有一个子表用于记录回款情况。我们现在想知道哪些订单还没有收到全部回款,也就是累计回款金额小于订单总金额的订单。部分数据如下:

ORDERS:

ID

CUSTOMERID

EMPLOYEEID

ORDER_DATE

ARRIVAL_DATE

10248

VINET

5

2012/07/04

2012/08/01

10249

TOMSP

6

2012/07/05

2012/08/16

10250

HANAR

4

2012/07/08

2012/08/05

10251

VICTE

3

2012/07/08

2012/08/05

10252

SUPRD

4

2012/07/09

2012/08/06

ORDER_DETAIL:

ID

ORDER_NUMBER

PRODUCTID

PRICE

COUNT

DISCOUNT

10814

1

48

102.0

8

0.15

10814

2

48

102.0

8

0.15

10814

3

48

306.0

24

0.15

10814

4

48

102.0

8

0.15

10814

5

48

204.0

16

0.15

ORDER_PAYMENT:

ID

PAY_DATE

AMOUNT

CHANNEL

INSTALMENTS

10814

2014/01/05

816.0

3

0

10848

2014/01/23

800.25

2

1

10848

2014/01/23

800.25

0

0

10848

2014/01/23

800.25

3

1

10966

2014/03/20

572.0

2

1

 

SQL的解决方案:

    简单地把这三个表 JOIN 起来是不对的,订单明细和订单回款表会发生多对多的关系。子表按订单 ID 分组后,就变成了具有唯一性的事实上的主键,多表之间就可以直接用订单 ID 进行连接了。

 

   SELECT

      Orders.ID,Detail.AMOUNT,Payment.PAY_AMOUNT

   FROM ORDERS Orders

   INNER JOIN

   (

      SELECT ID, SUM(PRICE*COUNT) AMOUNT

      FROM ORDER_DETAIL

      GROUP BY ID

   ) Detail

   ON Orders.ID=Detail.ID

   INNER JOIN

   (

      SELECT ID, SUM(AMOUNT) PAY_AMOUNT

      FROM ORDER_PAYMENT

      GROUP BY ID

   ) Payment

   ON Orders.ID=Payment.ID

   WHERE PAY_AMOUNT<Detail.AMOUNT

   ORDER BY ID

 

SPL的解决方案:

    子表对着订单 ID 分组后,订单 ID 就变成了事实上的主键。之后我们就可以把这些表当作一对一(同维表)关系来处理了。


A

1

=T("Orders.txt")

2

=T("OrderDetail.txt")

3

=T("OrderPayment.txt")

4

=A2.groups(ID; sum(PRICE*COUNT):AMOUNT)

5

=A3.groups(ORDER; sum(AMOUNT):PAY_AMOUNT)

6

=join(A1:Orders,ID; A4:Detail,ID; A5:Payment,ORDER)

7

=A6.new(Orders.ID, Detail.AMOUNT, Payment.PAY_AMOUNT)

8

=A7.select(PAY_AMOUNT<AMOUNT)

A1:导入订单表。

A2:导入订单明细表。

A3:导入订单回款表。

A4:分组汇总每个订单的总金额。

A5:分组汇总每个订单的回款总金额。

A6:使用函数 join() 将订单表和汇总后的明细表、回款表按照订单 ID 连接。

A7:创建由字段订单 ID、订单金额和回款金额组成的序表。

A8:从中选出回款金额小于订单金额的记录,即还没有完全付清的订单。

 

    因为 SQL 不提倡分步计算,要把所有过程写到一个句子里,导致这个例子的 SQL 语句有点复杂。要对来讲,SPL 使用分步的逻辑就比较清晰,大体上分为两步:第一步所有子表按照部分主键(主表的主键)进行分组,此时所有子表的事实主键与主表相同了;第二步将多个表按照其主键(或事实主键)进行连接。其实 SQL 和 SPL 的实现原理是一样的,但分步能够让代码更易写。

Student.txt

StudentContacts.txt

Employee.csv

Manager.txt

Orders.txt

OrderDetail.txt

OrderPayment.txt

SQL 与 SPL 对比系列:
SQL 和 SPL 的集合运算对比
SQL 和 SPL 的选出运算对比
SQL 和 SPL 的有序运算对比
SQL 和 SPL 的等值分组对比
SQL 和 SPL 的非等值分组对比
SQL 和 SPL 的有序分组对比
SQL 和 SPL 的一对一和一对多连接对比
SQL 和 SPL 的多对一连接对比
SQL 和 SPL 的多对多连接对比
SQL 和 SPL 的基本静态转置对比
SQL 和 SPL 的复杂静态转置对比
SQL 和 SPL 的动态转置对比
SQL 和 SPL 的递归对比