The [`DO`][1] command has no facility to actually return data (except with `RAISE`, or you could write to a (temp) table .. ). You need to [create a PL/pgSQL function][2] that can define a return type with `RETURNS` and call it. You could return the result with [`RETURN QUERY EXECUTE`][3]. But I suspect the whole operation can be simplified ... ###Rewrite as single SQL query You probably don't need plpgsql or loops at all. Consider this plain SQL query instead: 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; - Instead of looping through rows in table `coordinates`, join to the CTE and produce the whole result in one go. - As you aggregate per row of `coordinates` we need the primary key of this table (or any other unique set of columns) I assume a pk named `coordinates_id`. - I added the CTE `v` (for "values") on top to provide `_from` and `_to` timestamps once only. - I use `_from` and `_to` to limit the time range of the calender right away, instead of adding `WHERE` clauses to trim the surplus in the final `SELECT`. GREATEST('2011-02-02 00:00:00'::timestamp, v._from) LEAST('2012-04-01 05:00:00'::timestamp, v._to) - I use "ad-hoc rows" like demonstrated [in this related answer by @kgrittn][4] for a much simpler `JOIN` condition: ON (t.lat, t.lon) BETWEEN (c.bottomrightlat, c.topleftlon) AND (c.topleftlat, c.bottomrightlon) - I cast to time (`::time`) instead of using `extract ('hour' ..)`, because it's simpler and faster. I am not 100 % sure this is exactly what you are after, but it should be very close. [1]: http://www.postgresql.org/docs/current/interactive/sql-do.html [2]: http://www.postgresql.org/docs/current/interactive/sql-createfunction.html [3]: http://www.postgresql.org/docs/current/interactive/plpgsql-control-structures.html#PLPGSQL-STATEMENTS-RETURNING [4]: https://stackoverflow.com/a/11969718/939860