使用 PL/pgSQL 在循环中运行 CTE 查询

sm9*_*901 5 postgresql plpgsql

我正在尝试执行使用 plpgsql 在循环中重复调用的查询 - 循环遍历另一个表(命名坐标),其中包含网格的左上角和右下角纬度/经度坐标,我传递左上角和右下角纬度/ 经度值输入我的 CTE,以便显示在给定两个时间戳的这些坐标内发出的请求量(每小时)-。但是,我无法显示 CTE 的结果,并且收到以下错误消息:

ERROR:  query has no destination for result data
HINT:  If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT:  PL/pgSQL function "inline_code_block" line 6 at SQL statement
Run Code Online (Sandbox Code Playgroud)

为了使整个查询根据需要工作,我应该在这里更改什么?我的代码如下:

DO $$
<<outer_scope>> DECLARE
  coords RECORD;
BEGIN
    FOR coords IN SELECT topleftlat, topleftlon, bottomrightlat, bottomrightlon FROM coordinates LOOP
        WITH cal AS (
        SELECT generate_series('2011-02-02 00:00:00'::timestamp ,
                   '2012-04-01 05:00:00'::timestamp , 
                   '1 hour'::interval) AS stamp
    ),
    qqq AS (
      SELECT date_trunc('hour', calltime) AS stamp, count(*) AS zcount
      FROM mytable
      WHERE calltime >= '2011-02-13 11:55:11' 
        AND calltime <= '2012-02-13 01:02:21'
        AND (calltime::time >= '11:55:11' 
        OR calltime::time <= '01:02:21')
        AND lat BETWEEN coords.bottomrightlat AND coords.topleftlat
        AND lon BETWEEN coords.topleftlon AND coords.bottomrightlon
     GROUP BY date_trunc('hour', calltime)
    )
    SELECT cal.stamp, COALESCE (qqq.zcount, 0) AS zcount
    FROM cal
    LEFT JOIN qqq ON cal.stamp = qqq.stamp
    WHERE cal.stamp >= '2011-02-13 11:00:00' 
      AND cal.stamp <= '2012-02-13 01:02:21' 
      AND (
        extract ('hour' from cal.stamp) >= extract ('hour' from '2011-02-13 11:00:00'::timestamp) or
        extract ('hour' from cal.stamp) <= extract ('hour' from '2012-02-13 01:02:21'::timestamp) 
      )
    ORDER BY stamp ASC;

    END LOOP;
END;
$$;
Run Code Online (Sandbox Code Playgroud)

Erw*_*ter 5

DO命令无法实际返回数据(除了使用RAISE,或者您可以写入(临时)表 .. )。

您需要创建一个可以定义返回类型RETURNS并调用它的 PL/pgSQL 函数

你可以用 返回结果RETURN QUERY EXECUTE。但我怀疑整个操作可以简化......

重写为单个 SQL 查询

您可能根本不需要 plpgsql 或循环。考虑这个简单的 SQL 查询:

WITH v AS (
   SELECT '2011-02-13 11:55:11'::timestamp AS _from -- provide times once
         ,'2012-02-13 01:02:21'::timestamp AS _to
   )
, q AS (
   SELECT c.coordinates_id
        , date_trunc('hour', t.calltime) AS stamp
        , count(*) AS zcount
   FROM   v
   JOIN   mytable t ON  t.calltime BETWEEN v._from AND v._to
                   AND (t.calltime::time >= v._from::time OR
                        t.calltime::time <= v._to::time)
   JOIN   coordinates c ON (t.lat, t.lon) 
                   BETWEEN (c.bottomrightlat, c.topleftlon)
                       AND (c.topleftlat, c.bottomrightlon)
   GROUP  BY c.coordinates_id, date_trunc('hour', t.calltime)
   )
, cal AS (
   SELECT generate_series(GREATEST('2011-02-02 00:00:00'::timestamp, v._from)
                        , LEAST('2012-04-01 05:00:00'::timestamp, v._to)
                        , '1 hour'::interval) AS stamp
   FROM v
   )
SELECT q.coordinates_id, cal.stamp, COALESCE (q.zcount, 0) AS zcount
FROM   v, cal
LEFT   JOIN q USING (stamp)
WHERE (cal.stamp::time >= v._from::time OR
       cal.stamp::time <= v._to::time)
ORDER  BY q.coordinates_id, stamp;
Run Code Online (Sandbox Code Playgroud)
  • 不要循环遍历 table 中的行,而是coordinates加入 CTE 并一次性生成整个结果。

  • 当您聚合每一行时,coordinates我们需要这个表的主键(或任何其他唯一的列集),我假设一个名为coordinates_id.

  • 我加入了CTE v(即“值”)之上,以提供_from_to仅一次时间戳。

  • 我立即使用_from_to来限制日历的时间范围,而不是WHERE在最终的SELECT.

    GREATEST('2011-02-02 00:00:00'::timestamp, v._from)
    LEAST('2012-04-01 05:00:00'::timestamp, v._to)
    
    Run Code Online (Sandbox Code Playgroud)
  • 对于更简单的条件,我使用@kgrittn 在此相关答案中演示的“临时行” JOIN

         ON (t.lat, t.lon) 
    BETWEEN (c.bottomrightlat, c.topleftlon)
        AND (c.topleftlat, c.bottomrightlon)
    
    Run Code Online (Sandbox Code Playgroud)
  • 我转换为 time ( ::time) 而不是使用extract ('hour' ..),因为它更简单、更快。

我不是 100% 确定这正是您所追求的,但它应该非常接近。

  • @sm90901:修正了一个错字:`c.coordinates_id` -&gt; `q.coordinates_id`。我在这里假设,您想在结果中识别坐标。也许不是,那你就不需要它了。也许您甚至想聚合*所有*坐标,然后也从 CTE 中删除 `c.coordinates_id`。但是您的原始查询另有说明... (2认同)