it-swarm.com.de

Eindeutiger Schlüssel mit NULL

Diese Frage erfordert einen hypothetischen Hintergrund. Betrachten wir eine Tabelle employee mit Spalten name, date_of_birth, title, salary, die MySQL als RDBMS verwendet. Wenn eine bestimmte Person denselben Namen und dasselbe Geburtsdatum wie eine andere Person hat, handelt es sich definitionsgemäß um dieselbe Person (abgesehen von erstaunlichen Zufällen, bei denen wir zwei Personen mit dem Namen Abraham Lincoln haben, die am 12. Februar 1809 geboren wurden) eindeutiger Schlüssel für name und date_of_birth, dh "dieselbe Person nicht zweimal speichern". Betrachten Sie nun diese Daten:

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

Wenn ich jetzt versuche, die folgende Anweisung auszuführen, sollte und wird dies fehlschlagen:

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

Wenn ich dies versuche, wird es gelingen:

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

Und jetzt sehen meine Daten so aus:

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

Dies ist nicht das, was ich will, aber ich kann nicht sagen, dass ich mit dem, was passiert ist, überhaupt nicht einverstanden bin. Wenn wir in mathematischen Mengen sprechen,

{'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

Meine Vermutung ist, dass MySQL sagt: "Da ich nicht weiß , dass Jim Johnson mit einem NULL Geburtsdatum nicht bereits in dieser Liste enthalten ist Tisch, ich werde ihn hinzufügen. "

Meine Frage ist: Wie kann ich Duplikate verhindern, obwohl date_of_birth nicht immer bekannt ist? Das Beste, was ich bisher herausgefunden habe, ist, date_of_birth an einen anderen Tisch zu verschieben . Das Problem dabei ist jedoch, dass ich möglicherweise zwei Kassierer mit demselben Namen, Titel und Gehalt habe, unterschiedliche Geburtsdaten und keine Möglichkeit, beide ohne Duplikate aufzubewahren.

38
Jason Swett

Eine grundlegende Eigenschaft von eindeutiger Schlüssel ist, dass es eindeutig sein muss. Wenn Sie einen Teil dieses Schlüssels auf Null setzen, wird diese Eigenschaft zerstört.

Es gibt zwei mögliche Lösungen für Ihr Problem:

  • Ein Weg, der falsche Weg, wäre, ein magisches Datum zu verwenden, um Unbekanntes darzustellen. Dies bringt Sie nur über das DBMS-"Problem" hinaus, löst das Problem jedoch nicht in einem logischen Sinne. Erwarten Sie Probleme mit zwei "John Smith" -Einträgen mit unbekanntem Geburtsdatum. Sind diese Jungs ein und dasselbe oder sind sie einzigartige Individuen? Wenn Sie wissen, dass sie sich unterscheiden, haben Sie wieder dasselbe alte Problem - Ihr eindeutiger Schlüssel ist einfach nicht eindeutig. Denken Sie nicht einmal daran, eine ganze Reihe magischer Daten zuzuweisen, um "Unbekanntes" darzustellen - dies ist wirklich der Weg zur Hölle.

  • Eine bessere Möglichkeit besteht darin, ein EmployeeId-Attribut als Ersatzschlüssel zu erstellen. Dies ist nur eine beliebige Kennung, die Sie Personen zuweisen, die Sie wissen eindeutig sind. Dieser Bezeichner ist oft nur ein ganzzahliger Wert. Erstellen Sie dann eine Employee-Tabelle, um die EmployeeId (eindeutiger, nicht nullwertfähiger Schlüssel) mit den abhängigen Attributen in Beziehung zu setzen, in diesem Fall Name und Geburtsdatum (von denen jeder nullwertfähig sein kann). Verwenden Sie den EmployeeId-Ersatzschlüssel überall dort, wo Sie zuvor den Namen/das Geburtsdatum verwendet haben. Dies fügt Ihrem System eine neue Tabelle hinzu, löst jedoch das Problem unbekannter Werte auf robuste Weise.

22
NealB

Ich denke, MySQL macht es hier richtig. Einige andere Datenbanken (z. B. Microsoft SQL Server) behandeln NULL als einen Wert, der nur einmal in eine UNIQUE-Spalte eingefügt werden kann. Ich persönlich finde das jedoch merkwürdig und unerwartet.

Da Sie dies jedoch möchten, können Sie anstelle von NULL einen "magischen" Wert verwenden, beispielsweise ein Datum, das lange Zeit in der Vergangenheit lag

6
Mark Byers

Ihr Problem, keine Duplikate basierend auf dem Namen zu haben, ist nicht lösbar, da Sie keinen natürlichen Schlüssel haben. Wenn Sie ein falsches Datum für Personen eingeben, deren Geburtsdatum unbekannt ist, wird Ihr Problem nicht gelöst. John Smith, geboren am 01.01.01, wird immer noch eine andere Person sein als John Smithh, geboren am 09.03.2009.

Ich arbeite jeden Tag mit Namensdaten großer und kleiner Organisationen, und ich kann Ihnen versichern, dass sie immer zwei verschiedene Personen mit demselben Namen haben. Manchmal mit der gleichen Berufsbezeichnung. Das Geburtsdatum ist auch keine Garantie für die Einzigartigkeit, viele von John Smiths, die am selben Tag geboren wurden. Heck, wenn wir mit Arztdaten arbeiten, haben wir oft zwei Ärzte mit demselben Namen, Adresse und Telefonnummer (Vater- und Sohnkombinationen).

Am besten verwenden Sie eine Mitarbeiter-ID, wenn Sie Mitarbeiterdaten eingeben, um jeden Mitarbeiter eindeutig zu identifizieren. Überprüfen Sie anschließend den eindeutigen Namen in der Benutzeroberfläche. Wenn ein oder mehrere Übereinstimmungen vorhanden sind, fragen Sie den Benutzer, ob er sie gemeint hat. Erstellen Sie anschließend einen Deupping-Prozess, um Probleme zu beheben, wenn jemand versehentlich zwei IDs zugewiesen bekommt. 

5
HLGEM

Es gibt eine andere Möglichkeit, dies zu tun. Hinzufügen einer Spalte (nicht nullfähig) zur Darstellung des String-Werts der Spalte date_of_birth. Der neue Spaltenwert wäre "" (leere Zeichenfolge), wenn date_of_birth null ist.

Wir benennen die Spalte als date_of_birth_str und erstellen einen eindeutigen Constraint-Mitarbeiter (name, date_of_birth_str). Wenn also zwei Wiederholungen mit demselben Namen und einem null-Wert für date_of_birth geliefert werden, funktioniert die eindeutige Einschränkung weiterhin.

Die Bemühungen um die Wartung der beiden gleichbedeutenden Spalten und der Leistungsschaden der neuen Spalte sollten jedoch sorgfältig geprüft werden.

3
Mike Lue

Ich empfehle die Erstellung einer zusätzlichen Tabellenspalte checksum, die den md5-Hash von name und date_of_birth enthält. Löschen Sie den eindeutigen Schlüssel (name, date_of_birth), da er das Problem nicht löst. Erstellen Sie einen eindeutigen Schlüssel für die Prüfsumme.

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);

Diese Lösung verursacht einen geringen technischen Aufwand, da für jedes eingefügte Paar ein Hash generiert werden muss (für jede Suchabfrage dasselbe). Für weitere Verbesserungen können Sie einen Trigger hinzufügen, der in jedem Insert einen Hash generiert:

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;
1

Ich hatte ein ähnliches Problem, aber mit einem gewissen Unterschied. In Ihrem Fall hat jeder Mitarbeiter Geburtstag, auch wenn er nicht bekannt ist. In diesem Fall ist es logisch, wenn das System Mitarbeitern mit unbekannten Geburtstagen, ansonsten aber identischen Informationen zwei Werte zuordnet. Die von NealB akzeptierte Antwort ist sehr genau.

Das Problem, auf das ich gestoßen bin, war jedoch eines, bei dem das Datenfeld nicht unbedingt einen Wert hatte. Wenn Sie beispielsweise Ihrer Tabelle ein Feld "name_of_spouse" hinzugefügt haben, würde es nicht unbedingt einen Wert für jede Zeile der Tabelle geben. In diesem Fall ist der erste Aufzählungspunkt von NealB (der „falsche Weg“) tatsächlich sinnvoll. In diesem Fall sollte in die Spalte name_of_spouse für jede Zeile, in der kein Ehepartner bekannt war, eine Zeichenfolge "None" eingefügt werden. 

Die Situation, in der ich auf dieses Problem stieß, war das Schreiben eines Programms mit einer Datenbank zur Klassifizierung des IP-Verkehrs. Ziel war es, ein Diagramm des IP-Verkehrs in einem privaten Netzwerk zu erstellen. Jedes Paket wurde in einer Datenbanktabelle mit einem eindeutigen Verbindungsindex abgelegt, der auf IP-Quelle und Ziel, Anschlussquelle und Ziel, Transportprotokoll und Anwendungsprotokoll basiert. Viele Pakete verfügen jedoch einfach nicht über ein Anwendungsprotokoll. Beispielsweise sollten alle TCP - Pakete ohne Anwendungsprotokoll zusammen klassifiziert werden und einen eindeutigen Eintrag im Verbindungsindex belegen. Dies liegt daran, dass diese Pakete einen einzigen Rand meines Diagramms bilden sollen. In dieser Situation habe ich meine eigenen Ratschläge von oben eingeholt und eine Zeichenfolge "None" im Feld "Anwendungsprotokoll" gespeichert, um sicherzustellen, dass diese Pakete eine eindeutige Gruppe bilden.

0
kingledion

Die perfekte Lösung wäre die Unterstützung für funktionsbasierte UKs. Dies wird jedoch komplexer, da mySQL dann auch funktionsbasierte Indizes unterstützen muss. Dies würde die Notwendigkeit verhindern, "falsche" Werte anstelle von NULL zu verwenden, während Entwickler auch die Möglichkeit haben, zu entscheiden, wie NULL-Werte in Großbritannien behandelt werden sollen. Leider unterstützt mySQL derzeit keine solche Funktionalität, die mir bekannt ist. Daher gibt es keine Problemumgehungen.

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'))
);

(Beachten Sie die Verwendung der Funktion IFNULL () in der eindeutigen Schlüsseldefinition.)

0
Paul