RETURNING causes error: missing FROM-clause entry for table

RETURNING causes error: missing FROM-clause entry for table

我正在从 UUID WHERE empl_user_pub_uuid = 'e2bb39f1f28011eab66c63cb4d9c7a34' 获取用户数据。

因为我不想进行额外的查询来获取额外的用户数据,所以我试图让他们通过 INSERT

WITH _u AS (
    SELECT
        eu.empl_user_pvt_uuid,
        ee.email,
        ep.name_first
    FROM employees.users eu
    LEFT JOIN (
        SELECT DISTINCT ON (ee.empl_user_pvt_uuid)
            ee.empl_user_pvt_uuid,
            ee.email
        FROM employees.emails ee
        ORDER BY ee.empl_user_pvt_uuid, ee.t DESC
    ) ee ON eu.empl_user_pvt_uuid = ee.empl_user_pvt_uuid
    LEFT JOIN (
        SELECT DISTINCT ON (ep.empl_user_pvt_uuid)
            ep.empl_user_pvt_uuid,
            ep.name_first
        FROM employees.profiles ep
    ) ep ON eu.empl_user_pvt_uuid = ep.empl_user_pvt_uuid
    WHERE empl_user_pub_uuid = 'e2bb39f1f28011eab66c63cb4d9c7a34'
)
INSERT INTO employees.password_resets (empl_pwd_reset_uuid, empl_user_pvt_uuid, t_valid, for_empl_user_pvt_uuid, token)
SELECT 'f70a0346-a077-11eb-bd1a-aaaaaaaaaaaa', '6efc2b7a-f27e-11ea-b66c-de1c405de048', '2021-04-18 19:57:47.111365', _u.empl_user_pvt_uuid, '19d65aea-7c4a-41bc-b580-9d047f1503e6'
FROM _u
RETURNING _u.empl_user_pvt_uuid, _u.email, _u.name_first;

然而我得到:

[42P01] ERROR: missing FROM-clause entry for table "_u" 
Position: 994

我做错了什么?

根据关于 6.4. Returning Data From Modified Rows 的 Postgres 文档:

In an INSERT, the data available to RETURNING is the row as it was inserted.

但是您在这里试图从源 table 而不是目标 return 列。返回将无法从 _u table 的 return 列返回,而只能从 employees.password_resets table 的插入行返回。但是您可以编写用于插入的嵌套 cte,也可以 select 来自源 table 的数据。请尝试以下方法。

WITH _u AS (
    SELECT
        eu.empl_user_pvt_uuid,
        ee.email,
        ep.name_first
    FROM employees.users eu
    LEFT JOIN (
        SELECT DISTINCT ON (ee.empl_user_pvt_uuid)
            ee.empl_user_pvt_uuid,
            ee.email
        FROM employees.emails ee
        ORDER BY ee.empl_user_pvt_uuid, ee.t DESC
    ) ee ON eu.empl_user_pvt_uuid = ee.empl_user_pvt_uuid
    LEFT JOIN (
        SELECT DISTINCT ON (ep.empl_user_pvt_uuid)
            ep.empl_user_pvt_uuid,
            ep.name_first
        FROM employees.profiles ep
    ) ep ON eu.empl_user_pvt_uuid = ep.empl_user_pvt_uuid
    WHERE empl_user_pub_uuid = 'e2bb39f1f28011eab66c63cb4d9c7a34'
), I as

(
    INSERT INTO employees.password_resets (empl_pwd_reset_uuid, empl_user_pvt_uuid, t_valid, for_empl_user_pvt_uuid, token)
    SELECT 'f70a0346-a077-11eb-bd1a-aaaaaaaaaaaa', '6efc2b7a-f27e-11ea-b66c-de1c405de048', '2021-04-18 19:57:47.111365', _u.empl_user_pvt_uuid, '19d65aea-7c4a-41bc-b580-9d047f1503e6'
    FROM _u
)
select _u.empl_user_pvt_uuid, _u.email, _u.name_first from _u

确实,如前所述,INSERTRETURNING 子句只能看到插入的行。更具体地说,quoting the manual here:

The optional RETURNING clause causes INSERT to compute and return value(s) based on each row actually inserted (or updated, if an ON CONFLICT DO UPDATE clause was used). This is primarily useful for obtaining values that were supplied by defaults, such as a serial sequence number. However, any expression using the table's columns is allowed. The syntax of the RETURNING list is identical to that of the output list of SELECT. Only rows that were successfully inserted or updated will be returned. [...]

粗体 强调我的。
所以没有什么可以阻止您将 相关子查询 添加到 RETURNING 列表:

INSERT INTO employees.password_resets AS ep
       (empl_pwd_reset_uuid                  , empl_user_pvt_uuid                    , t_valid                     , for_empl_user_pvt_uuid, token)
SELECT 'f70a0346-a077-11eb-bd1a-aaaaaaaaaaaa', '6efc2b7a-f27e-11ea-b66c-de1c405de048', '2021-04-18 19:57:47.111365', eu.empl_user_pvt_uuid , '19d65aea-7c4a-41bc-b580-9d047f1503e6'
FROM   employees.users eu
WHERE  empl_user_pub_uuid = 'e2bb39f1f28011eab66c63cb4d9c7a34'
RETURNING for_empl_user_pvt_uuid AS empl_user_pvt_uuid  -- alias to meet your org. query
        , (SELECT email
           FROM   employees.emails
           WHERE  empl_user_pvt_uuid = ep.empl_user_pvt_uuid
           ORDER  BY t DESC  -- NULLS LAST ?
           LIMIT  1
          ) AS email
        , (SELECT name_first
           FROM   employees.profiles
           WHERE  empl_user_pvt_uuid = ep.empl_user_pvt_uuid
           -- ORDER  BY ???
           LIMIT  1
          ) AS name_first;

由于多种原因,这也比您的查询(或提议的查询)更有效

  • 我们不会 运行 对表 employees.emailsemployees.profiles 的所有行进行子查询 eeep。如果我们需要这些表的主要部分,那将是有效的,但我们只从每个表中获取感兴趣的一行。使用适当的索引,相关子查询对此更加有效。参见:

  • 我们不添加一个或多个 CTE 的开销。

  • 我们仅在 成功 INSERT 后获取额外数据 ,因此如果插入因任何原因未完成,也不会浪费时间. (请参阅顶部的引用!)

另外,可能最重要的是,这是正确的。我们使用实际插入的行中的数据 - after 插入它。 (请参阅顶部的引用!)在应用可能的默认值、触发器或规则之后。我们可以确定我们看到的是数据库中的实际内容(当前)。

profiles.name_first 没有 ORDER BY。那是不对的。要么只有一个符合条件的行,那么我们就不需要 DISTINCTLIMIT 1。或者可以有多个,那么我们还需要一个确定性的ORDER BY得到一个确定性的结果

如果 emails.t 可以为 NULL,您需要在 ORDER BY 子句中添加 NULLS LAST。参见:

  • Sort by column ASC, but NULL values first?

索引

理想情况下,您有这些多列索引(按此顺序排列列):

  • users (empl_user_pub_uuid, empl_user_pvt_uuid)
  • emails (empl_user_pvt_uuid, email)
  • profiles (empl_user_pvt_uuid, name_first)

然后,如果对表进行了足够的清理,您将获得三个仅索引扫描,整个操作会非常快。

获取 INSERT 之前的值?

如果你真的想要那个(我认为你不想要),请考虑:

  • Return pre-UPDATE column values using SQL only