web-dev-qa-db-ja.com

1対多の関係の履歴を保持する方法は?

単純な1対多(1:M)の関係がありますが、変更のためにhistoryを保持する必要があります。

たとえば、タスク従業員を多数持つことができますが、各従業員は-でタスクを1つしか持つことができません- 時間

従業員に割り当てられた過去のタスクを知るために、タスク割り当て履歴を保持する必要があります=。

私はcanを多対多(M:N)の関係に変換し、「フラグ」列を追加することを知っていますが、これは、1つだけを規定する関係の制約を維持しません- タスク割り当て済みにすることができます。

ケースにはデザインパターンまたはベストプラクティスがあると確信していますが、見つかりません。 Oracle DBMSを使用しています。

Employee列:

  • ID
  • タスクID(FK_Task_ID)
  • 従業員名
  • その他の従業員データ

Task列:

  • ID(PK)
  • タスクの詳細

これは直接的な(1:M)関係です。特定の従業員に割り当てられたすべてのタスクのログを保持するにはどうすればよいですか?簡単な解決策は、多対多の関係を作成し、次のように中間テーブルを追加することです。

Employee_Task列:

  • ID
  • 従業員ID(FK_従業員ID)
  • タスクID(FK_Task_ID)

これにより、各従業員は一度に1つのタスクしか割り当てられないという制約が取り除かれます。

3
Karim

あなたが提案しているもののためのデザインパターンがあるかわかりません。別の履歴テーブルはあなたが望むものを達成しますが、あなたの質問から、これはあなたが求めているものではないようです。

ただし、Oracleの代替案は、各従業員が単一のアクティブなタスクを持つことを保証する関数ベースの一意のインデックスを作成できることです。これは次のようになります。

CREATE TABLE task
( 
    task_id number(10) NOT NULL,
    task_name varchar2(100),
    task_description varchar2(500),
  CONSTRAINT task_pk PRIMARY KEY (task_id)
);

CREATE TABLE employee
( 
    employee_id number(10) NOT NULL,
    employee_name varchar2(100),
  CONSTRAINT employee_id PRIMARY KEY (employee_id)
);


CREATE TABLE employee_task
(
  task_id number(10) NOT NULL,
  employee_id number(10) NOT NULL,
  active_task char(1) check (active_task in ( 'Y', 'N' )),
  CONSTRAINT fk_employee_id FOREIGN KEY (employee_id)   REFERENCES employee(employee_id),
  CONSTRAINT fk_task_id FOREIGN KEY (task_id)   REFERENCES task(task_id)
);


CREATE UNIQUE INDEX only_one_active_per_employee ON employee_task (
    CASE WHEN active_task='Y' THEN employee_id ELSE NULL END
);

これは、BツリーインデックスにNULL値を格納しないOracleを利用します。つまり、タスクが "N"に設定されている場合、それはインデックスツリーに格納されないため、すべての "N"レコードは一意のインデックスの一部ではありません。別のアクティブなタスクも割り当てられているアクティブなタスクに従業員を割り当てようとした場合、一意のインデックス違反が発生するはずです。

0
blobbles

次のように、3つのテーブルがあるとします。1つは従業員用、もう1つはタスク用、もう1つは "task_assignments"用です。

create table employees(
  employee_id number primary key
, employee_name varchar2(64)
, additional_information varchar2(64)
);

create table tasks(
  task_id number primary key
, task_details varchar2(64)
);

create table task_assignments(
  employee_id number references employees(employee_id)
, task_id number references tasks(task_id)
, start_date date not null 
, end_date date
, unique (employee_id, start_date)
);

Task_assignmentsテーブルには、「履歴」データが格納されます。各従業員は、一度に1つのタスクのみを処理できると想定しています(質問で述べたとおり)。タスクには、start_date(または時間)とend_dateがあります。従業員がすでに「開いている」タスクを持っている場合(つまり、タスクのend_dateがnullの場合)、トリガーを使用してINSERTを防止できます。注:トリガーの例では、UPDATEはカバーされていません。

create or replace trigger one_active_task_only
before insert on task_assignments
for each row
declare
  open_tasks number := 0 ;
begin
  select count(*) into open_tasks
  from task_assignments
  where employee_id = :new.employee_id
    and end_date is null ;    

  if open_tasks >= 1 then
    raise_application_error (-20500,'This employee already has an open task');
  end if;
end one_active_task_only;
/

alter trigger one_active_task_only enable;

テストおよび詳細など: dbfiddle を参照

1
stefan

SQL2011標準 の「システムバージョンのテーブル」が必要なようです。 Oracleはそれら自体をサポートしていませんが、MS SQL Server 2016がそれらを実装する方法を読んだ場合(概念は system-versionedtemporal table と呼ばれます)、自分で十分に効率的にエミュレートできるパターンです- SQL Serverに組み込みのサポートが提供されるずっと前に、さまざまなプロジェクトの履歴/監査に、手動で実装した同様のパターンを使用しました(組み込みのサポートにより、物事がはるかに簡単になり、場合によってはより効率的になります。 )。

1
David Spillett