假设我正在编写一个MMORPG。我为实体character
建模,该实体可以包含多个属性,例如coating
,strength
,color
等。因为我事先并不知道这些属性(它们是什么以及有多少),所以我认为我为它创建了一个额外的表,如下所示:
CREATE TABLE character (INTEGER id, VARCHAR name, INTEGER player_id);
和
CREATE TABLE attributes (INTEGER character_id, VARCHAR key, VARCHAR value);
然后我可以介绍许多新属性。但是,我如何查询此构造? 查询
SELECT * FROM character JOIN attributes ON character.id=attributes.character_id;
只会显示单个属性。我是否必须多次加入attributes
表或是否有其他解决方案?
有没有办法让attribute.value
部分有不同的类型?按照我现在的方式进行操作会限制我VARCHAR
表示。
答案 0 :(得分:4)
另一种可能性是使用hstore而不是EAV模型。
CREATE TABLE character (id INTEGER, name VARCHAR,
player_id INTEGER, attributes hstore);
这样您就可以将属性存储为地图(键 - 值)。
insert into character (id, name, player_id, attributes)
values (1, 'test', 1, '"attribute1"=>"value1","attribute2"=>"value2"')
,(2, 'test', 1, '"attribute1"=>"value1","attribute3"=>"value3"');
select (each(attributes)).key, (each(attributes)).value
from character where id = 1;
key text value text
--------------------------
attribute1 value1
attribute2 value2
select id, attributes->'attribute3' as value
from character WHERE exist(attributes,'attribute3');
id value
---------------
2 "value3"
希望这有帮助。
<强>更新强>
我做了一个小基准来比较hstore和两个表。
CREATE OR REPLACE FUNCTION create_dummy_data()
RETURNS integer AS
$BODY$
DECLARE
cont1 INTEGER;
cont2 INTEGER;
sqlInsert VARCHAR;
BEGIN
CREATE TABLE character (id INTEGER PRIMARY KEY
,name VARCHAR
,player_id INTEGER);
CREATE TABLE attributes (character_id INTEGER
,key VARCHAR
,value VARCHAR
,FOREIGN KEY (character_id) REFERENCES character);
cont1 := 1;
WHILE cont1 < 10000 LOOP
sqlInsert := 'INSERT INTO character (id, name, player_id) VALUES (' || cont1 || ', ''character' || cont1 || ''', ' || cont1 || ');';
EXECUTE sqlInsert;
cont1 := cont1 + 1;
END LOOP;
cont1 := 1;
WHILE cont1 < 10000 LOOP
cont2 := 1;
WHILE cont2 < 10 LOOP
sqlInsert := 'INSERT INTO attributes (character_id, key, value) VALUES (' || cont1 || ', ''key' || cont2 || ''', ' || cont2 || ');';
EXECUTE sqlInsert;
cont2 := cont2 + 1;
END LOOP;
cont1 := cont1 + 1;
END LOOP;
CREATE TABLE character_hstore (id INTEGER
,name VARCHAR
,player_id INTEGER
,attributes hstore);
cont1 := 1;
WHILE cont1 < 10000 LOOP
sqlInsert := 'INSERT INTO character_hstore (id, name, player_id, attributes) VALUES (' || cont1 || ', ''character' || cont1 || ''', ' || cont1 || ', ''"key1"=>"1","key2"=>"2","key3"=>"3","key4"=>"4","key5"=>"5"'');';
EXECUTE sqlInsert;
cont1 := cont1 + 1;
END LOOP;
RETURN 1;
END;
$BODY$
LANGUAGE plpgsql;
select * from create_dummy_data();
DROP FUNCTION create_dummy_data();
我得到了以下结果:
explain analyze
SELECT ca.*
FROM character ca
JOIN attributes at ON ca.id = at.character_id
WHERE at.value = '1';
"Hash Join (cost=288.98..2152.77 rows=10076 width=21) (actual time=2.788..23.186 rows=9999 loops=1)"
CREATE INDEX ON attributes (value);
explain analyze
SELECT ca.*
FROM character ca
JOIN attributes at ON ca.id = at.character_id
WHERE at.value = '1';
"Hash Join (cost=479.33..1344.18 rows=10076 width=21) (actual time=4.330..13.537 rows=9999 loops=1)"
使用hstore:
explain analyze
SELECT *
FROM character_hstore
WHERE attributes @> 'key1=>1';
"Seq Scan on character_hstore (cost=0.00..278.99 rows=10 width=91) (actual time=0.012..3.530 rows=9999 loops=1)"
explain analyze
SELECT *
FROM character_hstore
WHERE attributes->'key1' = '1';
"Seq Scan on character_hstore (cost=0.00..303.99 rows=50 width=91) (actual time=0.016..4.806 rows=9999 loops=1)"
答案 1 :(得分:2)
您所描述的是实体 - 属性 - 值模型。谷歌EAV,你会发现很多关于它的信息。
是的查询是这种查询的一大缺点。选择变得非常大,复杂且可能很慢。
正如@doctore所描述的,有可用的数据库特定功能:
CREATE TABLE character (INTEGER id, VARCHAR name,
INTEGER player_id, attributes hstore);
您可以阅读有关hstore, follow the link的更多信息。
如果此实体是您的应用程序的核心,您可能需要考虑这种数据的专用键值存储。例如CouchDB