搞多层 json,SPL 才是专业的

业务数据经常存在层次关系,比如订单包含日期、客户和订单明细,订单明细又包含价格、数量、产品,产品则包含名称和分类。

json 的可嵌套结构很适合描述这种关系,比如订单数据:

css 复制代码
[   {   "order_id": 10248,"order_date": "2022-07-04","freight_rate": 2,"weight": 5,        "order_details": [            {   "price": 14.0,"quantity": 12.0,			    "product": {"product_id": 17,"name": "beef","category":"Food"},            },            {   "price": 9.0,"quantity": 10.0			    "product": {"product_id": 42,"name": "rice","category":"Food"}            }        ],
        "customer": {
            "customer_id": 87,"name": "abc inc",
            "city": "New York","phone_number": "26471510"
        }        
    },
    {   "order_id": 10249,"order_date": "2012-07-05","freight_rate": 10,"weight": 3,
        "order_details": [
            {   "price": 18.0,"quantity": 9.0
			    "product": {"product_id": 14,"name": "Computer","category": "Electronic"},
            }
        ],
        "customer": {
            "customer_id": 991,"name": "bcd.com",
            "city": "Los Angeles","phone_number": "(0251) 1031259"
        }
     }
...]

订单既有普通属性"订单号、日期、运费单价、重量",也包括嵌套属性"订单明细、客户",订单明细又包含嵌套属性"产品"。

对于这种可嵌套结构,常见的数据组织方式是多层嵌套表,即字段可以是表或记录。计算时,无论哪一层的表(记录),都是对象,都能一致运算。

但 SQL 天生只有平面表,无法实现这种方式。某些新型分析数据库在一定程度上支持嵌套结构,可以处理 json 数据。

比如要在订单中查出 order_id, order_date, 以及嵌套属性 customer 的 name 和 city,用 DuckDB 这样写:

csharp 复制代码
select order_id, order_date, customer.name, customer.city
from read_json_auto('orders.json')

这是把单行嵌套字段 customer 看成记录,用记录的方式取出其字段。但这种方法只能用于单行嵌套字段,多行的 order_details 就不支持了,这样写只能取出空值。

对多行嵌套字段,DuckDB 可以展开后计算。比如要在这个查询基础上,对 order_detail 中 quantity*price 汇总求和,得到订单金额 amount:

scss 复制代码
select order_id,any_value(order_date),
    any_value(customer.name),any_value(customer.city),
    sum(od.quantity * od.price) as amount
from read_json_auto('orders.json') as o,
    lateral unnest(o.order_details) as t(od)
group by order_id;

先展开 order_details,与 orders 隐式 join 成大平面表,然后按照订单号分组汇总求和,代码很绕。这样做,还不如转换成物理的平面表,计算时还能省去"展开"这个步骤。

DuckDB 也可以保持嵌套结构,使用 lambda 语法计算:

csharp 复制代码
select order_id, order_date,customer.name, customer.city,
    list_sum( list_transform(o.order_details,x -> x.price * x.quantity) ) as amount
from read_json_auto('orders.json') as o

要使用两个特殊函数,还要写显式的 lambda 语法,略显啰嗦。

对比一下,普通数据表做类似计算并没有这么麻烦。比如求订单运费单价和重量相乘再汇总的代码是这样:

scss 复制代码
select sum(freight_rate*weight) from read_json_auto('orders.json')

语法完全不一致。

实际上,DuckDB 把多行嵌套字段当成数组,而不是数据表。这就造成运算上的不一致,给代码书写和阅读都会带来麻烦。

esProc SPL 彻底实现了前面说的多层嵌套表机制,可以写出最简洁的代码:

less 复制代码
orders=json(file("orders.json").read())
orders.new(order_id,order_date,customer.name,customer.city,order_details.sum(price*quantity):amount)

对比一下计算总运费的代码:

python 复制代码
orders.sum(freight_rate*weight)

语法完全一致!

字段 order_details 与 orders 一样都是表对象,计算方法都一致。有这样简洁、一致的语法,SPL 处理可嵌套结构的数据,可以说是最专业的了。

Python 也支持多层结构,对于单行的嵌套字段,可以这样计算:

ini 复制代码
orders = pd.read_json('orders.json')
result_orders = orders[['order_id', 'order_date']].assign(
    customer_name=orders['customer'].str['name'],
    customer_city=orders['customer'].str['city']
)

取 customer 的字段多了个函数 str,不如 DuckDB 和 SPL 简洁。而且 str 函数仅支持可以转为字符串的简单类型,不支持多行的嵌套字段。

Python 也可以把多行的嵌套字段展开计算:

ini 复制代码
orders = pd.read_json('orders.json')
exploded_orders = orders.explode('order_details')
exploded_orders = pd.concat([exploded_orders,exploded_orders['order_details'].apply(pd.Series)], axis=1)

exploded_orders['amount'] = exploded_orders['price'] * exploded_orders['quantity']
result = exploded_orders.groupby('order_id').agg({'order_date':'first','amount': 'sum'}).reset_index()
final_result = result[['order_id', 'order_date', 'amount']]

先纵向展开 order_details,再横向展开每行的字段,变成平面表后,再计算分组汇总,这也很绕。多行嵌套结构还要展开两次,比 DuckDB 更啰嗦。

Python 也能保持嵌套结构,用 lambda 计算:

less 复制代码
orders = pd.read_json('orders.json')
orders['amount'] = orders['order_details'].apply(lambda details:sum(item['price'] * item['quantity'] for item in details))
result=orders[['order_id','order_date','amount']]

Python 是显式 lambda 语法,要写 lambda 关键字,还要定义参数,有点啰嗦。而且 lambda 函数中还要写显式的 for 循环,相当于两重循环,会增加理解的难度。这里的 for 虽然是简化写法,也还是要定义循环变量 item,还是啰嗦。

我们也对比一下计算运费的代码:

css 复制代码
total_freight=sum(orders['freight_rate']*orders['weight'])

语法也是完全不一致。

order_details 并不是 orders 那样的 Dataframe 对象,而是类似数组的列表类型。

在稍复杂的情况下,DuckDB 和 Python 的语法不一致等问题带来的麻烦会更明显,比如找出总金额大于 200,而且还包含 Electronics 类产品的订单,取得 order_id、order_date。

DuckDB 如果采用展开嵌套字段的方式,SQL 就比较难写了。展开 order_details、product 之后要再分组聚合计算订单金额,然后再基于这个结果筛选符合条件的订单,要写多层子查询或者 CTE 来保持数据结构的完整性。写出来的 SQL 会很长,调试起来就不太友好了,这里不再给出,只给出 lambda 的写法:

csharp 复制代码
select order_id, order_date
from read_json_auto('orders.json')
where 
list_sum(list_transform(order_details,x -> x.price * x.quantity)) > 200 and
array_length(list_filter(order_details, x -> x.product.category = 'Electronics')) > 0;

使用了三个特殊函数,两个显式 lambda 函数,与一般 SQL 相比,这个代码复杂、难理解。

Phython 展开方式的代码也很长,这里只给出 lambda 的写法:

ini 复制代码
orders = pd.read_json('orders.json')
final_orders = orders[
    orders['order_details'].apply(
        lambda x: any(item['product']['category'] == 'Electronics' for item in x) and 
                  sum(item['price'] * item['quantity'] for item in x) >= 200
    )]
result = final_orders[['order_id', 'order_date']]

apply 一个大循环加显式的 lambda 语法,套了两个小的 for 循环,还要定义循环变量,代码理解起来也费劲。

SPL 使用基本运算函数,不用显式的 lambda 语法,也不必写显式循环,代码最简洁、易懂:

less 复制代码
orders=json(file("orders.json").read())
orders.select(order_details.select@1(product.category=="Electronics") && order_details.sum(price*quantity)>200).new(order_id,order_date)

小结一下,SQL 天生只有平面表,无法实现嵌套结构。DuckDB 补上了嵌套结构,但如果展开嵌套结构再计算的话,还不如直接使用普通平面表。直接对着嵌套结构计算时,各层运算不一致,代码读写都麻烦。且多行嵌套结构要用特殊函数和显式 lambda 语法,代码啰嗦。Python 支持嵌套结构,但也存在和 DuckDB 类似的问题。SPL 表的字段可以是表,嵌套结构从上到下的数据组织都一致,都是对象,引用方法也一致,计算代码简洁、易懂,是最专业的多层嵌套结构计算语言。

点击下载免费的esProcSPL试用一下吧~~

相关推荐
百锦再7 小时前
React编程的核心概念:发布-订阅模型、背压与异步非阻塞
前端·javascript·react.js·前端框架·json·ecmascript·html5
DA022116 小时前
C++轻量HeaderOnly的JSON库
开发语言·c++·json
树下一少年20 小时前
docker-compose部署prometheus+grafana+node_exporter
docker·json·grafana·prometheus·node_exporter
JhonKI2 天前
【从零实现Json-Rpc框架】- 项目实现 - Dispatcher模块实现篇
qt·rpc·json
Python测试之道2 天前
Deepseek API+Python 测试用例一键生成与导出 V1.0.5(支持读取json及yml文件,虚拟环境及库安装指导保姆级指南)
python·json·测试用例
紧跟先前的步伐2 天前
【Goalng】第九弹-----文件操作、JSON处理
开发语言·后端·golang·json
我不是代码教父2 天前
[原创](现代C++ Builder 12指南): 再谈如何使用System.JSON?附加代码示例更加详细
c++·json·c++ builder 12·c++ builder
蜗牛沐雨2 天前
深入解析 JSON-RPC:从基础到高级应用
rpc·json
demonlg01122 天前
Go 语言标准库中encoding/json模块详细功能介绍与示例
开发语言·后端·云原生·golang·json
小宋要上岸2 天前
基于 Qt / HTTP/JSON 的智能天气预报系统测试报告
开发语言·qt·http·json