Существуют ли СУБД, которые допускают внешний ключ, который ссылается на представление (а не только базовые таблицы)?

22

Вдохновленный вопросом моделирования Django: Моделирование базы данных с множественными отношениями «многие ко многим» в Django . ДБ-дизайн это что-то вроде:

CREATE TABLE Book
( BookID INT NOT NULL
, BookTitle VARCHAR(200) NOT NULL
, PRIMARY KEY (BookID)
) ;

CREATE TABLE Tag
( TagID INT NOT NULL
, TagName VARCHAR(50) NOT NULL
, PRIMARY KEY (TagID)
) ;

CREATE TABLE BookTag
( BookID INT NOT NULL
, TagID INT NOT NULL
, PRIMARY KEY (BookID, TagID)
, FOREIGN KEY (BookID)  REFERENCES Book (BookID)
, FOREIGN KEY (TagID)   REFERENCES Tag (TagID)
) ;

CREATE TABLE Aspect
( AspectID INT NOT NULL
, AspectName VARCHAR(50) NOT NULL
, PRIMARY KEY (AspectID)
) ;

CREATE TABLE TagAspect
( TagID INT NOT NULL
, AspectID INT NOT NULL
, PRIMARY KEY (TagID, AspectID) 
, FOREIGN KEY (TagID)   REFERENCES Tag (TagID)
, FOREIGN KEY (AspectID)  REFERENCES Aspect (AspectID)
) ;

дб диаграмма

и вопрос заключается в том, как определить BookAspectRatingтаблицу и обеспечить ссылочную целостность, поэтому нельзя добавить оценку для (Book, Aspect)недопустимой комбинации.

AFAIK, сложные CHECKограничения (или ASSERTIONS), которые включают подзапросы и более одной таблицы, которые могли бы решить эту проблему, недоступны ни в одной СУБД.

Другая идея - использовать (псевдокод) представление:

CREATE VIEW BookAspect_view
  AS
SELECT DISTINCT
    bt.BookId
  , ta.AspectId
FROM 
    BookTag AS bt
  JOIN 
    Tag AS t  ON t.TagID = bt.TagID
  JOIN 
    TagAspect AS ta  ON ta.TagID = bt.TagID 
WITH PRIMARY KEY (BookId, AspectId) ;

и таблица, которая имеет внешний ключ для вышеуказанного представления:

CREATE TABLE BookAspectRating
( BookID INT NOT NULL
, AspectID INT NOT NULL
, PersonID INT NOT NULL
, Rating INT NOT NULL
, PRIMARY KEY (BookID, AspectID, PersonID)
, FOREIGN KEY (PersonID)   REFERENCES Person (PersonID)
, FOREIGN KEY (BookID, AspectID) 
    REFERENCES BookAspect_view (BookID, AspectID)
) ;

Три вопроса:

  • Существуют ли СУБД, допускающие (возможно, материализованные) VIEWс PRIMARY KEY?

  • Существуют ли СУБД , которые позволяют , FOREIGN KEYчто REFERENCESв VIEW(а не только базы TABLE)?

  • Может ли эта проблема целостности быть решена иначе - с помощью доступных функций СУБД?


Разъяснение:

Поскольку, вероятно, нет 100% удовлетворительного решения - и вопрос Джанго даже не мой! - Меня больше интересует общая стратегия возможной атаки на проблему, а не детальное решение. Таким образом, ответ типа «в СУБД-Х это можно сделать с помощью триггеров в таблице А» вполне приемлем.

ypercubeᵀᴹ
источник
Публикация в качестве комментария к вашим первым двум вопросам - и не обязательно для вас, как я уверен, вы уже знаете - но SQL Server не поддерживает первичные или внешние ключи для представлений.
Аарон Бертран
@ Аарон: да, спасибо. Я читал, что Oracle поддерживает PK costraints в представлениях. Но не уверен, что это сработает в этой ситуации. И ответ на 2-й вопрос (о FKs для представлений), вероятно, отрицательный в Oracle.
ypercubeᵀᴹ
Но мне интересно узнать, есть ли какое-либо другое решение (триггеры, проверка стоимости или другие комбо)
ypercubeᵀᴹ

Ответы:

9

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

    CREATE TABLE BookAspectCommonTagLink
    (  BookID INT NOT NULL
    , AspectID INT NOT NULL
    , TagID INT NOT NULL
--TagID is deliberately left out of PK
    , PRIMARY KEY (BookID, AspectID)
    , FOREIGN KEY (BookID, TagID) 
        REFERENCES BookTag (BookID, TagID)
    , FOREIGN KEY (AspectID, TagID) 
        REFERENCES AspectTag (AspectID, TagID)
    ) ;
Аляска
источник
О, классно. Единственная проблема, о которой я могу думать, - это сложность, возникающая при вставке / удалении BookTags и TagAspect. Например, каждый раз, когда новый BookTag (или TagAspect) удаляется, необходимо выполнить поиск, чтобы удалить соответствующие строки в этой таблице и / или изменить TagIDдругой тег, связанный с той же комбинацией BookAspect.
ypercubeᵀᴹ
И подобный поиск должен быть сделан для вставки в эти 2 таблицы. Но сложные правила требуют сложных процедур, так что это выглядит действительно хорошо.
ypercubeᵀᴹ
@ypercube Когда вы удаляете тег, вам нужно проверить и, возможно, переключиться на другой тег, связывающий ту же книгу и аспект. Однако, когда вы вставляете новые теги, нет необходимости делать какие-либо проверки, пока вам не нужно вставить рейтинг.
АК
1
Если специалист по устранению неполадок и лицо, занимающееся вводом данных, являются одним и тем же лицом, или если вы предоставляете сообщение об ошибке конечному пользователю, обязательно. Вы слишком много думаете о магазинах для одного человека, где вы делаете все.
Аарон Бертран
4
@AaronBertrand Вы только что оказали мне огромную услугу. Я заканчиваю статью под названием «Разработка баз данных с низким уровнем обслуживания», и я забыл упомянуть, что серверы приложений должны регистрировать исходные сообщения об ошибках, поступающие из баз данных. Я только добавил это. Спасибо за напоминание;)
АК
8

Я думаю, вы обнаружите, что во многих случаях сложные бизнес-правила не могут быть реализованы с помощью одной модели. Это один из тех случаев, когда, по крайней мере в SQL Server, я думаю, что триггер (предпочтительно вместо триггера) лучше подходит для вашей цели.

Аарон Бертран
источник
Эй, Аарон, не могли бы вы объяснить, почему в этом случае триггер является лучшим выбором, чем сущность и несколько ограничений?
АК
2
@AlexKuznetsov Конечно, потому что я не потратил 17 часов на размышления о том, как реализовать это с несколькими внешними ключами, состоящими из нескольких столбцов, и всей дополнительной логикой, которая может понадобиться для проверки и проверки ошибок в любом случае?
Аарон Бертран
2
Будьте осторожны с условиями гонки, которые может ввести наивная реализация триггеров. Например, одна транзакция может отключить книгу от тега, а другая все еще думает, что можно подключиться к соответствующему аспекту просто потому, что первая транзакция еще не зафиксирована. Сложности, представленные ответом @AlexKuznetsov, вероятно, меньше, чем сложность и хрупкость блокирующего «протокола», необходимого для предотвращения условий гонки в триггерах, ИМХО.
Бранко Димитриевич
8

В Oracle одним из способов принудительного применения такого рода ограничений декларативным способом было бы создание материализованного представления, настроенного на быстрое обновление при коммите, запрос которого идентифицирует все недопустимые строки (т. BookAspectRatingЕ. Строки, в которых нет совпадений BookAspect_view). Затем вы можете создать тривиальное ограничение для этого материализованного представления, которое будет нарушено, если в материализованном представлении есть какие-либо строки. Это позволяет минимизировать объем данных, которые необходимо дублировать в материализованном представлении. Однако это может вызвать проблемы, поскольку ограничение применяется только в тот момент, когда вы фиксируете транзакцию - многие приложения не написаны так, чтобы ожидать, что операция фиксации может завершиться неудачей, - и потому что нарушение ограничения может быть довольно сложным связать с определенной строкой или конкретной таблицей.

Джастин Кейв
источник
4

SIRA_PRISE позволяет это.

Хотя FK больше не называется «FK», а просто «ограничением базы данных», и «представление» фактически даже не нужно определять как представление, вы можете просто включить выражение, определяющее выражение, в объявление ограничение базы данных.

Ваше ограничение будет выглядеть примерно так

SEMIMINUS(BOOKASPECT , JOIN(BOOKTAG , TAGASPECT))

и вы сделали.

Однако в большинстве СУБД SQL вам придется выполнить анализ вашего ограничения, определить, как оно может быть нарушено, и реализовать все необходимые триггеры.

Эрвин Смут
источник
Я знаю. Это отражает то, что я считал важным во время написания.
Эрвин Смут
3

В PostgreSQL я не могу представить решение без привлечения триггеров, но оно, безусловно, может быть решено таким образом (будь то поддержание какого-либо материализованного представления или включение до запуска BookAspectRating). Нет внешних ключей, ссылающихся на view ( ERROR: referenced relation "v_munkalap" is not a table), не говоря уже о первичном ключе.

Dezso
источник