报表查询分析业务中的数据准备用什么,SQL Java or Python?
报表查询分析都要涉及数据准备,就是把原始数据加工成可以呈现或分析的形式(如常见的报表数据集)。不过,数据准备经常并不轻松,可能面临各种复杂的情况。
数据准备面临的挑战
多样性数据源
当前业务的数据来源非常丰富,理论上任何数据源都可能同时作为报表的数据源来源,RDB、NoSQL、CSV、Excel、Json、Hadoop 不一而足,这就产生了报表开发中的多样性数据源问题。而且这些数据源要求不仅能连接,还可能进行混合计算。报表分析是一项综合事务,基于多种同构或异构数据源进行混合分析也很常见。这就需要这些数据准备技术不仅可以对接多种数据源,并可以进行跨源混合计算,还能够与报表和 BI 工具紧密结合。
复杂计算
另一个问题是报表数据准备的复杂度。有些数据准备的复杂度很高,从原始数据加工成报表需要的形式要进行大量复杂编码才能实现。这当然也跟报表的业务性质有关了,不仅数据来源广泛、跨度大、数据处理逻辑也复杂,这就造就了复杂的数据准备过程。所以,我们经常能看到上千行 SQL、几百 KB 的存储过程。
集成性
一些报表使用报表工具开发,最后在应用中发布供用户使用。数据准备作为报表的一部分,自然要与报表应用集成在一起。另外一些临时使用的一次性报表虽然不需要被集成,但如果反复出现,也可能要被移植到应用系统中,这样就也要求集成性。如果数据准备技术的集成性不好,那对报表的作用将大打折扣
另一方面,数据准备技术是否支持热部署 / 热切换直接关系到报表开发运维的方便程度,如果支持报表修改后上载到应用中可以实时生效会很方便;反过来如果还要重启服务才能生效,对开发运维就很不方便了。
常用数据准备技术
SQL
SQL 是目前用于报表数据准备常用的方式,SQL 是专用的集合运算语言,完成结构化数据处理很简单,如常见的分组汇总,一个 group by 语句就写出来了。
使用 SQL 只需要引入对应数据库接口(如 JDBC)就可以使用,具有一定的集成性,但需要与数据库紧耦合。另外,发送 SQL 给数据库可以实时查询结果,也可以认为 SQL 支持热切换。
不过 SQL 的缺点也很明显,很难应对前面我们提到数据准备面临的挑战。
SQL 基于的数据库有“库”的概念,数据只有入库才能查询,这就很难应对多样性数据源的场景。很多数据源,像 NoSQL、本地文件、网络文件系统等都面临无 SQL 可用的局面,基于这些数据源的数据准备实现时要比 SQL 麻烦得多。
还有就是复杂计算的支持并不好,SQL 要实现这些复杂计算经常要嵌套多层,几百行的 SQL 很常见,不仅实现困难,修改也不方便。举个不太复杂的例子:
根据股票记录表查询股价连续上涨超过 5 天的股票及上涨天数(股价相等记为上涨)
SQL 实现:
select code, max(risenum) - 1 maxRiseDays from (select code, count(1) risenum from (select code, changeSign, sum(changeSign) over(partition by code order by ddate) unRiseDays from (select code, ddate, case when price >= lag(price) over(partition by code order by ddate) then 0 else 1 end changeSign from stock_record)) group by code, unRiseDays) group by code having max(risenum) > 5
感受到了么,虽然并不是很长,但是看懂也要花一些时间,更别提想到并写出来了。复杂计算 SQL 实现困难来源于关系代数理论,这并非是工程上(数据库本身)可以解决的。
很多数据库还提供了存储过程,存储过程是在 SQL 的基础上增加了过程控制机制,这样就可以应对多步的数据处理任务。但存储过程并没有改变 SQL 的能力,SQL 支持不好的计算在存储过程中仍然难写。
开发效率不高就导致难以应对没完没了的报表需求,一个上千行的 SQL、几百 KB 的存储过程无论如何也没办法轻松实现,自然无法低成本应付高频新增修改的、没完没了的报表需求。
由此看来,SQL 作为常见的数据准备技术并非十分理想,很多时候甚至非常不理想,那为什么使用仍然这么广泛呢?那是因为相对 SQL,Java 等技术表现更差。
Java
Java 作为图灵完备的语言理论上各类计算也都能完成,包括报表数据准备。而且,Java 可以很容易解决多样性数据源问题,毕竟各类数据源通常都提供的 Java 接口,因此可以顺利读取和使用,没有什么是硬编码解决不了的。
Java 另外一个优点是很容易与应用集成,报表工具或分析应用都提供了 Java 接口,Java 完成的数据准备天然可以给报表使用。
Java 的问题在于进行结构化数据计算并不方便,而报表数据准备大都是基于结构化数据进行的。Java 缺少结构化数据计算的必要类库,提供的数据类型过于基础,使得即使完成一个简单的分组汇总也好几十行代码(通用的会更复杂),相对 SQL 来说开发效率就太低了。
与 SQL 类似,低开发效率就无法很好对应没完没了的报表需求,Java 在这方面则更为严重。而且 Java 作为编译型语言不支持热切换,也很难应对频繁多变的报表需求
所以,综合来看 Java 除了具备很强的灵活性外,在数据准备面临挑战的几个方面也并不占优,很多情况下甚至不如 SQL。
Python
Python 作为新晋数据处理编程语言可以独立用于报表分析,其优势在于其丰富的程序库,如在数据分析方面大名鼎鼎的 Pandas。同 Java 类似,Python 也可以对接多种数据源,完成多数据源混合计算。
在复杂计算方面,Python 支持过程计算,在 DataFrame 等特性的加持下,计算实现要比 Java 简单很多,与 SQL 相比则不相伯仲,有的计算 SQL 更简单,而 Python 支持过程计算实现复杂计算上会更有优势。
因此那些相对临时(一次性使用)的报表使用 Python 开发也还算方便。但是如果这些报表需要持久化,集成到应用系统时这些报表就有可能需要使用其他技术重做,这是因为 Python 与主流的应用程序(通常是 Java 的)很难一体化集成部署,很可能还要多进程间通讯,性能和稳定性都不好,这会大大限制 Python 用于报表数据准备的使用场景。
在实现报表数据准备方面,这些技术都存在这样那样的问题,那么还有没有其他选择呢?
理想的集算器 SPL
使用开源集算器 SPL 可以完成报表数据准备工作,并很好应对各类挑战。
集算器 SPL 是一款开源数据处理引擎,提供了不依赖数据库的计算能力,擅长结构化数据计算,计算类库丰富可以满足各类数据准备工作。SPL 天然支持多种数据源(RDB、NoSQL、Json、CSV、Webservice 等),还可以实现跨数据源混合计算。敏捷语法与过程计算可以快速实现复杂数据准备任务,轻松应对没完没了的情况。
开放的多源支持
不同于数据库需要数据先入库再计算,SPL 面对多样性数据源时可以直接计算,这样可以充分利用不同数据源各自的优点(如文件的 IO 效率很高,NoSQL 可以存储文档数据,RDB 计算能力较强)。
SPL 目前直接支持几十种数据源(仍在扩展中),你听说过还是没听说过的数据源几乎都能性,不仅可以连接取数,还可以进行跨数据源混合计算,充分利用各类数据源的优点后,再实施跨源计算为报表提供数据准备。
敏捷语法与过程计算
SPL 设计了专门用于结构化数据计算的敏捷语法,不仅天然支持过程计算,数据处理可以分多步、按照自然思维一步一步实施,而且提供了丰富的计算类库,基于 SPL 可以更容易实现复杂计算。
上一段代码看一下效果:
【计算目标】要找出销售额占到一半的前 n 个客户(大客户)的订单情况。
A | |
1 | =file(“/opt/ods/orders.csv”).import@tc() |
2 | =A1.groups(customer;sum(amount):amount).sort(amount:-1) |
3 | =A2.sum(amount)/2 |
4 | =0 |
5 | =A2.pselect((A4=A4+amount,A4>=A3)) |
6 | =A2.(customer).to(,A5) |
7 | =A1.select(A6.pos(A1.customer)) |
通过分步的方式,先找到符合条件的大客户,再查询这些客户的详细订单信息。这些计算都是在库外完成的,可以使用文件等其他数据源。从实现的过程来看,SPL 的过程计算更加优秀,语法也更为简洁。
而前面提到的计算股票最长连续上涨天数,SPL 的实现是这样的:
A | ||
1 | =connect@l("orcl").query@x("select * from stock_record order by ddate") | |
2 | =A1.group(code) | |
3 | =A2.new(code,~.group@i(price<price[-1]).max(~.len())-1:maxrisedays) | 计算每只股票的连续上涨天数 |
4 | =A3.select(maxrisedays>=5) | 选出符合条件的记录 |
按交易日排好序,按股票分组,SPL 分组不会强制要求聚合,可以保留分组成员,基于每个分组计算连续上涨天数。计算时,将连涨的记录分到一组,然后求最大值 -1 就是最长连续上涨天数了,完全按照自然思维实现,不用绕来绕去。
SPL 的语法简洁程度也要优于 Python,实现同样的计算也更简单。比如我们要计算:000062 股票股价最高的三天的涨幅。
Python 实现:
import pandas as pd stock_file="D:/data/STOCKS.csv" stock\_data=pd.read\_csv(stock_file,dtype={'STOCKID':'object'}) stock\_62=stock\_data.query('STOCKID=="000062"').copy() ort\_pos=(-stock\_62\["CLOSING"\]).argsort() max3pos=sort_pos.iloc\[:3\] stock\_62s=stock\_62.shift(1) max3CL=stock_62\["CLOSING"\].iloc\[max3pos\] max3CLs=stock_62s\["CLOSING"\].iloc\[max3pos\] max3_rate=max3CL/max3CLs-1 print(max3_rate)
Python 的 argsort(…) 可以返回排序后的位置信息。由于 Python 中没有循环函数,也不可以在循环过程中利用位置信息来计算,只能绕一下,先找到股价最高的 3 天的股价,再找到股价最高 3 天的前一天的股价,两者计算后得到涨幅,有点麻烦。
SPL 实现:
A | ||
1 | =file(“D:/data/STOCKS.csv”).import@tc(#1:string,#2,#3) | |
2 | =A2.select(STOCKID=="000062") | |
3 | =A2.psort@z(CLOSING) | /股价排序位置 |
4 | =A3.m(:3) | /取前 3 个位置 |
5 | =A2.calc(A4,if(#==1,null,CLOSING/CLOSING[-1]-1)) | /按位置算涨幅 |
SPL中的 psort(…) 函数返回从小到大的位置信息,@z 选项则是逆序。calc(…) 函数是定位计算,利用成员的位置和相对位置进行计算。CLOSING[-1] 是当前成员的前一个成员,整体无论是过程化还是运算思维都很简洁。
编辑 SPL 脚本可以使用专门的 IDE 完成,简洁易用的开发环境,单步执行、设置断点,所见即所得的结果预览窗口…,编辑调试简单,开发效率也更高。
与应用无缝集成
SPL 为报表准备数据时,位置介于报表呈现工具(或 BI 工具)和数据源之间,可以与应用系统无缝集成。这样报表和 BI 工具就拥有了复杂多步数据准备的能力,既不与数据源耦合,也跟主应用独立。SPL 提供了标准 JDBC 和 ODBC 接口供报表工具调用,可以完全替代原有数据准备方式。
通过 JDBC 执行 / 调用 SPL 脚本很简单:
... Class.forName("com.esproc.jdbc.InternalDriver"); Connection conn =DriverManager.getConnection("jdbc:esproc:local://"); CallableStatement st = conn.prepareCall("{call splscript(?, ?)}"); st.setObject(1, 3000); st.setObject(2, 5000); ResultSet result=st.execute(); ...
SPL 是解释执行的,天然支持热切换。可以很好适应报表多变的修改需求,修改不需要重启即时生效。
数据处理逻辑位于 SPL 文件(.splx)中,修改后实时生效,相对 Java 等编译型语言需要重启服务有很大优势。
SPL 和 SQL 都是专用结构化数据计算语言,但 SPL 更开放、语法更简洁,相对 Java 和 Python 无论是在开发效率还是集成性、热切换等方面也更具优势,因此更适合用于报表数据分析的数据准备工作。