从 PostgreSQL 中的更新查询返回多个值

Returning multiple values from UPDATE query in PostgreSQL

我是编写数据库函数的新手,在执行 UPDATE 查询时,我需要 return 'last_login_at' 的值作为 OUT 参数。

这是我的函数的一个片段:

...
LOOP
    UPDATE "user" SET 
        last_login_at = current_timestamp, 
        first_name = p_first_name,
        last_name = p_last_name,
    WHERE ext_user_id = p_ext_user_id AND platform_id = p_platform_id
    RETURNING id INTO v_user_id;
    is_new := false;
    // The next 'CASE' is not valid - Need to replace it with a valid one.  
    has_logged_in_today = CASE
     WHEN date_part('day', age(current_timestamp, last_login_at)) > 1
     THEN true
     ELSE false
     END;
    IF FOUND THEN 
        EXIT;
    END IF;
..
..
END LOOP;

是否可以做多个RETURNING x INTO y
我们可以在 RETURNING x INTO y 中使用 CASE 语句吗?

编辑

我能够获得更好的结果,现在它看起来像这样:

   ...
    LOOP
        UPDATE "user" SET 
            login_consecutive_days = CASE 
                WHEN date_part('day', age(current_timestamp, last_login_at)) > 1 
                THEN 0
                ELSE login_consecutive_days + date_part('day', age(current_timestamp, last_login_at))
                END,
        login_max_consecutive_days = CASE
        WHEN date_part('day', age(current_timestamp, last_login_at)) = 1
             AND (login_consecutive_days+1 > login_max_consecutive_days)
        THEN login_consecutive_days+1
        ELSE login_max_consecutive_days
        END,
        last_login_at = current_timestamp, 
            num_sessions = num_sessions + 1,
            last_update_source = 'L',
            first_name = p_first_name,
            last_name = p_last_name,
            additional_data = p_additional_data
        WHERE ext_user_id = p_ext_user_id AND platform_id = p_platform_id
        RETURNING id,
        CASE
        WHEN date_part('day', age(current_timestamp, last_login_at)) = 0
        THEN true
        ELSE false
        END
    INTO v_user_id, is_first_login_today;
        is_new := false;
        IF FOUND THEN 
            EXIT;
        END IF;
    ...

唯一的问题是在 RETURNING 点,last_login_at 已经更新,所以 CASE 总是 returns TRUE

有什么神奇的方法可以解决我的问题吗?

您可以 return 多个列,语法为:

UPDATE "user" SET 
        last_login_at = current_timestamp, 
        first_name = p_first_name,
        last_name = p_last_name,
    WHERE ext_user_id = p_ext_user_id AND platform_id = p_platform_id
    RETURNING id, last_login_at 
        INTO v_user_id, v_login_at;

returning 子句遵循 SELECT 字段列表的大部分规则,因此您可以根据需要添加任意数量的列。

Update docs

Is there a magical solution to my problem?

实际上,有:在FROM子句中加入"user"table的另一个实例:

   UPDATE "user" <b>u</b>
   SET    login_consecutive_days = ...  -- unqualified column name

   <b>FROM   "user" u1</b>
   WHERE  <b>u.</b>ext_user_id = p_ext_user_id
   AND    <b>u.</b>platform_id = p_platform_id
   AND    <b>u.</b>id = <b>u1.</b>id                  -- must be unique not null (like the PK)
   RETURNING <b>u.</b>id, (<b>u1</b>.last_login_at < now() + interval '1 day')
   INTO   v_user_id, is_first_login_today;

   is_new := false;
   EXIT WHEN FOUND;

现在,table 别名 u 指的是 table 的 post-UPDATE 状态,但 u1 指的是查询开始时的快照。

详细解释:

  • Return pre-UPDATE Column Values Using SQL Only - PostgreSQL Version

Table - 将所有列引用限定为明确的,这绝不是一个坏主意,但在自连接之后它是必需的。

The manual about the short syntax EXIT WHEN FOUND.

您可以在 RETURNING 子句中使用任何表达式,包括 CASE 语句。恰好有一种更简单、更便宜的方法:

CASE WHEN date_part('day', age(current_timestamp, last_login_at)) = 0
THEN true ELSE false END

第 1 步:

CASE WHEN last_login_at < now() + interval '1 day'
THEN true ELSE false END

第 2 步:

(last_login_at < now() + interval '1 day')

只需使用 boolean 结果。如果 last_login_atNULL,你会得到 NULL.


旁白:
至于查询的其余部分:表达式可以简化,LOOP 是可疑的,你应该 never 使用 reserved words 作为标识符,即使双引号使有可能("user"),算法似乎依赖于 exact 24 小时间隔执行,这很容易出错。