“僵尸客户”指注册但从未下单的用户,需通过customers表与orders表关联判断,正确写法为NOT EXISTS子查询,而非WHERE order_date IS NULL或NOT IN。
“僵尸客户”不是数据库术语,而是业务场景描述:注册了但从未下单的用户。关键在于用 customers 表和 orders 表做关联判断,而不是靠时间字段(比如 last_login)模糊推测。
常见错误是写成 WHERE order_date IS NULL —— 这种写法默认两表已 JOIN,但没下单的客户根本不会出现在 orders 表里,NULL 值压根不会出现。
NOT EXISTS 是最直接、语义最清晰的方式,它明确表达“对每个客户,检查 orders 表中是否存在匹配记录”。相比 LEFT JOIN ... WHERE order_id IS NULL,它不依赖连接后是否生成空行,也不怕 orders 表里有重复或脏数据。
实操建议:
customer_id,例如:WHERE o.customer_id = c.customer_id
SELECT 1 就够了,不用 * 或具体字段,避免额外开销orders.customer_id 有索引,否则性能会随数据量陡增SELECT c.customer_id, c.emailFROM customers cWHERE NOT EXISTS ( SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id);
有人习惯写 customer_id NOT IN (SELECT customer_id FROM orders),但这个写法在 orders.customer_id 含 NULL 时会返回空结果集——因为 NOT IN 遇到任何 NULL 就整体判定为 UNKNOWN,被过滤掉。
另一个问题是子查询若返回重复 customer_id(比如一个客户多笔订单),IN 不报错但效率更低。
所以除非你100%确认 orders.customer_id 非空且去重,否则别碰 NOT IN。
线上查僵尸客户,通常不是为了全量导出,而是抽样分析或验证逻辑。直接跑全表可能锁表或拖慢从库。
建议:
LIMIT 100 看结果是否符合预期EXPLAIN 确认走了 customers 主键索引和 orders.customer_id 索引LIMIT 10000 OFFSET 0)+ 循环,避免单次查询内存溢出真正麻烦的不是语法,是确认 orders 表是否包含所有订单类型——比如退款订单、测试订单、系统补单,这些是否该算作“有效购买”。这得翻业务文档,不是写个子查询就能解决的。