Подтвердить что ты не робот

Можно указать уникальный индекс с NULL, разрешенным в Rails/ActiveRecord?

Я хочу указать уникальный индекс в столбце, но мне также нужно разрешить значения NULL (у нескольких записей могут быть значения NULL). При тестировании с помощью PostgreSQL я вижу, что у меня может быть 1 запись со значением NULL, но в следующем случае возникает проблема:

irb(main):001:0> u=User.find(5)
  User Load (111.1ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 5]]
=> #<User id: 5, email: "[email protected]", created_at: "2013-08-28 09:55:28", updated_at: "2013-08-28 09:55:28">
irb(main):002:0> u.email=nil
=> nil
irb(main):003:0> u.save
   (1.1ms)  BEGIN
  User Exists (4.8ms)  SELECT 1 AS one FROM "users" WHERE ("users"."email" IS NULL AND "users"."id" != 5) LIMIT 1
   (1.5ms)  ROLLBACK
=> false

Таким образом, даже если база данных позволяет это, Rails сначала проверяет, существует ли User с другим идентификатором, а столбцом email установлено значение NULL. Есть ли способ, которым может позволить не только база данных, но и Rails не будут проверять сначала, как и выше?

Идея состоит в том, что пользователям не нужно вводить электронное письмо, но если это так, мне нужно найти пользователя по электронной почте. Я знаю, что могу создать еще одну модель, чтобы связать пользователей с электронными сообщениями, но я бы скорее сделал это выше.

UPDATE. Здесь код миграции, который я создал для добавления столбца email:

class AddEmailToUsers < ActiveRecord::Migration
  def change
    add_column :users, :email, :string
    add_index :users, :email, :unique => true
  end
end

И вот код, который я добавил в модель User:

validates :email, uniqueness: true

Я забыл, что добавил validates вызов модели User. Поэтому имеет смысл, что Rails проверяет сначала. Я предполагаю, что единственный вопрос заключается в том, безопасно ли для баз данных иметь уникальный индекс и поля NULL? Есть ли способ указать в Rails, что я хочу проверить подлинность электронной почты, является уникальной, если она не была nil?

4b9b3361

Ответ 1

Ваша миграция будет работать и позволит умножать значения null (для большинства движков базы данных).

Но ваша проверка для класса пользователя должна выглядеть ниже.

validates :email, uniqueness: true, allow_nil: true

Ответ 2

Чтобы выяснить, почему это работает на уровне базы данных, вам нужно понять трехзначную логику, используемую в SQL: true, false, null.

null обычно считается неизвестным, поэтому его семантика в операциях обычно эквивалентна незнанию того, что это за конкретное значение, и если вы все еще можете решить ответ. Так, например, 1.0 * null - null, но null OR true - true. В первом случае умножение на неизвестное неизвестно, но во втором, вторая половина условного выражения делает все утверждение всегда истинным, поэтому не имеет значения, что находится на левой стороне.

Теперь, когда дело доходит до индексов, стандарт не указывает ничего, что позволяет поставщикам интерпретировать неизвестные средства. Лично я считаю, что уникальный индекс должен быть определен как в документах PostgreSQL:

Когда индекс объявляется уникальным, несколько строк таблицы с равными индексированными значениями не будут разрешены

Тогда должен быть вопрос, каково значение null = null? Правильный ответ должен быть null. Поэтому, если вы немного прочитаете строки этих документов PostgreSQL и скажете, что уникальный индекс запретит несколько строк, для которых оператор равенства возвращает true для указанного значения, тогда должны быть разрешены несколько значений null. Именно так работает PostgreSQL, поэтому в этой настройке вы можете иметь уникальный столбец с несколькими строками с null в качестве значения.

С другой стороны, если вы хотите интерпретировать определение уникального индекса для запрещения нескольких строк, для которых оператор неравенства не возвращает false, вы не сможете иметь несколько строк с null значениями. Кто предпочтет работать в этой противопожарной установке? Именно так Microsoft SQL Server определяет уникальный индекс.

Оба этих способа определения уникального индекса верны на основе стандартного определения SQL null 2003 года. Так что это действительно зависит от вашей базовой базы данных. Но, если сказать, я думаю, что большинство работает подобно PostgreSQL.