转置是SQL中常见的算法,比如静态/动态行列转置,正转置/逆转置,单层/多层,对齐/补齐,无计算列/有计算列。针对上述不同的场景,SQL要用不同的方法来处理,有时要用pivot函数,有些只能用group+case when,有时要用union,有时只能用高级语言实现对应的动态SQL。方法太多,程序员缺乏清晰的思路,导致代码难以书写。
免费的集算器支持动态脚本、有序计算、集合运算,可用一种方法实现不同场景下的行列转置,即:先取数、再构建空结果集、最后填入数据。集算器还支持独立使用、控制台执行、报表调用、JAVA代码调用,详情参考集算器辅助SQL编写的应用结构。
下面举例说明SQL开发中常见的转置问题,以及集算器的通用解法。
简单行列转置
数据库表SALES存储着订单数据,部分数据如下:
OrderID |
Client |
SellerId |
Amount |
OrderDate |
1 |
DSG |
13 |
19480 |
2014-06-20 00:00 |
2 |
ERN |
18 |
979 |
2014-06-13 00:00 |
3 |
JFE |
19 |
28972 |
2014-12-11 00:00 |
4 |
OFS |
21 |
4829 |
2014-02-24 00:00 |
5 |
ERN |
22 |
21392 |
2014-02-01 00:00 |
现在要计算出指定年份每个月订单的总金额、最大订单金额、最小订单金额,以及总订单数,并将数据转置成13列4行,即:四种算法是第一列,列名为subtotal,每个月占一列,列名分别是1、2、3、4…
集算器代码:A1取数:用SQL进行简单的分组汇总。结果如下:
A2创建空结果集:字段为“subtotal,1,2,3,4,5,6,7,8,9,10,11,12”。集合.string()可将集合成员合并为逗号分隔的字符串,${}可将字符串解析为表达式动态执行。
A3:向空结果集逐条填入数据。函数fno可取得字段数,fname可按序号取得字段名,field可按序号取得某一列,run可循环集合\结果集,record可追加记录。追加后A2如下:可以看到,实现转置的通用方法分为三步:SQL取数、用create函数创建空结果集、用run函数循环源数据并用record函数逐条追加,其中追加数据的代码会根据场景的不同而略有变化,下面详述。
动态行列转置
表liquors存储着各种酒的产地、类型、库存,部分源数据如下:Lid |
Name |
Type |
Production |
Stock |
1 |
42Below Vodka |
Vodka |
New Zealand |
301 |
2 |
Absolut Vodka |
Vodka |
Sweden |
95 |
3 |
Appleton Estate Reserve |
Rum |
Jamaica |
202 |
4 |
Bacardi Superior |
Rum |
Puerto Rico |
741 |
5 |
Baileys Irish Cream |
Cordials |
Ireland |
434 |
6 |
Ballantines Special 12Years |
Whisky |
Scotland |
237 |
7 |
Black Heart Rum |
Rum |
New Zealand |
338 |
现在要统计出每个产地每种类型的库存,其中每种类型都需要单独的字段。
集算器代码:思路仍然是取数、建空结果集、填数。A2是结果集中2至N列的列名,函数id等价于SQL中的distinct函数。A4对A1按照Production字段分组,A5循环每组数据,每次向A3追加一条记录。其中函数align可将数据按照指定集合分组,允许组内成员为空。
A3存储计算结果,如下:多列转一行
Students表格式如下:
name |
age |
sex |
kg |
A |
10 |
f |
30 |
B |
11 |
f |
35 |
C |
12 |
m |
33 |
需要将所有的记录拼在一行,期望结果如下:
Aage |
Asex |
Akg |
Bage |
Bsex |
Bkg |
Cage |
Csex |
Ckg |
10 |
f |
30 |
11 |
f |
35 |
12 |
m |
33 |
A2动态创建空序表,A3将A1组合为一条记录追加到A2。其中string(字段)表示将其他类型转为字符串,集合.string()表示将集合成员拼为字符串。也可用这句代码追加记录:>A2.record(A1.conj(~.array().to(2,4)))。
多层转置
库表kpi中,f_site相同的4条记录是一组数据,现在要对每组数据进行行列转置,使dataset_date对应的值变成字段,使字段ioh_kpi、idh_kpi、iol_kpi变成KPI Name列的字段值。部分源数据如下:
dataset_date |
f_site |
ioh_kpi |
idh_kpi |
iol_kpi |
2015/04/21 13:15 |
X6SF_SARF1 |
1 |
2 |
3 |
2015/04/21 13:30 |
X6SF_SARF1 |
9 |
1 |
2 |
2015/04/21 13:45 |
X6SF_SARF1 |
8 |
9 |
1 |
2015/04/21 14:00 |
X6SF_SARF1 |
7 |
8 |
9 |
2015/04/21 13:15 |
XC_01 |
2 |
3 |
4 |
2015/04/21 13:30 |
XC_01 |
11 |
12 |
13 |
2015/04/21 13:45 |
XC_01 |
21 |
22 |
23 |
2015/04/21 14:00 |
XC_01 |
31 |
32 |
33 |
集算器代码:
A2取得不重复的dataset_date,即["2015-04-21 13:15","2015-04-2113:30","2015-04-21 13:45","2015-04-21 14:00"]。B2:取得A1中字段名,从第3个开始,即["ioh_kpi","idh_kpi","iol_kpi"]。之后动态创建二维表A3,字段名依次为site,KPIName,"2015-04-21 13:15","2015-04-21 13:30","2015-04-2113:45","2015-04-21 14:00"。A4-B5使用循环语句来追加数据,等价于A1.group(f_site).run(…),但在步骤较多时比函数run结构清晰。结果如下:
逆转置
表tb1的查询结果只有1条记录,但字段数较多,如下:project |
operator1 |
actionTime1 |
operator2 |
actionTime2 |
operator3 |
actionTime3 |
poerator4 |
actionTime4 |
A |
Ashley |
20140404 |
Rachel |
20150101 |
Emily |
20140909 |
Ashley |
20150225 |
现在需要将该表转置为两个字段多条记录的二维表,如下:
project |
operator |
actionTime |
A |
Ashley |
20140404 |
A |
Rachel |
20150101 |
A |
Emily |
20140909 |
A |
Ashley |
20150225 |
((A1.fno()-1)/2)可算出结果集需要插入的记录数,之后用run函数循环追加记录。
如果tb包含多条记录,则应当使用如下代码:计算结果如下:
子表动态插入主表(子表长度不定)
Table1和Table2是主子表,通过ID关联,现在要将子表横向插入主表。已知子表记录经常变化,长度不定。
Table1部分数据如下:
ID |
Prob |
Cost |
Visible |
C3001 |
100 |
50 |
1 |
C3002 |
90 |
33 |
1 |
C3003 |
200 |
75 |
0 |
Table2部分如下数据:
ID |
Item |
Count |
C3001 |
555 |
4 |
C3001 |
666 |
5 |
C3001 |
777 |
6 |
C3002 |
888 |
10 |
C3002 |
111 |
20 |
C3003 |
222 |
50 |
期望的结果:
ID |
Prob |
Cost |
Visible |
Item1 |
Count1 |
Item2 |
Count2 |
Item3 |
Count3 |
C3001 |
100 |
50 |
1 |
555 |
4 |
777 |
6 |
666 |
5 |
C3002 |
90 |
33 |
1 |
888 |
10 |
111 |
20 |
||
C3003 |
200 |
75 |
0 |
222 |
50 |
用SQL连接主子表,按ID分组,按照分组最大的记录数动态创建空序表A4,再将主表和子表字段拼成记录追加到A4中。集算器的group函数可以只进行分组运算而不聚合,这一点与SQL不同。
A4存储计算结果,如下:子表动态插入主表(子表长度有限)
dColThread和dColQuestion是主子表,用tID字段关联。主表每条记录对应的status字段值有多个,但不超过5个,需要横向插入主表的Phone、Decline字段之间,依次命名为QuestionNo1、QuestionNo2…QuestionNo5。
dColThread部分数据如下:
tID |
ApplicationName |
User |
Phone |
Decline |
A01 |
mfc |
Bill |
+70000000 |
1 |
A02 |
mfc |
John |
+18761221 |
2 |
A03 |
java |
Jack |
+8014001231 |
6 |
A04 |
mfc |
Tim |
+008613133123 |
4 |
A05 |
db |
John |
+18761221 |
8 |
dColQuestion部分数据如下:
qID |
tID |
status |
1 |
A01 |
yes |
2 |
A01 |
no |
3 |
A01 |
yes |
4 |
A02 |
yes |
5 |
A03 |
no |
6 |
A04 |
no |
7 |
A04 |
no |
8 |
A05 |
yes |
A3创建固定字段的二维表,之后循环A2中的组,取当前组中status的字段值,并补足至少5条记录,再向A3追加一条完整记录。
A3存储计算结果,如下:补齐月份再转置
表tb有time、quantity这2个字段,存储着每天的货物销售量,其中某些月份的销售量可能为空。部分源数据如下:
time |
quantity |
2014-01-01 15:20:25 |
3 |
2014-02-21 16:11:23 |
2 |
2015-01-05 11:14:21 |
1 |
2015-02-11 15:21:11 |
2 |
需要将tb表整理成12条数据,字段包括固定的月份(值为1-12)、不固定的每年的销售量(跨两年则需要2个字段,跨三年则需要3个字段),示意如下:
月份 |
2013年的销售量 |
2014年的销售量 |
…年的销售量 |
A1用SQL执行分组汇总,A2取得年份列表,A3将A1按照12个月对齐分组,A4动态创建空二维表,A5循环A3的每组数据,每次向A4追加一条记录。其中A3如下:
A4存储计算结果,如下:
带计算列的转置
表tb1部分数据如下:
Prjno |
Subtask |
Ddate |
Num |
P9996 |
P9996-sub002 |
2015-01-01 |
123 |
P9996 |
P9996-sub002 |
2015-01-02 |
134 |
P9996 |
P9996-sub002 |
2015-01-03 |
345 |
P9996 |
P9996-sub002 |
2015-01-04 |
55 |
T0071 |
T-007-01 |
2015-01-01 |
3333 |
T0071 |
T-007-01 |
2015-01-02 |
356 |
T0071 |
T-007-01 |
2015-01-03 |
178 |
现在要输入日期,生成当月该日期前所有日期的项目总和,如输入2015-01-03希望得到:
Prjno |
Subtask |
2015-01-01 |
2015-01-02 |
2015-01-03 |
P9996 |
P9996-sub002 |
123 |
134 |
345 |
T0071 |
T-007-01 |
3333 |
356 |
178 |
查询数据,对源数据分组,循环每组数据,每次循环向空二维表插入一条记录。
动态定位行列转置
在库表tb中,userid相同的3条记录是一组数据,现在要将组记录转为行记录。库表tb部分数据如下:
userid |
type |
descr |
scooby |
dog |
dog |
scooby |
weight |
50 |
scooby |
hair |
long |
mickey |
mouse |
mouse |
mickey |
hair |
|
mickey |
weight |
2 |
理想的结果:
userid |
type |
hair |
weight |
mickey |
mouse |
2 |
|
scooby |
dog |
long |
50 |
函数align可将数据按某集合成员([‘hair’,’weight’])对齐,@n表示将无法对齐的数据单列一行,本案例中该行数据为mouse\dog对应的记录。结果如下:
三表关联列转行
有三张表,分别是学生表、成绩表、补考成绩,以stu_id为关联字段,如下:
Students
stu_id |
stu_name |
class_id |
1 |
Ashley |
1-1 |
2 |
Rachel |
1-1 |
3 |
Emily |
1-3 |
Exam
stu_id |
subject |
score |
1 |
java |
77 |
1 |
c++ |
80 |
2 |
java |
67 |
2 |
c++ |
58 |
3 |
java |
56 |
3 |
c++ |
85 |
Retest
stu_id |
subject |
score |
2 |
c++ |
78 |
3 |
java |
82 |
现在要查询三张表,得到每个学生的各科成绩、总成绩、补考成绩,如下:
stu_id |
stu_name |
java_score |
c++_score |
scoresSum |
javaRetest |
c++Retest |
1 |
Ashley |
77 |
80 |
156 |
||
2 |
Rachel |
67 |
58 |
125 |
78 |
|
3 |
Emily |
56 |
85 |
141 |
82 |