Python 和 SPL 对比 11——多对一关联
《Python和 SPL对比系列 10——一对 N关联》介绍了关联关系中的一对一和一对 N 关联,本文对比 Python 和 SPL 多对一关联的运算能力。
外键关联
表 A 的某些字段与表 B 的主键关联,表 A 的关联字段可以不唯一,表 B 的关联字段唯一,这就是多对一关联,也称作外键关联,即表 A 是事实表,表 B 是维表,A 表与 B 表主键关联的字段称为 A 指向 B 的外键,B 也称为 A 的外键表。如:
现有销售记录表和产品信息表,请汇总各类产品的销售额。
销售记录表、城市信息表、产品信息表部分内容如下:
销售记录表 (事实表):
recordid |
product |
sale_city |
amount |
… |
sr100001 |
p1003 |
c104 |
380 |
… |
sr100002 |
p1005 |
c103 |
400 |
… |
sr100003 |
p1003 |
c104 |
626 |
… |
… |
… |
… |
… |
… |
产品信息表(维表):
productid |
pclass |
… |
p1001 |
A |
… |
p1002 |
A |
… |
p1003 |
B |
… |
… |
… |
… |
Python
import pandas as pd sr_file1="D:\data\SaleRecord.csv" pt_file1="D:\data\Product.csv" record1=pd.read_csv(sr_file1) product1=pd.read_csv(pt_file1) r_pt=pd.merge(record1,product1,left_on="product",right_on="productid") pclass_sale=r_pt.groupby('pclass',as_index=False).amount.sum() print(pclass_sale) |
事实表
维表 事实表与维表外键关联
|
Python的 merge 函数关联两表,销售记录表 record1 是实事表,产品信息表 product1 是维表,record1 中的多条记录与 product1 的一条记录对应,两者的关联字段名不同,left_on 和 right_on 分别标出两表的关联字段,使两表关联成为一张宽表,最后分组汇总得到结果。
SPL
A |
B |
|
1 |
D:\data\SaleRecord.csv |
|
2 |
D:\data\Product.csv |
|
3 |
=file(A1).import@tc() |
|
4 |
=file(A2).import@tc() |
|
5 |
=A3.switch(product,A4:productid) |
/外键转维表记录 |
6 |
=A5.groups(product.pclass;sum(amount):amount) |
SPL的 switch 函数把外键字段转换成对应的维表记录,既然是记录当然可以引用字段,在分组时可以利用引用字段完成分组聚合运算。
一事实表,多维表
一张事实表可能关联多张维表,如:
续用上例的销售记录表(事实表)和产品信息表(维表 1),新增城市信息表(维表 2),请统计各省份各类产品的销售额。
城市信息表(维表 2):
cityid |
name |
province |
… |
c101 |
Beijing |
Beijing |
… |
c102 |
Tianjin |
Tianjin |
… |
c103 |
Harbin |
Heilongjiang |
… |
… |
… |
… |
… |
Python
#续用sr_file1和pt_file1 ct_file1="D:\data\City.csv" ct1=pd.read_csv(ct_file1) r_ct=pd.merge(record1,ct1,left_on="sale_city",right_on="cityid") r_ct_pdt=pd.merge(r_ct,product1,left_on="product",right_on="productid") ct_pdt_sale=r_ct_pdt.groupby(['province','pclass'],as_index=False).amount.sum() print(ct_pdt_sale) |
事实表关联维表 2 事实表关联维表 1 分组聚合 |
Python在关联多张维表时,通常是先关联一个再关联另一个,两次 merge 组成一张大宽表,最后利用宽表分组聚合。
SPL
A |
B |
|
… |
/A3是销售记录表,A4 是产品信息表 |
|
8 |
D:\data\City.csv |
|
9 |
=file(A8).import@tc() |
|
10 |
=A3.switch(product,A4:productid;sale_city,A9:cityid) |
/设置主键 |
11 |
=A10.groups(sale_city.province,product.pclass;sum(amount):amount) |
/分组聚合 |
SPL 的 switch 函数可以同时建立多个外键关系,如本例中的产品号 product 与产品信息表中的 productid,城市号 sale_city 与城市信息表中的 cityid,如果需要还可以建立更多的关联关系,关联后可以利用记录的字段完成分组聚合。和 Python 不同,SPL 可以一次解析多个关联关系,这使得关联关系明确且效率更高。
维表复用
一张事实表可能多次利用同一张维表,如:
现有销售记录表和城市信息表,请找出产品销售地与产地在同一省份的销售记录。
销售记录表 2(事实表):
recordid |
product |
product_city |
sale_city |
amount |
… |
sr100001 |
p1006 |
c105 |
c103 |
603 |
… |
sr100002 |
p1005 |
c105 |
c102 |
1230 |
… |
sr100003 |
p1003 |
c102 |
c102 |
885 |
… |
… |
… |
… |
… |
… |
… |
城市信息表2(维表):
cityid |
name |
province |
… |
c101 |
Beijing |
Beijing |
… |
c102 |
Tianjin |
Tianjin |
… |
c103 |
Harbin |
Heilongjiang |
… |
… |
… |
… |
… |
Python
sr_file2="D:\data\SaleRecord2.csv" ct_file2="D:\data\City2.csv" record2=pd.read_csv(sr_file2) ct2=pd.read_csv(ct_file2) r_ct2=pd.merge(record2,ct2,left_on="sale_city",right_on="cityid") r_ct_ct=pd.merge(r_ct2,ct2,left_on="product_city",right_on="cityid",suffixes=('_s', '_p')) r_ct_p_ct= r_ct_ct[r_ct_ct['province_s']==r_ct_ct['province_p']].recordid print(r_ct_p_ct) |
事实表第一次关联维表 事实表第二次关联维表
|
本例中的销售记录包含了产品销售城市号和产品产地号,两者都关联城市信息表的 cityid 可以完成关联。Python 还是老方子,merge 两次,第二次 merge 时会产生相同的字段名,但 Python 通过增加不同的后缀很好的处理了。
SPL
A |
B |
|
… |
… |
|
13 |
D:\data\SaleRecord2.csv |
|
14 |
D:\data\City2.csv |
|
15 |
=file(A13).import@tc() |
|
16 |
=file(A14).import@tc() |
|
17 |
=A15.switch(sale_city,A16:cityid;product_city,A16:cityid) |
/事实表关联维表 |
18 |
=A17.select(sale_city.province==product_city.province).(recordid) |
SPL 通过 switch 关联同一张维表,只不过是不同的外键(sale_city 和 product_city),然后利用关联后的记录字段选出。
多层维表
外键关联可能不止一层维表,会遇到多层维表的情况,如:
现有销售记录表、产品信息表和城市信息表,请找出产品销售地与产地在同一省份的销售记录。
销售记录表 (事实表):
recordid |
product |
sale_city |
amount |
… |
sr100001 |
p1003 |
c104 |
380 |
… |
sr100002 |
p1005 |
c103 |
400 |
… |
sr100003 |
p1003 |
c104 |
626 |
… |
… |
… |
… |
… |
… |
城市信息表(维表 1):
cityid |
name |
province |
… |
c101 |
Beijing |
Beijing |
… |
c102 |
Tianjin |
Tianjin |
… |
c103 |
Harbin |
Heilongjiang |
… |
… |
… |
… |
… |
产品信息表(维表 2):
productid |
product_city |
… |
p1001 |
c104 |
… |
p1002 |
c103 |
… |
p1003 |
c102 |
… |
… |
… |
… |
Python
sr_file3="D:\data\SaleRecord3.csv" ct_file3="D:\data\City3.csv" pt_file3="D:\data\Product3.csv" record3=pd.read_csv(sr_file3) product3=pd.read_csv(pt_file3) ct3=pd.read_csv(ct_file3) pdt_ct=pd.merge(product3,ct3,left_on="product_city",right_on="cityid") r_pdt_ct=pd.merge(record3,pdt_ct,left_on="product",right_on="productid") r_pdt_ct_ct=pd.merge(r_pdt_ct,ct3,left_on="sale_city",right_on="cityid",suffixes=('_s', '_p')) r_ct_p_ct2=r_pdt_ct_ct[r_pdt_ct_ct['province_s']==r_pdt_ct_ct['province_p']].recordid print(r_ct_p_ct2) |
关联产地和城市 关联销售记录和产品 关联销售地和城市
|
城市信息既是产品的维表又是销售记录的维表,产品信息也是销售记录的维表,这就构成了多层维表,而且有维表被多次关联。Python 中三次关联三次 merge。
SPL
A |
B |
|
… |
… |
|
20 |
D:\data\SaleRecord3.csv |
|
21 |
D:\data\City3.csv |
|
22 |
D:\data\Product3.csv |
|
23 |
=file(A20).import@tc() |
|
24 |
=file(A21).import@tc() |
|
25 |
=file(A22).import@tc() |
|
26 |
=A25.switch(product_city,A24:cityid) |
/产地关联城市 |
27 |
=A23.switch(sale_city,A24:cityid;product,A26:productid) |
/销售记录关联城市和产品 |
28 |
=A27.select(sale_city.province==product.product_city.province).(recordid) |
SPL 建立关联关系后可以一直使用,即使是又被建立关联后,如 A26 中建立了产地与城市的关联关系,在 A27 中建立销售记录与产品的关联关系后,产地与城市的关联关系依旧在,所以 A28 中可以有 product.product_city.province 的引用,这在多表关联的关系中是很方便的。
自关联
有时会遇到一张表既是事实表又是维表,即自己和自己关联的情况。如:
现有员工信息表,请列出所有员工及其上级的姓名。
员工信息表部分内容如下:
empid |
name |
superior |
… |
7902 |
FORD |
7566 |
… |
7788 |
SCOTT |
7566 |
… |
7900 |
JAMES |
7698 |
… |
… |
… |
… |
… |
Python
emp_file="D:\data\Employee_.csv" emp=pd.read_csv(emp_file) emp_s=pd.merge(emp,emp,left_on="superior",right_on="empid",suffixes=('', '_m'),how="left") emp_s_name=emp_s[['name','name_m']] print(emp_s_name) |
自关联
|
Python的本质还是两表关联。
SPL
A |
B |
|
… |
… |
|
30 |
D:\data\Employee_.csv |
|
31 |
=file(A30).import@tc() |
|
32 |
=A31.switch(superior,A31:empid) |
/自关联 |
33 |
=A32.new(name,superior.name:s_name) |
SPL 也没太大的区别,switch 建立 superior 和 empid 的关联。
环状关联
关联关系复杂时,可能会发生环装关联。如:
现有员工信息表和部门信息表,请找出北京经理的北京员工。
员工信息表:
empid |
name |
dept |
province |
… |
1 |
Rebecca |
6 |
Beijing |
… |
2 |
Ashley |
2 |
Tianjin |
… |
3 |
Rachel |
7 |
Heilongjiang |
… |
… |
… |
… |
… |
… |
部门信息表:
deptid |
name |
manager |
… |
1 |
Administration |
20 |
… |
2 |
Finance |
2 |
… |
3 |
HR |
162 |
… |
… |
… |
… |
… |
Python
emp_file2="D:\data\Employee_2.csv" dept_file2="D:\data\Department2.csv" emp2=pd.read_csv(emp_file2) dept2=pd.read_csv(dept_file2) d_emp=pd.merge(dept2,emp2,left_on="manager",right_on="empid") emp_d_emp=pd.merge(emp2,d_emp,left_on="dept",right_on="deptid",suffixes=('', '_m')) beijing_emp_m=emp_d_emp[(emp_d_emp['province']=="Beijing") & (emp_d_emp['province_m']=="Beijing")].name print(beijing_emp_m) |
部门表关联员工表 员工表关联部门表
选出
|
Python的两步关联相对独立,两步构成一个环状关联,形成一个宽表,最后选出。
SPL
A |
B |
|
… |
… |
|
35 |
D:\data\Employee_2.csv |
|
36 |
D:\data\Department2.csv |
|
37 |
=file(A35).import@tc() |
|
38 |
=file(A36).import@tc() |
|
39 |
=A38.switch(manager,A37:empid) |
/部门关联员工表 |
40 |
=A37.switch(dept,A38:deptid) |
/员工表关联部门表 |
41 |
=A40.select(province=="Beijing"&&dept.manager.province=="Beijing").(name) |
/选出 |
SPL 第一步建立部门和员工的关联关系,第二步建立员工和部门的关联关系,第三步选出,建好的关联关系可以直接拿来使用。上述的所有案例都是用 switch 函数完成关联,它有一个特点:关联后本身的字段值会被替换为关联后的记录,本身的记录值就不存在了,如果想保留本身的记录值可以用 join 函数完成关联,如本例的 A40 可以这样写:A40=A37.join(dept,A38:deptid,~:dpt),此时的 dept 就是关联后的记录,利用该记录引用就可以完成后续计算了,之前例子中的 switch 都可以用这种方式完成计算。
混合关联
数据分析中,可能会遇到同维关联、主子关联、外键关联同时发生的混合关联,此时的关联关系复杂,要理清楚关联关系。如:
利用订单表,订单明细表,产品信息表,员工信息表,出差信息表,客户信息表,城市信息表,计算出差时间大于 10 天的 90 后销售员在各省份销售黑龙江商品的金额。
关联关系如下:
Python
emp4 = pd.read_csv("D:\data\Employee4.csv") trv4 = pd.read_csv("D:\data\Travel4.csv") emp_inf = pd.merge(emp4,trv4,on=["empid","name"]) years = pd.to_datetime(emp_inf.birthday).dt.year emp_inf_c = emp_inf[(years>=1990) & (years<2000)&(emp_inf.time>=10)] clt4 = pd.read_csv("D:\data\Client4.csv") city4 = pd.read_csv("D:\data\City4.csv") sale_location = pd.merge(clt4,city4,left_on='city',right_on='cityid') pdt4 = pd.read_csv("D:\data\Product4.csv") pdt_location = pd.merge(pdt4,city4,left_on='city',right_on='cityid') detail4 = pd.read_csv("D:\data\Detail4.csv") order4 = pd.read_csv("D:\data\Order4.csv") detail_pdt = pd.merge(detail4,pdt_location,on='productid',how="left") order_sale_location = pd.merge(order4,sale_location,on='clientid',how="left") order_sale_location_emp = pd.merge(order_sale_location,emp_inf_c,left_on='saleid',right_on='empid',how="left",suffixes=('_c', '_e')) order_inf = order_sale_location_emp[order_sale_location_emp.empid.notnull()] order_detail = pd.merge(order_inf,detail_pdt,on='orderid',how="left",suffixes=('_s', '_p')) order_detail_Hljp = order_detail[order_detail.province_p=="Heilongjiang"] res = order_detail_Hljp.groupby(['empid','name_e','province_s'],as_index=False).price.sum() print(res) |
员工表和出差表
选出 90 后
客户表和城市表
产品表和城市表
订单明细和产品 订单和出售地
订单和员工
订单和订单明细 选出
分组聚合 |
本例中表很多,关联关系很复杂,按照我们介绍的 3 种关系关联: 同维关联(一对一),主子关联(一对多),外键关联(多对一)进行关联,如果出现了多对多的关联关系,那多半是错了,需要重新检查关联关系,否则多对多关联后很可能导致内存爆炸。Python 面对如此复杂的关联关系时,最好就是用 merge 两两关联,一步解析一个关联关系,虽然稍麻烦,但不容易出错。
SPL
A |
B |
|
… |
… |
|
43 |
=file("D:/data/Employee4.csv").import@tc() |
|
44 |
=file("D:/data/Travel4.csv").import@tc() |
|
45 |
=A44.join(empid,A43:empid,birthday) |
|
46 |
=A45.select((y=year(birthday),y>=1990&&y<2000&&time>=10)) |
|
47 |
=file("D:/data/Client4.csv").import@tc() |
|
48 |
=file("D:/data/City4.csv").import@tc() |
|
49 |
=A47.join(city,A48:cityid,province) |
|
50 |
=file("D:/data/Product4.csv").import@tc() |
|
51 |
=A50.join(city,A48:cityid,province) |
|
52 |
=file("D:/data/Detail4.csv").import@tc() |
|
53 |
=file("D:/data/Order4.csv").import@tc() |
|
54 |
=A52.join(productid, A51:productid,province:product_province) |
|
55 |
=A54.group(orderid) |
|
56 |
=A53.switch(orderid, A55:orderid;saleid, A46:empid;clientid, A49:clientid) |
|
57 |
=A56.select(saleid).new(saleid.empid:empid,saleid.name:sale_name,clientid.province:sale_location,orderid.select(product_province=="Heilongjiang").sum(price):price) |
|
58 |
=A57.groups(empid,sale_name,sale_location;sum(price):price).select(price) |
SPL 面对如此复杂的关联关系时,就显得很从容,同维、主子、外键关联关系都很明了,需要同时关联两个关联关系时,SPL 也可以同时解决,快捷还不易出错。
小结
Python 进行外键关联时,同样存在复制数据、每次只能解析一个关联关系的问题,另外每一次关联都是独立的,之前建立的关联关系,后续不可以重复利用,只能重新关联,这使得关联效率比较低。
SPL 不存在类似的问题,建立一次关联关系后,可以重复利用,运算效率更高。
英文版