web-dev-qa-db-fra.com

Y a-t-il un nom pour ce schéma de base de données de valeurs clés?

Nous traitons un flux de données de routine d'un client qui vient de refaçonner sa base de données d'un formulaire qui semble familier (une ligne par entité, une colonne par attribut) à un qui me semble inconnu (une ligne par entité par attribut):

Avant: une colonne par attribut

ID   Ht_cm   wt_kg   Age_yr  ... 
1      190      82     43    ...
2      170      60     22    ...
3      205      90     51    ...

Après: une colonne pour tous les attributs

ID    Metric   Value
 1     Ht_cm     190
 1     Wt_kg     82
 1     Age_yr    43
 1      ...
 2     Ht_cm     170
 2     Wt_kg     60
 2     Age_yr    22
 2     ...
 3     Ht_cm     205
 3     Wt_kg     90
 3     Age_yr    51
 3     ...

Y a-t-il un nom pour cette structure de base de données? Quels sont les avantages relatifs? L'ancienne méthode semble plus facile de placer des contraintes de validité sur des attributs spécifiques (non nuls, non négatifs, etc.) et plus facile à calculer des moyennes. Mais je peux voir comment il pourrait être plus facile d'ajouter de nouveaux attributs sans refactoriser la base de données. Est-ce une façon standard/préférée de structurer les données?

70
prototype

Cela s'appelle Entity-Attribute-Value (parfois aussi "paires nom-valeur") et c'est un cas classique de "cheville ronde dans un trou carré" lorsque les gens utilisent le modèle EAV dans une base de données relationnelle.

Voici une liste des raisons pour lesquelles vous ne devriez pas utiliser EAV:

  • Vous ne pouvez pas utiliser de types de données. Peu importe que la valeur soit une date, un nombre ou de l'argent (décimal). Ça va toujours être contraint à varchar. Cela peut être quelque chose d'un problème de performance mineur à un gros mal de ventre (jamais eu à chasser une variation d'un cent dans un rapport de cumul mensuel?).
  • Vous ne pouvez pas (facilement) appliquer des contraintes. Il faut une quantité ridicule de code pour appliquer "Tout le monde doit avoir une hauteur entre 0 et 3 mètres" ou "L'âge ne doit pas être nul et> = 0", contrairement aux 1-2 lignes que chacune de ces contraintes serait dans un système correctement modélisé.
  • En relation avec ce qui précède, vous ne pouvez pas facilement garantir que vous obtenez les informations dont vous avez besoin pour chaque client (l'âge peut être manquant pour l'un, puis le suivant peut ne pas avoir sa taille, etc.). Vous pouvez le faire, mais c'est beaucoup plus difficile que SELECT height, weight, age FROM Client where height is null or weight is null.
  • Encore une fois, les données en double sont beaucoup plus difficiles à détecter (que se passe-t-il si elles vous donnent deux âges pour un client? La désactivation des données, comme ci-dessous, vous donnera deux lignes de résultats si vous avez un attribut doublé. Si un client a deux entrées distinctes pour deux attributs, vous obtiendrez quatre lignes de la requête ci-dessous).
  • Vous ne pouvez même pas garantir la cohérence des noms d'attribut. "Age_yr" peut devenir "AGE_IN_YEARS" ou "age". (Certes, cela pose moins de problème lorsque vous recevez un extrait que lorsque les gens insèrent des données, mais quand même.)
  • Toute sorte de requête non triviale est un désastre complet. Pour relationaliser un système EAV à trois attributs afin de pouvoir l'interroger de manière rationnelle, il faut trois jointures de la table EAV.

Comparer:

SELECT cID.ID AS [ID], cH.Value AS [Height], cW.Value AS [Weight], cA.Value AS [Age]
FROM (SELECT DISTINCT ID FROM Client) cID 
      LEFT OUTER JOIN 
    Client cW ON cID.ID = cW.ID AND cW.Metric = "Wt_kg" 
      LEFT OUTER JOIN 
    Client cH ON cID.ID = cH.ID AND cW.Metric = "Ht_cm" 
      LEFT OUTER JOIN 
    Client cA ON cID.ID = cA.ID AND cW.Metric = "Age_yr"

À:

SELECT c.ID, c.Ht_cm, c.Wt_kg, c.Age_yr
FROM Client c

Voici une (très courte) liste des cas où vous devriez utiliser EAV:

  • Lorsqu'il n'y a absolument aucun moyen de le contourner et que vous devez prendre en charge les données sans schéma dans votre base de données.
  • Lorsque vous avez juste besoin de stocker des "trucs" et ne vous attendez pas à en avoir besoin sous une forme plus structurée. Mais attention, le monstre a appelé "l'évolution des exigences".

Je sais que je viens de passer cet article en détail pourquoi l'EAV est une idée terrible dans la plupart des cas - mais il y a quelques cas où c'est nécessaire/inévitable. cependant, la plupart du temps (y compris l'exemple ci-dessus), cela va être beaucoup plus compliqué que cela ne vaut. Si vous avez besoin d'une large prise en charge de la saisie de données de type EAV, vous devriez envisager de les stocker dans un système de valeurs-clés, par ex. Hadoop/HBase, CouchDB, MongoDB, Cassandra, BerkeleyDB.

95
Simon Righarts

valeur d'attribut d'entité (EAV)

Il est considéré comme un anti-modèle par beaucoup, y compris moi.

Voici vos alternatives:

  1. utiliser la base de données héritage de table

  2. utiliser des données XML et fonctions SQLXML

  3. utiliser une base de données nosql, comme HBase

19
Neil McGuigan

Dans PostgreSQL, un très bon moyen de gérer les structures EAV est le module supplémentaire hstore , disponible pour version 8.4 ou ultérieure. Je cite le manuel:

Ce module implémente le type de données hstore pour stocker des ensembles de paires clé/valeur dans une seule valeur PostgreSQL. Cela peut être utile dans divers scénarios, tels que des lignes avec de nombreux attributs rarement examinés ou des données semi-structurées. Les clés et les valeurs sont simplement des chaînes de texte.

Depuis Postgres 9.2, il y a aussi le type json et un hôte de fonctionnalités pour l'accompagner (- la plupart ajoutés avec 9. ).

Postgres 9.4 ajoute le type de données "JSON binaire" (largement supérieur!) jsonb à la liste des les options. Avec des options d'index avancées.

16
Erwin Brandstetter

C'est drôle de voir comment le modèle de base de données EAV est critiqué et même considéré comme un "anti-modèle" par certains.

En ce qui me concerne, les principaux inconvénients sont:

  • La courbe d'apprentissage est plus abrupte si vous vous lancez sur un projet qui a déjà commencé à utiliser EAV il y a quelque temps. En effet, les requêtes sont difficiles car vous augmentez considérablement le nombre de jointures (et de tables) et cela vous demandera plus de temps pour comprendre. Jetez simplement un œil au projet Magento et voyez comment les développeurs externes au projet ont du mal à travailler sur la base de données, mais la documentation est bien entretenue.
  • Ne convient pas pour les rapports, si vous avez besoin d'obtenir le nombre de personnes dont le nom a commencé par "M" etc ...

Cependant, vous ne devez certainement pas rejeter cette solution, et voici pourquoi:

  • Simon a parlé du monstre appelé "évolution des exigences". J'aime cette expression :). Et à mon humble avis, c'est précisément pourquoi l'EAV peut être un bon candidat, car cela est bien adapté au "changement", car vous pouvez ajouter autant d'attributs que vous le souhaitez assez facilement. Bien sûr, cela dépend des exigences que nous changeons. Si nous parlons d'une toute nouvelle entreprise, vous devrez bien sûr revoir votre modèle de données, mais l'EAV offre beaucoup de flexibilité. Ce n'est pas parce que cela demande plus de rigueur que c'est moins intéressant.
  • Il a également été dit que "vous ne pouvez pas utiliser de types de données". : C'est faux. Vous pouvez très bien avoir plusieurs tables de valeurs, une pour chaque dataType. Vous devez ensuite spécifier dans votre table d'attributs quel type de dataType est votre attribut. En fait, un mélange de relationnel/EAV classique avec une relation de classe offre beaucoup de potentiel intéressant dans la conception de la base de données.
11
Melvin PRESSOUYRE

Si vous avez une base de données qui utilise la structure EAV, il est possible d'interroger les données de différentes manières.

@ réponse de Simon montre déjà comment effectuer une requête en utilisant plusieurs jointures.

Exemples de données utilisées:

CREATE TABLE yourtable ([ID] int, [Metric] varchar(6), [Value] int);

INSERT INTO yourtable ([ID], [Metric], [Value])
VALUES (1, 'Ht_cm', 190),
    (1, 'Wt_kg', 82),
    (1, 'Age_yr', 43),
    (2, 'Ht_cm', 170),
    (2, 'Wt_kg', 60),
    (2, 'Age_yr', 22),
    (3, 'Ht_cm', 205),
    (3, 'Wt_kg', 90),
    (3, 'Age_yr', 51);

Si vous utilisez un SGBDR doté d'une fonction PIVOT ( SQL Server 2005 + / Oracle 11g + ), vous pouvez interroger les données de la manière suivante:

select id, Ht_cm, Wt_kg, Age_yr
from
(
  select id, metric, value
  from yourtable
) src
pivot
(
  max(value)
  for metric in (Ht_cm, Wt_kg, Age_yr)
) piv;

Voir SQL Fiddle with Demo

Si vous n'avez pas accès à une fonction PIVOT, vous pouvez utiliser une fonction d'agrégation avec une instruction CASE pour renvoyer les données:

select id,
  max(case when metric ='Ht_cm' then value else null end) Ht_cm,
  max(case when metric ='Wt_kg' then value else null end) Wt_kg,
  max(case when metric ='Age_yr' then value else null end) Age_yr
from yourtable
group by id

Voir SQL Fiddle with Demo

Ces deux requêtes renverront des données dans le résultat:

| ID | HT_CM | WT_KG | AGE_YR |
-------------------------------
|  1 |   190 |    82 |     43 |
|  2 |   170 |    60 |     22 |
|  3 |   205 |    90 |     51 |
10
Taryn