从函数返回的记录已连接列

时间:2014-08-19 20:24:53

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

我有一张表可以存储帐户随时间的变化。如果这些记录尚不存在,我需要将其与另外两个表联系起来,为特定日期创建一些记录。

为了让事情变得更容易(我希望),我已经封装了将正确的历史数据返回到一个函数中的查询,该函数包含帐户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;

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:1)

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

SELECT * FROM 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列表中调用set-returning函数(这是标准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;

Craig Ringer的相关答案有一个解释,为什么我们在外部查询中更好地分解:


Postgres 10 最终在SELECT列表中重新实现了设置返回功能,以修复意外的副作用。

相关问题