ivi*_*ish 11 sql postgresql relational-division
我有一个下表结构.
USERS
PROPERTY_VALUE
PROPERTY_NAME
USER_PROPERTY_MAP
我正在尝试从users
表中检索具有匹配属性的用户property_value
.
单个用户可以拥有多个属性.这里的示例数据有2个用户'1'的属性,但是可以有2个以上.我想在WHERE
子句中使用所有这些用户属性.
如果用户具有单个属性但是对于多于1个属性失败,则此查询有效:
SELECT * FROM users u
INNER JOIN user_property_map upm ON u.id = upm.user_id
INNER JOIN property_value pv ON upm.property_value_id = pv.id
INNER JOIN property_name pn ON pv.property_name_id = pn.id
WHERE (pn.id = 1 AND pv.id IN (SELECT id FROM property_value WHERE value like '101')
AND pn.id = 2 AND pv.id IN (SELECT id FROM property_value WHERE value like '102')) and u.user_name = 'user1' and u.city = 'city1'
Run Code Online (Sandbox Code Playgroud)
我理解,因为查询已经pn.id = 1 AND pn.id = 2
失败,因为pn.id
它可以是1或2但不能同时进行.那么如何重新编写它以使其适用于n个属性?
在上面的示例数据中,只有一个用户具有id = 1
在WHERE
子句中使用的匹配属性.查询应返回包含USERS
表的所有列的单个记录.
我正在开发一个应用程序,在UI上有一个用户列表页面,列出了系统中的所有用户.此列表包含用户ID,用户名,城市等信息 - 表中的所有列USERS
.用户可以拥有上面数据库模型中详述的属性.
用户列表页面还提供基于这些属性搜索用户的功能.搜索具有2个属性'property1'和'property2'的用户时,该页面应该只获取并显示匹配的行.根据上面的测试数据,只有用户'1'符合账单.
具有4个属性(包括 'property1'和'property2')的用户符合条件.但是由于缺少"property2",将仅排除只有一个属性"property1"的用户.
这是关系划分的一个例子.我在标题中添加了标签.
假设对USER_PROPERTY_MAP(property_value_id, user_id)
- 列的PK或UNIQUE约束按此顺序使我的查询快速.有关:
你还应该有一个索引PROPERTY_VALUE(value, property_name_id, id)
.再次,按此顺序排列.id
仅当您从中获取仅索引扫描时才添加最后一列.
有很多方法可以解决它.对于两个属性,这应该是最简单和最快的之一:
SELECT u.*
FROM users u
JOIN user_property_map up1 ON up1.user_id = u.id
JOIN user_property_map up2 USING (user_id)
WHERE up1.property_value_id =
(SELECT id FROM property_value WHERE property_name_id = 1 AND value = '101')
AND up2.property_value_id =
(SELECT id FROM property_value WHERE property_name_id = 2 AND value = '102')
-- AND u.user_name = 'user1' -- more filters?
-- AND u.city = 'city1'
Run Code Online (Sandbox Code Playgroud)
不访问表PROPERTY_NAME
,因为您似乎已根据示例查询将属性名称解析为ID.否则,您可以PROPERTY_NAME
在每个子查询中添加连接.
我们在这个相关问题下汇集了一系列技术:
@Mike和@Valera在各自的答案中都有非常有用的查询.为了使这更加动态:
WITH input(property_name_id, value) AS (
VALUES -- provide n rows with input parameters here
(1, '101')
, (2, '102')
-- more?
)
SELECT *
FROM users u
JOIN (
SELECT up.user_id AS id
FROM input
JOIN property_value pv USING (property_name_id, value)
JOIN user_property_map up ON up.property_value_id = pv.id
GROUP BY 1
HAVING count(*) = (SELECT count(*) FROM input)
) sub USING (id);
Run Code Online (Sandbox Code Playgroud)
仅在VALUES
表达式中添加/删除行.或者删除WITH
子句和JOIN
for no属性过滤器.
该问题与此类查询(计算所有部分匹配)的性能.我的第一个查询动态性较差,但通常要快得多.(只需测试EXPLAIN ANALYZE
.)特别是对于更大的表和越来越多的属性.
这种具有递归CTE的解决方案应该是一个很好的折衷方案:快速和动态:
WITH RECURSIVE input AS (
SELECT count(*) OVER () AS ct
, row_number() OVER () AS rn
, *
FROM (
VALUES -- provide n rows with input parameters here
(1, '101')
, (2, '102')
-- more?
) i (property_name_id, value)
)
, rcte AS (
SELECT i.ct, i.rn, up.user_id AS id
FROM input i
JOIN property_value pv USING (property_name_id, value)
JOIN user_property_map up ON up.property_value_id = pv.id
WHERE i.rn = 1
UNION ALL
SELECT i.ct, i.rn, up.user_id
FROM rcte r
JOIN input i ON i.rn = r.rn + 1
JOIN property_value pv USING (property_name_id, value)
JOIN user_property_map up ON up.property_value_id = pv.id
AND up.user_id = r.id
)
SELECT u.*
FROM rcte r
JOIN users u USING (id)
WHERE r.ct = r.rn; -- has all matches
Run Code Online (Sandbox Code Playgroud)
dbfiddle 在这里
增加的复杂性并不代表小额表,其中额外的开销超过任何利益,或者差异可以忽略不计.但它的扩展性能要好得多,而且越来越优于"计数"技术,增长表和越来越多的属性过滤器.
计数技术必须访问所有user_property_map
给定属性过滤器的所有行,而此查询(以及第一个查询)可以尽早消除不相关的用户.
使用当前表统计信息(合理设置,autovacuum
运行),Postgres了解每列中的"最常见值",并将在第一个查询中重新排序连接,以首先评估最具选择性的属性过滤器(或至少不是最不具选择性的属性过滤器).达到一定限度:join_collapse_limit
.有关:
对于第三个查询(递归CTE),这种"deus-ex-machina"干预是不可能的.为了帮助提高性能(可能很多),您必须先自己安排更多选择性过滤器.但即使是最坏情况的排序,它仍然会胜过计数查询.
有关:
更多血腥细节:
手册中有更多解释:
SELECT *
FROM users u
WHERE u.id IN(
select m.user_id
from property_value v
join USER_PROPERTY_MAP m
on v.id=m.property_value_id
where (v.property_name_id, v.value) in( (1, '101'), (2, '102') )
group by m.user_id
having count(*)=2
)
Run Code Online (Sandbox Code Playgroud)
要么
SELECT u.id
FROM users u
INNER JOIN user_property_map upm ON u.id = upm.user_id
INNER JOIN property_value pv ON upm.property_value_id = pv.id
WHERE (pv.property_name_id=1 and pv.value='101')
OR (pv.property_name_id=2 and pv.value='102')
GROUP BY u.id
HAVING count(*)=2
Run Code Online (Sandbox Code Playgroud)
property_name
如果propery_name_id是kown,则查询中不需要表.
如果您只想过滤:
SELECT users.*
FROM users
where (
select count(*)
from user_property_map
left join property_value on user_property_map.property_value_id = property_value.id
left join property_name on property_value.property_name_id = property_name.id
where user_property_map.user_id = users.id -- join with users table
and (property_name.name, property_value.value) in (
values ('property1', '101'), ('property2', '102') -- filter properties by name and value
)
) = 2 -- number of properties you filter by
Run Code Online (Sandbox Code Playgroud)
或者,如果您需要根据匹配数量下降的用户,您可以:
select * from (
SELECT users.*, (
select count(*) as property_matches
from user_property_map
left join property_value on user_property_map.property_value_id = property_value.id
left join property_name on property_value.property_name_id = property_name.id
where user_property_map.user_id = users.id -- join with users table
and (property_name.name, property_value.value) in (
values ('property1', '101'), ('property2', '102') -- filter properties by name and value
)
)
FROM users
) t
order by property_matches desc
Run Code Online (Sandbox Code Playgroud)
您正在AND
两个pn.id=1
和之间使用运算符pn.id=2
。那么你如何得到答案就在这之间:
(SELECT id FROM property_value WHERE value like '101') and
(SELECT id FROM property_value WHERE value like '102')
Run Code Online (Sandbox Code Playgroud)
所以就像上面的评论一样,使用or
运算符。
更新1:
SELECT * FROM users u
INNER JOIN user_property_map upm ON u.id = upm.user_id
INNER JOIN property_value pv ON upm.property_value_id = pv.id
INNER JOIN property_name pn ON pv.property_name_id = pn.id
WHERE pn.id in (1,2) AND pv.id IN (SELECT id FROM property_value WHERE value like '101' or value like '102');
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1276 次 |
最近记录: |