我正在开发一个带有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值和/或认识到它与'设为DEFAULT'相同?
我会尝试只修补Rails 2.3.x ActiveRecord内部版本,但我知道如果我这样做,那么在转换到Rails 3时我会被搞砸。
我已经考虑过尝试使用PL / pgSQL触发器来修复问题,但我无法弄清楚如何告诉PostgreSQL PL / pgSQL“取消定义”NEW.col2值或者说NEW.col2 := DEFAULT(不起作用)。
感谢答案和/或建议!
答案 0 :(得分:4)
更简单的方法是定义自己的序列,并在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
答案 1 :(得分: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应该是类似的。
希望这有帮助。
答案 2 :(得分:0)
找到有用的东西。我可能需要在很多表中使用不同的序列来增加第二个SERIAL列。下面是一个解决方案,其中任何数量的表只需要1个触发函数,其中col1由唯一序列递增。
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行为,但这将同时起作用。