代码之家  ›  专栏  ›  技术社区  ›  pilcrow

PostgreSQL:更新意味着跨分区移动

  •  21
  • pilcrow  · 技术社区  · 14 年前

    (注:更新后采用以下答案。)

    UPDATE 触发器和过程将记录从一个分区“移动”到另一个分区,如果 更新

    例如,我将表记录划分为活动记录和非活动记录,如下所示:

    create table RECORDS (RECORD varchar(64) not null, ACTIVE boolean default true);
    create table ACTIVE_RECORDS   ( check (ACTIVE) ) inherits RECORDS;
    create table INACTIVE_RECORDS ( check (not ACTIVE) ) inherits RECORDS;
    

    这个 INSERT 触发器和函数运行良好:新的活动记录放在一个表中,新的非活动记录放在另一个表中。我想要 ,将记录从一个子表“移动”到另一个子表,但我遇到一个错误,表明这可能不可能。

    触发器规范和错误消息:

    pg=> CREATE OR REPLACE FUNCTION record_update()
         RETURNS TRIGGER AS $$
         BEGIN
           IF (NEW.active = OLD.active) THEN
             RETURN NEW;
           ELSIF (NEW.active) THEN
             INSERT INTO active_records VALUES (NEW.*);
             DELETE FROM inactive_records WHERE record = NEW.record;
           ELSE
             INSERT INTO inactive_records VALUES (NEW.*);
             DELETE FROM active_records WHERE record = NEW.record;
           END IF;
           RETURN NULL;
         END;
         $$
         LANGUAGE plpgsql;
    
    pg=> CREATE TRIGGER record_update_trigger
           BEFORE UPDATE ON records
           FOR EACH ROW EXECUTE PROCEDURE record_update();
    
    pg=> select * from RECORDS;
    record | active 
    --------+--------
    foo    | t         -- 'foo' record actually in table ACTIVE_RECORDS
    bar    | f         -- 'bar' record actually in table INACTIVE_RECORDS
    (2 rows)
    
    pg=> update RECORDS set ACTIVE = false where RECORD = 'foo';
    ERROR:  new row for relation "active_records" violates check constraint "active_records_active_check"
    

    使用触发器过程(返回NULL等)向我建议在调用触发器之前检查约束并引发错误,这意味着我当前的方法无法工作。这能起作用吗?

    以下是 更新 我最终使用的触发过程与分配给每个分区的过程相同。功劳全在你 Bell ,其答案为我提供了在分区上触发的关键洞察:

    CREATE OR REPLACE FUNCTION record_update()
    RETURNS TRIGGER AS $$
    BEGIN
      IF ( (TG_TABLE_NAME = 'active_records' AND NOT NEW.active)
            OR
           (TG_TABLE_NAME = 'inactive_records' AND NEW.active) ) THEN
        DELETE FROM records WHERE record = NEW.record;
        INSERT INTO records VALUES (NEW.*);
        RETURN NULL;
      END IF;
    
      RETURN NEW;
    END;
    $$
    LANGUAGE plpgsql;
    
    1 回复  |  直到 7 年前
        1
  •  18
  •   Synchro    10 年前

    它可以工作,执行移动的触发器只需要为每个分区定义,而不是为整个表定义。因此,请像处理表定义和INSERT触发器一样开始

    CREATE TABLE records (
     record varchar(64) NOT NULL,
     active boolean default TRUE
    );
    
    CREATE TABLE active_records (CHECK (active)) INHERITS (records);
    CREATE TABLE inactive_records (CHECK (NOT active)) INHERITS (records);
    
    CREATE OR REPLACE FUNCTION record_insert()
    RETURNS TRIGGER AS $$
    BEGIN
      IF (TRUE = NEW.active) THEN
        INSERT INTO active_records VALUES (NEW.*);
      ELSE
        INSERT INTO inactive_records VALUES (NEW.*);
      END IF;
      RETURN NULL;
    END;
    $$
    LANGUAGE plpgsql;
    
    CREATE TRIGGER record_insert_trigger
     BEFORE INSERT ON records
     FOR EACH ROW EXECUTE PROCEDURE record_insert();
    

    ... 让我们有一些测试数据。。。

    INSERT INTO records VALUES ('FirstLittlePiggy', TRUE);
    INSERT INTO records VALUES ('SecondLittlePiggy', FALSE);
    INSERT INTO records VALUES ('ThirdLittlePiggy', TRUE);
    INSERT INTO records VALUES ('FourthLittlePiggy', FALSE);
    INSERT INTO records VALUES ('FifthLittlePiggy', TRUE);
    

    现在是分区上的触发器。if NEW.active=OLD.active检查在检查active的值时是隐式的,因为我们首先知道表中允许什么。

    CREATE OR REPLACE FUNCTION active_partition_constraint()
      RETURNS TRIGGER AS $$
        BEGIN
          IF NOT (NEW.active) THEN
            INSERT INTO inactive_records VALUES (NEW.*);
            DELETE FROM active_records WHERE record = NEW.record;
            RETURN NULL;
          ELSE
            RETURN NEW;
          END IF;
        END;
        $$
        LANGUAGE plpgsql;
    
    CREATE TRIGGER active_constraint_trigger
      BEFORE UPDATE ON active_records
      FOR EACH ROW EXECUTE PROCEDURE active_partition_constraint();
    
    CREATE OR REPLACE FUNCTION inactive_partition_constraint()
      RETURNS TRIGGER AS $$
        BEGIN
          IF (NEW.active) THEN
            INSERT INTO active_records VALUES (NEW.*);
            DELETE FROM inactive_records WHERE record = NEW.record;
            RETURN NULL;
          ELSE
            RETURN NEW;
          END IF;
        END;
        $$
        LANGUAGE plpgsql;
    
    CREATE TRIGGER inactive_constraint_trigger
      BEFORE UPDATE ON inactive_records 
      FOR EACH ROW EXECUTE PROCEDURE inactive_partition_constraint();
    

    scratch=> SELECT * FROM active_records;
          record      | active 
    ------------------+--------
     FirstLittlePiggy | t
     ThirdLittlePiggy | t
     FifthLittlePiggy | t
    (3 rows)
    
    scratch=> UPDATE records SET active = FALSE WHERE record = 'ThirdLittlePiggy';
    UPDATE 0
    scratch=> SELECT * FROM active_records;
          record      | active 
    ------------------+--------
     FirstLittlePiggy | t
     FifthLittlePiggy | t
    (2 rows)
    
    scratch=> SELECT * FROM inactive_records;
          record       | active 
    -------------------+--------
     SecondLittlePiggy | f
     FourthLittlePiggy | f
     ThirdLittlePiggy  | f
    (3 rows)