jzadeh
jzadeh

Reputation: 703

Modifying PL/pgSQL function using generate_series()

I am working on a version of PostgreSQL 8.3 that does not support the variant of the generate_series() functions for date/time series. I have this ugly workaround at the moment that calls the function with a third argument like:

select table_union('2012-12-01', '2013-02-20', 79)

I have to compute the last parameter manually to determine the number of days forgenerate_series().

What is the best way to modify this script so that I only need two arguments in the function call?

Is there a way to modify the below code to work the same way only given two arguments to the function like this?

select table_union('2012-12-01', '2013-02-20')
create or replace function table_union(date_from date, date_to date, numday int)
returns void language plpgsql as $$
declare
    day_1 date;
    _stop_ bigint := (date_from::date - date_to::date)::int; 
begin
    for day_1 in 
        select date_from + s.a as dates from generate_series(0, $3 ) as s(a)
    loop
        execute 'insert into dhcp.dhcp_map select * from dhcp.final_map_'|| trim( leading ' ' from to_char(extract(month from day_1), '09')) ||'_'|| 
        trim( leading ' ' from to_char(extract(day from day_1), '09'));  --to_char introduces a leadin space use trim to remove
    end loop;
end; $$;

Update: I tried modifying my code after the great suggestions in the answer below but still have some errors:


create or replace function 
table_union(date_from date, date_to date) 
returns void language plpgsql 
as $func$ 
declare day_1 date; 
begin 
for day_1 in select date_from
 + s.a as dates from generate_series(0, (date_to - date_from)) 
as s(a) 
loop 
execute 
'insert into dhcp.dhcp_map select * from dhcp.final_map_'||
 array_to_string(ARRAY(SELECT to_char(date_from + generate_series(0, (date_to - date_from)), 'MM_DD')) ) ; 
end loop;
 end; 
$func$;

Upvotes: 1

Views: 1516

Answers (1)

Erwin Brandstetter
Erwin Brandstetter

Reputation: 658112

CREATE OR REPLACE FUNCTION table_union(date_from date, date_to date)
  RETURNS void LANGUAGE plpgsql AS
$func$
BEGIN
   EXECUTE '
INSERT INTO dhcp.dhcp_map
SELECT * FROM dhcp.final_map_'
   || array_to_string(ARRAY(SELECT to_char(date_from
                      + generate_series(0, (date_to - date_from)), 'MM_DD')), '
UNION ALL
SELECT * FROM dhcp.final_map_'
   );
END
$func$;

Call:

SELECT table_union('2012-12-01', '2013-01-10');

Major points

  • Most important for performance: instead of one INSERT statement per day I generate and execute a single INSERT statement for everything. With RETURNS text and RETURN in place of EXECUTE you can see the generated statement:

    INSERT INTO dhcp.dhcp_map
    SELECT * FROM dhcp.final_map_12_01
    UNION ALL
    SELECT * FROM dhcp.final_map_12_02
    UNION ALL
    SELECT * FROM dhcp.final_map_12_03
    ...
    

    ->sqlfiddle for Postgres 8.3.
    As long as the amount of data to be inserted f9its into RAM, this is substantially faster.

  • If your INSERT should be huge you may want stick with one INSERT per day.

CREATE OR REPLACE FUNCTION table_union_huge(date_from date, date_to date)
  RETURNS void LANGUAGE plpgsql AS
$func$
BEGIN
FOR i IN 0 .. (date_to - date_from)
LOOP
   EXECUTE 'INSERT INTO dhcp.dhcp_map
SELECT * FROM dhcp.final_map_'|| (date_from + i)::text;
END LOOP;
END
$func$; 
  • Remove redundant parameter $3 from function call. Replace with simple subtraction $2 - $1. Returns an integer (difference in days) in Postgres.

  • Simplified generate_series() call.

  • Replaced LOOP with ARRAY constructor and array_to_string() to create a single statement.

  • Largely simplified string handling. Just use the pattern MM_DD with to_char() to extract your string from the date.

All Links to the manual of Postgres 8.3.

Upvotes: 3

Related Questions