Лучший способ применения ограничений между таблицами в базе данных - PullRequest
3 голосов
/ 17 марта 2009

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

Parent table  
ID    DATE_MIN   DATE_MAX
----- ---------- ----------
1     01/01/2009 01/03/2009
...

Child table
PARENT_ID  DATE
---------- ----------
1          01/02/2009
1          01/12/2009   <--- HAVE TO FAIL!
...

Я вижу два подхода:

  • Создание материализованных представлений при фиксации, как показано в этой статье (или другом эквиваленте в других СУБД).
  • Использовать хранимые процедуры и триггеры.

Любой другой подход? Какой самый лучший вариант?

ОБНОВЛЕНИЕ : Мотивация этого вопроса , а не о «наложении ограничений на базу данных или на приложение». Я думаю, что это усталый вопрос, и каждый делает так, как она любит. И, я извиняюсь за хулителей, я разрабатываю с ограничениями на базу данных. Отсюда возникает вопрос: «Каков наилучший вариант управления ограничениями между таблицами в базе данных?». Я добавил "внутри базы данных" в заголовок вопроса.

ОБНОВЛЕНИЕ 2 : Кто-то добавил тег " oracle ". Конечно, материализованные представления являются инструментами oracle, но меня интересует вариант any , независимо от того, используется ли он в oracle или других RDBMS.

Ответы [ 4 ]

4 голосов
/ 17 марта 2009

РЕДАКТИРОВАТЬ: Дана Саня удалил свой пост, который должен был поместить его в слой данных независимо от возражений DBA.


Причины, по которым администраторы баз данных кричат ​​на разработчиков, таких как Дана, заключаются в том, что между приложениями и базами данных существует соотношение 1: 1. Они видят это, потому что они видят данные как поддерживающие приложение, и их приложение должно хранить данные только в одном месте.

Администраторы БД рассматривают данные как наиболее важную вещь, и им все равно, будет приложение приходить или уходить.

Если вы утратите использование MS Word, неужели вы все равно сможете получить доступ к своим документам? Нет, данные важны для вас, приложение - нет.

Если вы когда-либо позволяли чему-либо обходить ваше приложение, чтобы получить доступ к вашим данным, вы утратили ограничения на уровне данных. Если ваши ограничения находятся на уровне вашей базы данных, десятки приложений смогут использовать его.

В идеале, вы никогда бы не предоставили INSERT, UPDATE или DELETE кому-либо. Вместо этого вы предоставите EXECUTE On для пакетов, которые сделают CRUD для вас. Если вы делаете это с самого начала, возможность добавлять правила к ВСТАВКЕ РЕБЕНКА (например, проверять, находится ли рождение между родительскими датами) практически бесконечна.

2 голосов
/ 25 марта 2010

Ограничения базы данных

Лучший способ применить ограничение базы данных (ограничение, охватывающее два или более отношений - из которых ограничение ссылочной целостности - это частный случай с синтаксической сокращенной записью, foreign key / references операторов) будет декларативно с помощью стандартного оператора SQL:

create assertion <name> check (<condition>)

В вашем случае что-то вроде:

create assertion Child_DATE_between_MIN_MAX check (
  not exists (
    select DATE_MIN, DATE, DATE_MAX
      from Parent, Child
     where ID = PARENT_ID
       and DATE < DATE_MIN
       and DATE > DATE_MAX
  )
)

ОБНОВЛЕНИЕ: Я забыл, что <condition> является строго логическим значением, поэтому старый код был неправильным.

К сожалению (умеренный сарказм здесь) большинство SQL-СУБД не реализуют ASSERTIONs.

Таким образом, остается реализовать эту проверку процедурно , с хранимыми процедурами и триггерами или проверочными ограничениями, если они доступны. В этом случае необходимо вызывать одни и те же хранимые процедуры для обновления как родительских, так и дочерних отношений; поэтому одна процедура и два триггера или проверка ограничений.

Ответ Люркера В самом деле показывает такое решение, но для него требуется аналогичная проверка на дочерние отношения.

Озабоченность выступлениями

Damien_The_Unbeliever, в комментарии к тот же ответ , утверждает:

1) Возможно, вы несете полный сканирование таблицы для каждой вставки / обновления

Здесь я остановлюсь на этом возражении, потому что оно очень распространено и может показаться действительным даже для ОБОБЩЕНИЙ (вероятно, это распространенное заблуждение, которое убеждает пользователей не спрашивать о них разработчиков SQL-СУБД, даже если они это в стандарте).

Ну да, он прав ... если кто-то использует СУБД, которая отстой!

Существует интересная часть теории, которую можно применить к поддержанию целостности: Дифференциальное реляционное исчисление (доступно как .pdf здесь ; вы также найти адекватную трактовку предмета в каждой достойной книге о теории БД).

Основная идея заключается в том, что можно применять ограничения целостности, часто проверяя только подмножества отношений, связанных с обновлением. Более строго, цитируя реферат связанной статьи:

... Формальное дифференцирование первого порядка предложения полезны в поддержании целостность базы данных, так как раз ограничение базы данных выражается как предложение первого порядка, его производная в отношении сделки обеспечивает необходимое и достаточное условие для поддержания целостности. The производная часто намного проще тест, чем исходное ограничение так как он поддерживает целостность дифференциально, предполагая целостность до сделки и тестирования только за новые нарушения . ...

Существуют и другие методы работы с поддержанием добавочных ограничений целостности . У разработчиков СУБД нет веских причин игнорировать такую ​​теорию. Фактически, авторы Введение любителя в ограничения целостности и проверку целостности в SQL ( .pdf ) написали во введении:

1 Введение

... Коммерческие продукты реляционных СУБД однако поддерживает SQL (например, например, Oracle [Ora99] или DB2 [IBM99]) не поддерживать более продвинутые формы ограничений. Даже сегодня более 8 лет после того, как стандарт SQL'92 имеет было выдано, ни один из этих коммерческих Система поддерживает утверждения , наиболее общая форма ограничения в SQL! Научная литература , т.е. исследования документы, посвященные целостности на С другой стороны, обеспечить богатство многообещающие результаты, применимые к очень общие и мощные формы ограничения целостности . ...

Итак, пожалуйста: попросите вашего поставщика СУБД SQL (коммерческой или бесплатной / с открытым исходным кодом) внедрить ПОЛОЖЕНИЯ сейчас и, по крайней мере, с разумной производительностью.

1 голос
/ 17 марта 2009

Я бы пошел сохраненный процесс и запустить маршрут; одна из их основных целей - обеспечить целостность данных на уровне БД.

Большинство баз данных также имеют некоторую форму проверочных ограничений, в которой почти все, что вы можете добавить в предложение WHERE, может использоваться как проверка данных:

CREATE FUNCTION CheckFnctn()
RETURNS int
AS 
BEGIN
   DECLARE @retval int
   SELECT @retval = COUNT(*) 
   FROM PARENT
   INNER JOIN CHILD ON PARENT.ID = CHILD.PARENT_ID
   WHERE CHILD.DATE < PARENT.DATE_MIN OR CHILD.DATE > PARENT.DATE_MAX
   RETURN @retval
END;
GO
ALTER TABLE CHILD
ADD CONSTRAINT chkDates CHECK (dbo.CheckFnctn() = 0 );
GO
0 голосов
/ 17 марта 2009

Хорошо, в конкретном примере я бы пошел на избыточное хранение избыточных данных. С помощью комбинации CHECK и FK (и супер-ключей) мы гарантируем, что данные всегда правильные, затем мы оборачиваем представление и запускаем его, чтобы скрыть детали реализации:

create table dbo.Parents (
    ParentID int IDENTITY(1,1) not null,
    ValidFrom datetime not null,
    ValidTo datetime not null,
    /* Natural Key column(s) */
    CONSTRAINT PK_dbo_Parents PRIMARY KEY (ParentID),
    CONSTRAINT UQ_dbo_Parents_DRI UNIQUE (ParentID, ValidFrom, ValidTo),
    /* Unique constraint on Natural Key */
    CONSTRAINT CK_dbo_Parents_ValidDates CHECK (ValidFrom <= ValidTo) /* Semi-open interval */
)
go
alter table dbo.Parents add constraint DF_dbo_Parents_ValidFrom DEFAULT (CURRENT_TIMESTAMP) for ValidFrom
go
alter table dbo.Parents add constraint DF_dbo_Parents_ValidTo DEFAULT (CONVERT(datetime,'99991231')) for ValidTo
go
create table dbo._Children (
    ChildID int IDENTITY(1,1) not null, /* We'll need this in the update trigger */
    ParentID int not null,
    ChildDate datetime not null,
    _ParentValidFrom datetime not null,
    _ParentValidTo datetime not null,
    CONSTRAINT PK_dbo__Children PRIMARY KEY (ChildID),
    CONSTRAINT FK_dbo__Children_Parents FOREIGN KEY (ParentID,_ParentValidFrom,_ParentValidTo) REFERENCES dbo.Parents (ParentID,ValidFrom,ValidTo) ON UPDATE CASCADE,
    CONSTRAINT CK_dbo__Children_ValidDate CHECK (_ParentValidFrom <= ChildDate and ChildDate < _ParentValidTo) /* See, semi-open */
)
go
alter table dbo._Children add constraint DF_dbo__Children_ChildDate DEFAULT (CURRENT_TIMESTAMP) for ChildDate
go
create view dbo.Children (ChildID,ParentID,ChildDate)
with schemabinding
as
select ChildID,ParentID,ChildDate from dbo._Children
go
create trigger dbo.T_Children_I on dbo.Children instead of insert
as
begin
    set nocount on

    insert into dbo._Children (ParentID,ChildDate,_ParentValidFrom,_ParentValidTo)
    select i.ParentID,i.ChildDate,p.ValidFrom,p.ValidTo
    from
        inserted i
            inner join
        dbo.Parents p
            on
                i.ParentID = p.ParentID
end
go
create trigger dbo.T_Children_U on dbo.Children instead of update
as
begin
    set nocount on
    if UPDATE(ChildID)
    begin
        RAISERROR('Updates to ChildID are not allowed',16,1)
        return
    end

    update c
    set
        ParentID = i.ParentID,
        ChildDate = i.ChildDate,
        _ParentValidFrom = p.ValidFrom,
        _ParentValidTo = p.ValidTo
    from
        inserted i
            inner join
        dbo._Children c
            on
                i.ChildID = c.ChildID
            inner join
        dbo.Parents p
            on
                i.ParentID = p.ParentID
end
go
insert into dbo.Parents(ValidFrom,ValidTo)
select '20081201','20090101' union all
select '20090201','20090301'
/* (2 row(s) affected) */
go
insert into dbo.Children (ParentID,ChildDate)
select 1,'20081215'
/* (1 row(s) affected) */
go
insert into dbo.Children (ParentID,ChildDate)
select 1,'20090115'
/*
Msg 547, Level 16, State 0, Procedure T_Children_I, Line 6
The INSERT statement conflicted with the CHECK constraint "CK_dbo__Children_ValidDate". The conflict occurred in database "Play", table "dbo._Children".
The statement has been terminated.
*/
go
update dbo.Parents set ValidTo = '20090201' where ParentID = 1
/* (1 row(s) affected) */
go
insert into dbo.Children (ParentID,ChildDate)
select 1,'20090115'
/* (1 row(s) affected) */
go
update dbo.Parents set ValidTo = '20090101' where ParentID = 1
/*
Msg 547, Level 16, State 0, Line 1
The UPDATE statement conflicted with the CHECK constraint "CK_dbo__Children_ValidDate". The conflict occurred in database "Play", table "dbo._Children".
The statement has been terminated.
*/
go
insert into dbo.Children (ParentID,ChildDate)
select 2,'20090215'
/* (1 row(s) affected) */
go
update dbo.Children set ChildDate = '20090115' where ParentID=2 and ChildDate = '20090215'
/*
Msg 547, Level 16, State 0, Procedure T_Children_U, Line 11
The UPDATE statement conflicted with the CHECK constraint "CK_dbo__Children_ValidDate". The conflict occurred in database "Play", table "dbo._Children".
The statement has been terminated.
*/
go
delete from dbo.Children
/* (3 row(s) affected) */
go
/* Clean up after testing */
drop view dbo.Children
drop table dbo._Children
drop table dbo.Parents
go

Это для SQL Server. Протестировано в 2005 году, но должно работать как минимум в 2000 и 2008 годах. Бонусом здесь является то, что даже если триггер отключен (например, вложенные триггеры отключены), вы не можете получить неверные данные в базовых таблицах

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...