搞多层 json,SPL 才是专业的

简介: 业务数据常具层次关系,如订单包含客户、明细及产品等嵌套信息,JSON 的可嵌套结构非常适合描述这种关系。然而,传统 SQL 仅支持平面表,处理嵌套结构时需展开或使用特殊函数与 lambda 语法,代码复杂且不一致。Python 虽支持多层结构,但对多行嵌套字段的处理较为繁琐。相比之下,SPL 实现了多层嵌套表机制,字段皆为对象,计算方法一致,代码简洁易懂,是处理嵌套结构数据的专业语言。欢迎体验免费 esProc SPL!

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

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

[   {
      "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 这样写:

select order_id, order_date, customer.name, customer.city
from read_json_auto('orders.json')

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

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

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 语法计算:

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 语法,略显啰嗦。

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

select sum(freight_rate*weight) from read_json_auto('orders.json')

语法完全不一致。

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

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

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

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

orders.sum(freight_rate*weight)

语法完全一致!

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

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

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 也可以把多行的嵌套字段展开计算:

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 计算:

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,还是啰嗦。

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

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 的写法:

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 的写法:

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 语法,也不必写显式循环,代码最简洁、易懂:

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试用一下吧~~

相关文章
|
11月前
|
JSON 前端开发 JavaScript
解锁JSON的奇妙世界:从基础到高级应用,一文搞懂JSON的妙用(上)
解锁JSON的奇妙世界:从基础到高级应用,一文搞懂JSON的妙用(上)
178 0
解锁JSON的奇妙世界:从基础到高级应用,一文搞懂JSON的妙用(上)
|
11月前
|
存储 JSON NoSQL
解锁JSON的奇妙世界:从基础到高级应用,一文搞懂JSON的妙用(下)
解锁JSON的奇妙世界:从基础到高级应用,一文搞懂JSON的妙用(下)
347 0
|
11月前
|
JSON JavaScript 前端开发
解锁JSON的奇妙世界:从基础到高级应用,一文搞懂JSON的妙用(中)
解锁JSON的奇妙世界:从基础到高级应用,一文搞懂JSON的妙用(中)
104 0
|
6月前
|
JSON 数据格式 Python
Python编程:利用JSON模块编程验证用户
Python编程:利用JSON模块编程验证用户
46 1
|
8月前
|
JSON Java Android开发
Android 开发者必备秘籍:轻松攻克 JSON 格式数据解析难题,让你的应用更出色!
【8月更文挑战第18天】在Android开发中,解析JSON数据至关重要。JSON以其简洁和易读成为首选的数据交换格式。开发者可通过多种途径解析JSON,如使用内置的`JSONObject`和`JSONArray`类直接操作数据,或借助Google提供的Gson库将JSON自动映射为Java对象。无论哪种方法,正确解析JSON都是实现高效应用的关键,能帮助开发者处理网络请求返回的数据,并将其展示给用户,从而提升应用的功能性和用户体验。
165 1
|
JSON fastjson Java
由浅入深C系列八:如何高效使用和处理Json格式的数据
本文简要介绍在c语言环境中使用cJSON处理JSON数据的方法,及提供相关示例代码。
|
JSON 缓存 搜索推荐
手写JAVA实现个性化业务的Excel转JSON,效率提高99.99%
由JSON数据的格式可以看出,每读取一个目录数据,都可以看成一个JSONObject,JSONObject有两个键值对,text代表目录的名字、children代表子目录。 一个父目录可以有多个子目录,所以children用的JSONArray去表示。
610 0
手写JAVA实现个性化业务的Excel转JSON,效率提高99.99%
|
SQL 存储 JSON
MySQL数据库基础:JSON函数各类操作一文详解
MySQL数据库基础:JSON函数各类操作一文详解
815 0
MySQL数据库基础:JSON函数各类操作一文详解
|
前端开发 开发工具 git
一篇文章讲清楚关于package.json几点让人迷惑的知识点,建议收藏
一篇文章讲清楚关于package.json几点让人迷惑的知识点,建议收藏