Dar*_*roo 3 postgresql relational-division query-performance
我有一个 Postgresql 11 数据库。假设我有一张桌子,叫做houses。它应该有数十万条记录。
CREATE TABLE houses (
pkid serial primary key,
address varchar(255) NOT NULL,
rent float NOT NULL
);
Run Code Online (Sandbox Code Playgroud)
现在,我的房子有我想在数据库中注册的功能。由于可能的功能列表会很长(几十个)并且会随着时间的推移而演变,因为我不想在表屋中添加一长串列并使用“ALTER TABLE”不断更改表,我想到了这些功能有一个单独的表格:
CREATE TABLE house_features (
pkid serial primary key,
house_pkid integer NOT NULL,
feature_name varchar(255) NOT NULL,
feature_value varchar(255)
);
CREATE INDEX ON house_features (feature_name, feature_value);
ALTER TABLE house_features ADD CONSTRAINT features_fk FOREIGN KEY (house_pkid) REFERENCES houses (pkid) ON DELETE CASCADE;
Run Code Online (Sandbox Code Playgroud)
平均而言,每个房屋记录在house_features表中将有 10-20 条记录。
到目前为止,这似乎是一个简单高效的模型:我可以添加尽可能多的不同功能,控制上层(应用层和/或 GUI)中feature_name和feature_value的可能值。每次应用程序发展时我都不必更改数据库,我需要一种新的功能。
例如,假设我有以下功能:
显然,将布尔值、整数和浮点数存储为字符串并不是很有效,这也是我需要处理的事情。我正在考虑为每个 XXX 类型(字符串、布尔值、浮点数、整数)设置一个单独的house_features_XXX表。
但这甚至不是我的问题。
我的问题是:我如何搜索具有某些特征的房屋?
例如,假设我想搜索具有地下室、白色墙壁和倾斜屋顶类型的房屋。我可以在应用程序层动态创建一个查询,如:
SELECT sq1.* FROM
( SELECT house_pkid FROM house_features WHERE feature_name = 'has_basement' AND feature_value = 'True' ) AS sq1
JOIN
( SELECT house_pkid FROM house_features WHERE feature_name = 'wallcolors' AND feature_value = 'white' ) AS sq2
ON sq1.house_pkid = sq2.house_pkid
JOIN
( SELECT house_pkid FROM house_features WHERE feature_name = 'rooftype' AND feature_value = 'inclined' ) AS sq3
ON sq1.house_pkid = sq3.house_pkid
;
Run Code Online (Sandbox Code Playgroud)
但这似乎不是那么有效,尤其是考虑到 house_features 上可能有几十个条件。
有一个更好的方法吗 ?
您可以尝试将这些特征聚合成一个 JSON 值,然后搜索多个特征的组合非常容易:
select h.*, hf.features
from houses
join (
select house_id, jsonb_object_agg(feature_name, feature_value) as features
from house_features
group by house_id
) hf on hf.house_pkid = h.pkid
where hf.features @> '{"rooftype": "flat", "has_basement", "true", "wallcolors": "white"}';
Run Code Online (Sandbox Code Playgroud)
可以通过向重复特征名称的子选择添加 WHERE 子句来提高性能,例如:
where feature_name in ('rooftype', 'has_basement', 'wallcolors')
Run Code Online (Sandbox Code Playgroud)
甚至
where (feature_name, feature_value) in (('rooftype', 'flat') ('has_basement', 'true'), ('wallcolors', 'white'))
Run Code Online (Sandbox Code Playgroud)
外部条件仍然是必要的,因为内部where
将包括不具备所有功能的房屋。
这也有一个优点(在我看来),你只能得到一行包含所有特征,而不是每个特征一行。
除非您非常频繁地移除、添加和更改房屋的功能,否则将它们存储为house
表 ( features
)上的单个 JSONB 列并删除该house_features
表,可能是一种替代方法。在这种情况下,您可以在列上创建索引以加快搜索速度。