目 录CONTENT

文章目录

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

DarkAthena
2026-01-02 / 0 评论 / 0 点赞 / 3 阅读 / 0 字

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

背景

在postgresql中,unnest函数用于解开数组类型的嵌套,能把一个数组变成多行,但不解开一行中的多个字段;当unnest处于from后面时,如果解开的一行有多个字段,同时也会解开成多个字段。
也就是说,在pg中可以通过控制unnest在sql语句中的位置,来决定展开的层级。

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

测试

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

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的字段没有展开

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后面不一样

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,字段解开

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之一

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可以直接解开

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

json数组也可以解开

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

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

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里改了还是不行,要解嵌套

select * from table( ...);

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

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由于是关键字,报错

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后面,创建不报错,使用报错

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的效果了

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

select count(1) from t1;

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

select .... from t1;

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

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

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin
博主关闭了所有页面的评论