Postgres中如何判断查询1结果是否至少有一个在查询2结果中?
嘿,我来帮你搞定这个问题!你遇到的cardinality_violation错误,根源是把返回多行的子查询放在了IN的左侧——Postgres要求IN左侧如果是子查询的话,必须只能返回单个值(单行单列),但你的users表显然有多个id,自然就触发报错了。
想要实现“查询1的结果里至少有一个存在于查询2的结果中”这个需求,有几种更合适的写法:
1. 用EXISTS子查询(最推荐,性能最优)
EXISTS是Postgres里做这类存在性检查的首选,它只要子查询返回至少一行,就会返回true,完全匹配你“至少有一个存在”的需求。针对你的例子,写法可以是:
SELECT * FROM items WHERE EXISTS ( SELECT 1 FROM users u JOIN user_items ui ON u.id = ui.user_id WHERE ui.item_id = 1 );
这个查询的逻辑是:只要存在某个用户的id同时出现在users表和item_id=1的user_items记录里,就会返回items表的所有数据。而且EXISTS会在找到第一个匹配项后就停止扫描,性能比其他方法好很多。
2. 用ANY操作符适配你的原有思路
如果你想保留类似你最初的子查询拆分写法,可以用ANY来处理多行集合。比如:
SELECT * FROM items WHERE EXISTS ( SELECT 1 FROM users u WHERE u.id = ANY(SELECT user_id FROM user_items WHERE item_id = 1) );
这里ANY会把右侧子查询的结果当成一个集合,检查用户id是否在这个集合里,只要有一个匹配,就满足条件。
3. 数组交集判断(适合小数据集)
Postgres支持数组操作,你可以把两个查询的结果转成数组,然后用&&操作符判断交集是否非空:
SELECT * FROM items WHERE (SELECT array_agg(id) FROM users) && (SELECT array_agg(user_id) FROM user_items WHERE item_id = 1);
不过这种方法要注意,如果你的数据集很大,把整个结果集转成数组会占用较多内存,性能不如EXISTS,所以更适合小数据量的场景。
最后再提一下你原来的写法问题:(SELECT id FROM users) IN (...)这种写法错误是因为IN左侧需要是单个值,而不是多行结果。如果要检查两个集合的交集,一定要用上面这些专门处理集合存在性的方法哦。
内容的提问来源于stack exchange,提问作者Razinar

