2

假设我正在编写 MMORPG。我建模了一个实体character,它可以具有多种属性,例如、coating等。因为我事先不知道这些属性(它们是什么以及有多少),所以我以为我为它创建了一个额外的表,如下所示:strengthcolor

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代表。

4

2 回答 2

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)"
于 2012-09-27T10:18:23.023 回答
2

您所描述的是实体-属性-值模型。Google EAV,你会发现很多关于它的信息。

是的,查询是这种查询的一大缺点。选择变得非常大、复杂并且可能很慢。

正如@dotore 所描述的,有一些数据库特定的功能可用:

CREATE TABLE character (INTEGER id, VARCHAR name,
                    INTEGER player_id, attributes hstore);

您可以阅读有关hstore 的更多信息,请点击链接

如果此实体是您的应用程序的中心,您可能需要考虑为此类数据使用专用的键值存储。例如CouchDB

于 2012-09-27T10:26:06.923 回答