非等值连接是JOIN条件使用BETWEEN、>=、

什么是非等值连接,和普通 JOIN 有什么区别
非等值连接就是 JOIN 条件里不用 =,而是用 BETWEEN、>=、、<code> 或其他范围/逻辑判断。它不找“完全相等”的记录,而是找“落在某个区间内”的匹配。
常见场景包括:用户订单按时间归属到促销活动周期、IP 地址匹配地理区域、成绩划分等级、价格区间打标签。这类查询无法用 INNER JOIN ... ON a.id = b.id 解决,必须靠范围条件驱动关联。
注意:MySQL、PostgreSQL、SQL Server 都支持,但 Oracle 对非等值连接的执行计划更敏感,容易走全表扫描。
写法示例:用 BETWEEN 匹配时间区间
假设你有两张表:orders(含 order_time)和 promotions(含 start_time、end_time),要查每个订单属于哪个促销活动:
SELECT o.order_id, p.promo_name FROM orders o JOIN promotions p ON o.order_time BETWEEN p.start_time AND p.end_time;
关键点:
-
BETWEEN是闭区间(包含端点),如果业务要求严格在活动期内(不含开始/结束瞬间),得改用o.order_time > p.start_time AND o.order_time - 如果一个订单时间落在多个活动区间里,会返回多行——这是正常行为,不是 bug;需要去重或加
LIMIT 1(配合ORDER BY)时得小心逻辑 - 索引只能加速单边比较,
BETWEEN无法让数据库同时高效利用start_time和end_time的索引,通常只用上其中一个
性能陷阱:为什么查询突然变慢
非等值连接最容易触发性能问题,尤其数据量稍大时:
- 数据库优化器往往放弃使用索引,转为嵌套循环(Nested Loop)逐条比对,复杂度接近 O(m×n)
- 如果
promotions表有 1000 条活动,orders表有 10 万条,最坏情况要做 1 亿次时间比较 - PostgreSQL 可能生成
Hash Join或Merge Join,但前提是连接条件是等值;一旦出现BETWEEN,基本只剩Nested Loop
优化建议:
- 给用于范围判断的字段建索引,例如
CREATE INDEX idx_promo_time ON promotions(start_time, end_time)(复合索引顺序很重要) - 把大表放
FROM左侧,小表放JOIN右侧(让数据库优先遍历小表) - 在 WHERE 中提前过滤掉明显不相关的记录,比如先限定
order_time >= '2024-01-01'
替代方案:用 LATERAL 或窗口函数避免笛卡尔积
当非等值连接导致结果爆炸(比如一个订单匹配 50 个活动),又只需要最近/最高优的一个时,可以用子查询 + LATERAL(PostgreSQL / SQL Server)或相关子查询(MySQL 8.0+):
SELECT o.order_id,
(SELECT p.promo_name
FROM promotions p
WHERE o.order_time BETWEEN p.start_time AND p.end_time
ORDER BY p.priority DESC
LIMIT 1) AS promo_name
FROM orders o;
或者用 LATERAL(更清晰):
SELECT o.order_id, p.promo_name FROM orders o LEFT JOIN LATERAL ( SELECT promo_name FROM promotions p WHERE o.order_time BETWEEN p.start_time AND p.end_time ORDER BY p.priority DESC LIMIT 1 ) p ON true;
注意:LATERAL 要求数据库版本支持;MySQL 不支持,得用上面的相关子查询写法;而且子查询里不能引用外部其他字段(除非显式出现在 WHERE 中)。
范围匹配看着简单,真正上线前一定得看执行计划里的 Rows Removed by Filter 和实际耗时——很多“逻辑正确”的写法,跑起来就卡住。










