如何在PostgreSQL中有效地设置减法联接表?
我有以下表格:如何在PostgreSQL中有效地设置减法联接表?,sql,postgresql,performance,relational-division,set-operations,Sql,Postgresql,Performance,Relational Division,Set Operations,我有以下表格: 工作单位-不言自明 工人-不言自明 技能-每个工作单元都需要一些技能,如果你想在上面工作的话。每个工人都精通许多技能 工作单元技能-联接表 工人技能-加入表 工人可以请求分配给她的下一个适当的免费最高优先级(无论这意味着什么)工作单元 目前我有: SELECT work_units.* FROM work_units -- some joins WHERE NOT EXISTS ( SELECT skill_id FROM work_unit
-不言自明工作单位
-不言自明工人
-每个工作单元都需要一些技能,如果你想在上面工作的话。每个工人都精通许多技能技能
-联接表工作单元技能
-加入表工人技能
目前我有:
SELECT work_units.*
FROM work_units
-- some joins
WHERE NOT EXISTS (
SELECT skill_id
FROM work_units_skills
WHERE work_unit_id = work_units.id
EXCEPT
SELECT skill_id
FROM workers_skills
WHERE worker_id = 1 -- the worker id that made the request
)
-- AND a bunch of other conditions
-- ORDER BY something complex
LIMIT 1
FOR UPDATE SKIP LOCKED;
但这种情况会使查询速度降低8-10倍
有没有更好的方法来表示工作单位的技能应该是工人的技能的子集,或者改进当前查询的内容
更多背景:
skills
表相当小
工作单位
和工人
的相关技能都很少
work\u units\u skills
在work\u unit\u id
上有索引
- 我尝试将
workers\u skills
上的查询移动到CTE中。这带来了轻微的改善(10-15%),但仍然太慢
- 任何用户都可以拾取没有技能的工作单元。也就是说,空集是每个集合的子集
根据目前的信息,我只能凭直觉回答。尝试删除EXCEPT语句,看看它是否会显著加快。如果有,您可以再次添加该零件,但使用WHERE条件。
根据我的经验,集合运算符(减/除、并集、交集)是性能的杀手。您可以使用以下查询
SELECT wu.*
FROM work_units wu
LEFT JOIN work_units_skills wus ON wus.work_unit_id = wu.id and wus.skill_id IN (
SELECT id
FROM skills
EXCEPT
SELECT skill_id
FROM workers_skills
WHERE worker_id = 1 -- the worker id that made the request
)
WHERE wus.work_unit_id IS NULL;
(感谢史蒂夫·钱伯斯提供的大部分数据)
你肯定应该有工作单位技能(技能id)
,工人技能(工人id)
和工作单位(id)
的索引。
如果您想加快速度,甚至可以创建索引work\u units\u skills(skill\u id,work\u unit\u id)
和workers\u skills(worker\u id,skill\u id)
,避免访问这些表
子查询是独立的,如果结果不大,则外部联接应该相对较快。一个简单的加速方法是使用而不是,除了。后者将删除重复项,这在这里是不必要的,并且可能会很慢
另一种可能更快的方法是使用另一个NOT EXISTS
代替EXCEPT
:
...
WHERE NOT EXISTS (
SELECT skill_id
FROM work_units_skills wus
WHERE work_unit_id = work_units.id
AND NOT EXISTS (
SELECT skill_id
FROM workers_skills ws
WHERE worker_id = 1 -- the worker id that made the request
AND ws.skill_id = wus.skill_id
)
)
演示
-去除测试的限制
位掩码解决方案
在以前的数据库设计中没有任何更改,只需添加2个字段
第一个:将long或bigint(与您的DBMS相关)插入worker
第二:另一个长的或大的单位进入工作单位
这些字段显示工作单位的技能和工人的技能。例如,假设技能表中有8条记录。
(请注意,技能记录以小字体显示)
1-一些技能1
2-一些技能2
...
8-一些技能8
如果我们想将技能1,3,6,7设置为一个工作单元,只需使用这个数字01100101
(我建议使用二进制0,1位置的反向版本,以支持将来的其他技能。)
实际上,您可以使用10个基数来添加到数据库中(101而不是01100101)
可以为工人生成类似的编号。任何工人都可以选择一些技能。所以,我们可以将所选项目转换为一个数字,并将其保存在Worker表的附加字段中
最后,要为任何工作人员找到合适的工作单位子集,只需从工作单位中选择,并按位使用,如下所示。
A:我们正在搜索与他/她相关的工作单位的特定工作人员(显示每个工作人员的技能)的新字段。
B:显示每个工作单元技能的新工作单元字段
select * from work_units
where A & B = B
注意:
1:当然,这是最快的方法,但也有一些困难。
2:当添加或删除新技能时,我们会遇到一些额外的困难。但这是一种权衡。添加或删除新技能很少发生。
3:我们应该使用技能、工作单位技能和工人技能。但在搜索中,我们只使用新字段
此外,此方法还可用于标签管理系统,如堆栈溢出标签。(请参见下文的更新)
此查询使用简单的左连接查找良好的工作单元
,以在请求工作人员拥有的较短技能表中查找缺少的技能。诀窍是,每当缺少技能时,联接中就会有一个空值,该值被转换为1
,而工作单元
将通过保留所有0
值(即max
为0
来移除
作为经典SQL,这将是引擎优化的最有针对性的查询:
SELECT work_unit_id
FROM
work_units_skills s
LEFT JOIN
(SELECT skill_id FROM workers_skills WHERE worker_id = 1) t
ON (s.skill_id=t.skill_id)
GROUP BY work_unit_id
HAVING max(CASE WHEN t.skill_id IS NULL THEN 1 ELSE 0 END)=0
-- AND a bunch of other conditions
-- ORDER BY something complex
LIMIT 1
FOR UPDATE SKIP LOCKED;
更新
为了捕获没有技能的工作单位
,我们将工作单位
表放入联接:
SELECT r.id AS work_unit_id
FROM
work_units r
LEFT JOIN
work_units_skills s ON (r.id=s.work_unit_id)
LEFT JOIN
(SELECT skill_id FROM workers_skills WHERE worker_id = 1) t
ON (s.skill_id=t.skill_id)
GROUP BY r.id
HAVING bool_or(s.skill_id IS NULL) OR bool_and(t.skill_id IS NOT NULL)
-- AND a bunch of other conditions
-- ORDER BY something complex
LIMIT 1
FOR UPDATE SKIP LOCKED;
相关子查询正在惩罚您,尤其是额外使用Exception
为了解释您的查询,您只对工作单位id
感兴趣,而指定的工人拥有该工作单位的所有技能?(如果某个工作单元具有与其关联的技能,但指定用户没有该技能,则排除该工作单元?)
这可以通过连接和分组方式实现,而根本不需要关联
SELECT
work_units.*
FROM
work_units
--
-- some joins
--
INNER JOIN
(
SELECT
wus.work_unit_id
FROM
work_unit_skills wus
LEFT JOIN
workers_skills ws
ON ws.skill_id = wus.skill_id
AND ws.worker_id = 1
GROUP BY
wus.work_unit_id
HAVING
COUNT(wus.skill_id) = COUNT(ws.skill_id)
)
applicable_work_units
ON applicable_work_units.work_unit_id = work_units.id
-- AND a bunch of other conditions
-- ORDER BY something complex
LIMIT 1
子查询将工人的技能集与每个工作单元的技能集进行比较。如果工作单元拥有工人没有的任何技能,则该行的ws.skill\u id
将NULL
,并且COUNT()
将忽略NULL
,这意味着COUNT(ws.skill\u id)
将低于COUNT(ws.skill\u id)
,从而工作
SELECT
work_units.*
FROM
work_units
--
-- some joins
--
LEFT JOIN
(
SELECT
wus.work_unit_id
FROM
work_unit_skills wus
LEFT JOIN
workers_skills ws
ON ws.skill_id = wus.skill_id
AND ws.worker_id = 1
WHERE
ws.skill_id IS NULL
GROUP BY
wus.work_unit_id
)
excluded_work_units
ON excluded_work_units.work_unit_id = work_units.id
WHERE
excluded_work_units.work_unit_id IS NULL
-- AND a bunch of other conditions
-- ORDER BY something complex
LIMIT 1
SELECT
work_units.*
FROM
work_units
--
-- some joins
--
INNER JOIN
(
SELECT
w.id AS work_unit_id
FROM
work_units w
LEFT JOIN
work_units_skills wus
ON wus.work_unit_id = w.id
LEFT JOIN
workers_skills ws
ON ws.skill_id = wus.skill_id
AND ws.worker_id = 1
GROUP BY
w.id
HAVING
COUNT(wus.skill_id) = COUNT(ws.skill_id)
)
applicable_work_units
ON applicable_work_units.work_unit_id = work_units.id
SELECT wu.*
FROM work_units wu
-- some joins
WHERE wu.id IN
(
SELECT wus.work_unit_id
FROM work_units_skills wus
LEFT JOIN workers_skills ws ON ws.skill_id = wus.skill_id AND ws.worker_id = 1
GROUP BY wus.work_unit_id
HAVING COUNT(*) = COUNT(ws.skill_id)
)
-- AND a bunch of other conditions
-- ORDER BY something complex
LIMIT 1
FOR UPDATE SKIP LOCKED;
create index idx_ws on workers_skills (worker_id, skill_id);
create index idx_wus on work_units_skills (skill_id, work_unit_id);
select *
from work_units
where id in (select work_unit_id
from work_units_skills
group by work_unit_id
having array_agg(skill_id) <@ array(select skill_id
from workers_skills
where worker_id = 6))
and ... other conditions here ...
order by ...