验证在另一个表上具有条件时,数据库中的唯一性验证

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数据库上依赖于另一个表的"条件"索引?验证具有条件,数据库唯一性验证的解决方案是您的条件取决于同一个表上的属性.还是另一种解决方法?

Erw*_*ter 6

不幸的是,没有像前一个问题那样简单和干净的解决方案.

这应该做的工作:

  • is_publishedChild表中添加冗余标志

    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)