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 代码更加冗长难懂。比如,某支股票的最大连续上涨天数:

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 个客户,并按销售额从大到小排序。

sql 复制代码
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 是 Java 下开源的数据处理引擎,架构简单,集成方便,可持久化存储数据,有足够的计算能力,这些特点与 SQLite 类似。

**SPL 架构简单,**不用配置服务、节点、集群,只要引入 SPL 的 Jar 包,就可以部署在 Java 环境中。

SPL 提供了 JDBC 接口,可以方便地集成到 Java 应用中,简单的查询类似 SQL。

sql 复制代码
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 语法,比如分组汇总:

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 文件:

sql 复制代码
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 除了支持自有数据格式的持久化,也支持其他数据源,同样是通过序表为媒介。比如:

sql 复制代码
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 还有更多针对结构化数据的流程处理功能,可进一步提高开发效率,比如:每轮循环取一批而不是一条记录;某字段值变化时循环一轮。

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

sql 复制代码
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 就无法支持了。

SPL已开源免费,欢迎前往乾学院社区了解更多!

免费下载

相关推荐
斯普信专业组12 分钟前
Elasticsearch高性能实践
大数据·elasticsearch·搜索引擎
中科岩创20 分钟前
重庆轨道交通4号线寸滩地铁站自动化监测
大数据·网络·物联网
jiamiyanjie1 小时前
为什么ETH 3.0需要Lumoz的ZK算力网络?
大数据
PersistJiao2 小时前
餐饮平台数仓建模案例
大数据·spark·数仓建模
AdSet聚合广告3 小时前
APP、小程序对接聚合广告平台,有哪些广告变现策略?
大数据·人工智能·microsoft·小程序·个人开发
gikod5 小时前
【笔记】架构上篇Day6 法则四:为什么要顺应技术的生命周期?
大数据·人工智能·笔记·架构
jjw_zyfx6 小时前
Django和websocket实现简单的多人聊天
websocket·django·sqlite
智慧化智能化数字化方案6 小时前
2023 数据资产管理实践白皮书(6.0版)+解读
大数据
helloworld工程师7 小时前
Dubbo的负载均衡及高性能RPC调用
java·大数据·人工智能
千瓜7 小时前
2024年特别报告,「十大生活方式」研究数据报告
大数据·数据挖掘·数据分析·业界资讯·新媒体