Python 如何调用 SPL 脚本
【摘要】
作为 client 端的 python 通过服务端的 jdbc 接口调用 SPL 脚本,非常容易实现 python 程序对 SPL 脚本的集成。具体开发要求、使用详细情况,请前往乾学院:Python 如何调用 SPL 脚本!
集算器提供了 JDBC 接口,Python 可以通过该接口调用 SPL。结构图如下:
Python 调用 SPL 脚本是通过 py4j 接口 socket 方式访问 esProcJDBC Server,调用 dfx 脚本访问数据库或数据源获取数据。
esProcJDBC Server 封装了 JDBC 作为服务端,在运行 python 程序前,需要先启动服务端的 jvm。简单来说就是,将启动 JAVA 应用程序时加载集算器所需的 jar 包及配置文件放到项目中。需要注意的是,集算器 JDBC 所要求的 JDK 版本不得低于 1.8。
下面我们以 win10 系统、集算器安装在 D:\Program Files\raqsoft 路径 (后面简称[集算器目录]) 及集算器自带 java 为例来说明。
1. 加载驱动 jar
集算器 JDBC 是个完全嵌入式计算引擎,使用时需要 esProc JDBC 驱动、raqsoftConfig.xml, 主要依赖 jar 包如下:esproc-bin-20210811.jar
// 集算器计算引擎及 JDBC 驱动包commons-math3-3.6.1.jar
// 数据统计分析py4j-0.10.9.2.jar
// 与 java 通信包esproc-py4j-server.2.10.jar
//esproc jdbc 服务端接口包 (此包放在外部库)
这里将上述 jar 包放在 d:/app/lib 下来说明,当然也可以放在其它目录或修改脚本加载的 jar。
启动 esProcJDBC Server 服务脚本start_jdbc_server.bat:
@echo off
rem START_HOME 为集算器安装目录,Server 运行时所依赖的集算器环境变量
set START_HOME="D:\Program Files\raqsoft"
set JAVA_HOME="D:\Program Files\raqsoft\common"
set EXECJAVA="D:\Program Files\raqsoft\common\jre\bin\java"
cd d:\app
rem parameter ip port path
rem example: start_server.bat localhost 25333 d:\tmp\data
start "dm" %EXECJAVA% -Xms128m -Xmx8520m -cp .; -Djava.ext.dirs=./lib; %START_HOME%\common\jdbc; -Dstart.home=%START_HOME%\esProc com.esproc.jdbc.EsprocJdbcServer %1 %2 %3
脚本设置了 java 运行环境变量,其中 START_HOME 为集算器安装的目录。脚本除了加载集算器目录下所需要的 jar 包外,还启用了它的环境配置等,如调用外部库,寻找 dfx 脚本文件等。脚本启动时缺省参数 ip=127.0.0.1,port=25333, 也可以更改 ip、port 参数。
部署和启动集算服务器的详细过程还可以参考Java如何远程调用 SPL脚本。
2. 部署 raqsoftConfig.xml
集算器的运行环境配置文件 raqsoftConfig.xml 包含了集算器主路径、dfx 文件寻址路径 (本文为 demo 文件夹)、jdbc 数据源等各类信息, 存放在 [集算器目录]\esProc\config 下。start_jdbc_server.bat 脚本中设置 -Dstart.home=%START_HOME%\esProc,java 启动时会自动加载它,以便在执行 SPL 脚本时,在目录 demo 下查找对应的 dfx 文件或数据文件。
raqsoftConfig.xml 的具体信息也可再参考Java如何远程调用 SPL脚本。
3.Python 调用
启动esProcJDBC Server后,Python 就可以调用 SPL 脚本了。Python 程序中调用 SPL 脚本,与 Python 中调用 SQL 和存储过程类似,将查询的数据以表结构形式 (具体为拆分成表头与数据两部分) 返回给 python。下面来看看具体是怎样实现的。
A 、执行 SPL 语句如创建一个字段为 AA,BB,CC 的数据表,插入 3 条记录后返回其结果集。
SPL 脚本的代码为:
=create(AA,BB,CC).record(to(9))
Python 实现脚本如下:
import pandas as pd
from py4j.java_gateway import JavaGateway, GatewayClient
#缺省方式连接 server, 参数传递时python 集合转换为 java 集合
gateway = JavaGateway(auto_convert=True)
# 连接 server 时也可指定 ip,port 连接
#gateway = JavaGateway(GatewayClient(address="192.168.0.10",port=25333), auto_convert=True)
# 获取连接对象
conn = gateway.entry_point.getApp()
#程序主体处理部分
dfx="=create(AA,BB,CC).record(to(9))"
#执行脚本关返回结果集
result = conn.query(dfx)
# table header
cols = list(conn.getColumns())
# table data
rows = []
for lines in list(result):
rows.append(tuple(lines))
#数据存入 dataframe
df = pd.DataFrame(data=rows, columns=cols)
print(df)
conn.close()
输出结果:
AA BB CC
0 1 2 3
1 4 5 6
2 7 8 9
由于采用 py4j 作为通信模块,用户并不需要过多关注收发数据等细节。conn.getColumns()返回的数据是JavaArray类型,需要用list()(或tuple())转换数组(或元组)。有关python与java通信中的数据类型转换可参考相关的py4j文档,本jdbc接口返回为Table结构,转换采用list()或tuple即可。
B 、在 SPL 中访问本地文件如目录 demo 下有文本文件 aaa.txt,其内容为:
pid age name work
101 11 Tom techer
102 12 Jack manager
103 12 Joan driver
104 13 Billy doctor
105 15 Carl driver
Python 实现部分脚本如下:
……
#程序主体处理部分
dfx= "select * from aaa.txt"
result = conn.query(dfx)
……
输出结果:
pid age name work
0 101 11 Tom techer
1 102 12 Jack manager
2 103 12 Joan driver
3 104 13 Billy doctor
4 105 15 Carl driver
查询文本文件与查询 SQL 表数据类似,将返回的数据结果集通过 pandas 格式化输出。
C、带参数的 SPL 语句
参数是 SQL 语句的一个重要组成部分,同样,SPL 语句中也支持参数的使用。如上例查询 aaa.txt 文件中的部分数据,要求查询 age 大于 12 的记录。
Python 实现的部分代码如下:
……
#程序主体处理部分
dfx=“select * from aaa.txt where age>?”
result = conn.query(dfx,[12])
cols = list(conn.getColumns())
……
输出结果:
pid age name work
0 104 13 Billy doctor
1 105 15 Carl driver
参数以数组方式传递给 java 端,数组大小与 SPL 要求的参数个数保持一致,切记JavaGateway()时需要指明参数 auto_convert=True。
D、有数据源的 SPL 语句
mytest.dfx 脚本如下:
A | B | |
1 | =connect("mysql") | //连接 mysql 数据库 |
2 | =A1.query@x("select * from employee") | //查询 employee 表数据 |
3 | return A2 | //返回数据 |
Python实现的部分代码如下:
……
#程序主体处理部分
dfx="call mytest"
result = conn.query(dfx)
cols = list(conn.getColumns())
……
输出结果:
EID NAME SURNAME GENDER ... BIRTHDAY HIREDATE DEPT SALARY
0 1 Rebecca Moore F ... 1974-11-20 2005-03-11 R&D 7000
1 2 Ashley Wilson F ... 1980-07-19 2008-03-16 Finance 11000
2 3 Rachel Johnson F ... 1970-12-17 2010-12-01 Sales 9000
3 4 Emily Smith F ... 1985-03-07 2006-08-15 HR 7000
4 5 Ashley Smith F ... 1975-05-13 2004-07-30 R&D 16000
……
E、支持返回嵌套结果集:
市场中应用 Nosql 数据库非常多,其查询数据结果集中常带嵌套结构。现以外部库MongoDB为例说明, 在调用前需要设置 MongoDB 外部库为可用状态:
A | B | |
1 | =mongo_open("mongodb://127.0.0.1:27017/raqdb") | //连接 mongodb 数据库 |
2 | =mongo_shell(A1,"storage.find()").fetch() | //查询 storage 表数据 |
3 | >mongo_close(A1) | //关闭连接 |
4 | return A2 | //返回数据 |
A2返回结果:
Python 实现的部分代码如下:
……
#程序主体处理部分
dfx=“call MongoTest”
result = conn.query(dfx)
cols = list(conn.getColumns())
……
输出结果:
_id name items
0 1000 Storage Alpha category name
food apple
food banana
tool h…
1 1001 Storage Beta category name
food pear
food peach
food gra…
通过调用 SPL 脚本查询数据表,将其返回结果集存放到 pandas 格式化输出。嵌套结构支持序表或序列,对于更复杂的结构,也可在 java 端自定义接口进行数据转换。
F、游标查询:
相对于 query() 查询,游标查询分为执行 SPL 脚本与循环获取数据处理。我们用前面的 mysql 为数据源的 mytest.dfx 脚本来说明游标查询。
curs = gateway.entry_point.getApp()
dfx="call mytest"
#执行 SPL 脚本
curs.cursor(dfx)
cols = list(curs.getColumns())
# fetch data
rows = []
while(curs.hasNext()):
result = list(curs.fetch(5))
for line in result:
rows.append(list(line))
df = pd.DataFrame(data=rows, columns=cols)
print(df)
……
游标查询时先执行 SPL 语句curs.cursor(dfx),再多次循环curs.fetch()获取数据。获取数据接口fetch(size)省略参数时size=1000。
G、返回多个结果集
Python从集算器获取返回的多个结果集列表时,每个结果集由表头与数据组成。
A | B | |
1 | =create(AO,BO,CO).record(to(6)) | //构造表 A1 |
2 | =create(AS,BS).record(to(6,11)) | //构造表 A2 |
3 | =create(AT,BT,CT).record(to(20,25)) | //构造表 A3 |
4 | return A1,A2,A3 | //同时返回多个表 |
dfx=“call multiTable”
#执行 SPL 脚本, 返回结果集列表
results = conn.mquery(dfx)
for result in results:
rows = []
cols = tuple(result[0]) # header
for lines in tuple(result[1]): # data
rows.append(tuple(lines))
df = pd.DataFrame(data=rows, columns=cols)
print(df)
app.close()
查询 SPL 返回多结果集时,使用 mquery() 接口,通过遍历结果集列表,将每个结果集存放到 pandas 格式化输出。
python client调用esProcJdbc模块不同于Jaydebeapi调用 jdbc,每次运行 python 程序不需要启动 jvm,避免了运行效率不高问题。 同时,对开发用户来说,简明的开发流程,使用方便,让 Python 集成 SPL 也变得非常容易。
上述内容就是 Python 调用 SPL 脚本的常用方式了,其他程序调用 SPL 的用法:
Java 如何调用 SPL 脚本
Java 如何远程调用 SPL 脚本
Birt 调用 SPL 脚本
JasperReport 调用 SPL 脚本
HTTP 调用 SPL
想了解更多集算器应用集成用法的小伙伴儿可以去官网上的在线教程中查看
使用 esproc-bin-20220729.jar 无法正常执行
已经修改,更新 esproc-py4j-server.2.10.jar 此包即可
已按照教程中所示进行了配置,但还是无法运行,java 控制台一闪而过,关键是没有任何报错信息,也没有日志。请大神帮忙看看,谢谢
截图如上,还请帮忙看看,谢谢
您好,已修改,执行回显内容如截图,依然闪退。
去掉 start “dm”, 看看反馈信息
感谢回复!!
首先,我按照您截图里面的 bat 信息,发现原帖中 bat 有点问题,应为:start “dm” %EXECJAVA% -Xms128m -Xmx8520m -cp .;lib; -Djava.ext.dirs=./lib;%START_HOME%\common\jdbc; -Dstart.home=%START_HOME%\esProc com.esproc.jdbc.EsprocJdbcServer %1 %2
原帖中少了;lib 以及./lib; 后面多了个空格
我按照截图中更正后执行依然报错,报错信息如截图所示,应该是缺少某个包,烦请再帮忙看一下。
缺少 jar:esproc-bin-20220402.jar
估计 bat 脚本与目录 lib 没有放在同一级目录下。 正常情况下,lib 下会有 esproc-bin-20220402.jar 文件
您看这样对吗?
看上去是对的,但你的系统为什么 java 加载不了 lib 下的 jar 包,不行就一个个单独加上.
我按照示例编写读取 txt 的代码:
dfx=‘SELECT * FROM data.txt’
result=conn.query(dfx)
print(list(conn.getColumns()))
终端报错:
表达式:
pid age name work
中表达式逻辑错误
经检查,数据文件是用连续空格分隔的,改成 Tab 就正确读取了。那么如何读取用连续空格分隔的数据文件呢?
1、由于简单 Sql 不支持 可改为在 spl 脚本中指定文本的分隔符,再调用此脚本
txt_read.splx:
A1: =file(“d:/tmp/data/aaa.txt”).import@t (;, " ")
A2:return A1
2、py 文件调用脚本:
dfx=“call txt_read”
#执行脚本关返回结果集
result = conn.query(dfx)
#table header
cols = list(conn.getColumns())
#table data
rows = []
for lines in list(result):
rows.append(tuple(lines))
#数据存入 dataframe
df = pd.DataFrame(data=rows, columns=cols)
print(df)
3、测试结果:
Athlete Event Score
0 s1 Vault 90
1 s21 Floor 91
2 s22 Floor 92
感谢回复!
深入使用下去又遇到新的疑问:
1. 我翻了不少教程,没找到通过 python 调用 spl 获取 xls 和 xlsx 数据的示例代码,想知道如何实现,以及一个文件中有多个 sheet 的情况下能否读取所有 sheet?
2. 要连接多种数据库,似乎需要在集算器 GUI 中先配置好数据源,再在 python 代码中通过名称连接。想知道有没有相应的 API,支持将创建数据源、选择驱动、连接并获取数据的一系列动作都在 python 代码中完成?
谢谢!
1 先学会 SPL 里这样访问 xls/xlsx 的方法,然后再集成到 python 里。乾学院上这种例程很多,这件事和 python 其实没有直接关系,所以不会在 python 接口文档中来示例了。
2 SPL 没法认识 python 的这些数据源。除了做接口外,我们也没兴趣去丰富 python 的功能,所以不可能做成你期望的样了。当然,SPL 的数据源也不一定在 GUI 配置,可以用代码写出来,那些希望的动作都可以在 SPL 里完成。
好的,我的想法是在 SPL 中测试成功后,保存成 spl 格式(spl 脚本是文本格式,dfx 和 splx 脚本都是二进制格式,不方便用代码生成)脚本,再用 python 的 call 来调用,现在遇到问题:
1.1 读取 excel 的脚本是 A1 格 =file(“D:/pyspl/app/c.xlsx”).xlsimport@tx(),SPL 中执行成功获取了第一个 sheet 的内容,保存成 e.spl 后用 python 调用,报错:
Caused by: com.scudata.common.RQException: 单元格 A1 中有错误
org/apache/poi/poifs/filesystem/FileMagic
1.2 访问 mysql 的脚本是 A1=connect(“mysql”),A2=A1.query(“SELECT * FROM user”),A3>A1.close(),同样在 SPL 中执行成功获取数据,保存成 m.spl 后用 python 调用,报错:
java.sql.SQLException: 单元格 A1 中有错误
mysql: Communications link failure
Caused by: javax.net.ssl.SSLKeyException: RSA premaster secret error
Caused by: java.security.NoSuchAlgorithmException: SunTls12RsaPremasterSecret KeyGenerator not available
2.excel 文件中有多个 sheet 时,能否获取所有 sheet 名字?
3. 在解决了 1. 的问题后,能否在 python 中直接生成查询脚本的文本内容并提交给 SPL 执行,而不用先在磁盘上保存脚本文件再执行?
谢谢!
前两个问题和 python 没关系,先在 SPL 中调试好,有问题要也请直接问 SPL 相关的问题,不必扯到 python 上。
第 3 个问题其实仍然和 Python 没多大关系,Python 就是用 SPL 的 JDBC 完成调用的,所以还是问 JDBC 能不能支持一大堆文本型的 SPL 来执行(是可以的,用回车和 TAB 隔开就行,去找 SPL JDBC 的文档和贴子)。但是,原则不推荐这么做,这样代码的可读性太差了,希望代码有一定的动态能力,可以参考 SPL 的宏概念(学习贴里都专题讲)。
然而,话说回来,折腾 xls 和常规数据库的本事,Python 本身也不差,为什么要集成 SPL 进来?SPL 是 Java 体系,和 python 的通信总归是不太流畅,没有强的理由,不建议集成这两个东西。要么全用 Python 做,要么全用 SPL 做。
我就是在 SPL 中调试好确实能用的语句,保存成脚本后在 python 内不能用,才来寻找解决方案
关于后面的问题,我解释一下我的使用场景:
我将数据处理过程划分为三要素:1. 数据源;2. 数据处理流程;3. 数据处理对象(即字段)
根据用户实时需求,1. 和 3. 千变万化(大数据体系要面对多种类型数据源,随着建设开展可能还会不断增加新的类型;而每次用户操作时关心的字段也不同),2. 的种类则很有限(但每一个具体流程的步骤都很多),而且有时还要面对连 Excel 公式都用不利索的初级用户,属于即使将 dfx 模板准备好都不知道怎么修改的程度,因此我的程序用于对复杂流程的可视化再包装。之所以一直询问 python 中的用法,是因为我之前的程序是 python 开发的,针对每种支持的数据源(Excel、csv、sqlite、access、mysql、pg、sqlserver、oracle)都要单独实现连接方法(连接后的处理流程可以实现统一描述,但有很多数据源就算连接也不容易实现),发现 SPL 后了解到它对更多数据源支持更统一的访问方法,所以希望能够和我的程序配合使用
SPL JDBC 能搞通的事(比如你期望的送一堆代码去执行),原则上 python 也会通。如果还不通,那才可能是 python 接口的问题,当然这东西也有可能真有问题,但先确认不是 SPL JDBC 本身就没弄对的问题。包括生成一个.spl 文件去执行的事。就是写清 python 中什么样的代码执行不了,这边才会调试。
动态代码建议用宏去实现,尽量不要拼太多的脚本,可读性太差了。一般代码不会到处都要动态的。