子查询结果包含性对比:解决子查询返回多行错误
首先,你遇到的More than one value was returned by a sub-query错误,本质是用了**单值比较运算符(比如=)**去匹配一个返回多条结果的子查询。比如如果你的子查询是获取用户123的所有登录日期,它会返回多行,数据库没法用=去匹配多个值,自然就报错了。
而且更关键的是:你要的是覆盖用户123所有登录日期的用户(还可以有更多日期),不是只要登录过其中某一天的用户,所以直接把=改成IN也满足不了需求——IN只能筛选出登录过123至少一天的用户,没法确保覆盖全部日期。
下面给你两种通用的解决方案,适用于大多数关系型数据库(MySQL、PostgreSQL、SQL Server等):
方法1:GROUP BY + HAVING 统计匹配日期数
核心思路是:先拿到用户123的所有唯一登录日期,再统计其他用户登录过的日期中,和123重合的数量是否等于123的总日期数——如果相等,说明该用户覆盖了123的所有日期。
假设你的表名为user_login_dates,字段是user_id(用户ID)和login_date(登录日期),SQL代码如下:
SELECT uld.user_id FROM user_login_dates uld -- 关联用户123的所有唯一登录日期 JOIN ( SELECT DISTINCT login_date FROM user_login_dates WHERE user_id = '123' ) AS user123_dates ON uld.login_date = user123_dates.login_date GROUP BY uld.user_id -- 确保当前用户的匹配日期数等于123的总日期数 HAVING COUNT(DISTINCT uld.login_date) = ( SELECT COUNT(DISTINCT login_date) FROM user_login_dates WHERE user_id = '123' );
补充说明:
- 用
DISTINCT是为了避免同一用户同一天多次登录导致计数重复; - 如果用户123没有任何登录记录,这个查询会返回空结果(因为子查询的计数为0,HAVING条件永远不满足),符合逻辑;
- 这个方法会自动包含那些登录过更多日期的用户,因为我们只统计和123重合的部分,只要数量达标就会被选中。
方法2:EXISTS + NOT EXISTS 双重排除
核心思路是:筛选出不存在任何一个用户123登录过但当前用户没登录的日期的用户——换句话说,就是用户123的每一个登录日期,当前用户都有记录。
SQL代码如下:
SELECT DISTINCT uld.user_id FROM user_login_dates uld WHERE NOT EXISTS ( -- 查找用户123有但当前用户没有的日期 SELECT 1 FROM user_login_dates u123 WHERE u123.user_id = '123' AND NOT EXISTS ( SELECT 1 FROM user_login_dates uld2 WHERE uld2.user_id = uld.user_id AND uld2.login_date = u123.login_date ) );
补充说明:
- 外层的
DISTINCT用来去重,避免同一个用户ID多次出现; - 这个逻辑更直观,直接排除“漏了123某一天”的用户,剩下的就是符合要求的;
- 同样,若用户123无登录记录,查询会返回所有用户(因为内层子查询永远找不到符合条件的日期,NOT EXISTS为真),如果需要处理这种边界情况,可以加个判断:
AND EXISTS(SELECT 1 FROM user_login_dates WHERE user_id='123')。
为什么原来的查询会报错?
举个例子,如果你原来的查询是类似这样:
SELECT user_id FROM user_login_dates WHERE login_date = (SELECT login_date FROM user_login_dates WHERE user_id='123');
这里的子查询返回了用户123的所有登录日期(多行),而=只能匹配单个值,数据库不知道该拿哪一行去比较,所以抛出More than one value was returned by a sub-query错误。就算改成IN,也只能拿到登录过123至少一天的用户,没法满足你的核心需求。
内容的提问来源于stack exchange,提问作者user

