在同一个表上使用多个 JOIN 查询的替代方法?

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_namefeature_value的可能值。每次应用程序发展时我都不必更改数据库,我需要一种新的功能。

例如,假设我有以下功能:

  • 特征名称:具有可能特征值的“屋顶类型”:“平坦”或“倾斜”
  • feature_name : 'wallcolors' with possible feature_value : 'white', 'beige', 'blue', 'green', etc.. (15种不同的可能值)
  • feature_name : 'has_basement' 可能的 feature_value : 'True' 或 'False'。
  • feature_name : 'number_of_doors' 与可能的 feature_value 任何整数编码为字符串(所以 '0', '1', '2', ...)。
  • feature_name : 'floor_surface' 具有可能的 feature_value 任何给定浮点数编码为字符串(例如:'155.2')

显然,将布尔值、整数和浮点数存储为字符串并不是很有效,这也是我需要处理的事情。我正在考虑为每个 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 上可能有几十个条件。

有一个更好的方法吗 ?

a_h*_*ame 6

您可以尝试将这些特征聚合成一个 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表,可能是一种替代方法。在这种情况下,您可以在列上创建索引以加快搜索速度。