57

この質問には、いくつかの仮説的な背景が必要です。MySQL を RDBMS として使用して、列、、、employeeを持つテーブルを考えてみましょう。ある人物が別の人物と同じ名前と生年月日を持っている場合、それらは定義上、同じ人物であるため (1809 年 2 月 12 日に生まれたエイブラハム リンカーンという名前の 2 人がいるという驚くべき偶然を除けば)、ユニークキーオンとは「同じ人を二度収納しない」という意味です。ここで、次のデータを検討してください。namedate_of_birthtitlesalarynamedate_of_birth

id name        date_of_birth title          salary
 1 John Smith  1960-10-02    President      500,000
 2 Jane Doe    1982-05-05    Accountant      80,000
 3 Jim Johnson NULL          Office Manager  40,000
 4 Tim Smith   1899-04-11    Janitor         95,000

次のステートメントを実行しようとすると、失敗するはずです。

INSERT INTO employee (name, date_of_birth, title, salary)
VALUES ('Tim Smith', '1899-04-11', 'Janitor', '95,000')

これを試してみると、成功します:

INSERT INTO employee (name, title, salary)
VALUES ('Jim Johnson', 'Office Manager', '40,000')

そして今、私のデータは次のようになります。

id name        date_of_birth title          salary
 1 John Smith  1960-10-02    President      500,000
 2 Jane Doe    1982-05-05    Accountant      80,000
 3 Jim Johnson NULL          Office Manager  40,000
 4 Tim Smith   1899-04-11    Janitor         95,000
 5 Jim Johnson NULL          Office Manager  40,000

これは私が望んでいることではありませんが、起こったことに完全に同意できないとは言えません。数学的集合の観点から話すと、

{'Tim Smith', '1899-04-11'} = {'Tim Smith', '1899-04-11'} <-- TRUE
{'Tim Smith', '1899-04-11'} = {'Jane Doe', '1982-05-05'} <-- FALSE
{'Tim Smith', '1899-04-11'} = {'Jim Johnson', NULL} <-- UNKNOWN
{'Jim Johnson', NULL} = {'Jim Johnson', NULL} <-- UNKNOWN

私の推測では、MySQL は「生年月日のあるジム・ジョンソンがまだこのテーブルにないことを知らないので、彼を追加します」と言っていると思います。NULL

私の質問は、常に知られているわけではありませんが、どうすれば重複を防ぐことができますか? date_of_birthこれまでに思いついた最善の方法はdate_of_birth、別のテーブルに移動することです。ただし、これに関する問題は、たとえば、同じ名前、役職、給与、異なる生年月日を持つ 2 人のレジ係がいて、重複せずに両方を保存する方法がなくなる可能性があることです。

4

11 に答える 11

33

一意のキーの基本的な特性は、一意でなければならないということです。そのキーの一部を Nullable にすると、このプロパティが破棄されます。

この問題には、次の 2 つの解決策があります。

  • 間違った方法の 1 つは、魔法の日付を使用して不明を表すことです。これは、DBMS の「問題」を解決するだけで、論理的な意味での問題を解決するものではありません。生年月日が不明な 2 つの「John Smith」エントリの問題が予想されます。これらの人は同一人物ですか、それともユニークな個人ですか?それらが異なることがわかっている場合は、同じ古い問題に戻ります.一意のキーは一意ではありません. 「不明」を表すために魔法の日付の全範囲を割り当てることさえ考えないでください-これは本当に地獄への道です.

  • より良い方法は、代理キーとして EmployeeId 属性を作成することです。これは、一意であることがわかっている個人に割り当てる任意の識別子です。多くの場合、この識別子は単なる整数値です。次に、従業員テーブルを作成して、従業員 ID (一意の null 非許容キー) を依存アトリビューターであると思われるもの、この場合は名前と生年月日 (いずれも null 可能である可能性があります) に関連付けます。以前に名前/生年月日を使用していたすべての場所で、EmployeeId サロゲート キーを使用します。これにより、システムに新しいテーブルが追加されますが、不明な値の問題が確実に解決されます。

于 2010-11-03T15:31:53.223 に答える
7

checksumとの md5 ハッシュを含むname追加のテーブル列を作成することをお勧めしますdate_of_birth(name, date_of_birth)問題が解決しないため、一意のキーを削除します。チェックサムで一意のキーを 1 つ作成します。

ALTER TABLE employee 
    ADD COLUMN checksum CHAR(32) NOT NULL;

UPDATE employee 
SET checksum = MD5(CONCAT(name, IFNULL(date_of_birth, '')));

ALTER TABLE employee 
    ADD UNIQUE (checksum);

このソリューションでは、ハッシュを生成する必要がある挿入されたペアごとに小さな技術的オーバーヘッドが発生します (すべての検索クエリで同じこと)。さらに改善するために、すべての挿入でハッシュを生成するトリガーを追加できます。

CREATE TRIGGER before_insert_employee 
BEFORE INSERT ON employee
FOR EACH ROW
    IF new.checksum IS NULL THEN
      SET new.checksum = MD5(CONCAT(new.name, IFNULL(new.date_of_birth, '')));
    END IF;
于 2018-02-28T08:23:33.157 に答える
7

MySQLはここでそれを行うと思います。他の一部のデータベース (Microsoft SQL Server など) は、NULL を UNIQUE 列に 1 回だけ挿入できる値として扱いますが、個人的には、これは奇妙で予期しない動作であることがわかりました。

ただし、これは必要なものであるため、NULL の代わりに、かなり前の日付などの「魔法の」値を使用できます。

于 2010-11-02T20:29:27.150 に答える
5

自然キーがないため、名前に基づいて重複がないという問題は解決できません。生年月日が不明な人に偽の日付を入れても問題は解決しません。1900/01/01に生まれたジョンスミスは、1960/03/09に生まれたジョンスミスとはまだ違う人になるでしょう。

私は大小の組織の名前データを毎日使用しており、常に同じ名前の2人の異なる人がいることを保証できます。同じ役職の場合もあります。誕生日も一意性を保証するものではなく、同じ日に生まれたジョン・スミスがたくさんいます。医師のオフィスデータを扱うとき、同じ名前、住所、電話番号(父と息子の組み合わせ)を持つ2人の医師がいることがよくあります。

各従業員を一意に識別するために従業員データを挿入する場合は、従業員IDを取得するのが最善の策です。次に、ユーザーインターフェイスで一意の名前を確認し、一致するものが1つ以上ある場合は、それらを意味するかどうかをユーザーに確認し、「いいえ」と答えた場合は、レコードを挿入します。次に、誰かが誤って2つのIDを割り当てられた場合に問題を修正するために、重複排除プロセスを構築します。

于 2010-11-03T14:50:29.103 に答える
3

それを行う別の方法があります。date_of_birth列の文字列値を表す列(null不可)を追加します。date_of_birthがnullの場合、新しい列の値は ""(空の文字列)になります。

列にdate_of_birth_strという名前を付け、一意の制約employee(name、date_of_birth_str)を作成します。したがって、2つのリコアが同じ名前でnullのdate_of_birth値を持つ場合でも、一意性制約は機能します。

ただし、2つの同じ意味の列の保守作業、および新しい列のパフォーマンスへの悪影響は、慎重に検討する必要があります。

于 2010-11-03T01:56:45.550 に答える
0

完璧な解決策は関数ベースの UK のサポートですが、mySQL も関数ベースのインデックスをサポートする必要があるため、これはより複雑になります。これにより、NULL の代わりに「偽の」値を使用する必要がなくなり、開発者は UK で NULL 値を処理する方法を決定できるようになります。残念ながら、mySQL は現在、私が認識している機能をサポートしていないため、回避策が残されています。

CREATE TABLE employee( 
 name CHAR(50) NOT NULL, 
 date_of_birth DATE, 
 title CHAR(50), 
 UNIQUE KEY idx_name_dob (name, IFNULL(date_of_birth,'0000-00-00 00:00:00'))
);

(一意のキー定義でのIFNULL()関数の使用に注意してください)

于 2011-10-21T20:25:17.450 に答える
0

私は1つの解決策を探していましたが、Alexander Yancharukが提案したのは私にとって良い考えでした. しかし、私の場合、列は外部キーであり、employee_id は null にすることができます。

私はこの構造を持っています:


+----+---------+-------------+
| id | room_id | employee_id |
+----+---------+-------------+
|  1 |       1 | NULL        |
|  2 |       2 | 1           |
+----+---------+-------------+

また、employee_id NULLの room_id は複製できません

次のように、挿入前にトリガーを追加することを解決しました。

DELIMITER $$
USE `db`$$
CREATE DEFINER=`root`@`%` TRIGGER `db`.`room_employee` BEFORE INSERT ON `room_employee` FOR EACH ROW
BEGIN
    IF EXISTS (
            SELECT room_id, employee_id
            FROM room_employee
            WHERE (NEW.room_id = room_employee.room_id AND NEW.employee_id IS NULL AND room_employee.employee_id IS NULL)
        ) THEN
        CALL `The room Can not be duplicated on room employee table`;
    END IF;
END$$
DELIMITER ;

room_idemployee_idに固有の制約も追加しました

于 2020-11-06T11:30:29.810 に答える