【问题标题】:Rails misbehaving wrt Postgres SERIAL NOT NULL columnRails 行为不端 wrt Postgres SERIAL NOT NULL 列
【发布时间】:2010-10-13 00:14:39
【问题描述】:

我正在开发一个(当前)带有 PostgreSQL 8.4 数据库后端的 Rails 2.3.x 应用程序。在我的 Rails 应用程序中,我有一个与数据库表对应的模型,该表具有两列数据类型为 SERIAL 并设置为 NOT NULL。我将这些列之一设置为 Rails 中的主键和 PostgreSQL 约束。

表定义:

CREATE TABLE problem_table
(
  col1 serial NOT NULL,
  col2 serial NOT NULL,
  other_col1 character varying,
  other_col2 character varying,
  ...,
  CONSTRAINT problem_table_pkey PRIMARY KEY (col1)
);

模型类定义:

class ModelClass1 < ActiveRecord::Base
  self.table_name = 'problem_table'
  self.primary_key = 'col1'
end

我的问题是关于非主键 SERIAL NOT NULL 列。当我尝试执行 Rails ActiveRecord::Base#create 时,Rails 正确地没有为主键 SERIAL NOT NULL 列设置值,而是为另一个列设置了 NULL 值,这导致 PostgreSQL 抱怨NOT NULL 列被设置为 NULL。

我告诉 Rails 做什么:

ModelClass1.create(
  other_col1: 'normal'
  other_col2: 'data',
  ...
);

Rails 告诉 PostgreSQL 什么:

INSERT INTO problem_table (
  col2, 
  other_col1, 
  other_col2,
  ...
) VALUES (
  NULL,
  'normal',
  'data',
  ...
);

我的问题是,我怎样才能让 Rails 停止为该列传递 NULL 并且不传递任何内容,让 DEFAULT nextval(my_seq) 接管?或者,如果这不可能,我如何告诉 PostgreSQL 在传递时忽略此 NULL 值和/或将其识别为与“设置为默认值”相同?

我会尝试只修补 Rails 2.3.x ActiveRecord 内部,但我知道如果我这样做了,我会在过渡到 Rails 3 时搞砸。

我已经研究过在插入之前尝试使用 PL/pgSQL 触发器来修复问题,但我不知道如何使用 PL/pgSQL 告诉 PostgreSQL '取消定义' NEW.col2 值或说 NEW.col2 := DEFAULT(不起作用)。

感谢您的回答和/或建​​议!

【问题讨论】:

    标签: ruby-on-rails postgresql ruby-on-rails-3 plpgsql


    【解决方案1】:

    更简单的方法可能是定义您自己的序列,并在 ActiveRecord 回调中使用 Postgres 自己的nextval()nextval() 在一个原子操作中处理将序列推进一步和返回下一个值。

    在迁移中:

    def self.up
        execute "CREATE SEQUENCE myseq"
    end
    
    def self.down
        execute "DROP SEQUENCE myseq"
    end    
    

    在模型中:

    before_save :set_column_from_sequence, :on => :create
    
    def set_column_from_sequence
        self.mycolumn = self.class.connection.select_value("SELECT nextval('myseq')")
    end
    

    【讨论】:

    • 谢谢!在我的 rails 2.3 到 3.1 升级期间有很大帮助
    【解决方案2】:

    不确定确切的 PL/pgSQL 语法(我的 PostgreSQL 安装在家里,所以我不能玩弄它)但在 Oracle PL/SQL 中我会做类似的事情

    CREATE OR REPLACE TRIGGER MYSCHEMA.PROBLEM_TABLE_BI
      BEFORE INSERT ON MYSCHEMA.PROBLEM_TABLE
      REFERENCING NEW AS NEW
      FOR EACH ROW
    BEGIN
      IF :NEW.COL2 IS NULL THEN
        :NEW.COL2 := MY_SEQ.NEXT_VAL;
      END IF;
    END PROBLEM_TABLE_BI;
    

    PL/pgSQL 应该类似。

    希望这会有所帮助。

    【讨论】:

    • 谢谢。我现在有一些基于这个策略的东西!
    • @chrisgoddard - 这就是生活。 :-}
    【解决方案3】:

    找到了一些有用的东西。我可能需要在相当多的表中使用不同的序列进行递增的第二个 SERIAL 列。下面是一个解决方案,其中任何数量的包含 col1 按唯一序列递增的表只需要 1 个触发函数。

    CREATE OR REPLACE FUNCTION fn_set_col1_as_nextval_sequence_if_null()
      RETURNS trigger AS
    $BODY$
      BEGIN
        IF NEW.col1 IS NULL THEN
          SELECT nextval(TG_ARGV[0]) INTO NEW.col1;
        END IF;
        RETURN NEW;
      END;
    $BODY$
      LANGUAGE 'plpgsql';
    
    CREATE TRIGGER trg_set_col1_as_nextval_sequence_on_problem_table_create
    BEFORE INSERT
    ON problem_table
    FOR EACH ROW
    EXECUTE PROCEDURE fn_set_col1_as_nextval_sequence_if_null('problem_table_col1_seq');
    

    如果可能的话,我仍然想弄清楚如何修复 2.3.x 和 3.0 的 Rails 行为,但这会在此期间起作用。

    【讨论】:

      猜你喜欢
      • 2015-06-04
      • 2020-10-16
      • 2019-11-16
      • 2021-03-27
      • 2016-07-14
      • 2012-01-27
      • 2013-07-26
      • 1970-01-01
      • 2020-03-22
      相关资源
      最近更新 更多