4

テーブル間の制約をチェックするための最良の方法を探しています。たとえば、日付の子レコードの値が 2 つの親行列の日付範囲内にあるかどうかを確認するには、次のようにします。例えば:

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つのアプローチを見ています:

  • この記事(または他の RDBMS で同等のもの) に示されているように、コミット時にマテリアライズド ビューを作成します。
  • ストアド プロシージャとトリガーを使用します。

他のアプローチはありますか?最良の選択肢はどれですか?

UPDATE : この質問の動機は、「データベースまたはアプリケーションに制約を課す」ことではありません。これは疲れた質問だと思います。誰もが彼女の好きなようにしています。そして、中傷者には申し訳ありませんが、データベースに制約を付けて開発しています。ここからは、「データベースのテーブル間の制約を管理するための最良のオプションはどれですか?」という質問です。質問のタイトルに「データベース内」を追加しました。

更新 2 : 誰かが「 oracle」タグを追加しました。もちろん、具体化されたビューはオラクルツールですが、オラクルまたは他のRDBMSに関係なく、どのオプションにも興味があります。

4

4 に答える 4

4

編集: Dana the Sane は、DBA の異議に関係なく、データ層に配置する予定だった投稿を削除しました。


DBA が Dana のような開発者を非難する理由は、アプリケーションとデータベースの比率が 1:1 であると想定しているからです。これは、アプリをサポートするためにそこにデータがあり、アプリがデータを 1 か所に保存するだけでよいためです。

DBA はデータを最も重要なものと見なしており、アプリの出入りは気にしません。

MS Word を使用できなくなったとしても、ドキュメントにアクセスできるかどうか気にしますか? いいえ、あなたにとってデータは重要ですが、アプリはそうではありません。

アプリを迂回してデータにアクセスできるようにした場合、データ層の制約が失われます。制約がデータベース層にある場合、多数のアプリですべて使用できます。

理想的には、INSERT、UPDATE、または DELETE を誰にも許可しないでください。代わりに、CRUD を実行する EXECUTE On パッケージを付与します。最初からこれを行うと、CHILD の INSERT にルールを追加する機能 (生年月日が親の日付の間にあるかどうかを確認するなど) は事実上無限になります。

于 2009-03-17T14:48:35.747 に答える
2

データベースの制約

データベース制約(2 つ以上の関係にまたがる制約 -参照整合性制約は、構文上の省略形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-DBMS は ASSERTION を実装していません。

したがって、ストアド プロシージャとトリガー、または可能な場合はチェック制約を使用して、このチェックを手続き的に実装する必要があります。この場合、親と子の両方の関係を更新するために、同じストアド プロシージャを呼び出す必要があります。つまり、1 つのプロシージャと 2 つのトリガーまたはチェック制約です。

Lurker Indeed の回答はそのような解決策を示していますが、 Child 関係についても同様のチェックが必要です。

公演に関するお悩み

Damien_The_Unbeliever は、同じ回答へのコメントで次のように主張しています。

1) 挿入/更新ごとにテーブル全体のスキャンが発生している可能性があります

ここで、この反論に対処します。これは非常に一般的であり、ASSERTION に対しても有効に見える可能性があるためです (これは、ユーザーが SQL-DBMS の実装者であるとわかっていても、SQL-DBMS の実装者に質問しないように説得する一般的な誤解である可能性があります)標準)。

ええ、そうです、彼の言うとおりです。

完全性維持に適用できる興味深い理論があります: Differential Relational Calculus (ここで.pdf として入手できます。DB 理論に関するまともな本には、このテーマの適切な扱いもあります)。

コアとなるアイデアは、しばしば更新に含まれる関係のサブセットのみをチェックする整合性制約を適用できるということです。より厳密には、リンクされた論文の要約を引用します。

... データベースの制約が一次文として表現されると、トランザクションに関するその導関数が完全性を維持するための必要かつ十分な条件を提供するため、一次文の正式な微分はデータベースの完全性を維持するのに役立ちます。デリバティブは、トランザクションの前に完全性を想定し、新しい違反のみをテストすることによって完全性を差別的に維持するため、多くの場合、元の制約よりもテストがはるかに簡単です。...

増分整合性制約のメンテナンスを処理する手法は他にもあります。DBMS 開発者がそのような理論を無視する正当な理由はありません。実際、An Amateur's Introduction to Integrity Constraints and Integrity Checking in SQL ( .pdf ) の著者は、序文に次のように書いています。

1 はじめに

... SQL をサポートする市販のリレーショナル DBMS 製品 (たとえば、Oracle [Ora99] や DB2 [IBM99] など) は、より高度な形式の制約をサポートしていません。SQL'92 標準が発行されてから 8 年以上が経過した今日でも、これらの商用システムのいずれも、SQL の制約の最も一般的な形式であるアサーションをサポートしていません! 一方、完全性に特化した科学文献、つまり研究論文は、完全性制約の非常に一般的で強力な形式に適用できる豊富な有望な結果を提供します。...

したがって、SQL-DBMS サプライヤ (商用またはフリー/オープン ソース) に、少なくとも妥当なパフォーマンスでASSERTION を今すぐ実装するよう依頼してください。

于 2010-03-25T07:59:53.033 に答える
0

わかりました、特定の例では、冗長データを冗長に格納します。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 でも動作するはずです。ここでのボーナスは、トリガーが無効になっている場合でも (たとえば、ネストされたトリガーがオフになっている場合)、ベース テーブルに間違ったデータが残ることはありません。

于 2009-03-17T17:25:14.653 に答える