2010-06-20 2 views
7

Vor einiger Zeit habe ich das Buch SQL and Relational Theory by C. J. Date gelesen. Der Autor ist dafür bekannt, dass er die dreiwertige Logik von SQL (3VL) kritisiert. 1)Optionen zum Entfernen von NULL-fähigen Spalten aus einem DB-Modell (um die dreiwertige Logik von SQL zu vermeiden)?

Der Autor macht einige Stärken, warum 3VL in SQL sollte vermieden werden, er ist jedoch nicht skizzieren, wie ein Datenbankmodell aussehen würde, wenn Nullable-Spalten nicht erlaubt waren. Ich habe ein wenig darüber nachgedacht und habe folgende Lösungen gefunden. Wenn ich andere Gestaltungsmöglichkeiten verpasst habe, würde ich gerne von ihnen hören!

1) Datum Kritik an der SQL der 3VL wiederum wurde auch kritisiert: siehe this paper by Claude Rubinson (enthält die ursprüngliche Kritik von C. J. Date).


Beispieltabelle

Als Beispiel nehmen die folgende Tabelle, in der wir eine NULL festlegbare Spalte (DateOfBirth):

# +-------------------------------------------+ 
# |     People     | 
# +------------+--------------+---------------+ 
# | PersonID | Name  | DateOfBirth | 
# +============+--------------+---------------+ 
# | 1   | Banana Man | NULL   | 
# +------------+--------------+---------------+ 

Option 1: Emuliert NULL durch ein Flag und einen Standardwert:

Anstatt die Spalte auf Null zu setzen, wird jeder Standardwert angegeben (z. 1900-01-01). Eine zusätzliche Spalte BOOLEAN gibt an, ob der Wert in DateOfBirth einfach ignoriert werden soll oder ob er tatsächlich Daten enthält.

# +------------------------------------------------------------------+ 
# |        People'        | 
# +------------+--------------+----------------------+---------------+ 
# | PersonID | Name  | IsDateOfBirthKnown | DateOfBirth | 
# +============+--------------+----------------------+---------------+ 
# | 1   | Banana Man | FALSE    | 1900-01-01 | 
# +------------+--------------+----------------------+---------------+ 

Option 2: eine Spalte von -zulässige in eine separate Tabelle zum Drehen:

Die nullable Spalte durch eine neue Tabelle ersetzt (DatesOfBirth). Wenn ein Datensatz für diese Spalte keine Daten hat, wird es keinen Eintrag in der neuen Tabelle sein:

# +---------------------------+ 1 0..1 +----------------------------+ 
# |   People'   | <-------> |   DatesOfBirth  | 
# +------------+--------------+   +------------+---------------+ 
# | PersonID | Name  |   | PersonID | DateOfBirth | 
# +============+--------------+   +============+---------------+ 
# | 1   | Banana Man | 
# +------------+--------------+ 

Während dies wie die bessere Lösung scheint, würde dies möglicherweise in vielen Tabellen führt, die sein müssen beigetreten für eine einzelne Abfrage. Da OUTER JOIN s nicht erlaubt sind (weil sie NULL in die Ergebnismenge einführen würden), könnten möglicherweise alle notwendigen Daten nicht mehr wie bisher mit nur einer einzigen Abfrage abgerufen werden.


Frage: Gibt es noch andere Möglichkeiten zur Beseitigung von NULL (und wenn ja, was sind sie)?

+0

könnten Sie kurz erklären, warum die Dreiwertigkeitslogik vermieden werden sollte. Der Grund dafür ist, dass Sie mindestens ein Bit mehr speichern müssen. Aber wenn Sie stattdessen eine andere Spalte hinzufügen, ergibt das keinen Sinn.Eine weitere Tabelle führt zu Abfrage-Overhead. Ein anderer Grund, den ich mir vorstellen kann, ist, dass Sie den Nullwert behandeln müssen, aber Sie haben es auch mit Ihren Lösungen. – TooAngel

+0

@TooAngel: Es geht nicht nur darum, ein zusätzliches Bit zu speichern. Es geht darum, Abfrageergebnisse zu erhalten, die keinen (allgemeinen) Sinn ergeben, z. 'COUNT (*)' zählt nicht 'NULL' oder' NULL' ist nie gleich 'NULL' (weil' NULL' grundsätzlich die Bedeutung von "unbekannt" hat). - Ich schlage vor, Sie lesen das 5-seitige Papier, das ich in der Fußnote verlinkt habe. Es enthält die (anscheinend fehlerhafte, aber immer noch sehr aufschlussreiche) Kritik von 3VL. Vielleicht möchten Sie auch den Wikipedia-Artikel über ternäre Logik ausprobieren: http://en.wikipedia.org/wiki/Ternary_logic – stakx

+0

Zum oben genannten sollte ich den Hauptpunkt hinzufügen, der wegen der manchmal "nicht intuitiven" ist Ergebnisse, die Sie dank 3VL erhalten, Ergebnisse werden leicht falsch interpretiert. Oder noch schlimmer, eine Frage ist nicht, was man denkt, und man wird (richtige) Ergebnisse bekommen, die nicht richtig erscheinen. Ein letzter Punkt ist, dass "NULL" oft verwendet wird, um verschiedene Dinge zu bedeuten, z. "unknown", "missing", "not applicable" usw., was es noch schwieriger macht, korrekte Abfragen zu formulieren und das Ergebnis aus der DB korrekt zu interpretieren. – stakx

Antwort

4

Ich sah Date Kollege Hugh Darwen dieses Thema in einer ausgezeichneten Präsentation "Umgang mit fehlenden Informationen ohne Verwendung von NULL" zu diskutieren, die auf the Third Manifesto website verfügbar ist.

Seine Lösung ist eine Variante Ihres zweiten Ansatzes. Es ist sechste Normalform, mit Tischen sowohl Geburtsdatum zu halten und Kennungen, wo es unbekannt ist:

# +-----------------------------+ 1 0..1 +----------------------------+ 
# |   People'    | <-------> |   DatesOfBirth  | 
# +------------+----------------+   +------------+---------------+ 
# | PersonID | Name   |   | PersonID | DateOfBirth | 
# +============+----------------+   +============+---------------+ 
# | 1   | Banana Man |   ! 2   | 20-MAY-1991 | 
# | 2   | Satsuma Girl |   +------------+---------------+ 
# +------------+----------------+ 
#         1 0..1 +------------+ 
#         <-------> | DobUnknown | 
#           +------------+ 
#           | PersonID | 
#           +============+ 
#           | 1   | 
#           +------------+ 

von Menschen wählen dann alle drei Tabellen erfordert Beitritt, einschließlich vorformulierten die unbekannten Geburtsdaten anzuzeigen.

Natürlich ist dies etwas theoretisch. Der Zustand von SQL ist in diesen Tagen noch nicht ausreichend fortgeschritten, um all dies zu bewältigen. Hughs Vortrag deckt diese Mängel ab. Eine Sache, die er erwähnt, ist nicht ganz richtig: Einige Varianten von SQL unterstützen mehrere Zuweisungen - zum Beispiel Oracle's INSERT ALL syntax.

+0

Ich habe das Papier auf Third gelesen Manifest und ganz wie die Lösung, vor allem, weil es die Ambiguität dessen löst, was genau "NULL" bedeutet. Während diese Trennung verschiedener Bedeutungen von "NULL" in separate Tabellen die Datenqualität verbessert, stimme ich Ihnen zu, dass das Abfragen der Daten tatsächlich schwieriger wird. – stakx

+1

+1 für den Darwen-Artikel. Kleinere Anmerkung: Wenn die Beziehungen in der Tat 1: 0..1 sind, wie angegeben, dann ist Mehrfachvergabe kein Problem. – onedaywhen

1

ich es nicht gelesen haben, aber es gibt einen Artikel namens Wie fehlende Informationen Griff mit S-by-C auf der der Third Manifesto Website, die von Hugh Darwen und C. J. Datum laufen gelassen. Dies ist nicht von C.J.Datum, aber ich nehme an, dass, da es einer der Artikel auf dieser Website ist, es wahrscheinlich seinen Meinungen ähnlich ist.

+0

Es gibt einige interessante Informationen auf dieser Website. Das spezifische Papier, das Sie erwähnen, scheint jedoch akademischer zu sein: Es gibt Beispiele in der Sprache von Tutorial D und nicht in SQL, was heute tatsächlich verwendet wird. (+1 für den Link zur Website.) – stakx

+0

@stakx: Ah, wundert mich nicht, ich nehme an, ich habe die Webseite vor einer Weile gefunden, nachdem ich eine andere Frage über SO gelesen hatte, aber ich fand es ein bisschen zu akademisch, um es wirklich zu lesen durch irgendetwas. –

0

Eine Alternative kann das entity-attribute-value Modell:

entity attribute value 
1  name   Banana Man 
1  birthdate 1968-06-20 

Wenn das Geburtsdatum unbekannt ist, würde man nur die entsprechende Zeile weglassen.

+0

Ich vermute, das Problem mit diesem Modell ist, dass Sie einen einzigen Typ für Spalte "value" wählen müssen, der für alle möglichen Dinge geeignet ist (dh dies würde das Parsen von Strings oder sogar 'BLOB's auf der Client-Seite erfordern). – stakx

+0

@stakx: Sie könnten eine 'type' Spalte zusammen mit' str_value', 'int_value',' real_value' Spalten hinzufügen. Und ich denke, SQLIte unterstützt EAV nativ und speichert Integer, Floats und Strings effizient in derselben Spalte – Andomar

+0

Ich wusste nicht, dass SQLite native Unterstützung für das EAV-Modell hat. Es ist ein interessantes Feature, obwohl es ein Nicht-Standard (?) Ist, das andere RDBMS möglicherweise nicht haben. Danke für die Information! – stakx

0

Option 3: Onus auf dem Aufzeichnungs Schriftsteller:

CREATE TABLE Person 
(
    PersonId int PRIMARY KEY IDENTITY(1,1), 
    Name nvarchar(100) NOT NULL, 
    DateOfBirth datetime NOT NULL 
) 

Warum ein Modell contort null Darstellung zu ermöglichen, wenn es Ihr Ziel, sie zu beseitigen ist?

+0

* Jede * Lösung für meine Frage würde eventuell 'NOT NULL' in allen Spalten haben (wo zutreffend). Aber die eigentliche Frage - wenn Sie meinen Beitrag nicht auf den letzten Satz reduzieren - war, * wie * ein relationales Datenmodell ** geändert werden müsste, damit es noch fehlende oder unbekannte Informationen ** für manche aufnehmen kann Attribute. Ihre Lösung jedoch * schließt * fehlende Informationen für die Spalte 'DateOfBirth' vollständig aus und vermisst somit das wichtigste Bit. – stakx

+0

Wenn Sie immer noch den dritten Wert zulassen, müssen Sie noch Logik über den dritten Wert schreiben. Ich weiß nicht, warum Ihre Implementierung des dritten Wertes besser wäre als die Implementierung eines db-Anbieters. –

+1

Ich möchte * rid * vom dritten Wert ('UNKNOWN') bekommen, * aber * gleichzeitig noch die Tatsache darstellen können, dass einige Informationen fehlen (oder unbekannt, oder was auch immer). Dies ist offensichtlich möglich. Ich untersuche verschiedene Möglichkeiten, dies zu tun. Sie sagen vielleicht: "Warum nicht einfach' NULL' verwenden, es ist nur für diesen Zweck da? " - Weil das 3VL * zu verwirrenden und nicht intuitiven Abfragen und Abfrageergebnissen führen kann. Selbst ohne 3VL muss man natürlich * irgendwie * mit möglicherweise fehlenden Informationen umgehen - aber dies mit 2VL zu tun, könnte einige logische Fehler verhindern. – stakx

0

Sie können auch null in der Ausgabe mit COALESCE entfernen.

SELECT personid /*primary key, will never be null here*/ 
     , COALESCE(name, 'no name') as name 
     , COALESCE(birthdate,'no date') as birthdate 
FROM people 

Nicht alle Datenbanken unterstützen COALESCE, aber fast alle haben eine Ersatzoption namens
IFNULL(arg1, arg2) oder etwas simular, die die gleiche (aber nur für zwei Argumente) tun wird.

1

Ich empfehle Sie für Ihre Option 2. Ich bin ziemlich sicher, Chris Date würde auch, weil im Wesentlichen was Sie tun, ist normalisieren 6NF, die höchstmögliche Normalform, die Date was jointly responsible for introducing. Ich zweite Sekunde die empfohlene Darwen's paper auf die Behandlung fehlender Informationen.

Da OUTER JOIN wird nicht zugelassen werden (weil sie würde NULL einführen in die Ergebnismenge), alle notwendigen Daten könnten möglicherweise nicht mehr mit nur einer einzigen Abfrage wie zuvor abgeholt werden.

... dies ist nicht der Fall, aber ich stimme zu, dass das Problem der äußeren Verbindung nicht explizit in der Darwen-Papier erwähnt wird; Es war die eine Sache, die mich fehlte. Die ausdrückliche Antwort kann in einem anderen Buch des Datums gefunden werden ...

Beachten Sie zuerst, dass Datum und Darwens wirklich relationale Sprache Tutorial D nur einen Join-Typ hat, der die natürliche Verbindung ist. Die Begründung ist, dass nur ein Join-Typ tatsächlich benötigt wird.

Das Datum Buch, das ich erwähnt ist die ausgezeichnete SQL and Relational Theory: How to Write Accurate SQL Code:

4,6: Eine Bemerkung zu Outer Join: „Relational gesprochen, [Outer Join ist] eine Art Schrotflinte Ehe: Es zwingt Tabellen in ein Art der Union-ja, ich meine Union, nicht Join-auch wenn die fraglichen Tabellen entsprechen den üblichen Anforderungen für die Union ... Es tut dies, in Wirkung, durch Auffüllen einer oder beider Tabellen mit Nullen vor die Union tun, damit sie den üblichen Anforderungen schließlich entsprechen, aber es gibt n o Grund, warum die Polsterung nicht mit richtigen Werten erfolgen statt nulls

Arbeiten mit dem Beispiel und den Standardwert ‚1900-01-01‘ als ‚padding‘, die Alternative zu Outer-Joins könnte wie folgt aussehen Darwen Papier proses zwei explizite Tabellen, sagen BirthDate und BirthDateKnown, aber die SQL würde nicht viel anders wie sein

SELECT p.PersonID, p.Name, b.DateOfBirth 
    FROM Person AS p 
     INNER JOIN BirthDate AS b 
      ON p.PersonID = b.PersonID 
UNION 
SELECT p.PersonID, p.Name, '1900-01-01' AS DateOfBirth 
    FROM Person AS p 
WHERE NOT EXISTS (
        SELECT * 
        FROM BirthDate AS b 
        WHERE p.PersonID = b.PersonID 
       ); 

: eine Semi-Verbindung zu BirthDateKnown anstelle des Semi-Unterschieds zu BirthDate oben.

Beachten Sie die oben genannten Verwendungen JOIN und INNER JOIN nur, weil Standard-SQL-92 NATURAL JOIN und UNION CORRESPONDING sind im wirklichen Leben SQL Produkte nicht weitgehend umgesetzt (kann nicht ein Zitat finden, aber IIRC Darwen war weitgehend verantwortlich für die beiden letztgenannten es in der Herstellung Standard).

Weitere beachten Sie die oben genannte Syntax sieht nur langatmig, weil SQL im Allgemeinen langatmig ist. In der reinen relationalen Algebra ist es eher wie (Pseudocode):

Person JOIN BirthDate UNION Person NOT MATCHING BirthDate ADD '1900-01-01' AS DateOfBirth; 
0

Eine Möglichkeit ist ausdrücklich option types, analog zu Haskells Maybe Funktors zu verwenden.

Leider haben viele existierende SQL-Implementierungen schlechte Unterstützung für benutzerdefinierte algebraische Datentypen und noch schlechtere Unterstützung für benutzerdefinierte Typkonstruktoren, die Sie wirklich sauber ausführen müssen.

Dies stellt eine Art von "null" nur für die Attribute wieder her, für die Sie explizit danach fragen, aber ohne nulls dumme dreiwertige Logik. Nothing == Nothing ist True, nicht unknown oder null.

Unterstützung für benutzerdefinierte algebraische Typen hilft auch, wenn es ein paar Gründe für die fehlenden Informationen sind zum Beispiel eine Datenbank Äquivalent des folgenden Haskell Typ eine gute Lösung für die offensichtliche Anwendung wäre:

data EmploymentStatus = Employed EmployerID | Unemployed | Unknown 

(natürlich unterstützt eine Datenbank diese müssten auch die mehr kompliziert als üblich Fremdschlüssel zu unterstützen, die mit ihm kommt.)

Kurz dieser, ich mit APC ‚s und onedaywhen‘ s Antworten stimmen über 6NF.

+1

SQL 'NULL' bedeutet nicht" nichts ", sondern" unbekannt ". Daher ist es richtig, dass "NULL = NULL" nicht wahr ist: Wenn Sie zwei Unbekannte vergleichen, können Sie nicht sicher sein, dass sie gleich sind. – stakx

+0

Es ist "richtig", weil es so definiert ist, aber es ist _hugely_ aus mehreren Gründen problematisch. Konsultieren Sie http://www.amazon.com/Foundation-Object-Relational-Databases-Manifesto/dp/0201309785 für die blutigen Details, die in diesen Kommentar nicht passen. (Es ist auch ein alter Krieg, der nicht neu debattiert werden muss, aber die Pro-Null-Seite ist objektiv in der Sache falsch.) –

Verwandte Themen