2014-01-30 114 views
1

我需要一个功能来自动管理数据库中的分区。我发现一个函数每次创建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; 
+0

您应该提供您发现一次创建了2个月的函数的源代码,以及(一如既往!)您的Postgres版本。 –

回答

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

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,你需要知道它。

  • 使用对象标识符类型regclassmaster_table以验证它存在且具有用于SEARCH_PATH当前设置可见光和防止SQL注入同时。在这个相关答案细节:
    Table name as a PostgreSQL function parameter

  • 不要使用the outdated and discouraged ALIAS FOR clause。改为使用参数名称,就像您对所有其他函数参数所做的一样。

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

  • 我也放弃了变数v_date_tov_rule_name与表达式替换它们,因为这些都是在我的代码只能使用一次。