相关的子查询正在惩罚您,尤其是在额外使用 EXCEPT 的情况下。
套用您的查询,您只对work_unit_id
指定工作人员何时具有该工作单元的所有技能感兴趣? (如果 work_unit 具有与之关联的技能,但指定的用户没有该技能,则排除该 work_unit?)
这可以通过 JOIN 和 GROUP BY 来实现,根本不需要关联。
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_id
则将NULL
用于该行,并且由此NULL
被忽略,COUNT()
这意味着COUNT(ws.skill_id)
它将低于COUNT(wus.skill_id)
,因此work_unit
将从子查询的结果中排除。
这假定workers_skills
表是唯一的,(work_id, skill_id)
并且work_unit_skills
表是唯一的(work_unit_id, skill_id)
。如果不是这种情况,那么您可能需要修改该HAVING
子句(例如COUNT(DISTINT wus.skill_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
这将所有工作单元的技能与工人的技能进行比较,并且只保留工作单元具有工人不具备的技能的行。
然后,GROUP BY
工作单元得到一个需要忽略的工作单元列表。
通过将这些加入到您现有的结果中LEFT
,您可以通过指定.excluded_work_units.work_unit_id IS NULL
有用的在线指南将参考anti-join
和anti-semi-join
。
编辑:
一般来说,我建议不要使用位掩码。
不是因为它很慢,而是因为它无法正常化。代表多项数据的单个字段的存在是一般的 sql-code-smell / sql-anti-pattern,因为数据不再是原子的。 (这会导致未来的痛苦,尤其是当您到达一个拥有如此多技能的世界,以至于它们不再适合为位掩码选择的数据类型时,或者在管理频繁或复杂的更改时技能组合。)
也就是说,如果性能仍然是一个问题,那么去规范化通常是一个非常有用的选择。我建议将位掩码保存在单独的表中,以明确它们是非规范化/缓存的计算结果。但总的来说,这种选择应该是最后的手段,而不是第一反应。
编辑: 示例修订始终包括没有技能的 work_units...
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
excluded_work_units
代码版本(上面的第二个示例查询)应该可以在不需要修改这种极端情况的情况下工作(并且是我最初尝试用于实时性能指标的版本)。