Greenplum 函数内嵌套查询在query中调用的替代方案

背景

在使用数据库过程中,用户可能会封装一些函数,用来返回一些映射值。

例如,有一张表存储了学号和学生名字,写一个函数,根据学号,返回学生名字。

screenshot

SQL如下 :

create table tab(id int primary key, std_name name);

create or replace function f1(int) returns name as 
$$

  select std_name from tab where id=$1;

$$
 language sql strict volatile;

dwh=> insert into tab values (1,'abc');
INSERT 0 1

dwh=> select f1(1);
 f1  
-----
 abc
(1 row)

使用Greenplum时,可能需要注意,因为如果函数中有QUERY,但是这个函数又在QUERY中调用的话,可能会报错。

目前执行计划还不支持这种查询:

dwh=> select f1(id) from tab;
ERROR:  XX000: function cannot execute on segment because it accesses relation "public.tab" (functions.c:155)  (seg0 slice1 e45f16674.cloud.nu17:3017 pid=20659) (cdbdisp.c:1326)
DETAIL:  SQL function "f1" during startup
LOCATION:  cdbdisp_finishCommand, cdbdisp.c:1326

出错原因,在检查segment节点query tree时,如果发现树中还有非catalog类的对象,则报错。
可能是master节点对这种QUERY的处理没有彻底。

/**
 * Walker for querytree_safe_for_segment.
 */
bool querytree_safe_for_segment_walker(Node *expr, void *context)
{
        Assert(context == NULL);

        if (!expr)
        {
                /**
                 * Do not end recursion just because we have reached one leaf node.
                 */
                return false;
        }

        switch(nodeTag(expr))
        {
                case T_Query:
                        {
                                Query *q = (Query *) expr;

                                if (!allow_segment_DML &&
                                        (q->commandType != CMD_SELECT
                                         || q->intoClause != NULL
                                         || q->resultRelation > 0))
                                {
                                        elog(ERROR, "function cannot execute on segment because it issues a non-SELECT statement");
                                }

                                ListCell * f = NULL;
                                foreach(f,q->rtable)
                                {
                                        RangeTblEntry *rte = (RangeTblEntry *) lfirst(f);

                                        if (rte->rtekind == RTE_RELATION)
                                        {
                                                Assert(rte->relid != InvalidOid);

                                                Oid namespaceId = get_rel_namespace(rte->relid);

                                                Assert(namespaceId != InvalidOid);

                                                if (!(IsSystemNamespace(namespaceId) ||
                                                          IsToastNamespace(namespaceId) ||
                                                          IsAoSegmentNamespace(namespaceId)))
                                                {
                                                        elog(ERROR, "function cannot execute on segment because it accesses relation \"%s.%s\"",
                                                                        quote_identifier(get_namespace_name(namespaceId)), quote_identifier(get_rel_name(rte->relid)));
                                                }
                                        }
                                }
                                query_tree_walker(q, querytree_safe_for_segment_walker, context, 0);  
...

使用with也不能解决这个问题,因为不会等收到master节点后再调用f1

dwh=> with t as (select * from tab) select f1(id) from t;
ERROR:  XX000: function cannot execute on segment because it accesses relation "public.tab" (functions.c:155)  (seg0 slice1 e45f16674.cloud.nu17:3017 pid=26324) (cdbdisp.c:1326)
DETAIL:  SQL function "f1" during startup
LOCATION:  cdbdisp_finishCommand, cdbdisp.c:1326

dwh=> with t as (select * from tab) select * from t;
 id | std_name 
----+----------
  1 | abc
  2 | abc
(2 rows)

聚合后还是不行
dwh=> with t as (select array_agg(id) ag from tab) select unnest(ag) from t;
 unnest 
--------
      2
      1
(2 rows)

dwh=> with t as (select array_agg(id) ag from tab) select f1(unnest(ag)) from t;
ERROR:  XX000: query plan with multiple segworker groups is not supported (cdbdisp.c:302)
HINT:  likely caused by a function that reads or modifies data in a distributed table
CONTEXT:  SQL function "f1" statement 1
LOCATION:  cdbdisp_dispatchToGang, cdbdisp.c:302

怎么解决这个问题呢?
可以用子查询(括号中的部分)替代 function 。

dwh=> select *, (select std_name from tab where tab.id=t.id) from tab t;
 id | std_name | ?column? 
----+----------+----------
  1 | abc      | abc
(1 row)
<think>嗯,用户这次问的是Greenplum中的greatest函数。从问题来看,用户已经对Greenplum有一定了解,可能是个数据分析师或者数据库开发人员,需要具体掌握这个函数的使用细节。之前引用的资料里提到了GP支持所有类型的不变形函数,而greatest正符合这个特性——它是个典型的不变形函数,输入确定时输出总是确定的。引用[3]提到的generate_series函数倒是可以结合演示,比如用来生成测试数据验证greatest的功能。用户特别强调要功能和示例,说明可能是要立即用到实际工作中。所以不仅要解释语法,还得给出贴近场景的案例。考虑到用户之前的引用提到过数值类型和字符串类型,示例应该覆盖这两类,最好再加个时间戳的用法——毕竟数据库业务里日期处理很常见。注意到引用[3]里有个用序列生成主键的案例设计很巧妙,不过这次用不上。倒是可以借鉴它的思路,用generate_series构造测试数据来演示greatest的比较逻辑。比如比较两列随机数时,能直观看到greatest取每行最大值的特性。需要提醒陷阱点:空值处理容易被忽略。引用[1]强调过参数列表的概念,正好说明当greatest遇到null时,除非参数全为null才返回null,这点要特别提示,因为实际查询经常遇到数据不全的情况。最后补充使用场景时,联想到用户引用过窗口函数row_number(),说明用户可能处理复杂分析。那么结合分区取最新记录的案例应该能命中ta的需求——这种用法在分析用户行为轨迹时特别实用。</think> ### Greenplum 中 `greatest` 函数详解 #### **功能说明** `greatest` 是 Greenplum 内置的 **比较函数**,作用为从参数列表中返回**最大值**(按数据类型自然排序规则)。它支持以下特性: - ✅ **多参数比较**:可接受任意数量参数 - ✅ **多类型支持**:数值、字符串、时间戳等 - 🚫 **空值处理**:若参数均非 `NULL` 则返回最大值;若存在 `NULL` 则返回 `NULL` - ⚡ **符合不变型函数特征**:给定相同参数时输出恒定(引用[^1]) #### **语法格式** ```sql greatest(value1, value2, ..., valueN) ``` --- #### **使用示例** **1. 数值比较** ```sql -- 返回最大值 100 SELECT greatest(10, 100, -5, 50); -- 结果:100 ``` **2. 字符串比较**(按字母顺序) ```sql -- 'orange' 在字母序中最大 SELECT greatest('apple', 'orange', 'banana'); -- 结果:orange ``` **3. 时间戳比较** ```sql -- 返回最新时间 SELECT greatest( '2023-01-01 08:00:00'::timestamp, '2023-01-01 12:30:00'::timestamp, '2023-01-01 10:15:00'::timestamp ); -- 结果:2023-01-01 12:30:00 ``` **4. 空值处理** ```sql -- 参数含 NULL 时返回 NULL SELECT greatest(10, 20, NULL, 5); -- 结果:NULL ``` **5. 结合 `generate_series` 生成测试数据**(引用[^3]) ```sql -- 生成两列随机数并取每行最大值 SELECT a, b, greatest(a, b) AS max_val FROM ( SELECT (random()*100)::int AS a, (random()*100)::int AS b FROM generate_series(1, 5) ) t; -- 示例输出: -- a | b | max_val -----+----+--------- -- 23 | 87 | 87 -- 45 | 12 | 45 ``` --- #### **典型应用场景** 1. **动态阈值过滤**:在 WHERE 子句中筛选超过指定阈值的记录 ```sql SELECT * FROM sales WHERE revenue > greatest(1000, (SELECT avg(revenue) FROM sales)); ``` 2. **多列取极值**:快速计算多字段的最大值 ```sql SELECT greatest(temp_morning, temp_noon, temp_night) AS max_temp_daily FROM weather_data; ``` 3. **数据清洗**:结合 `COALESCE` 处理空值 ```sql SELECT greatest( COALESCE(col1, -9999), COALESCE(col2, -9999) ) FROM raw_table; ``` --- #### **注意事项** 1. **参数类型需一致**:所有输入参数必须是可比较的相同类型,否则触发类型错误: ```sql -- 错误示例:数值与字符串混合 SELECT greatest(10, '20'); ``` 2. **空值传播**:如应用中需忽略 `NULL`,可先用 `COALESCE` 替换默认值,例如: ```sql SELECT greatest( COALESCE(col1, -2147483648), -- 用最小值替代NULL COALESCE(col2, -2147483648) ); ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值