Self-managing PostgreSQL partition tables

前端 未结 4 968
深忆病人
深忆病人 2021-01-03 08:52

I am trying to make a self-managing partition table setup with Postgres. It all revolves around this function but I can\'t seem to get Postgres to accept my table names. Any

4条回答
  •  既然无缘
    2021-01-03 09:48

    You are mixing double precision output of date_part() with text '-'. That doesn't make sense to PostgreSQL. You would need an explicit cast to text. But there is a much simpler way to do all of this:

    startdate:=date_part('year',to_timestamp(NEW.date))
    ||'-'||date_part('month',to_timestamp(NEW.date))
    ||'-'||date_part('day',to_timestamp(NEW.date));

    Use instead:

    startdate := to_char(NEW.date, 'YYYY-MM-DD');
    

    This makes no sense either:

    EXECUTE 'CREATE TABLE $1 (
            CHECK (date >= DATE $2 AND date < DATE $3 )
        ) INHERITS (pings)' USING quote_ident(tablename),startdate,enddate;

    You can only supply values with the USING clause. Read the manual here. Try instead:

    EXECUTE 'CREATE TABLE ' || quote_ident(tablename) || ' (
                CHECK ("date" >= ''' || startdate || ''' AND
                       "date" <  ''' || enddate   || '''))
                INHERITS (ping)';
    

    Or better yet, use format(). See below.

    Also, like @a_horse answered: You need to put your text values in single quotes.

    Similar here:

    EXECUTE 'INSERT INTO $1 VALUES (NEW.*)' USING quote_ident(tablename);

    Instead:

    EXECUTE 'INSERT INTO ' || quote_ident(tablename) || ' VALUES ($1.*)'
    USING NEW;
    

    Related answer:

    • How to dynamically use TG_TABLE_NAME in PostgreSQL 8.2?

    Aside: While "date" is allowed for a column name in PostgreSQL it is a reserved word in every SQL standard. Don't name your column "date", it leads to confusing syntax errors.

    Complete working demo

    CREATE TABLE ping (ping_id integer, the_date date);
    
    CREATE OR REPLACE FUNCTION trg_ping_partition()
      RETURNS trigger AS
    $func$
    DECLARE
       _tbl text := to_char(NEW.the_date, '"ping_"YYYY_DDD_') || NEW.ping_id;
    BEGIN
       IF NOT EXISTS (
          SELECT 1
          FROM   pg_catalog.pg_class c
          JOIN   pg_catalog.pg_namespace n ON n.oid = c.relnamespace
          WHERE  n.nspname = 'public'  -- your schema
          AND    c.relname = _tbl
          AND    c.relkind = 'r') THEN
    
          EXECUTE format('CREATE TABLE %I (CHECK (the_date >= %L AND
                                                  the_date <  %L)) INHERITS (ping)'
                  , _tbl
                  , to_char(NEW.the_date,     'YYYY-MM-DD')
                  , to_char(NEW.the_date + 1, 'YYYY-MM-DD')
                  );
       END IF;
    
       EXECUTE 'INSERT INTO ' || quote_ident(_tbl) || ' VALUES ($1.*)'
       USING NEW; 
    
       RETURN NULL;
    END
    $func$ LANGUAGE plpgsql SET search_path = public;
    
    CREATE TRIGGER insbef
    BEFORE INSERT ON ping
    FOR EACH ROW EXECUTE PROCEDURE trg_ping_partition();
    
    • Update: Later versions of Postgres have more elegant ways to check if a table exists:

      • How to check if a table exists in a given schema
    • to_char() can take a date as $1. That's converted to timestamp automatically.
      The manual on date / time functions.

    • (Optionally) SET the search_path for the scope of your function to avoid misconduct with a changed search_path setting.

    • Multiple other simplifications and improvements. Compare the code.

    Tests:

    INSERT INTO ping VALUES (1, now()::date);
    INSERT INTO ping VALUES (2, now()::date);
    INSERT INTO ping VALUES (2, now()::date + 1);
    INSERT INTO ping VALUES (2, now()::date + 1);
    

    SQL Fiddle.

提交回复
热议问题