sql - 对可以具有不同属性的实体建模的最佳方法是什么?

标签 sql postgresql attributes entity entity-relationship

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

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 表示形式。

最佳答案

另一种可能性是使用 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)"

关于sql - 对可以具有不同属性的实体建模的最佳方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12618604/

相关文章:

postgresql - 将一年添加到 postgresql 中的日期字段

python-3.x - Python Dataclass - 从属性名称获取元数据

sql - Oracle SQL - 如何选择和组合多行数据

mysql - 修剪列中逗号分隔值中的前导零

c# - MVC C# web 服务连接两个表并合并输出

java - 从同一实体上的内部连接获取 Hibernate 实体

java - 如何在JAVA jdbc中运行SQL(MYSQL)存储过程?

python - Django - 获取相关集合中的对象计数

java - 有没有办法在 servlet session 中创建 "one time use"属性?

javascript - 你如何使用 JavaScript 添加/删除隐藏在 <p hidden> 中