Проверка уникальности в базе данных, когда проверка имеет условие для другой таблицы
Я задал подобный вопрос здесь при проверке уникальности в базе данных, когда проверка имеет условие, но мои требования изменились, отсюда и этот вопрос.
Использование проверок уникальности в 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
Итак, модель Child
имеет два атрибута: parent_id
(Ассоциация с Parent
) а также text
(текстовый атрибут). Модель Parent
имеет один атрибут: is_published
(логическое). text
должен быть уникальным для всех моделей типа Child
если его parent.is_published
правда.
Использование уникального индекса, как предложено в http://robots.thoughtbot.com/the-perils-of-uniqueness-validations, слишком ограничивающее, поскольку оно будет применять ограничение независимо от значения is_published.
Кто-нибудь знает об "условном" индексе для базы данных PostgreSQL, который зависит от другой таблицы? Решение при проверке уникальности в базе данных, когда проверка имеет условие, состоит в том, когда ваше условие зависит от атрибута в той же таблице. Или другой способ исправить это?
1 ответ
К сожалению, не существует такого простого и понятного решения, как на предыдущий вопрос.
Это должно сделать работу:
Добавить избыточный флаг
is_published
кChild
ТаблицаALTER TABLE child ADD column is_published boolean NOT NULL;
Сделай это
DEFAULT FALSE
или что у вас обычно есть в родительских столбцах при вставке.
Это должно бытьNOT NULL
чтобы избежать лазейки сNULL
значения и значения по умолчаниюMATCH SIMPLE
поведение во внешних ключах:
Ограничение внешнего ключа из двух столбцов, только если третий столбец НЕ равен NULLДобавить (казалось бы, бессмысленно, пока) уникальное ограничение на
parent(parent_id, is_published)
ALTER TABLE parent ADD CONSTRAINT parent_fk_uni UNIQUE (parent_id, is_published);
поскольку
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;
Затем добавьте частичный УНИКАЛЬНЫЙ индекс, как в предыдущем ответе.
CREATE UNIQUE INDEX child_txt_is_published_idx ON child (text) WHERE is_published;
Конечно, при вставке строк в 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;