包含 "Sailors(水手)""Reserves(预订记录)" 和 "Boats(船只)" 三个关系表,数据如下:
预订记录表(R1)
| sid | bid | day |
|---|---|---|
| 22 | 101 | 10/10/96 |
| 58 | 103 | 11/12/96 |
船只表(B1)
| bid | bname | color |
|---|---|---|
| 101 | tiger | red |
| 103 | lion | green |
| 105 | hero | blue |
水手表(S1)
| sid | sname | rating - | age |
|---|---|---|---|
| 22 | dustin | 7 | 45.0 |
| 31 | lubber | 8 | 55.5 |
| 58 | rusty | 10 | 35.0 |
水手表(S2)
| sid | sname | rating | age |
|---|---|---|---|
| 28 | yuppy | 9 | 35.0 |
| 31 | lubber | 8 | 55.5 |
| 44 | guppy | 5 | 35.0 |
| 58 | rusty | 10 | 35.0 |
下面是查询案例
查询 1:查询预订了 103 号船只的水手姓名
三种方法:
sql
SELECT S.sname
FROM Sailors S, Reserves R
WHERE S.sid = R.sid AND R.bid = 103;
SELECT S.sname
FROM Sailors S
WHERE S.sid IN (SELECT R.sid
FROM Reserves R
WHERE R.bid = 103);
SELECT S.sname
FROM Sailors S
WHERE EXISTS (SELECT *
FROM Reserves R
WHERE R.bid = 103 AND S.sid = R.sid);
查询 2:查询至少预订过一艘船的水手
sql
SELECT S.sid
FROM Sailors S, Reserves R
WHERE S.sid = R.sid;
相关问题:
1.给该查询添加DISTINCT关键字会有区别吗?
对 sid 去重,只返回 "有预约记录的唯一水手 ID"。
2.若将SELECT子句中的S.sid替换为S.sname,效果如何?
结果列从 "水手 ID" 变成 "水手姓名",但重复逻辑和 sid 一致
3.对替换后的查询添加DISTINCT关键字会有区别吗?
有区别,且区别分两种场景:
场景 1:姓名无重复(如示例)
无 DISTINCT:姓名重复(和预约次数一致);
加 DISTINCT:姓名去重,只返回 "有预约记录的唯一水手姓名"。
场景 2:姓名有重复(比如 sid=1 和 4 都叫张三,且都有预约)
无 DISTINCT:姓名 "张三" 会出现(sid=1 的预约次数 + sid=4 的预约次数)次;
加 DISTINCT:姓名 "张三" 只出现 1 次(不管多少个同名水手、多少次预约)。
优化查询
sql
SELECT DISTINCT S.sid, S.sname
FROM Sailors S, Reserves R
WHERE S.sid = R.sid;
DISTINCT作用于 "所有选中的列"(sid+sname),而非单列 ------ 只有当 sid 和 sname同时重复时,才会去重,既保留 ID + 姓名的完整信息,又避免重复,是更实用的写法。
查询 3:查询预订过红色或绿色船只的水手 ID
sql
SELECT S.sid
FROM Sailors S, Boats B, Reserves R
WHERE S.sid=R.sid AND R.bid=B.bid
AND (B.color='red' OR B.color='green');
--FROM三张表先做全量笛卡尔积,再通过WHERE过滤有效关联和颜色
--条件,效率略低,结果不会自动去重!如果某水手同时预订了红色和绿
--色船只,其 ID 会重复出现
(SELECT S.sid
FROM Sailors S, Boats B, Reserves R
WHERE S.sid=R.sid AND R.bid=B.bid AND B.color='red')
UNION
( SELECT S.sid FROM Sailors S, Boats B, Reserves R
WHERE S.sid=R.sid AND R.bid=B.bid AND B.color='green';
--UNION关键字会自动剔除重复的水手 ID(即使某水手同
--时订了红、绿船,ID 只显示一次);
查询 4:查询预订过红色和绿色船只的水手 ID
sql
--方法 1(多表连接):
SELECT S.sid
FROM Sailors S, Boats B1, Reserves R1, Boats B2, Reserves R2
WHERE S.sid = R1.sid AND R1.bid = B1.bid
AND S.sid = R2.sid AND R2.bid = B2.bid
AND (B1.color = 'red' AND B2.color = 'green');
--方法 2(使用INTERSECT交集查询)
(SELECT S.sid
FROM Sailors S, Boats B, Reserves R
WHERE S.sid = R.sid AND R.bid = B.bid AND B.color = 'red')
INTERSECT
(SELECT S.sid
FROM Sailors S, Boats B, Reserves R
WHERE S.sid = R.sid AND R.bid = B.bid AND B.color = 'green');
--方法 3(使用子查询IN):
SELECT S.sid
FROM Sailors S, Boats B, Reserves R
WHERE S.sid = R.sid AND R.bid = B.bid AND B.color = 'red'
AND S.sid IN (SELECT S2.sid
FROM Sailors S2, Boats B2, Reserves R2
WHERE S2.sid = R2.sid AND R2.bid = B2.bid AND B2.color = 'green');
查询 5:查询预订了所有船只的水手姓名
sql
--方法 1(使用EXCEPT差集查询):
SELECT S.sname FROM Sailors S
WHERE NOT EXISTS -- 条件:"不存在以下差集"则保留该水手
(SELECT B.bid FROM Boats B) -- 集合A:所有船只的bid(全部船)
EXCEPT
(SELECT R.bid FROM Reserves R WHERE R.sid = S.sid));
-- 集合B:当前水手S预约过的船只bid
-- NOT EXISTS 表示:如果 "没预约过的船只 bid" 是
--空集(即差集无元素)→ 说明 S 预约了所有船 → 保留该水手;
--方法 2(双重NOT EXISTS):
--找不到任何一艘船 B,是水手 S 没预约过的
-- → 等价于「水手 S 预约了所有船只」。
SELECT S.sname
FROM Sailors S
WHERE NOT EXISTS (SELECT B.bid
FROM Boats B
WHERE NOT EXISTS (SELECT R.bid
FROM Reserves R
WHERE R.bid = B.bid AND R.sid = S.sid));
-- NOT EXISTS 如果下面的 "内层查询" 找不到任何行,就保留当前水手 S
查询 6:查询年龄最大的水手姓名及年龄
sql
SELECT S.sname, S.age
FROM Sailors S
WHERE S.age = (SELECT MAX(S2.age) FROM Sailors S2);
查询 7:查询每艘红色船只的预订次数、
sql
SELECT B.bid, COUNT(*) AS scount
FROM Boats B, Reserves R
WHERE R.bid = B.bid AND B.color = 'red'
GROUP BY B.bid;
-- 方法 2(使用HAVING子句筛选红色船只):
SELECT B.bid, COUNT(*) AS scount
FROM Boats AS B, Reserves AS R
WHERE R.bid = B.bid
GROUP BY B.bid
HAVING B.bid IN (SELECT bid
FROM Boats
WHERE color = 'red');
查询 8:查询每个评级对应的最年轻水手姓名
sql
步骤 1:查询每个评级的最小年龄
SELECT MIN(S.age)
FROM Sailors S
GROUP BY S.rating;
步骤 2:关联查询最年轻水手姓名
SELECT S1.sname
FROM Sailors AS S1,
(SELECT S2.rating, MIN(S2.age) AS minage
FROM Sailors AS S2
GROUP BY S2.rating) AS R
WHERE S1.rating = R.rating AND S1.age = R.minage;
查询 9:查询满足特定条件的每个评级对应的最年轻水手年龄
场景 1:年龄≥18 岁,且每个评级至少有 2 名符合该年龄条件的水手
sql
SELECT S.rating, MIN(S.age) AS minage
FROM Sailors S
WHERE S.age >= 18
GROUP BY S.rating
HAVING COUNT(*) > 1;
场景 2:年龄≥18 岁,且每个评级至少有 2 名水手(年龄不限)
sql
SELECT S.rating, MIN(S.age) AS minage
FROM Sailors S
WHERE S.age >= 18
GROUP BY S.rating
HAVING 1 < (SELECT COUNT(*)
FROM Sailors S2
WHERE S2.rating = S.rating);
查询 10:查询平均年龄在所有评级中最小的评级
sql
SELECT Temp.rating
FROM (SELECT S.rating, AVG(S.age) AS avgage
FROM Sailors S
GROUP BY S.rating) Temp
WHERE Temp.avgage = (SELECT MIN(Temp.avgage) FROM Temp);