4

我正在开发一个(当前)带有 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 (这不起作用)。

答案和/或建议表示赞赏!

4

3 回答 3

4

更简单的方法可能是定义您自己的序列,并nextval()在 ActiveRecord 回调中使用 Postgres 自己的。 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
于 2010-11-24T03:02:16.637 回答
0

不确定精确的 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 应该类似。

希望这可以帮助。

于 2010-10-13T11:49:36.080 回答
0

找到了一些有用的东西。我可能需要在相当多的表中使用不同的序列进行递增的第二个 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 行为,但这将同时起作用。

于 2010-10-13T15:55:58.917 回答