删除管理分区的函数中的某些表

时间:2014-01-30 18:37:16

标签: function postgresql plpgsql partitioning dynamic-sql

我需要一个自动管理数据库分区的功能。我找到了一个一次创建2个月的功能,并将其调整为适用于具有不同主键和创建日期键的宿舍和多个表。
现在我需要将它从删除最后一个季度的分区更改。相反,如果它们来自前一年,我只希望它删除四分之一的分区。如何更改前几个季度的代码呢?

相关代码:

-- check if the partition for the previous quarter exists
v_date_from := date_trunc('quarter', v_current_date - '3 month'::interval);
v_partition_name := master_table || '_Q' || EXTRACT(QUARTER FROM v_date_from) || '_' || EXTRACT(YEAR FROM v_date_from);
v_rule_name := 'rule_' || master_table || '_Q' || EXTRACT(QUARTER FROM v_date_from) || '_' || EXTRACT(YEAR FROM v_date_from);

SELECT COUNT(*) = 1 INTO v_exists FROM pg_tables WHERE schemaname = 'public' AND tablename = v_partition_name;

IF (v_exists) THEN

    EXECUTE 'DROP RULE ' || v_rule_name || ' ON ' || master_table;
    EXECUTE 'DROP TABLE ' || v_partition_name;

END IF;

完成功能:

CREATE OR REPLACE FUNCTION manage_partitions(timestamp without time zone, master_table character varying, prime_key character varying, prime_date character varying) RETURNS void AS
$BODY$
DECLARE

    -- name of the next partition and rule (and interval boundaries)
    v_partition_name    VARCHAR(32);
    v_rule_name         VARCHAR(32);

    v_date_from         TIMESTAMP;
    v_date_to           TIMESTAMP;

    -- current date (if NULL, a current timestamp is used)
    v_date              ALIAS FOR $1;
    v_current_date      TIMESTAMP;

    -- used just for checking existence of the partitions
    v_exists            BOOLEAN;

BEGIN

    IF (v_date IS NULL) THEN
        v_current_date := current_timestamp;
    ELSE
        v_current_date := v_date;
    END IF;

    -- check if the partition for the previous quarter exists
    v_date_from := date_trunc('quarter', v_current_date - '3 month'::interval);
    v_partition_name := master_table || '_Q' || EXTRACT(QUARTER FROM v_date_from) || '_' || EXTRACT(YEAR FROM v_date_from);
    v_rule_name := 'rule_' || master_table || '_Q' || EXTRACT(QUARTER FROM v_date_from) || '_' || EXTRACT(YEAR FROM v_date_from);

    SELECT COUNT(*) = 1 INTO v_exists FROM pg_tables WHERE schemaname = 'public' AND tablename = v_partition_name;

    IF (v_exists) THEN

        EXECUTE 'DROP RULE ' || v_rule_name || ' ON ' || master_table;
        EXECUTE 'DROP TABLE ' || v_partition_name;

    END IF;

    -- create a partition for this quarter
    v_date_from := date_trunc('quarter', v_current_date);
    v_date_to := v_date_from + '3 month';
    v_partition_name := master_table || '_Q' || EXTRACT(QUARTER FROM v_date_from) || '_' || EXTRACT(YEAR FROM v_date_from);
    v_rule_name := 'rule_' || master_table || '_Q' || EXTRACT(QUARTER FROM v_date_from) || '_' || EXTRACT(YEAR FROM v_date_from);

    SELECT COUNT(*) = 1 INTO v_exists FROM pg_tables WHERE schemaname = 'public' AND tablename = v_partition_name;

    IF (NOT v_exists) THEN

        EXECUTE 'CREATE TABLE ' || v_partition_name || ' (PRIMARY KEY (' || prime_key || '), CHECK (' || prime_date || ' >= ''' || v_date_from || ''' AND ' || prime_date || ' < ''' || v_date_to || ''')) INHERITS (' || master_table || ')';
        EXECUTE 'CREATE RULE ' || v_rule_name || ' AS ON INSERT TO ' || master_table || ' DO INSTEAD INSERT INTO ' || v_partition_name || ' VALUES (NEW.*)';
        -- if you need to create indexes / foreign keys / whatever on the partition, you may do it here

    END IF;

    -- create a partition for next quarter
    v_date_from := date_trunc('quarter', v_current_date + '3 month'::interval);
    v_date_to := v_date_from + '3 month';
    v_partition_name := master_table || '_Q' || EXTRACT(QUARTER FROM v_date_from) || '_' || EXTRACT(YEAR FROM v_date_from);
    v_rule_name := 'rule_' || master_table || '_Q' || EXTRACT(QUARTER FROM v_date_from) || '_' || EXTRACT(YEAR FROM v_date_from);

    SELECT COUNT(*) = 1 INTO v_exists FROM pg_tables WHERE schemaname = 'public' AND tablename = v_partition_name;

    IF (NOT v_exists) THEN

        EXECUTE 'CREATE TABLE ' || v_partition_name || ' (PRIMARY KEY (' || prime_key || '), CHECK (' || prime_date || ' >= ''' || v_date_from || ''' AND ' || prime_date || ' < ''' || v_date_to || ''')) INHERITS (' || master_table || ')';
        EXECUTE 'CREATE RULE ' || v_rule_name || ' AS ON INSERT TO ' || master_table || ' DO INSTEAD INSERT INTO ' || v_partition_name || ' VALUES (NEW.*)';

        -- if you need to create indexes / foreign keys / whatever on the partition, you may do it here

    END IF;

END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;

2 个答案:

答案 0 :(得分:1)

您正在使用的代码过时。对于任何版本的Postgres来说,效率也相当低。 (我对你从哪里得到它感兴趣?)

使用现代SQL和PL / pgSQL的功能完全重写整个功能

CREATE OR REPLACE FUNCTION manage_partitions(
     v_date       timestamp
    ,master_table regclass
    ,prime_key    text
    ,prime_date   text)
  RETURNS void AS
$func$
DECLARE
   v_current_date   timestamp := COALESCE(v_date, now()); -- fallback for NULL
   v_date_from      timestamp;
   v_partition_name text;

BEGIN
   -- drop partition for previous quarter of previous year if exists --
   v_date_from := date_trunc('quarter', v_current_date - interval '3 month');

   IF v_date_from < date_trunc('year', now()) THEN -- your (odd?) condition
      v_partition_name := master_table || to_char(v_date_from, '"_q"Q_YYYY');

      EXECUTE format(
         'DROP RULE  IF EXISTS %I ON %s;
          DROP TABLE IF EXISTS %I'
         ,'rule_' || master_table || to_char(v_date_from, '"_q"Q_YYYY')
         ,master_table
         ,v_partition_name);
   END IF;

   -- create partition for this quarter --
   v_date_from := date_trunc('quarter', v_current_date);
   v_partition_name := master_table || to_char(v_date_from, '"_q"Q_YYYY');

   IF NOT EXISTS (
      SELECT 1 FROM pg_tables t
      WHERE  t.schemaname = 'public'
      AND    t.tablename = v_partition_name) THEN

      EXECUTE format(
         'CREATE TABLE %$1I (
             PRIMARY KEY (%$2I), CHECK (%$3L >= %$4L AND %$3L < %$5L))
             INHERITS (%$6s);
          CREATE RULE %$7I AS ON INSERT TO %$6s DO INSTEAD
          INSERT INTO %$1I VALUES (NEW.*)'
         ,v_partition_name
         ,prime_key
         ,prime_date
         ,v_date_from
         ,v_date_from + interval '3 month'
         ,master_table
         ,'rule_' || master_table || to_char(v_date_from, '"_q"Q_YYYY')
      );
   END IF;

   -- create partition for next quarter --
   v_date_from := date_trunc('quarter', v_current_date + interval '3 month');
   v_partition_name := master_table || to_char(v_date_from, '"_q"Q_YYYY');

   IF NOT EXISTS (
      SELECT 1 FROM pg_tables t
      WHERE  t.schemaname = 'public'
      AND    t.tablename = v_partition_name) THEN

      EXECUTE format(
         'CREATE TABLE %$1I (
             PRIMARY KEY (%$2I), CHECK (%$3L >= %$4L AND %$3L < %$5L))
             INHERITS (%$6s);
          CREATE RULE %$7I AS ON INSERT TO %$6s DO INSTEAD
          INSERT INTO %$1I VALUES (NEW.*)'
         ,v_partition_name
         ,prime_key
         ,prime_date
         ,v_date_from
         ,v_date_from + interval '3 month'
         ,master_table
         ,'rule_' || master_table || to_char(v_date_from, '"_q"Q_YYYY')
      );
   END IF;

END
$func$ LANGUAGE plpgsql;
  • 您可以在声明时分配变量以简化代码。

  • 替换

    '_Q' || EXTRACT(QUARTER FROM v_date_from) || '_' || EXTRACT(YEAR FROM v_date_from)
    

    更简单,更快

    to_char(v_date_from, '"Q_"Q_YYYY')
    

    to_char() in the manual

  • 使用上级IF EXISTS (...) THEN ...。然后我们可以删除无用的变量v_exists。详情:
    PL/pgSQL checking if a row exists - SELECT INTO boolean

  • 查看format()上的手册页(Postgres 9.1 + )。如果您打算使用动态SQL,则需要了解它。

  • 使用regclass的对象标识符类型master_table来验证它是否存在,并且对于search_path的当前设置是可见的,并同时阻止SQL注入。这个相关答案的细节:
    Table name as a PostgreSQL function parameter

  • 请勿使用the outdated and discouraged ALIAS FOR clause。请改用参数名称,就像您已经为所有其他函数参数一样。

  • 请注意我是如何用小写字母('Q' - &gt;'q')替换标识符中的大写字母(这是一个坏主意)。更多在这个相关答案的最后一段:
    Define table and column names as arguments in a plpgsql function?

  • 我还删除了变量v_date_tov_rule_name,并将其替换为表达式,因为这些只在我的代码中使用过一次。

答案 1 :(得分:0)

我认为您需要更改以下行:

SELECT COUNT(*) = 1 INTO v_exists FROM pg_tables 
WHERE schemaname = 'public' AND tablename = v_partition_name;

类似于:

SELECT COUNT(*) = 1 INTO v_exists FROM pg_tables 
WHERE schemaname = 'public' AND tablename = v_partition_name 
and v_date_from < date_trunc('year', current_date);

仅当分区来自上一年时,才会返回1