Archive

Archive for January, 2012

Du modèle EAV, ou comment s’arracher les cheveux, pour plein de raisons

January 2nd, 2012 6 comments

Dans le cadre du projet SOIF (qui fera l’objet d’un prochain billet), je travaille actuellement sur un job de transformation d’une base Puppet facts pour injecter dans iTop.

Avant tout, un peu de contexte : Puppet intègre la commande facter qui renvoie tout un ensemble d’informations sous la forme attribut=valeur, et qui est facilement extensible, bref c’est très intéressant. Là où le bat blesse, c’est que – logiquement – ces informations sont stockées en base de données en utilisant le modèle de conception EAV pour Entity Attribute Value.

Ce qui donne :

+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255) | NO   | MUL | NULL    |                |
| updated_at | datetime     | YES  |     | NULL    |                |
| created_at | datetime     | YES  |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+

et

mysql> desc fact_values;
+--------------+----------+------+-----+---------+----------------+
| Field        | Type     | Null | Key | Default | Extra          |
+--------------+----------+------+-----+---------+----------------+
| id           | int(11)  | NO   | PRI | NULL    | auto_increment |
| value        | text     | NO   |     | NULL    |                |
| fact_name_id | int(11)  | NO   | MUL | NULL    |                |
| host_id      | int(11)  | NO   | MUL | NULL    |                |
+--------------+----------+------+-----+---------+----------------+

Donc, pour obtenir par exemple la fact serialnumber pour un hôte donné, cela donne quelque chose du genre :


select fact_values.value FROM fact_values LEFT JOIN hosts ON fact_values.host_id = hosts.id LEFT JOIN fact_names ON fact_values.fact_name_id = fact_names.id WHERE hosts.name = 'xxx' AND fact_names.name = 'serialnumber';

Mais maintenant, retour à mon objectif : travailler par lot, c’est à dire pour tous les enregistrements de la table hosts. Se pose donc la question de comment faire pour obtenir la liste de tous les hôtes, avec la valeur serialnumber, à froid, pour l’expert SQL que je ne suis pas, mon premier jet à donné :

SELECT hosts.id, fact_values.value FROM hosts LEFT JOIN fact_values ON hosts.id = fact_values.host_id LEFT JOIN fact_names ON fact_names.id = fact_values.fact_name_id WHERE fact_names.name = 'serialnumber';

qui semble fonctionné à priori, mais, MAIS, cela ne renvoie que les enregistrements pour lesquels il existe la valeur serialnumber.

Après de nombreuses recherches, il semblerait que la seule solution à mon problème soit :

SELECT 
  hosts.id, 
  (SELECT fact_values.value
    FROM fact_values
    JOIN fact_names
     ON fact_names.id = fact_values.fact_name_id
    WHERE fact_names.name = 'serialnumber' 
     AND fact_values.host_id = hosts.id) as serialnumber,
FROM hosts;

et là, on le voit tout de suite, ça devient compliqué. Pourquoi ? parce que je n’ai pas seulement besoin du serialnumber mais de nombreux autres champs, et pour chaque champ que je veux, je dois donc rajouter un nouveau SELECT, je vous laisse deviner l’impact sur les performances.

Bref, EAV, ça pue. Bien évidemment si vous avez une autre solution, n’hésitez pas à commenter !

Categories: Sysadmin Tags: , ,