两列之一为空时的唯一索引

Unique index when one of two columns is null

(Rails 4.2.1, Sqlite3) 我有三个模型——M1、M2、M3。

M1belongs_toM2

M1 也belongs_toM3

M1 有一个 :name(字符串)字段。

我有以下必须验证的限制条件:

1) M1 记录可以关联 M2 或 M3,但不能同时关联两者。

2) M1名称必须在指定的M2或M3中是唯一的。

我已经在模型中实现了约束条件 (1),它按预期工作。 (我提到它只是因为它可能与场景有关)。

对于约束(2),我在迁移中添加了一个索引如下:

add_index :m1s, [:name, :m2_id, :m3_id], unique: true, name: "idx_m1_name"

然后我打电话给:

> m2 = M2.create! # success
> m1_1 = M1.create!(name: 'm1_1', m2: m2) #success
> m1_2 = M1.create!(name: 'm1_1', m2: m2) # this line should fail, but doesn't

m1_1 和 m1_2 被创建 - 我预计 m1_2 应该由于唯一性约束而失败。

我检查了索引是否按预期添加。此外,根据约束 1,m3_id 在 m1_1 和 m1_2 中均为 nil,不确定是否相关。

为什么约束没有被检查?

所以在这两种情况下 m3_id 都是 NULL?在 Sqlite3 中,空值被认为与唯一索引上下文中的另一个空值不同。

For the purposes of unique indices, all NULL values are considered to different from all other NULL values and are thus unique

https://sqlite.org/lang_createindex.html

我认为 MySQL 和 Postgres 也一样。

你可以用两个索引来表示。

add_index :m1s, [:name, :m2_id], unique: true
add_index :m1s, [:name, :m3_id], unique: true

由于出于唯一性目的,空值被认为是不同的,因此第一个索引不会限制仅设置了 m3_id 的行,第二个索引反之亦然。

(您可能也对 CHECK constraint that checks that exactly one of m2_id or m3_id is null. In Rails, CHECK constraints have the minor caveat of requiring the schema file to be expressed in SQL. For a more general solution to "indices that only apply to some rows", see partial indices 感兴趣。)