Options pour l'élimination des colonnes Nullable d'un modèle DB (afin d'éviter la logique à trois valeurs de SQL)?

StackOverflow https://stackoverflow.com/questions/3079885

Question

quelque temps il y a, je l'ai lu dans le livre SQL et Relational théorie par CJ date . L'auteur est bien connu pour avoir critiqué la logique à trois valeurs de SQL (3VL). 1)

L'auteur fait quelques points forts sur les raisons 3VL doit être évité dans SQL, mais il ne fait pas les grandes lignes comment un modèle de base de données ressemblerait si les colonnes nullables ne sont pas autorisés . J'ai pensé à ce sujet pour un peu et ont mis au point avec les solutions suivantes. Si j'ai raté d'autres options de conception, je voudrais entendre parler!

1) critique de date de 3VL de SQL a été à son tour critiqué aussi: voir ce document par Claude Rubinson (y compris la critique originale par date CJ).


Tableau Exemple:

À titre d'exemple, prenez le tableau suivant où nous avons une colonne nullable (de DateOfBirth):

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

Option 1: Emulation NULL par un pavillon et une valeur par défaut:

Au lieu de faire le nullable de colonne, toute valeur par défaut est spécifiée (par exemple 1900-01-01). Une colonne de BOOLEAN supplémentaire précisera si la valeur DateOfBirth doit simplement être ignorée ou si elle contient effectivement des données.

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

Option 2: Transformer une colonne nullable dans une table séparée:

La colonne nullable est remplacée par une nouvelle table (DatesOfBirth). Si un enregistrement ne dispose pas de données pour cette colonne, il n'y aura pas un enregistrement dans la nouvelle table:

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

Bien que cela semble la meilleure solution, cela peut donner lieu à de nombreuses tables qui doivent être jointes une seule requête. Étant donné que OUTER JOINs ne seront pas autorisés (parce qu'ils introduiraient NULL dans le jeu de résultats), toutes les données nécessaires pourraient ne plus être tiré par les cheveux avec une seule requête que précédemment.


Question: Y at-il d'autres options pour éliminer NULL (et si oui, quels sont-ils)?

Était-ce utile?

La solution

J'ai vu collègue de date Hugh Darwen discuter de cette question dans une excellente présentation « Comment faire pour gérer l'information manquante sans utiliser NULL », qui est disponible sur le site Troisième Manifeste .

Sa solution est une variante de votre deuxième approche. Il est sixième forme normale, avec des tables de tenir les deux dates de naissance et des identificateurs où il est inconnu:

#  +-----------------------------+ 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          |
#                                            +------------+

La sélection de personnes nécessite alors joindre les trois tableaux, y compris pour indiquer les boilerplate dates de naissance inconnues.

Bien sûr, cela est un peu théorique. L'état de SQL ces jours-ci est toujours pas suffisamment avancé pour gérer tout cela. La présentation de Hugh couvre ces lacunes. Une chose qu'il mentionne est pas tout à fait correcte: des saveurs de SQL suportent affectations multiples - par exemple syntaxe Oracle INSERT ALL .

Autres conseils

Je vous recommande d'aller pour votre option 2. Je suis assez certaine date Chris serait aussi parce que l'essentiel ce que vous faites est complètement normalisant 6NF , la forme normale la plus élevée possible qui date a été conjointement responsable de l'introduction . Je le recommande deuxième papier de Darwen sur le traitement des informations manquantes.

  

Comme Jointures externes ne seront pas autorisés (parce qu'ils introduiraient NULL   dans le jeu de résultats), toutes les données nécessaires pourraient éventuellement plus   être extrait avec une seule requête que précédemment.

... ce n'est pas le cas, mais je suis d'accord la question de jointure externe n'est pas mentionné explicitement dans le document Darwen; ce fut la seule chose qui m'a laissé à désirer. La réponse explicite se trouve dans un autre livre de date ...

Tout d'abord, notez que la date et de langue vraiment relationnelle Darwen Tutoriel D a mais un type de jointure étant la jointure naturelle. La justification est que seul un type de jointure est réellement nécessaire.

Le livre Date j'ai fait allusion est l'excellent SQL et Relational Théorie: Comment écrire du code SQL précis :

  

4.6: Une remarque sur jointure externe: « parlant relationnellement, [jointure externe est] une   sorte de mariage de fusil de chasse: Elle oblige les tables en une sorte d'union oui, je   faire union moyenne, pas adhérer, même lorsque les tables en question ne parviennent pas à   se conformer aux exigences habituelles pour l'union ... Il fait cela, en   effet, par un rembourrage ou à la fois des tableaux avec des valeurs nulles avant de faire   le syndicat, ce qui rend les rendre conformes à ces exigences habituelles   après tout. Mais il n'y a aucune raison pour que le rembourrage ne doit pas être fait   avec des valeurs propres au lieu de nulls

En utilisant votre exemple et la valeur par défaut « 1900-01-01 » comme « padding », l'alternative à la jointure externe pourrait ressembler à ceci:

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

Le papier de Darwen proses deux tableaux explicites, disent BirthDate et BirthDateKnown, mais le SQL ne serait pas très différent par exemple un demi pour rejoindre BirthDateKnown en place de la différence à demi BirthDate ci-dessus.

Notez que le ci-dessus utilise JOIN et INNER JOIN seulement parce que ne sont pas largement mis en œuvre la norme SQL-92 NATURAL JOIN et UNION CORRESPONDING dans la vie réelle des produits SQL (ne peut pas trouver une citation, mais IIRC Darwen était en grande partie responsable de ces deux derniers faisant dans la norme ).

De plus, notez l'apparence de syntaxe ci-dessus poussif long seulement parce que SQL en général est de longue haleine. Dans pur l'algèbre relationnelle, il est plus comme (pseudo code):

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

Une alternative peut être le modèle -valeur d'attribut d'entité :

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

Si la date de naissance était inconnu, vous auriez juste omettez sa ligne.

Option 3: Il incombe à l'auteur d'enregistrement:

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

Pourquoi contorsionner un modèle pour permettre une représentation nulle lorsque votre objectif est de les éliminer?

Vous pouvez éliminer null dans la sortie et en utilisant COALESCE .

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

Pas toutes les bases de données soutenir COALESCE, mais ont presque tous une option de repli appelé
IFNULL(arg1, arg2) ou Simular quelque chose qui fera le même (mais seulement pour 2 arguments) .

Une option consiste à utiliser href="http://en.wikipedia.org/wiki/Option_type" explicite , analogues au foncteur Maybe de Haskell.

Malheureusement, beaucoup d'implémentations SQL existantes ont un mauvais support pour les types de données algébriques définies par l'utilisateur et un soutien encore plus pauvres pour les constructeurs de type défini par l'utilisateur que vous avez vraiment besoin de le faire proprement.

Ceci permet de récupérer une sorte de « nulle » uniquement pour les attributs où vous demander explicitement, mais sans ridicule logique à trois valeurs de null. Nothing == Nothing est True, pas unknown ou null.

Prise en charge des types algébriques définis par l'utilisateur permet également quand il y a quelques raisons d'informations manquantes, par exemple une base de données équivalente du type Haskell suivante serait une bonne solution pour l'application évidente:

data EmploymentStatus = Employed EmployerID | Unemployed | Unknown

(Bien sûr, une base de données supportant ce devrait également soutenir la contrainte de clé étrangère plus-que d'habitude compliqué qui vient avec elle.)

Court de cela, je suis d'accord avec APC 's et onedaywhen 'réponses sur 6NF.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top