程序语言根据其设计目的不同,其侧重的基本数据类型也不同。JAVA、C#等语言被设计用来进行通用的应用程序开发,其基本数据类型是字符串、数字、布尔等原子数据类型,以及数组和通用对象。SQL、PowerBuilder、R、集算器esProc等语言被设计用来进行数据处理,其基本数据类型是有结构的二维数据表对象。比如这句SQL:SELECT T1.id,T1.name,T1.value FROM T1 LEFT JOIN T2 ON T1.id=T2.id, 这里的T1,T2以及计算结果就是这种数据类型。用多个字段组成一条记录,由多条结构相同的记录组成二维数据,这样的数据及其字段名的组合就是有结构的二维数据表对象。
数据处理处理语言为什么不用原子类型和通用对象作为基本类型?尝试把上面那句SQL里的T1,T2用JAVA中的数组或者ArrayList对象来表示,你会发现:复杂度立刻增加数倍,代码的长度更会陡增几十倍!
数据处理语言中的基本数据类型都是有结构的二维数据表对象,这并非巧合,而是有着深刻的原因。
对应真实业务。现实世界中的业务数据大都是结构化数据,比如工资表,有员工编号、员工姓名、部门、日期、税前工资、税后工资等等;比如零售记录,有订单时间、门店编号、收银台编号、收银员编号、商品名称、单价等等;再比如网站日志,有浏览时间、URL、访问者IP、浏览器版本等属性。这些属性相当于字段,每条数据结构都一样,虽然其存储方式经常是文本而非数据库,但实质上仍然是结构化数据,用有结构的二维数据表对象来表示是再自然不过的事情了。有结构的二维数据表对象可以最直观地呈现业务数据,最真实的表达实际业务,不论是存储、计算、交换还是分享,这种形式的数据都是最方便最容易理解的。
易于实现批量处理。业务数据通常是结构相同的数据,比如之前提过的工资表、零售记录、网站日志。处理这类数据时,个别情况下会针对某条记录的某个字段,但绝大多数情况下都是以记录为单位对所有数据进行处理的,比如:根据税前工资计算出税后工资;根据单价和商品数量计算出金额;统计出每天每个IP的在线时长。上述处理方式就是批量处理。实现批处理,可以像JAVA那样按行号列号循环遍历数组的每个成员,也可以像SQL、集算器esProc那样直接按业务字段名操作数据,后者简单易用无需循环语句,更利于程序员从业务角度直观方便地操作数据,相应的代码也更加简短易读。
符合关系代数。关系代数是E.F. Codd专门为数据处理和数据查询而设计的底层理论,它用基本运算、连接运算、聚合运算、除法运算详尽地描述了业务数据之间的关联关系和运算规律,理论上可以完成数据处理和数据查询中任意难度的计算问题。由于关系代数简洁而完备,数据库因而大都是按照这一理论来设计的,E.F. Codd更被称为关系型数据库之父。有结构的二维数据表对象正是E.F. Codd推荐的数据类型,它可以比较完美地表达关系代数中的各类运算,从而轻松实现数据处理中的计算问题。事实上,数据库结果集就是最早的有结构的二维数据表对象。
可以看到,由于可以对应真实的业务数据,易于实现批处理计算,符合关系代数理论,因此各类数据处理程序语言不约而同地使用有结构的二维数据表对象作为基本数据类型。使用二维数据表对象,代码简洁易懂,开发效率更高,下面再举几个例子说明这一点:
SQL的结果集(resultSet):按图书类型分组,求平均价格大于15元的图书,它们的均价是多少?
select avg(price),type from books group by type having avg(price)>15
集算器esProc的序表(TSeq):按部门分组,求各部门销量前10的产品。
products.group(department).(~.top(quantity;10)
PowerBuilder的数据窗口(datawindow):将订单按照价格排序
Order.SetSort('value d') Order.Sort()
R语言的数据框(data.frame):将orders表和customer表按照customerID进行左关联。
merge(A1,B1,by.x="CustomerID",by.y="CustomerID",all.x=TRUE)
SQL、集算器esProc、R的代码对比:将订单数据按照部门分组,汇总各部门的订单数量和销售额。
SQL:
Select count(*),sum(sales) from orders group by Dept
集算器esProc:
orders.groups(Dept; count(~), sum(sales))
R语言:
result<-aggregate(orders$ sales,list(orders $ Dept),sum) result$count<-tapply(orders $ sales, orders $ Dept,length)
结果集、序表、数据窗口、数据框,它们虽然都是有结构的二维数据表对象,功能也基本相同,但它们之间还是有着细微的差别的。
SQL结果集资料丰富,使用范围广泛,通用性较好,简单易用,是数据处理语言中最主流的数据类型。但SQL没有完全实现关系代数,导致有些运算不够方便,比如集合除法。
数据窗口一般会从SQL取数,最终结果也会返回数据库,它的主要功能是打通了数据和UI控件之间的隔阂,让程序员可以快速设计出交互性优异的数据库应用程序。DataWindow的主要功能是数据呈现和编辑,只有针对单表的计算,数据处理能力比较弱。
数据框具有一定的结构化计算能力,但从上面的例子可以看出,它的语法较难理解,实现同样的功能相对复杂。这是因为R主要的功能是科学统计计算,重点的数据类型是数列和矩阵,数据框是后来为了实现结构化数据计算才新增的数据类型。从这一点来看,数据框的专业性不如其他三种。
序表在数据处理方面比较专业,它具有SQL结果集的一般优点,对关系代数实现得也比较彻底。序表还具有有序的特点,适合解决数据处理中和顺序有关的难题,比如:比上期、同期比,排名、相对区间计算等。序表还具有泛型的特点,建立数据之间的关联关系更加容易,可以用对象的形式来轻松访问多级关联的数据。与SQL相比,序表的缺点在于它是纯内存对象,无法直接处理大数据。
可以看到,有结构的二维数据表对象和数据处理程序语言的专业程度直接相关,前者的功能越强,后者的专业程度也越高。反之也一样,如果一门语言缺乏有结构的二维数据表对象,那这门语言的在数据处理方面就很难称得上专业。考察一门程序设计语言是否能够高效开发数据分析处理的程序,关键就是看其有没有专业的二维数据表对象以及相应的类库。
Perl常被用于字符串检索,具有一定的数据处理能力,但其代码冗长复杂,算不上是专业的数据处理语言。比如进行最简单的分组汇总时,Perl的代码如下:
%groups=(); foreach(@carts){ $name = $_->[1]; if($groups{$name} == null){ $groups{$name}=[$_]; } else{ push($groups{$name},$_); } } my @result=(); foreach( keys(%groups)){ $value=0; while($row=pop $groups{$_}){ $value += $row->[2]; } push @result,[$_,$value]; }
Python的写法稍简单些,但和SQL、集算器esProc、R来比,开发效率上仍有量级的差异。示例代码如下:
result=[] for key, items in groupby(data, itemgetter(0)): value1=0 value2=0 for subitem in items: value1+=subitem[1] value2+=subitem[2] result.append([key,value1,value2]) print(result)
Perl和Python在数据处理方面不够专业,最重要的原因就是缺乏有结构的二维表数据对象。
集算器esProc序表不仅是有结构的二维表数据对象,而且还具备有序、泛型、分步计算等特性,比同类语言的专业性更强。比如实现一个较复杂的计算目标:找出连续上涨超过5天的股票。集算器esProc的代码如下: