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?
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:
SELECT height, weight, age FROM Client where height is null or weight is null
.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:
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.
valeur d'attribut d'entité (EAV)
Il est considéré comme un anti-modèle par beaucoup, y compris moi.
Voici vos alternatives:
utiliser la base de données héritage de table
utiliser des données XML et fonctions SQLXML
utiliser une base de données nosql, comme HBase
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.
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:
Cependant, vous ne devez certainement pas rejeter cette solution, et voici pourquoi:
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 |