CREATE TABLE S ( S# CHAR(8), SNAME CHAR(8) NOT NULL DEFAULT 'Unknown', AGE TINYINT, SEX CHAR(1), PRIMARY KEY (S#), CHECK (SEX='M' OR SEX='F'))
示例——课程表(自参照外码):
CREATE TABLE C ( C# CHAR(4) PRIMARY KEY, CNAME CHAR(8) NOT NULL UNIQUE, PC# CHAR(4) FOREIGN KEY REFERENCES C(C#))
示例——选课表(复合主码 + 双外码):
CREATE TABLE SC ( S# CHAR(8), C# CHAR(4), GRADE TINYINT, PRIMARY KEY (S#, C#), FOREIGN KEY (S#) REFERENCES S(S#), FOREIGN KEY (C#) REFERENCES C(C#), CHECK ((GRADE IS NULL) OR (GRADE BETWEEN 0 AND 100)))
2.3 修改基本表(ALTER TABLE)
-- 增加新列ALTER TABLE S ADD LOCATION CHAR(30)ALTER TABLE S ADD RESUME CHAR(100) NOT NULL-- 修改列定义ALTER TABLE S ALTER COLUMN RESUME CHAR(80)-- 删除列/约束ALTER TABLE S DROP COLUMN LOCATION
SELECT * FROM S ORDER BY AGE ASC, SNAME DESC-- 按列序号排序SELECT fname, sal * 0.2 FROM faculty ORDER BY 2
3.7 更名运算(别名)
-- 属性更名SELECT SNAME '姓名', SEX '性别', 2007-AGE '出生日期'FROM S ORDER BY 出生日期-- 关系更名(自连接)SELECT S2.S#FROM SC AS S1, SC AS S2WHERE S1.S# = 's1' AND S1.C# = 'c1' AND S2.C# = 'c1' AND S1.GRADE < S2.GRADE
3.8 字符串操作
LIKE 匹配规则:
%:匹配零个或多个字符
_:匹配任意单个字符
[a-f] / [abcdef]:指定范围内的字符
[^a-f]:不在指定范围内的字符
ESCAPE 转义:
-- 用 x 作为转义字符WHERE c1 LIKE 'x%%xx' ESCAPE 'x'
索引利用:
LIKE 'd%'(前缀匹配)→ 可能用到索引
LIKE '%d'(后缀匹配)→ 用不到索引(需要全文检索)
3.9 全文检索
创建:
CREATE FULLTEXT CATALOG catalog_nameCREATE FULLTEXT INDEX ON table_name (column_name) KEY INDEX index_name ON catalog_name
查询:
-- 精确匹配SELECT * FROM documents WHERE CONTAINS(*, 'database and dataspace')SELECT * FROM documents WHERE CONTAINS(author, 'Jim Gray AND NOT Jeff Ullman')-- 自由文本匹配SELECT * FROM documents WHERE FREETEXT(content, 'Adaptive Query Processing')
CONTAINS 支持:AND、OR、NOT、NEAR、短语精确匹配
3.10 连接类型
连接条件分类:
自然连接 (NATURAL):公共属性相等,公共属性只出现一次
ON <谓词>:满足谓词条件,公共属性出现两次
USING (A1, A2, …):指定属性相等,这些属性只出现一次
连接类型分类:
类型
说明
INNER JOIN(内连接)
舍弃不匹配的元组
LEFT OUTER JOIN(左外连接)
内连接 + 左边失配元组(右补 NULL)
RIGHT OUTER JOIN(右外连接)
内连接 + 右边失配元组(左补 NULL)
FULL OUTER JOIN(全外连接)
内连接 + 两边失配元组
CROSS JOIN(交叉连接)
笛卡尔积
UNION JOIN(并连接)
左边失配 + 右边失配
外连接必须有连接条件;内连接没有连接条件等价于笛卡尔积
3.11 空值(NULL)
空值测试:
WHERE GRADE IS NULL -- 不能写 GRADE = NULL
空值运算规则:
除 IS [NOT] NULL 之外,空值不满足任何查找条件
NULL 参与算术运算 → 结果为 NULL
NULL 参与比较运算 → 结果视为 FALSE(SQL-92 中为 UNKNOWN)
DISTINCT 对 NULL 的处理:两行全 NULL 是否算重复?(SQL Server 算重复)
空值处理函数:
-- 单值替换SELECT S#, C#, ISNULL(GRADE, '缺考') FROM SC-- 多值取第一个非空SELECT S#, C#, COALESCE(GRADE, 0) FROM SC
排序时的空值:
升序:空值最后输出
降序:空值最先输出
这是 SQL Server 的默认行为,不同数据库可能不同
3.12 聚集函数
函数
作用
AVG
平均值
MIN
最小值
MAX
最大值
SUM
总和
COUNT
计数
易错点: 聚集函数不能直接出现在 WHERE 子句中
-- 错误!SELECT S# FROM SC WHERE GRADE = MAX(GRADE)-- 正确(用子查询)SELECT S# FROM SC WHERE GRADE = (SELECT MAX(GRADE) FROM SC)
COUNT(*) vs COUNT(列名):
COUNT(*):统计所有行(包括 NULL)
COUNT(列名):统计该列非 NULL 的行数
3.13 分组(GROUP BY)
GROUP BY 列名 [HAVING 条件表达式]
GROUP BY 按指定列分组,每组使用聚集函数
HAVING 对分组进行筛选,只将聚集函数作用于满足条件的分组
示例:
-- 每个学生的最高、最低、平均成绩SELECT S#, MAX(GRADE), MIN(GRADE), AVG(GRADE)FROM SCGROUP BY S#-- 最低成绩及格的学生的平均成绩SELECT S#, AVG(GRADE)FROM SCGROUP BY S#HAVING MIN(GRADE) >= 60
火眼金睛: SELECT 子句中的非聚集列,必须出现在 GROUP BY 子句中
-- 错误!A 不在 GROUP BY 中SELECT A FROM R GROUP BY B-- 正确SELECT A FROM R GROUP BY A
WHERE vs HAVING 的区别:
WHERE:在分组前过滤元组,不能用聚集函数
HAVING:在分组后过滤分组,能用聚集函数
“白马非马”——WHERE 过滤的是行,HAVING 过滤的是组
3.14 嵌套子查询
(1)集合成员资格 — IN 子查询
-- 选修了c1号课程的学生姓名SELECT SNAME FROM SWHERE S# IN (SELECT S# FROM SC WHERE C# = 'c1')
(2)集合比较 — SOME / ALL 子查询
表达式 比较运算符 SOME (子查询) -- 至少一个满足表达式 比较运算符 ALL (子查询) -- 全部满足
等价关系:
= SOME ↔ IN
<> ALL ↔ NOT IN
< SOME:小于最大值
< ALL:小于最小值
示例——平均成绩最高的学生:
SELECT S#FROM SCGROUP BY S#HAVING AVG(GRADE) >= ALL ( SELECT AVG(GRADE) FROM SC GROUP BY S#)
(3)存在性测试 — EXISTS 子查询
-- 选修了c1号课程的学生姓名(相关子查询)SELECT SNAME FROM SWHERE EXISTS ( SELECT * FROM SC WHERE C# = 'c1' AND S# = S.S#)
IN 子查询与外层无关,每个子查询执行一次;
EXISTS 子查询与外层有关,需要执行多次 → 相关子查询
(4)除法的 SQL 表达
选修了全部课程的学生姓名:
不存在任何一门课程,该学生没有选
SELECT SNAME FROM S S1WHERE NOT EXISTS ( SELECT C# FROM C C1 WHERE NOT EXISTS ( SELECT * FROM SC WHERE C# = C1.C# AND S# = S1.S# ))
至少选修了s1号学生选修的所有课程的学生名:
不存在一门课程s1选了而所求学生没选
SELECT SNAME FROM S S2WHERE NOT EXISTS ( SELECT C# FROM SC SC1 WHERE SC1.S# = 's1' AND NOT EXISTS ( SELECT * FROM SC WHERE C# = SC1.C# AND S# = S2.S# ))
(5)重复元组测试 — UNIQUE
-- 只教授一门课程的老师SELECT PNAME FROM PROFWHERE UNIQUE ( SELECT P# FROM PC WHERE PC.P# = PROF.P#)
3.15 派生关系
SQL-92 中允许在 FROM 子句中使用子查询:
SELECT SNAME, AVG_GRADEFROM ( SELECT SNAME, AVG(GRADE) FROM S, SC WHERE SC.S# = S.S# GROUP BY SC.S#) AS result(SNAME, AVG_GRADE)WHERE AVG_GRADE >= 60
派生关系 vs 视图:派生关系是临时的,仅在当前查询中有效
3.16 公用表表达式(CTE / WITH)
SQL Server 语法:
WITH Sum_T(S#, Sum_G) AS ( SELECT S#, SUM(grade) FROM sc GROUP BY S# ), Avg_Sum_T(avg_sum_G) AS ( SELECT AVG(sum_G) FROM Sum_T )SELECT S#FROM Sum_T, Avg_Sum_TWHERE Sum_G > avg_sum_G
3.17 集合操作
操作
说明
UNION [ALL]
并
INTERSECT [ALL]
交
EXCEPT [ALL]
差
缺省去除重复元组
INTERSECT 优先级高于 UNION 和 EXCEPT
UNION vs UNION ALL:
UNION:去重,可能排序
UNION ALL:保留重复,更快
注意: UNION 和 UNION ALL 在一起不满足结合律!
顺序不同,结果可能不同(因为 UNION 会去重)
EXCEPT ALL 的实现技巧: 用标记列 + COUNT 实现
3.18 常用函数
日期函数:
GETDATE():当前日期
DATEPART(datepart, date):提取日期部分
DAY/MONTH/YEAR(date):日/月/年
DATEDIFF(datepart, startdate, enddate):日期差
DATEADD(datepart, number, date):日期加减
字符串函数:
LEN(string):字符个数
LOWER/ UPPER(string):大小写转换
REVERSE(string):反转
SUBSTRING(string, start, length):截取
技巧: 计算字符出现次数 = LEN(s) - LEN(REPLACE(s, 'a', ''))
四、SQL 数据修改功能
4.1 插入(INSERT)
-- 插入单条元组INSERT INTO PROF (P#, PNAME, D#)VALUES ('P123', '王明', 'D08')-- 插入子查询结果INSERT INTO EXCELLENT (S#, GRADE)SELECT S#, AVG(GRADE)FROM SCGROUP BY S#HAVING AVG(GRADE) > 90-- 批量插入BULK INSERT 表名 FROM 数据文件WITH (FIELDTERMINATOR = ',', ROWTERMINATOR = '\n', ...)
@@ROWCOUNT:返回受上一语句影响的行数
4.2 删除(DELETE)
DELETE FROM 表名 [WHERE 条件]
示例:
-- 删除王明老师的任课记录DELETE FROM PCWHERE P# IN (SELECT P# FROM PROF WHERE PNAME = '王明')
TRUNCATE TABLE:
删除所有行,不记录单个行删除操作
比 DELETE 速度快,使用更少的系统和事务日志资源
IDENTITY 计数器重置为种子值
不能用于有外键引用的表
4.3 更新(UPDATE)
UPDATE 表名SET 列名 = 表达式 [, 列名 = 表达式 ...][WHERE 条件]
示例——条件更新:
-- 工资超过2000的缴10%所得税,其余缴5%UPDATE PROFSET SAL = CASE WHEN SAL > 2000 THEN SAL * 0.9 ELSE SAL * 0.95END
注意写法①②(两次 UPDATE)可能导致错误:先将 >2000 的调整后,可能部分变成 <=2000 又被二次调整
使用 CASE 表达式一次性更新才正确
WITH CHECK OPTION:对视图进行 INSERT/UPDATE 时,检查是否满足视图定义中的条件
5.2 示例
-- 计算机系教师视图CREATE VIEW COMPUTER_PROF AS SELECT P#, PNAME, SAL FROM PROF, DEPT WHERE PROF.D# = DEPT.D# AND DEPT.DNAME = '计算机系'-- 系工资统计视图CREATE VIEW DEPTSAL(D#, LOW, HIGH, AVERAGE, TOTAL) AS SELECT D#, MIN(SAL), MAX(SAL), AVG(SAL), SUM(SAL) FROM PROF GROUP BY D#
SELECT S#, C#, 'classification' = CASE WHEN GRADE < 80 THEN 'poor' WHEN GRADE BETWEEN 80 AND 90 THEN 'middle' WHEN GRADE > 90 THEN 'excellent' ELSE 'Unknown' ENDFROM SC
-- 不允许男同学选修张老师的课CREATE ASSERTION ASSE2 CHECK ( NOT EXISTS ( SELECT * FROM SC WHERE C# IN (SELECT C# FROM C WHERE TEACHER = '张') AND S# IN (SELECT S# FROM S WHERE SEX = 'M') ))
8.8 触发器(Trigger)
ECA 模型: Event-Condition-Action(事件-条件-动作)
主动数据库:pull vs push
触发器事件: INSERT、DELETE、UPDATE
触发器作用:
维护约束
商业规则
监控(如传感器数据)
辅助缓存/物化视图维护
简化应用设计
触发器语法(ANSI):
CREATE TRIGGER trigger-name BEFORE/AFTER INSERT/DELETE/UPDATE ON table-name [OF column-name] REFERENCING OLD/NEW ROW/TABLE AS identifier FOR EACH ROW / STATEMENT WHEN (search-condition) triggered-SQL-statement
行级触发器 vs 语句级触发器:
FOR EACH ROW:每行触发一次,可用 OLD ROW / NEW ROW
FOR EACH STATEMENT:每语句触发一次,可用 OLD TABLE / NEW TABLE
SQL Server 触发器:
使用 inserted 和 deleted 逻辑表
AFTER 触发器(默认)
INSTEAD OF 触发器(替代触发器,常用于视图更新)
CREATE TRIGGER S_SC_DeleteON S AFTER DELETEAS IF @@ROWCOUNT = 0 RETURN DELETE FROM SC WHERE S# IN (SELECT S# FROM deleted)
BEFORE 触发器示例:
-- 成绩不及格则改为60分CREATE TRIGGER pass_grade_triggerBEFORE INSERT ON SCREFERENCING NEW ROW AS nrowFOR EACH ROWWHEN (nrow.GRADE < 60) BEGIN SET nrow.GRADE = 60 END