1

I am trying to compute a aggregated tsrange from a set of row that I extract from an SQL query. Problem is that I keep getting errors that the input parameter is not being passed in.

CREATE OR REPLACE AGGREGATE range_merge(anyrange)
(
    sfunc = range_merge,
    stype = anyrange
);

DROP FUNCTION IF EXISTS aggregate_validity(entity_name regclass, entry bigint);

CREATE OR REPLACE FUNCTION aggregate_validity(entity_name regclass, entry bigint) returns tsrange AS
$$
DECLARE
    result tsrange;
BEGIN
   EXECUTE format('select range_merge(valid) from %s where entity_id = %U', entity_name, entry) into result;
   return result;
END
$$ LANGUAGE plpgsql;

When I do:

select * from aggregate_validity(country, 1);

I get an error stating that the entity name and entry do not exist. It does not seem to parameterize the input into the statement properly.

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
kafka
  • 573
  • 1
  • 11
  • 28

2 Answers2

1

Function:

EXECUTE format('select range_merge(valid) from %s where entity_id=%U',entity_name, entry) 
into result;
=>
EXECUTE format('select range_merge(valid) from %I where entity_id=%s',entity_name, entry) 
into result;
--%I for identifier, %s for value

Call:

select * from aggregate_validity(country, 1)
=>
select * from aggregate_validity('country', 1);

db<>fiddle demo

Lukasz Szozda
  • 162,964
  • 23
  • 234
  • 275
1
CREATE OR REPLACE AGGREGATE range_merge(anyrange) (
   SFUNC = range_merge
 , STYPE = anyrange
);

-- DROP FUNCTION IF EXISTS aggregate_validity(entity_name regclass, entry bigint);
CREATE OR REPLACE FUNCTION aggregate_validity(entity_name regclass, entry bigint, OUT result tsrange)
  LANGUAGE plpgsql AS
$func$
BEGIN
   EXECUTE 'SELECT range_merge(valid) FROM ' || entity_name || ' WHERE entity_id = $1'
   INTO result
   USING entry;
END
$func$;

Call:

SELECT aggregate_validity('country', 1);

db<>fiddle here

The call does not need SELECT * FROM, as the function returns a single value per definition.

I used an OUT parameter to simplify (OUT result tsrange). See:

Don't concatenate the entry value into the SQL string. Pass it as value with the USING clause. Cleaner, faster.

Since entity_name is passed as regclass, it's safe to simply concatenate (which is a bit cheaper). See:

Plus, missing quotes and incorrect format specifiers, as Lukasz already provided.

Your custom aggregate function range_merge() has some caveats:

  1. I wouldn't name it "range_merge", that being the name of the plain function range_merge(), too. While that's legal, it still invites confusing errors.

  2. You are aware that the function range_merge() includes gaps between input ranges in the output range?

  3. range_merge() returns NULL for any NULL input. So if your table has any NULL values in the column valid, the result is always NULL. I strongly suggest that any involved columns shall be defined as NOT NULL.

If you are at liberty to install additional modules, consider range_agg by Paul Jungwirth who is also here on Stackovflow. It provides the superior function range_agg() addressing some of the mentioned issues.

If you don't want to include gaps, consider the Postgres Wiki page on range aggregation.

I would probably not use aggregate_validity() at all. It obscures the nested functionality from the Postgres query planner and may lead so suboptimal query plans. Typically, you can replace it with a correlated or a LATERAL subquery, which can be planned and optimized by Postgres in context of the outer query. I appended a demo to the fiddle:

db<>fiddle here

Related:

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • execute a nested select statement within a select statement? I am bit confused how to do sO – kafka Oct 12 '20 at 13:56
  • `country` does not work SELECT aggregate_validity('country', 1); `country` is seen as text – kafka Oct 12 '20 at 14:26
  • @kafka: I added some more details how to use a subquery instead. Also, `SELECT aggregate_validity('country', 1);` should work just fine, as demonstrated in both fiddles. Note that `'country'` is an **untyped string literal** here. If you pass a value of type `text`, you need to cast explicitly! Like: `SELECT aggregate_validity(country_variable_as_text_type::regclass, 1);` See: https://stackoverflow.com/a/19943343/939860, https://stackoverflow.com/a/25005922/939860, https://stackoverflow.com/a/13676871/939860 – Erwin Brandstetter Oct 12 '20 at 15:37
  • Also consider the additional module `range_agg`. I added pointers above. – Erwin Brandstetter Oct 13 '20 at 23:06
  • I have at this stage already ensured for gaps an overlaps, hence this does not need to taken care of - but thanks for the reminder. – kafka Oct 16 '20 at 16:44