sqlpostgresqlpostgresql-9.3lateral

How to rewrite a SELECT ... CROSS JOIN LATERAL ... statement for older PostgreSQL versions?


I am faced with a query similar to this:

SELECT *
FROM invoicable_interval i,
  LATERAL partition_into_months(i.start_date, i.stop_or_current_date) p;

... where partition_into_months is defined similar to this:

CREATE FUNCTION partition_into_months(start_date DATE, stop_date DATE)
  RETURNS TABLE (start_date DATE, stop_date DATE, days INT)
AS $$ ... $$
LANGUAGE sql
IMMUTABLE;

So I am doing a cross join with a variable interval for the secondary table, hence the (redundant) keyword LATERAL.

This works fine with PostgreSQL 9.3.6 and 9.4.2, however, with 9.1.3 I get the following error message:

[42601] ERROR: syntax error at or near "."
  Position: 398

The indicated position is the dot in the expression "i.start_date".

How can I rewrite this SQL query in order to port it back to PostgreSQL 9.1.3 ?


Solution

  • PostgreSQL supports calling set-returning functions in the SELECT clause. This is somewhat deprecated now that we have LATERAL and is certainly discouraged because it has rather erratic behaviour, but it remains useful.

    In your case you could write:

    SELECT 
      i.*,
      (partition_into_months(i.start_date, i.stop_or_current_date)).*
    FROM invoicable_interval i;
    

    However, this may result in one call to partition_into_months per column returned because (fn).* is basically macro-expanded into (fn).col1, (fn).col2, .... To avoid this, you can wrap it in a subquery, e.g.

    SELECT (x.i).*, (x.p).*
    FROM
    (
      SELECT 
        i,
        partition_into_months(i.start_date, i.stop_or_current_date) p
      FROM invoicable_interval i
    ) x(i,p);
    

    Note that weird results will be encountered in the presence of multiple set returning functions in the SELECT list. It isn't a cross-join like you would expect. For example, compare:

    SELECT generate_series(1,4), generate_series(1,4)
    

    to

    SELECT generate_series(1,4), generate_series(1,3);