求助 一个简单 sql 查询问题;就拿简单学生选课来举例

2022-05-20 09:17:19 +08:00
 zzyphp111

需求: 查出 X 个学生选择相同科目,且这个科目只被这个 X 个学生选,得到科目 id 集合。

数据表

uid obj_id
A 数学
B 数学
A 语文
B 语文
C 语文
D 语文
A 英语
C 英语
A 体育
C 体育

举例:

比方 A,B,C,D 学生选了且仅选 的结果集:  [语文课] 
比方 A 学生 选了且仅选 的结果集是空 [] (因为 A 所选科目还有其他学生。)
比方 A,B 学生 选了且仅选 的结果集是 [数学]  (因为语文课还有 C 学生所以不算)
比方 D 学生 选了且仅选 的结果集是空 []  (因为语文课还有 ABC 学生所以不算) 
比方 A ,C 学生 选了且仅选 的结果集是 [英语,体育] 

提问: 如何表达这个 sql ?

2217 次点击
所在节点    数据库
12 条回复
VersionGod1
2022-05-20 09:47:34 +08:00
我处理这个问题可能会先查询出 X 个学生选择的相同科目,再查出科目对应的 X 个同学,用 Java 进行匹配
dqzcwxb
2022-05-20 09:54:47 +08:00
有没有可能在代码里写好逻辑?
zzyphp111
2022-05-20 09:58:16 +08:00
主要考虑的点在于中间数据量巨大, 类似 A 学生这种 ,实际中间流程数据量巨大, 因为他雨露均沾, 最后算出来是空结果, 就感觉代码逻辑写的太邋遢了。

想想有没有什么好的 sql 妙招。
nuanshen
2022-05-20 10:07:46 +08:00
# 给个 mysql 的,不过不知道数据量多的情况下性能如何
```mysql
SELECT
t.uids,
GROUP_CONCAT( t.obj_id ) obj_ids
FROM (
SELECT
t.obj_id,
GROUP_CONCAT( t.uid ) uids
FROM (
# 以下可替换成数据表
SELECT 'A' uid, '数学' obj_id UNION
SELECT 'B' uid, '数学' obj_id UNION
SELECT 'A' uid, '语文' obj_id UNION
SELECT 'B' uid, '语文' obj_id UNION
SELECT 'C' uid, '语文' obj_id UNION
SELECT 'D' uid, '语文' obj_id UNION
SELECT 'A' uid, '英语' obj_id UNION
SELECT 'C' uid, '英语' obj_id UNION
SELECT 'A' uid, '体育' obj_id UNION
SELECT 'C' uid, '体育' obj_id
# end
) t
GROUP BY t.obj_id
) t
GROUP BY t.uids
```
klo424
2022-05-20 10:19:05 +08:00
按逻辑写的,不保证效率,假设表是 test ,common 是 A/C 两个人共选的课程,HAVING COUNT(1) = 2 代表有 2 条(有几个人就=几,目的是排除 A/C 有一个没有选这课),exc 排除掉其他人选的课。

WITH common AS (
SELECT obj_id
FROM test
WHERE uid IN ('A','C')
GROUP BY obj_id
HAVING COUNT(1) = 2
), exc AS (
SELECT obj_id
FROM test
WHERE uid NOT IN ('A','C')
)
SELECT *
FROM test
WHERE obj_id IN (SELECT obj_id FROM common)
AND obj_id NOT IN (SELECT obj_id FROM exc)
zzyphp111
2022-05-20 10:21:05 +08:00
找到解决方案了 ,在这里多谢 :"flw" 大佬,以及各位想出来的好办法

答案为:
select tmp_group.obj_id from (select obj_id, group_concat(uid) as uids from group_tag group by obj_id order by obj_id) as tmp_group where tmp_group.uids = 'A,B';

一个 sql 搞定,非常漂亮!
finull
2022-05-20 10:24:48 +08:00
这题是有参数输入的啊,输入是 uid 列表

select obj_id from ( select obj_id from `table` group by object_id having count({input}) = 4 ) t where not exists ( select * from `table` where obj_id = t.obj_id and uid not in ( {input} ) );
eason1874
2022-05-20 10:37:05 +08:00
我的写法:

SELECT `obj_id` FROM `test` WHERE `obj_id` IN (SELECT `obj_id` FROM `test` WHERE `uid` IN ('A','B','C','D') GROUP BY `obj_id` HAVING COUNT(`obj_id`) = 4) GROUP BY `obj_id` HAVING COUNT(`obj_id`) = 4;

有两个 HAVING COUNT 4 ,是查询 uid 的数量,需要随着输入一起变。帖子里有几种写法了,楼主可以测测哪种更快
ColinZeb
2022-05-20 13:58:28 +08:00
@zzyphp111 uid 没排序直接 group-concat 不会出意外吗
zzyphp111
2022-05-20 18:29:55 +08:00
@ColinZeb #9 嗯,group_concat 函数要求 对入参需要先排序, 我这边用 go 的 sort 比较方便快捷:

```go
sort.Slice(ids, func(i, j int) bool {
return ids[i] < ids[j]
})
```

其次是要注意 mysql 的配置 group_concat_max_len 默认是 1024 长度,如果 uids 的 长度超了需要额外考虑, 或者提前配置 数据库配置参数。

进入 mysql 状态,输入:show variables like 'group_concat_max_len';
l00t
2022-06-08 16:30:06 +08:00
@zzyphp111 #10 18 天过去了,楼主还没掉坑吗?
zzyphp111
2022-06-08 17:51:25 +08:00
@l00t #11 已经解决了啊,上面给出结论了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/854069

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX