Franchement, la meilleure option est "pas EAV". Envisagez d'utiliser hstore
champs, XML
, ou json
.
Dans PostgreSQL, il n'y a aucun avantage en termes de performances à utiliser des tables par type de données. NULL
les valeurs sont stockées dans un NULL
compact bitmap, cela fait donc très peu de différence si vous avez un tuple comme (NULL, NULL, NULL, 42, NULL, NULL)
ou juste (42)
.
Cela vous permet également d'ajouter CHECK
contrainte imposant qu'exactement un champ doit être non-NULL
, vous n'obtenez donc pas plusieurs valeurs de types différents.
Démo :
regress=> CREATE TABLE eav_ugh (
entity_id integer,
int_value integer,
numeric_value numeric,
text_value text,
timestamp_value timestamp with time zone,
CONSTRAINT only_one_non_null CHECK (
(int_value IS NOT NULL AND numeric_value IS NULL AND text_value IS NULL AND timestamp_value IS NULL) OR
(int_value IS NULL AND numeric_value IS NOT NULL AND text_value IS NULL AND timestamp_value IS NULL) OR
(int_value IS NULL AND numeric_value IS NULL AND text_value IS NOT NULL AND timestamp_value IS NULL) OR
(int_value IS NULL AND numeric_value IS NULL AND text_value IS NULL AND timestamp_value IS NOT NULL)
)
);
CREATE TABLE
regress=> insert into eav_ugh (entity_id, numeric_value) select x, x from generate_series(1,5000) x;
INSERT 0 5000
regress=> select pg_relation_size('eav_ugh');
pg_relation_size
------------------
229376
(1 row)
regress=> CREATE TABLE no_null_cols(entity_id integer, numeric_value numeric);
CREATE TABLE
regress=> insert into no_null_cols (entity_id, numeric_value) select x, x from generate_series(1,5000) x;
INSERT 0 5000
regress=> select pg_relation_size('no_null_cols');
pg_relation_size
------------------
229376
(1 row)
regress=> SELECT sum(pg_column_size(eav_ugh)) FROM eav_ugh;
sum
--------
164997
(1 row)
regress=> SELECT sum(pg_column_size(no_null_cols)) FROM no_null_cols;
sum
--------
164997
(1 row)
Dans ce cas, le bitmap nul n'ajoute aucun espace, probablement en raison des exigences d'alignement.