【DuckDB】duckdb和postgresql对于unnest函数的区别

【DuckDB】duckdb和postgresql对于unnest函数的区别

背景

在postgresql中,unnest函数用于解开数组类型的嵌套,能把一个数组变成多行,但不解开一行中的多个字段;当unnest处于from后面时,如果解开的一行有多个字段,同时也会解开成多个字段。

也就是说,在pg中可以通过控制unnest在sql语句中的位置,来决定展开的层级。

duckdb的unnest函数和pg的就不一样了,下面用一些用例来进行说明

测试

构造一些数据,创建一些自定义类型

sql 复制代码
D create table t_1(c1 numeric,c2 varchar);
 ty_1[];
D create type ty_1 as struct(c1 numeric,c2 varchar);
D create type tyt_1 as ty_1[];
D insert into t_1 values (1,'a');
D insert into t_1 values (2,'b');

使用postgresql的方式,放在from后面,但和postgresql全展开不一样,duckdb的字段没有展开

sql 复制代码
D select * from  unnest( array(select (c1,c2)::ty_1 from t_1));
┌──────────────────────────────────────┐
│                unnest                │
│ struct(c1 decimal(18,3), c2 varchar) │
├──────────────────────────────────────┤
│ {'c1': 1.000, 'c2': a}               │
│ {'c1': 2.000, 'c2': b}               │
└──────────────────────────────────────┘

直接放select后,字段同样也没有展开,但是明显表达式转换规则和在from后面不一样

sql 复制代码
D select  unnest( array(select (c1,c2)::ty_1 from t_1));
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ unnest((SELECT CASE  WHEN ((array_agg(#1) IS NULL)) THEN (list_value()) ELSE array_agg(#1) END FROM (SELECT CAST(main."row"(c1, c2) AS ty_1) F...  │
│                                                       struct(c1 decimal(18,3), c2 varchar)                                                       │
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ {'c1': 1.000, 'c2': a}                                                                                                                           │
│ {'c1': 2.000, 'c2': b}                                                                                                                           │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

指定recursive := true或者max_depth := 2,字段解开

sql 复制代码
D select  unnest( array(select (c1,c2)::ty_1 from t_1),recursive := true);
┌───────────────┬─────────┐
│      c1       │   c2    │
│ decimal(18,3) │ varchar │
├───────────────┼─────────┤
│         1.000 │ a       │
│         2.000 │ b       │
└───────────────┴─────────┘
D select  unnest( array(select (c1,c2)::ty_1 from t_1), max_depth := 2);
┌───────────────┬─────────┐
│      c1       │   c2    │
│ decimal(18,3) │ varchar │
├───────────────┼─────────┤
│         1.000 │ a       │
│         2.000 │ b       │
└───────────────┴─────────┘

报错,必须指定recursive或max_depth之一

sql 复制代码
D select  unnest( array(select (c1,c2)::ty_1 from t_1), true);
Binder Error:
UNNEST - unsupported extra argument, unnest only supports recursive := [true/false] or max_depth := #

LINE 1: select  unnest( array(select (c1,c2)::ty_1 from t_1), true);
                ^

json可以直接解开

sql 复制代码
D SELECT unnest({'a': 42, 'b': 84});
┌───────┬───────┐
│   a   │   b   │
│ int32 │ int32 │
├───────┼───────┤
│  42   │  84   │
└───────┴───────┘

json数组也可以解开

sql 复制代码
D SELECT unnest([{'a': 42, 'b': 84},{'a': 22, 'b': 11}],recursive := true);
┌───────┬───────┐
│   a   │   b   │
│ int32 │ int32 │
├───────┼───────┤
│    42 │    84 │
│    22 │    11 │
└───────┴───────┘

数据字典中存在unnest这个函数

sql 复制代码
D select * from pg_proc where proname='unnest';
┌───────┬─────────┬──────────────┬──────────┬─────────┬─────────┬───┬─────────────┬────────┬────────┬────────────┬───────────┬────────┬──────────┐
│  oid  │ proname │ pronamespace │ proowner │ prolang │ procost │ ... │ protrftypes │ prosrc │ probin │ prosqlbody │ proconfig │ proacl │ proisagg │
│ int64 │ varchar │    int64     │  int32   │  int32  │  int32  │   │    int32    │ int32  │ int32  │  varchar   │   int32   │ int32  │ boolean  │
├───────┼─────────┼──────────────┼──────────┼─────────┼─────────┼───┼─────────────┼────────┼────────┼────────────┼───────────┼────────┼──────────┤
│  108  │ unnest  │      0       │   NULL   │  NULL   │    0    │ ... │    NULL     │  NULL  │  NULL  │ NULL       │   NULL    │  NULL  │ false    │
├───────┴─────────┴──────────────┴──────────┴─────────┴─────────┴───┴─────────────┴────────┴────────┴────────────┴───────────┴────────┴──────────┤
│ 1 rows                                                                                                                   31 columns (13 shown) │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

ORACLE的table函数,在PG里一般改成unnest,但在DUCKDB里改了还是不行,要解嵌套

sql 复制代码
select * from table( ...);

但from后面的unnest不支持传两个参数

sql 复制代码
D select * from unnest( array(select (c1,c2)::ty_1 from t_1),recursive := true);
Binder Error:
No function matches the given name and argument types 'unnest(STRUCT(c1 DECIMAL(18,3), c2 VARCHAR)[], BOOLEAN)'. You might need to add explicit type casts.
        Candidate functions:
        unnest(ANY)


LINE 1: select * from unnest( array(select (c1,c2)::ty_1 from t_1),recursive ...
                      ^

尝试创建宏,table由于是关键字,报错

sql 复制代码
D create macro table(i) as unnest(i);
Parser Error:
syntax error at or near "table"

LINE 1: create macro table(i) as unnest(i);
                     ^

创建宏,同样无法放在from后面,创建不报错,使用报错

sql 复制代码
D create macro unnest_table(i) as table select * from unnest(i, max_depth := 2);
D select * from unnest_table(array[(1,'a'),(2,'b')] ::tyt_1);
Binder Error:
Invalid named parameter "max_depth" for function unnest
Function does not accept any named parameters.

LINE 1: select * from unnest_table(array[(1,'a'),(2,'b')] ::tyt_1);
                      ^
D drop macro unnest_table;

改成放在from后面,能得到类似oracle和postgresql的效果了

sql 复制代码
D create macro unnest_table(i) as table select unnest(i,recursive := true) column_value;
D select * from unnest_table(array[(1,'a'),(2,'b')] ::tyt_1);
┌───────────────┬─────────┐
│      c1       │   c2    │
│ decimal(18,3) │ varchar │
├───────────────┼─────────┤
│         1.000 │ a       │
│         2.000 │ b       │
└───────────────┴─────────┘
D select * from unnest_table(array[1,2,3]);
┌──────────────┐
│ column_value │
│    int32     │
├──────────────┤
│            1 │
│            2 │
│            3 │
└──────────────┘

总结和题外话

虽然PG和DUCKDB的unnest有不少差异,以至于PG的SQL在DUCKDB上并不能完全兼容,但DUCKDB的unnest函数设计看上去似乎比PG的更合理,能用的场景也更多,能自由控制展开的层级。

只是习惯了ORACLE明确区分select列表和from的行为后,对于duckdb和postgresql里这种在select里能直接返回多行的行为感觉很是诡异,这么说可能有人不理解,我举个例子:

当你知道下面的SQL执行的结果是10

sql 复制代码
select count(1) from t1;

而且知道下面的select里没有使用任何聚合函数,那么不管select里使用了什么表达式还是函数,你一定会判断这个SQL能返回10行

sql 复制代码
select .... from t1;

但是这只是ORACLE的行为,在duckdb和postgresql就不一样了,如果里面有使用unnest这样的函数(这种函数甚至可以自定义创建),那么你在不确定原始数据的内容时,根本不知道这个sql会返回多少行,而且由于一行变多行,很可能引入意料之外的join笛卡尔积。

但是换个角度想想,既然能接受 聚合函数 的存在,为什么就不能有展开 /分散 函数呢?聚合是多行变一行,分散是一行变多行,想到这里便释然了。

相关推荐
小陈工1 天前
Python Web开发入门(十七):Vue.js与Python后端集成——让前后端真正“握手言和“
开发语言·前端·javascript·数据库·vue.js·人工智能·python
科技小花1 天前
数据治理平台架构演进观察:AI原生设计如何重构企业数据管理范式
数据库·重构·架构·数据治理·ai-native·ai原生
一江寒逸1 天前
零基础从入门到精通MySQL(中篇):进阶篇——吃透多表查询、事务核心与高级特性,搞定复杂业务SQL
数据库·sql·mysql
D4c-lovetrain1 天前
linux个人心得22 (mysql)
数据库·mysql
阿里小阿希1 天前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
荒川之神1 天前
Oracle 数据仓库雪花模型设计(完整实战方案)
数据库·数据仓库·oracle
做个文艺程序员1 天前
MySQL安全加固十大硬核操作
数据库·mysql·安全
不吃香菜学java1 天前
Redis简单应用
数据库·spring boot·tomcat·maven
一个天蝎座 白勺 程序猿1 天前
Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
数据库·apache·etl·iotdb
不知名的老吴1 天前
Redis的延迟瓶颈:TCP栈开销无法避免
数据库·redis·缓存