一个比 SQLite 还好用的数据库神器

很多小微型应用程序也需要一些数据处理和计算能力,如果集成一个数据库就显得太沉重了,这种情况下 SQLite 是一个不错的选择,它架构简单,集成方便,可持久化存储数据,并提供 SQL 实现计算能力。
但是,对于某些较复杂的场景,SQLite 仍有不足之处。
SQLite 面对复杂场景的不足

数据源支持

SQLite 像个数据库,可以对自有的库文件提供较好的支持,但应用程序有时还要处理其它形式的数据,比如文本文件、Excel、其它数据库、Restful 等 Web 上的数据。SQLite 只支持 csv 文件的读取,不支持其他数据源,除非硬编码。而且,SQLite 虽然支持 csv 文件,但使用过程很繁琐,需要先用命令行创建数据库,再用 create 命令创建表结构,然后用 import 命令导入数据,最后才能用 SQL 查询数据。
除了常规结构化数据,现代应用还会经常碰到 Json,XML 等复杂格式的数据。SQLite 有计算 Json 串的能力,但不支持直接读取多层数据源,包括 Json 文件 /RESTful,需要硬写代码,或再借助第三方类库,拼成 insert 语句插入数据表,代码很繁琐。SQLite 也不能计算 XML 串,更不能读取 XML 文件 /WebService。
应用程序有时需要把数据写成格式通用的文件,以便输出、转移或交换,有时候还要把数据主动写入其他数据源。但 SQLite 只能将数据持久化到自有的库文件,不能直接写入外部数据源,包括基本的 csv 文件。

复杂计算

SQLite 采用 SQL 语句做计算,SQL 的优点和缺点都会继承下来。SQL 接近自然语言,学习门槛低,容易实现简单的计算,但不擅长复杂的计算,经常会造成代码会繁琐难懂。
即使一些不太复杂的计算,SQL 实现起来也不容易。比如,计算每个客户销售额最大的 3 笔订单:
select * from (select *, row_number() over (partition by Client order by Amount desc) as row_number from Orders) where row_number<=3
这个例子要计算组内的前 N 条记录,需要用窗口函数生成组内的序号伪列,再过滤伪列,代码因而显得复杂。
复杂些的计算,SQL 代码更加冗长难懂。比如,某支股票的最大连续上涨天数:

select max(continuousdays)
from (
    select count(*) continuousdays
    from (
        select sum(risingflag) over (order by day) norisingdays
        from (
           select day, case when price>lag(price) over (order by day) then 0 else 1 end risingflag 
           from tbl
        )
    ) group by norisingdays
)

SQL 很难直接表达连续上涨的概念,只能换个方法变相实现,即通过累计不涨天数来计算连续上涨天数,这种方法技巧性强,编写难度大且不易理解。而且 SQL 难以调试,导致维护困难。

再看个例子:找出销售额占到一半的前 n 个客户,并按销售额从大到小排序。

with A as
    (select client,amount,row_number() over (order by amount) ranknumber
    from sales)
select client,amount
from (select client,amount,sum(amount) over (order by ranknumber) acc
     from A)
where acc>(select sum(amount)/2 from sales)
order by amount des

SQL 很难处理恰好要过线的客户,只能换个方法变相实现,即计算销售额从小到大的累计值,反过来找出累计值不在后一半的客户。这种方法技巧性强,代码冗长,而且难以调试。

除此之外,SQLite 的日期和字符串函数也不够丰富,比如缺乏季度增减、工作日计算等,这些缺点限制了 SQLite,不适合计算需求较复杂的场景。

流程处理

SQL 本身缺乏流程处理能力,数据库会借助存储过程实现完整的业务逻辑,但 SQLite 不支持存储过程,也就无法直接实现完整的业务逻辑,只能借助主应用的能力,将 SQL 数据对象转为应用中的数据对象(比如 Java 的 resultSet/List<EntityBean> 等对象),再用主程序的 for/if 等语句处理流程,最后再转回 SQL 的数据对象,代码非常繁琐。复杂的业务逻辑要在 SQL 对象和主应用的对象之间转换多次,更加麻烦,这里就不展示了。
esProc SPL 解决 SQLite 困难
如果要为 Java 小微型应用提供数据处理和计算能力,还有一个更好的选择:esProc SPL。
esProc SPL 一个开源的数据处理引擎,架构简单,集成方便,可持久化存储数据,有足够的计算能力,这些特点与 SQLite 类似。
SPL 架构简单,不用配置服务、节点、集群,只要引入 SPL 的 Jar 包,就可以部署在 Java 环境中。
SPL 提供了 JDBC 接口,可以方便地集成到 Java 应用中,简单的查询类似 SQL。
GitHub:https://github.com/SPLWare/esProc
图片

 


Class.forName("com.esproc.jdbc.InternalDriver");
Connection conn =DriverManager.getConnection("jdbc:esproc:local://");
Statement statement = conn.createStatement();
ResultSet result = statement.executeQuery("=T(\"D:/Orders.csv\").select(Amount>1000 && like(Client,\"*s*\"))");

SPL 支持数据持久化,可以将数据保存到自有数据格式(集文件)中,比如批量新增记录:

  A
1 =create(OrderID,Client,SellerID,Amount,OrderDate)
2

=A1.record([201,"HDR",9,2100.0,date("2021-01-01"),
202,"IBM",9,1900,date("2021-01-02"),

203,"APPLE",4,1900,date("2021-01-03")])

3 =file("d:/Orders.btx").export@ab(A2)
上面 A3 代码 export@ab,@a 表示追加,@b 表示集文件格式

除了直接持久化,也可以先处理内存中的序表(SPL 的结构化数据对象,可类比为 SQL 结果集),再将序表覆盖写入集文件,具体做法是将 export@ab 改为 export@b。这种方式性能不如 SQLite,但小微型应用的数据量普遍不大,覆写的速度通常可接受。

组表是 SPL 的另一种自有数据格式,支持高性能批量增删改,适用于大数据量高性能计算(这不是本文重点)。

除了自有格式,SPL 也可以将数据保存到 csv 文件中,只要把 A3 改为:

file("d:/Orders.csv").export@tc(A2)

SPL 有足够的计算能力,支持各类 SQL 式计算,包括分组后计算(窗口函数):

  A B
1 =Orders.new(Client,Amount) // 选出部分字段
2 =Orders.select(Amount>1000 && like(Client,\"*s*\")) // 模糊查询
3 = Orders.sort(Client,-Amount) // 排序
4 = Orders.id(Client) // 去重
5 =Orders.groups(year(OrderDate):y,Client;sum(Amount):amt).select(amt>3000) // 分组汇总
6 =[Orders.select(Amount>3000),A1.select(year(OrderDate)==2009)].union() // 并集
7 =Orders.groups(year(OrderDate):y,Client;sum(Amount):amt).select(like(Client,\"*s*\")) // 子查询
8 =A5.derive(amt/amt[-1]-1: rate) // 跨行

SPL 提供了基本的 SQL 语法,比如分组汇总:

$select year(OrderDate) y,month(OrderDate) m, sum(Amount) s,count(1) c from {Orders} Where Amount>=? and Amount<? ;arg1,arg2
除了这些基础能力外,SPL 还能克服了 SQLite 的各种不足,全面支持各类数据源,具有更强的计算能力,流程处理方便,可以面对更复杂的应用场景。

数据源支持

SPL 读取 csv 文件只需一步,在 Java 里嵌入下面的 SPL 代码:T("d:/Orders.csv").select(Amount>2000 && Amount<=3000)

函数 T 不仅可以读取集文件,也可以读取 csv 文件,并生成序表。SPL 导入数据时,数据类型会自动解析,不必手工指定。整个过程无需多余编码,比 SQLite 方便多了。
如果 csv 格式不规范,还可以使用 import 函数指定分隔符、字段类型、跳过行数,并处理转义符、引号、括号等,比 SQLite 提供的功能丰富多了。
SPL 内置多种数据源接口,包括 tsv、xls、Json、XML、RESTful、WebService,以及其他数据库,甚至支持 Elasticsearch、MongoDB 等特殊数据源。
这些数据源都可以直接使用,非常方便。对于其他未列入的数据源,SPL 也提供了接口规范,只要按规范输出为 SPL 的结构化数据对象,就可以进行后续计算。
SPL 可直接解析多层数据源。读取并计算 Json 文件:
json(file("d:/xml/emp_orders.json").read()).select(Amount>2000 && Amount<=3000)
json(httpfile("http://127.0.0.1:6868/api/orders").read()).select(Amount>2000 && Amount<=3000)

XML 文件:

  A
1 =file("d:/xml/emp_orders.xml").read()
2 =xml(A1,"xml/row")
3 =A2.select(Amount>1000 && Amount<=2000 && like@c(Client,"*business*"))
WebService:
  A
1 =ws_client("http://127.0.0.1:6868/ws/RQWebService.asmx?wsdl")
2 =ws_call(A1,"RQWebService":"RQWebServiceSoap":"getEmp_orders")
3 =A2.select(Amount>1000 && Amount<=2000 && like@c(Client,"*business*"))
SPL 序表支持多层结构数据,比 SQL 库表的二维结构更容易表达 Json/XML,计算代码也更简单。这部分内容不是本文重点,就此略过。

跨源计算

SPL 开放性较好,可以直接计算多种数据源,这些数据源可以和 SPL 集文件进行跨源计算。比如,对集文件和 csv 进行内关联分组汇总:

join(T("d:/Orders.btx"):o,SellerId; T("d:/Emp.csv"):e,EId).groups(e.Dept;sum(o.Amont))

外部数据源之间也可以方便地进行跨源计算。比如 csv 和 RESTful 左关联:

join@1(json(httpfile("http://127.0.0.1:6868/api/orders").read()):o,SellerId; T("d:/Emp.csv"):e,EId)

写成多步骤的形式更易读:
  A
1 =Orders=json(httpfile("http://127.0.0.1:6868/api/orders").read())
2 =Employees=T("d:/Emp.csv")
3 =join@1(Orders:o,SellerId;Employees:e,EId)
只用 SPL 语言就可以实现跨源计算,不必借助 Java 或命令行,代码简短易懂,比 SQL 的开发效率高得多。

任意数据源的持久化

SPL 除了支持自有数据格式的持久化,也支持其他数据源,同样是通过序表为媒介。比如:

file("d:/Orders.csv").export@t(A2)          //csv文件
file("d:/Orders.xlsx").xlsexport@t(A2)      //xls文件
file("d:/Orders.json").write(json(A2))      //json文件
特别地,SPL 支持任意数据库的持久化,以 Oracle 为例:
  A B
1 =connect("orcl") / 连接外部 oracle
2 =T=A1.query("select * from salesR where SellerID=?",10) / 批量查询,序表 T
3 =NT=T.derive() / 复制出新序表 NT
4 =NT.field("SELLERID",9) / 批量修改新序表
5 =A1.update(NT:T,sales;ORDERID) / 持久化

数据库的持久化以序表为媒介,其优点相当明显:函数 update 可自动比对修改(增改删)前后的序表,能够方便地实现批量数据地持久化。

计算能力

SPL 支持有序计算、集合计算、分步计算、关联计算,可以简化复杂的结构化数据计算。
简单的比如,计算每个客户销售额最大的 3 笔订单:

Orders.group(Client).(~.top(3;Amount))

SPL 代码很直观,先按 Client 分组,再对各组(即符号~)计算 TopN。SPL 之所以代码简单,表面上是因为 SQL 没有 top 函数而 SPL 直接提供了,本质是因为 SPL 有真正的行号字段,或者说,SPL 支持有序集合。SPL 代码简单,还因为集合化更加彻底,可以实现真正的分组,即只分组不汇总,这就可以直观地计算组内数据。
复杂些的计算,SPL 实现起来也不难最大连续上涨天数:
  A
1 =tbl.sort(day)
2 =t=0,A1.max(t=if(price>price[-1],t+1,0))
SPL 容易表达连续上涨的概念,先按日期排序;再遍历记录,发现上涨则计数器加 1。这里既用到了循环函数 max,也用到了有序集合,代码中 [-1] 表示上一条,是相对位置的表示方法,price [-1] 表示上一个交易日的股价,比整体移行(lag 函数)更直观。

再看个例子,求销售额占到一半的前 n 个客户:

  A B
2 =sales.sort(amount:-1) / 销售额逆序排序,可在 SQL 中完成
3 =A2.cumulate(amount) / 计算累计序列
4 =A3.m(-1)/2 / 最后的累计即总额
5 =A3.pselect(~>=A4) / 超过一半的位置
6 =A2(to(A5)) / 按位置取值

SPL 集合化成更彻底,可以用变量方便地表达集合,并在下一步用变量引用集合继续计算,因此特别适合多步骤计算。将大问题分解为多个小步骤,可以方便地实现复杂的计算目标,代码不仅简短,而且易于理解。此外,多步骤计算天然支持调试,无形中提高了开发效率。

上面例子使用了有序计算、集合计算、分步计算,SPL 从简单到复杂的计算都可以很好的完成。此外,SPL 还支持游离记录,可以用点号直观地引用关联表,从而简化复杂的关联计算。
SPL 还提供了更丰富的日期和字符串函数,在数量和功能上远远超过传统数据库。
值得一提的是,为了进一步提高开发效率,SPL 还创造了独特的函数语法。

流程处理

SPL 本身提供了流程控制语句,配合内置的序表对象,可以方便地实现完整的业务逻辑。
分支结构:
  A B
2  
3 if T.AMOUNT>10000 =T.BONUS=T.AMOUNT*0.05
4 else if T.AMOUNT>=5000 && T.AMOUNT<10000 =T.BONUS=T.AMOUNT*0.03
5 else if T.AMOUNT>=2000 && T.AMOUNT<5000 =T.BONUS=T.AMOUNT*0.02
循环结构:
  A B
1 =db=connect("db")  
2 =T=db.query@x("select * from sales where SellerID=? order by OrderDate",9)
3 for T =A3.BONUS=A3.BONUS+A3.AMOUNT*0.01
4   =A3.CLIENT=CONCAT(LEFT(A3.CLIENT,4), "co.,ltd.")
5  

上述代码之外,SPL 还有更多针对结构化数据的流程处理功能,可进一步提高开发效率,比如:每轮循环取一批而不是一条记录;某字段值变化时循环一轮。

上面的业务逻辑可保存为脚本文件,并置于应用程序外,以存储过程的形式被调用:

Class.forName("com.esproc.jdbc.InternalDriver");
Connection conn =DriverManager.getConnection("jdbc:esproc:local://");
CallableStatement statement = conn.prepareCall("{call queryOrders()}");
statement.execute();
SPL 是解释型代码,修改后不必编译就可直接运行,也不必重启应用,可有效降低维护成本。外置的 SPL 脚本不仅可以有效降低系统耦合性,还具有热切换的特点。SQLite 不支持存储过程,也就不能将业务逻辑外置于主应用,耦合性高,应用结构差。
SPL 在 Java 下明显优于 SQLite,但对于非 Java 应用就会麻烦一点了,只能使用独立的 ODBC 服务或 HTTP 服务的方式,架构不够轻便,集成性也下降了。需要注意的是,android 属于 Java 体系,SPL 可以正常运行,但 iOS 目前还没有较成熟的 JVM 环境,SPL 就无法支持了。

GitHub:https://github.com/SPLWare/esProc

 

作者|GitHubDaily

posted @ 2023-06-25 10:32  古道轻风  阅读(804)  评论(0编辑  收藏  举报