【问题标题】:Record returned from function has columns concatenated从函数返回的记录具有串联的列
【发布时间】:2014-10-13 01:58:20
【问题描述】:

我有一个表格,用于存储帐户随时间的变化。如果这些记录尚不存在,我需要将其与其他两个表连接起来以创建特定日期的一些记录。

为了让事情变得更简单(我希望如此),我将返回正确历史数据的查询封装到一个函数中,该函数接受帐户 ID 和日期。

如果我执行"Select * account_servicetier_for_day(20424, '2014-08-12')",我会得到预期的结果(从函数返回的所有数据在单独的列中)。如果我在另一个查询中使用该函数,我会将所有列合并为一个:

("2014-08-12 14:20:37",hollenbeck,691,12129,20424,69.95,"2Mb/1Mb 20GB Limit",2048,1024,20.000)

我正在使用“x86_64-slackware-linux-gnu 上的 PostgreSQL 9.2.4,由 gcc (GCC) 4.7.1 编译,64 位”。

查询:

Select
    '2014-08-12' As day, 0 As inbytes, 0 As outbytes, acct.username, acct.accountid, acct.userid,
    account_servicetier_for_day(acct.accountid, '2014-08-12')
From account_tab acct
Where acct.isdsl = 1
    And acct.dslservicetypeid Is Not Null
    And acct.accountid Not In (Select accountid From dailyaccounting_tab Where Day = '2014-08-12')
Order By acct.username

功能:

CREATE OR REPLACE FUNCTION account_servicetier_for_day(_accountid integer, _day timestamp without time zone) RETURNS setof account_dsl_history_info AS
$BODY$
DECLARE _accountingrow record;
BEGIN
  Return Query
  Select * From account_dsl_history_info
  Where accountid = _accountid And timestamp <= _day + interval '1 day - 1 millisecond'
  Order By timestamp Desc 
  Limit 1;
END;
$BODY$ LANGUAGE plpgsql;

【问题讨论】:

  • 当您在标量上下文中选择一条记录时,它会被转换为一个字符串。您可能需要从子查询中的函数中进行选择并将其连接到 account_tab
  • @cdhowie 它没有转换为字符串。它仍然是函数返回的复合类型account_dsl_history_info
  • @ClodoaldoNeto 公平点。我怀疑当他在他选择的编程环境中检索这个值时,它不会理解如何将它表示为除字符串之外的任何东西。 (有些驱动程序可以处理创纪录的值,有些则不能。)
  • 很遗憾,Postgres 9.3 会提供方便的JOIN LATERAL
  • 我应该接受哪个答案? @ClodoaldoNeto 首先根据我最初说明(错误地)我使用的是 Postgresql 9.3 的问题正确回答。但 ErwinBrandstetter 给出了更完整的答案,发布了有关 v9.2 的信息。

标签: sql postgresql join plpgsql set-returning-functions


【解决方案1】:

通常,分解从函数返回的行并获取单个列:

<b>SELECT * FROM</b> account_servicetier_for_day(20424, '2014-08-12');


关于查询:

Postgres 9.3 或更新版本

清理器JOIN LATERAL:

SELECT '2014-08-12' AS day, 0 AS inbytes, 0 AS outbytes
     , a.username, a.accountid, a.userid
     , f.*   -- but avoid duplicate column names!
FROM   account_tab a
     , account_servicetier_for_day(a.accountid, '2014-08-12') f  -- <-- HERE
WHERE  a.isdsl = 1
AND    a.dslservicetypeid IS NOT NULL
AND    NOT EXISTS (
   SELECT 1
   FROM   dailyaccounting_tab
   WHERE  day = '2014-08-12'
   AND    accountid = a.accountid
   )
ORDER  BY a.username;

LATERAL 关键字在这里是隐式的,函数总是可以引用更早的 FROM 项。 The manual:

LATERAL 也可以在函数调用 FROM 项之前,但在此 case 是噪声词,因为函数表达式可以参考 无论如何,之前的 FROM 项目。

相关:

FROM 列表中带有逗号的短符号(大部分)等同于 CROSS JOIN LATERAL(与 [INNER] JOIN LATERAL ... ON TRUE 相同),因此会从函数调用不返回任何行的结果中删除行。要保留此类行,请使用 LEFT JOIN LATERAL ... ON TRUE

...
FROM  account_tab a
LEFT  JOIN LATERAL account_servicetier_for_day(a.accountid, '2014-08-12') f ON TRUE
...

另外,如果可以避免,请不要使用NOT IN (subquery)。这是几种方法中最慢和最棘手的:

我建议改为NOT EXISTS

Postgres 9.2 或更早版本

您可以在SELECT 列表中调用集合返回函数(这是标准 SQL 的 Postgres 扩展)。出于性能原因,这最好在子查询中完成。分解外部查询中的(众所周知的!)行类型以避免重复评估函数:

SELECT '2014-08-12' AS day, 0 AS inbytes, 0 AS outbytes
     , a.username, a.accountid, a.userid
     , (a.rec).*   -- but avoid duplicate column names!
FROM  (
   SELECT *, account_servicetier_for_day(a.accountid, '2014-08-12') AS rec
   FROM   account_tab a
   WHERE  a.isdsl = 1
   AND    a.dslservicetypeid Is Not Null
   AND    NOT EXISTS (
       SELECT 1
       FROM   dailyaccounting_tab
       WHERE  day = '2014-08-12'
       AND    accountid = a.accountid
      )
   ) a
ORDER  BY a.username;

克雷格·林格的相关回答和解释,为什么我们最好在外部查询中分解:

Postgres 10 移除了 SELECT 中集合返回函数行为中的异常:

【讨论】:

    【解决方案2】:

    使用from子句中的函数

    Select
        '2014-08-12' As day,
        0 As inbytes,
        0 As outbytes,
        acct.username,
        acct.accountid,
        acct.userid,
        asfd.*
    From
        account_tab acct
        cross join lateral
        account_servicetier_for_day(acct.accountid, '2014-08-12') asfd
    Where acct.isdsl = 1
        And acct.dslservicetypeid Is Not Null
        And acct.accountid Not In (Select accountid From dailyaccounting_tab Where Day = '2014-08-12')
    Order By acct.username
    

    【讨论】:

    • 我在“.”处或附近出现语法错误在 account_servicetier_for_day(acct.accountid, ...
    • 9.2 似乎不支持横向交叉连接。我们的开发(9.2.4)服务器与我们的生产服务器(9.3.4)不同步。我将升级我们的开发服务器并回复您。
    猜你喜欢
    • 1970-01-01
    • 2012-02-28
    • 1970-01-01
    • 2017-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多