yor*_*bro 4 postgresql indexing database-design ruby-on-rails unique-index
当验证有条件但我的要求已经改变时,我在数据库中的唯一性验证中询问了类似的问题,因此这个问题.
当有多个进程时,在Rails中使用唯一性验证是不安全的,除非在数据库上强制执行约束(在我的情况下是PostgreSQL数据库,所以请参阅http://robots.thoughtbot.com/the-perils-of-uniqueness-validations).
在我的例子中,唯一性验证是有条件的:只有在另一个模型上的另一个属性变为真时才应该强制执行.所以我有
class Parent < ActiveRecord::Base
# attribute is_published
end
class Child < ActiveRecord::Base
belongs_to :parent
validates_uniqueness_of :text, if: :parent_is_published?
def parent_is_published?
self.parent.is_published
end
end
Run Code Online (Sandbox Code Playgroud)
因此模型Child有两个属性:( parent_id与...关联Parent)和text(文本属性).该模型Parent有一个属性:( is_published布尔值).如果是真的,那么text在所有类型的模型中应该是唯一的.Childparent.is_published
使用http://robots.thoughtbot.com/the-perils-of-uniqueness-validations中建议的唯一索引太过限制,因为无论is_published的值如何,它都会强制执行约束.
是否有人知道PostgreSQL数据库上依赖于另一个表的"条件"索引?验证具有条件时,数据库中唯一性验证的解决方案是您的条件取决于同一个表上的属性.还是另一种解决方法?
不幸的是,没有像前一个问题那样简单和干净的解决方案.
这应该做的工作:
is_published向Child表中添加冗余标志
ALTER TABLE child ADD column is_published boolean NOT NULL;
Run Code Online (Sandbox Code Playgroud)
DEFAULT FALSE在插入时,使它或您在父列中通常具有的任何内容.
它需要NOT NULL避免在外键中具有NULL值和默认MATCH SIMPLE行为的漏洞:
仅当第三列为NOT NULL时才有两列外键约束
添加一个(看似毫无意义的)唯一约束 parent(parent_id, is_published)
ALTER TABLE parent ADD CONSTRAINT parent_fk_uni
UNIQUE (parent_id, is_published);
Run Code Online (Sandbox Code Playgroud)
由于parent_id是主键,因此无论如何组合都是唯一的.但这是以下fk约束所必需的.
而不是引用的parent(parent_id)一个简单的外键约束,创建一个多列外键(parent_id, is_published)用ON UPDATE CASCADE.
这样,child.is_published系统可以自动维护和强制执行状态,并且比使用自定义触发器实现的更可靠:
ALTER TABLE child
ADD CONSTRAINT child_special_fkey FOREIGN KEY (parent_id, is_published)
REFERENCES parent (parent_id, is_published) ON UPDATE CASCADE;
Run Code Online (Sandbox Code Playgroud)然后像在上一个答案中一样添加部分UNIQUE索引.
CREATE UNIQUE INDEX child_txt_is_published_idx ON child (text)
WHERE is_published;
Run Code Online (Sandbox Code Playgroud)当然,在child表中插入行时,您将被迫使用当前的当前状态parent.is_published.但重点是:强制执行参照完整性.
或者,不是调整现有的架构,而是完整的布局:
CREATE TABLE parent(
parent_id serial PRIMARY KEY
, is_published bool NOT NULL DEFAULT FALSE
--, more columns ...
, UNIQUE (parent_id, is_published) -- required for fk
);
CREATE TABLE child (
child_id serial PRIMARY KEY
, parent_id integer NOT NULL
, is_published bool NOT NULL DEFAULT FALSE
, txt text
, FOREIGN KEY (parent_id, is_published)
REFERENCES parent (parent_id, is_published) ON UPDATE CASCADE
);
CREATE UNIQUE INDEX child_txt_is_published_idx ON child (text)
WHERE is_published;
Run Code Online (Sandbox Code Playgroud)