_FFF航海日志
草帽一伙面向海上夕阳和之国樱花下的草帽团草帽团在海边休憩

DREAM · ADVENTURE · FREEDOM

Hi, _FFF!

心向大海,自由启航。

翻开航海日志

向着下一片海,记录学习、生活与每一次发现。

sql基本语法笔记(2026-10-16)

SQL 学习笔记

复习与查阅版 · 2026-10-16 · MySQL 8.0 / Navicat

刷题时查这一份。 完整原表、题目和参考答案放在练习附录,掌握情况和待办放在学习进度。

想查什么 去哪里
查询顺序、别名、筛选 1. 查询与筛选
计数、去重、分组、排序 2. 汇总与排序
两表怎么配、连接后为什么多行 3. JOIN 与每行的含义
拆步骤、子查询、WITH 4. 分步查询
条件计数、条件金额、比例 5. CASE 与条件统计
字符串、日期、时间差、NULL 6. 常用函数
排名、COUNT OVER、累计、前后行 7. 窗口函数
订单、复购、购买比例、留存 8. 常见指标
建表、增删改、视图、索引 9. 数据与数据库对象
报错和结果不对时怎么查 10. 检查方法

也可搜索带方括号的知识点标记;每个标记只出现一次。代码示例中的表、字段须与自己的数据对应。


1. 查询与筛选

一条查询怎么组成

【SELECT】决定显示哪些列;FROM 指定来源;WHERE 筛选参与查询的行。

SELECT product_name, sale_price
FROM product
WHERE product_type = '衣服';

【CALCULATED_COLUMN】表达式也能作为结果列,例如 sale_price - purchase_price AS profit。它只计算查询结果,不给原表新增一列。

【SQL_ORDER】书写顺序:

SELECT → FROM / JOIN / ON → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT

理解结果时,先确定来源和连接,再筛行、分组汇总、筛组、形成输出、去重、排序。窗口计算发生在同层筛行和分组之后。这是理解模型,数据库实际执行方案可能不同。

别名与引号

【ALIAS】列别名是结果的名字,表别名是来源的简称。

写法 含义
sale_price AS price 结果列叫 price
FROM product AS p 用 p 代表 product
p.product_name 读取 p 的 product_name 列
FROM (...) AS t 用 t 代表整份子查询结果

【ALIAS_SCOPE】MySQL 中,同层 SELECT 刚定义的列别名:

可以引用 不能直接引用
ORDER BY;适当的 GROUP BY、HAVING WHERE;SELECT 中的另一个表达式

要筛选新算出的 profit 或排名:重写短表达式,或先放进子查询 / WITH,再由外层使用。

【SQL_LITERAL】列名读取数据;字符串和日期加英文单引号;数字通常不加引号。

示例 表示什么
product_name 列
'product_name' 固定文字
'衣服'、'2026-10-16' 文字、日期值
3000 数字
`product_name` 用反引号包住的列名

常用筛选写法

知识点 写法与含义 记住这一点
【WHERE】筛行 WHERE sale_price >= 1000 只保留条件为 TRUE 的行
【AND_OR】条件组合 A AND (B OR C) AND 优先于 OR;混用时用括号表达意图
【LIKE】模糊匹配 LIKE 'abc%' 开头;'%abc' 结尾;'%abc%' 包含 % 可匹配零个或多个字符;LIKE 不是函数
【BETWEEN】闭区间 BETWEEN 500 AND 1000 包含 500 和 1000
【IS_NULL】判断缺失 IS NULL / IS NOT NULL 不用 = NULL
【IN】名单筛选 IN ('衣服', '办公用品') 只有一项也合法

1000 <= 售价 < 3000 要写成两个条件:sale_price >= 1000 AND sale_price < 3000。

【NULL】NULL 表示未知或缺失;0、''、' '、'NULL' 都不是 NULL。NOT NULL 也不等于禁止空字符串或空格。


2. 汇总与排序

聚合与计数

【AGGREGATE】不分组时对筛选后的全部行汇总;分组时各组独立汇总。

知识点 写法 作用
【SUM】合计 SUM(sale_price) 非 NULL 数值相加
【AVG】平均 AVG(purchase_price) 除以非 NULL 值的个数
【MAX】最大 MAX(sale_price) 不会自动带出对应的商品名
【MIN】最小 MIN(regist_date) 日期最小表示最早
【COUNT】计数 COUNT(*) / COUNT(score) 全部行数 / score 非 NULL 的行数
【COUNT_DISTINCT】去重计数 COUNT(DISTINCT user_id) 不同非 NULL 用户编号的个数

成绩是 90、NULL、0、60 时:

COUNT(*) COUNT(score) SUM(score) AVG(score)
4 3 150 50

零参与统计,NULL 被上述数值聚合跳过。 没有可计算的非 NULL 值时,SUM / AVG / MAX / MIN 返回 NULL;COUNT 返回 0。

去重与分组

【DISTINCT】SELECT DISTINCT user_id, score 按这两列的组合去重。它只改变查询结果,不删除原表记录。

按用户去重时应保留用户编号;两个不同用户评分相同,不能因此合成一个用户。

【GROUP_BY】决定每组由什么识别;例如按种类汇总,结果每个种类一行。

SELECT product_type, COUNT(*) AS product_count
FROM product
GROUP BY product_type;

【GROUP_COLUMNS】分组后优先显示分组键和聚合结果。按种类分组,却直接显示某件商品名,会出现“一组里该选哪个名字”的问题。

MySQL 能识别部分函数依赖:主键可确定同表其他列。但不能据此随意输出任意未分组列。多列 GROUP BY 按列值的组合分组。

【HAVING】WHERE 筛原始行,HAVING 筛汇总后的组:

SELECT product_type, COUNT(*) AS product_count
FROM product
WHERE sale_price >= 500
GROUP BY product_type
HAVING COUNT(*) >= 2;

排序与取行

【ORDER_BY】多个排序键从左到右比较,前一项相同才比后一项。

ORDER BY sale_price DESC, purchase_price ASC, product_id ASC

ASC 升序,DESC 降序;每个排序字段分别写方向。SELECT 中列的先后不决定排序。

【NULL_ORDER】MySQL 默认 NULL 升序在前、降序在后。需要“已知值升序,NULL 最后”时:

ORDER BY purchase_price IS NULL ASC, purchase_price ASC

【LIMIT】LIMIT 3 最多取 3 行;LIMIT 1, 4 跳过 1 行,再取最多 4 行。要取最高的几条,应先明确 ORDER BY。


3. JOIN 与每行的含义

怎么连接

【JOIN】INNER JOIN 只保留满足连接条件的配对。

SELECT p.product_name, sp.shop_name
FROM product AS p
JOIN shopproduct AS sp ON p.product_id = sp.product_id;

【JOIN_CONDITION】ON 要比较能对应的两边字段。误写 p.product_id = p.product_id,会让每件商品配上右表所有记录。

【LEFT_JOIN】左表没配上也保留,右表字段填 NULL;配上多条就产生多行。

连接后,一行代表什么

【JOIN_GRAIN】用附录中的 users 与 orders 左连接,得到以下结果:

用户编号 订单编号 付款金额
U01 1001 100
U01 1002 100
U02 1003 100
U03 NULL NULL
U04 NULL NULL

这里有 5 行、4 位用户、3 笔订单、2 位买家。一位用户可以有多笔订单,不能把连接后的行数直接当人数。

【JOIN_GROUP】数不同对象,要数对应的编号:

想数什么 在上述结果中怎么数
全部用户 COUNT(DISTINCT u.user_id) → 4
购买用户 COUNT(DISTINCT o.user_id) → 2
订单 COUNT(o.order_id) → 3,本例每笔订单只出现一次

LEFT JOIN 补出的空行也被 COUNT(*) 计入,因此不能用它直接数右表匹配记录。

ON 与 WHERE

【JOIN_ON_WHERE】想保留全部商品,只补东京店的信息:

FROM product AS p
LEFT JOIN shopproduct AS sp
    ON p.product_id = sp.product_id
   AND sp.shop_name = '东京'

若把店名条件移到 WHERE,只会留下店名为东京的结果行;没配上的商品会被筛掉。WHERE 筛掉后,不会重新补一行 NULL。

【JOIN_UNMATCHED】找没匹配上的左表行,可在 LEFT JOIN 后用 WHERE sp.product_id IS NULL。判断列应选右表匹配成功时保证非空的标识列。

一对多连接后的金额

【JOIN_PREAGG】订单 1001 金额 100,有两条明细,直接连接会出现:

订单编号 订单金额 明细商品
1001 100 A
1001 100 B

此时 SUM(订单金额) 会得到 200。处理步骤:明细先按订单汇总 → 每笔订单一行 → 连接订单 → 按用户汇总。 完整代码见附录 E06。

不能用 SUM(DISTINCT paid_amount) 通用修复:两笔真实的 100 元订单也会被合成一个金额。

【JOIN_THREE】三表连接时逐段找对应编号:订单通过 user_id 配用户,通过 order_id 配订单件数。每连接一次,都检查行数和一行的含义。见附录 E12。


4. 分步查询

先看内层结果的形状

【SUBQUERY】子查询是查询中再包含查询。先问外层需要什么:

知识点 用在哪里 内层提供什么
【SCALAR_SUBQUERY】标量子查询 比较或输出一个值 一列,最多一行;没返回行时为 NULL
【SELECT_SUBQUERY】SELECT 子查询 给当前行增加一个值 必须满足标量要求
【FROM_SUBQUERY】FROM / JOIN 子查询 当作一张表继续查 可以多行多列,MySQL 要求表别名
【IN_SUBQUERY】名单子查询 WHERE product_id IN (...) 与左边相匹配的一列,可多行
【EXISTS】存在判断 WHERE EXISTS (...) 找到至少一行就成立
【NOT_EXISTS】不存在判断 WHERE NOT EXISTS (...) 一行也找不到才成立

例如,查售价高于全表均价的商品:

SELECT product_name, sale_price
FROM product
WHERE sale_price > (SELECT AVG(sale_price) FROM product);

EXISTS 常写 SELECT 1,关注有没有行。不要改成不分组的 SELECT COUNT(*):即使计数为 0,它也返回一行。

找“没有东京经营记录”,是在内层寻找东京记录,再对存在性取反;不能简单把内层条件改成店名不等于东京。见附录 E23。

内层结果作为表

SELECT product_type, total_price
FROM (
    SELECT product_type, SUM(sale_price) AS total_price
    FROM product
    GROUP BY product_type
) AS product_total
WHERE total_price > 4000;

内层每种商品一行;外层能筛 total_price,因为它已经是来源中的列。

【SUBQUERY_SCOPE】外层只能使用内层输出的列和整份结果的别名。内层叫 ord,整份结果叫 totaltable,外层应读 totaltable.order_id。

WITH:给中间结果起名

【WITH】只在当前语句内有效,不创建永久表。

WITH product_profit AS (
    SELECT product_name, sale_price - purchase_price AS profit
    FROM product
)
SELECT product_name, profit
FROM product_profit
WHERE profit > 1000;

多个中间结果放在一个 WITH 后,用逗号分隔多个 名称 AS (...);分号放在整条语句末尾。子查询和 WITH 都可以用,按步骤是否清楚来选。

关联子查询

【CORRELATED_SUBQUERY】内层使用外层当前行的信息。

SELECT p1.product_name,
       (SELECT AVG(p2.sale_price)
        FROM product AS p2
        WHERE p2.product_type = p1.product_type) AS type_avg_price
FROM product AS p1;

外层走到运动T恤 → 当前种类是衣服 → 内层取两件衣服的均价 2500 → 显示在这一行旁边。

p1、p2 是同一张表在不同查询层中的别名。按种类关联才能算同类均价;按唯一商品编号关联只会留下自己。


5. CASE 与条件统计

根据条件生成值

【CASE】给当前行生成标签或数值:

CASE
    WHEN sale_price >= 3000 THEN '高价'
    WHEN sale_price >= 1000 THEN '中价'
    ELSE '低价'
END AS price_label

【CASE_PRIORITY】同一个 CASE 采用第一个成立的分支。先判断高价,再判断中价;多个独立 CASE 要各自写清条件。CASE 生成值,WHERE 筛行。

数数量,还是加金额

知识点 表达式 条件成立时取什么
【CONDITIONAL_COUNT】条件计数 SUM(CASE WHEN sale_price >= 3000 THEN 1 ELSE 0 END) 取 1,用于数符合条件的行
【CONDITIONAL_AMOUNT】条件金额 SUM(CASE WHEN repeat_flag = 1 THEN user_amount ELSE 0 END) 取金额,用于算符合条件的金额合计

COUNT 会数 0。 COUNT(CASE WHEN ... THEN 1 ELSE 0 END) 会同时数到 1 和 0;需要数符合条件的行时,用上面的 0/1 求和,或让未满足分支返回 NULL。

【RATIO】比例先写清分子、分母,再决定在哪一步筛选。分母若要全部商品,就不能先 WHERE 只留下高价商品。

ROUND(
    SUM(CASE WHEN sale_price >= 3000 THEN 1 ELSE 0 END) / COUNT(*),
    2
)

这是高价数量占全部数量的比例。0.50 表示 50%;分母可能为零时见下一章的 NULLIF。


6. 常用函数

普通函数处理当前行;聚合函数汇总多行;窗口函数在保留明细的同时跨行计算。函数可以嵌套,先算内层。

数值与字符串速查

知识点 写法 例子或提醒
【ABS】绝对值 ABS(数值) ABS(-5) → 5
【MOD】余数 MOD(被除数, 除数) MOD(880, 100) → 80;不整除判断用 <> 0
【ROUND】四舍五入 ROUND(表达式, 位数) ROUND(8.2567, 3) → 8.257;0 位表示取整
【CONCAT】拼接 CONCAT(product_name, '-', product_type) 任意参数为 NULL,结果为 NULL
【CHAR_LENGTH】字符数 CHAR_LENGTH('打孔器') 3;问几个字时用它
【LENGTH】字节数 LENGTH(_utf8mb4'打孔器') 9;取决于编码
【LOWER】小写 LOWER('SQL学习') sql学习
【UPPER】大写 UPPER('sql学习') SQL学习
【SUBSTRING】按位置截取 SUBSTRING('abcdef', 2, 3) bcd;从 1 开始,第三项是取几个字符
【SUBSTRING_INDEX】按分隔符截取 SUBSTRING_INDEX('a@b.com', '@', -1) b.com;1 取左边,-1 取右边
【TRIM】去两端普通空格 TRIM(' data analysis ') 中间空格保留

SUBSTRING 也支持 SUBSTRING(文字 FROM 起点 FOR 个数)。SUBSTRING_INDEX 遇到多个分隔符时,正数从左数,负数从右数。

替换文字

【REPLACE】REPLACE(原文字, 旧片段, 新片段),替换所有匹配片段,匹配区分大小写。

SELECT REPLACE('mysql_sql', 'sql', 'SQL') AS new_name;

结果为 mySQL_SQL。找不到旧片段就返回原文字。SELECT 只显示结果;要写回原表,用 UPDATE,见第 9 章。

提取与推算日期

知识点 写法 作用
【YEAR】年份 YEAR(regist_date) 返回年份数字
【MONTH】月份 MONTH(regist_date) 返回月份,不包含年份
【DAY】日号 DAY(regist_date) 当月第几天
【DATE_FORMAT】格式化 DATE_FORMAT(regist_date, '%Y-%m') 返回年月文字
【DATE_ADD】往后推 DATE_ADD(regist_date, INTERVAL 7 DAY) 7 天后的日期
【DATE_SUB】往前推 DATE_SUB('2009-09-20', INTERVAL 6 DAY) 2009-09-14

日期格式符:%Y 年、%m 月、%d 日、%H 时、%i 分、%s 秒。

跨年统计不能只按 MONTH 分组;可按年月分组。往前 6 天加当天,共 7 个自然日。

日期差与实际时长

【DATEDIFF】只计算日期部分:DATEDIFF(结束日期, 开始日期),忽略时分秒。

【TIMESTAMPDIFF】按指定单位计算时间差:TIMESTAMPDIFF(单位, 开始时间, 结束时间),结果为整数。

单位 含义
SECOND 秒
MINUTE 完整分钟
HOUR 完整小时
DAY 完整的 24 小时

单位不加引号。想保留小数小时,先算秒,再除以 3600。

SELECT TIMESTAMPDIFF(
    SECOND, '2022-03-22 08:45:00', '2022-03-22 17:00:00'
) / 3600 AS work_hours;

结果:29700 秒 ÷ 3600 = 8.25 小时。直接按 HOUR 算得到 8,之后 ROUND 不能找回丢掉的分钟。

从 3 月 22 日 23:00 到 3 月 23 日 01:00:

算法 结果
DATEDIFF(结束, 开始) 1 天,日期变了
TIMESTAMPDIFF(DAY, 开始, 结束) 0,未满 24 小时
TIMESTAMPDIFF(SECOND, 开始, 结束) / 3600 2 小时

两种函数用途、参数顺序都不同,保留各自用法。平均工时通常先算准确时长,再求平均,最后按题意 ROUND。你的完整提交见附录 C01。

日期范围

【DATE_RANGE】DATE 只含日期,BETWEEN '2009-09-01' AND '2009-09-30' 可以包含整个 9 月。

【DATETIME_RANGE】DATETIME 含时间,完整自然月优先写“本月月初及之后、下月月初之前”:

WHERE regist_time >= '2009-09-01 00:00:00'
  AND regist_time <  '2009-10-01 00:00:00'

上界若写 9 月 30 日,通常只到当天零点,会漏掉白天和晚上。边界样例见附录 C02。

类型与缺失值

知识点 写法 记住这一点
【CAST】转换类型 CAST('0001' AS SIGNED INTEGER) → 1 转数字会丢前导零;转换结果不修改原列类型
【COALESCE】缺失时取备用值 COALESCE(purchase_price, 0) 从左到右取第一个非 NULL 值;0 和空串不触发替换
【NULLIF】相等时变 NULL NULLIF(previous_amount, 0) 等于 0 返回 NULL,否则返回原值

是否把缺失金额补 0,要按业务定义决定。仅显示“未登记”时可以用文字备用值,后续金额计算仍应保留数值列。

【NULL_ARITHMETIC】普通加减乘除遇到 NULL,结果为 NULL,包括 0 * NULL。聚合通常跳过 NULL,不能把两种规则混用。


7. 窗口函数

OVER 与分区

【OVER】函数决定算什么;OVER 内规定在哪些行中、按什么顺序计算。结构示意:

函数(参数) OVER (
    PARTITION BY 分区列
    ORDER BY 排序列
    ROWS 计算范围
)

按需求选择各项。SUM、AVG、COUNT 可以配 OVER;ROUND 等普通函数用来包住计算结果,不能随意加 OVER。

【PARTITION_BY】按分区独立计算,保留明细行:

目标 写法 结果每行代表什么
每类商品一个均价 GROUP BY product_type 后 AVG 一种商品类别
每件商品旁边显示同类均价 AVG(...) OVER(PARTITION BY product_type) 一件商品

COUNT 后面可以加 OVER

【WINDOW_COUNT】保留每位员工,同时显示所在岗位的人数:

SELECT staff_id, post,
       COUNT(*) OVER(PARTITION BY post) AS post_count
FROM staff_tb;

岗位依次为产品、产品、运营时,人数列是 2、2、1。COUNT(*) OVER() 不分区,三行都显示 3;COUNT(列) 仍忽略该列的 NULL。

三种排名放在一起记

成绩按降序为 4、4、2:

知识点 结果 用途
【ROW_NUMBER】连续序号 1、2、3 每行一个不同序号
【RANK】并列跳号 1、1、3 同分并列,后面跳名次
【DENSE_RANK】并列不跳号 1、1、2 同分并列,下个分数接下一名
ROW_NUMBER() OVER(ORDER BY score DESC, student_id ASC)

同分按唯一学号确定先后。若 RANK 需要同分并列,窗口里只按成绩等排名依据排序,唯一编号放到最终展示排序中。

【WINDOW_ORDER】窗口内 ORDER BY 决定怎样计算;查询末尾 ORDER BY 决定怎样展示。两者不能相互替代。

每类前几名

【TOP_N】先算排名,再由外层筛选:

WITH ranked_products AS (
    SELECT product_type, product_id, sale_price,
           RANK() OVER(
               PARTITION BY product_type ORDER BY sale_price DESC
           ) AS rk
    FROM product
)
SELECT product_type, product_id, sale_price, rk
FROM ranked_products
WHERE rk <= 2
ORDER BY product_type, sale_price DESC, product_id;

RANK 取前两名可能超过两行;DENSE_RANK 取前两个不同分值;ROW_NUMBER 每类最多两行。同层 WHERE 不能直接筛窗口函数或其新别名。

均值、累计与移动平均

知识点 表达式
【WINDOW_AVG】同类均价 AVG(sale_price) OVER(PARTITION BY product_type)
【RUNNING_SUM】从头累计到当前行 SUM(sale_amount) OVER(ORDER BY sale_date ROWS UNBOUNDED PRECEDING)
【MOVING_AVG】当前行加前两行的均值 AVG(sale_amount) OVER(ORDER BY sale_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)

销售额按日期依次为 100、200、150、250:

第几行 当天金额 累计金额 最近三行均值
1 100 100 100
2 200 300 150
3 150 450 150
4 250 700 200

不足三行用实际存在的行;AVG 还会忽略范围中的 NULL。最近三行是否等于最近三天,取决于日期是否连续且每天一行。

ROWS:相对当前行的范围

【ROWS】只规定取哪些行;用 SUM 还是 AVG,才决定怎样计算。

边界或范围 含义
CURRENT ROW 当前行
2 PRECEDING / 3 FOLLOWING 前第 2 行 / 后第 3 行
UNBOUNDED PRECEDING / FOLLOWING 分区第一行 / 最后一行
ROWS UNBOUNDED PRECEDING 从分区第一行到当前行
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 前两行到当前行,最多三行

逐行累计时保留明确的 ROWS 范围;省略范围后的默认规则可能把排序值相同的行一起纳入。同一日期多行时,按题意补充能确定先后的排序列。

先 WHERE 留下第 2~4 天再累计,得到 200、350、600;全部算完再用外层筛第 2~4 天,显示 300、450、700。筛选放在哪一层,会改变参与窗口计算的数据。

上一行、下一行与环比

知识点 表达式 四行金额 100、200、150、250 的结果
【LAG】上一行 LAG(sale_amount) OVER(ORDER BY sale_date) NULL、100、200、150
【LEAD】下一行 LEAD(sale_amount) OVER(ORDER BY sale_date) 200、150、250、NULL

默认偏移一行,缺少对应行时返回 NULL。前后行不自动等于昨天、明天,也不由 ROWS 范围控制偏移。

【CHANGE_RATE】环比 =(本期 − 上期)÷ 上期。先用 LAG 输出 previous_amount,再由外层计算:

ROUND((sale_amount - previous_amount) / NULLIF(previous_amount, 0), 4)

四行增长率为 NULL、1.0000、-0.2500、0.6667。上期为 0 或 NULL 时,本写法返回 NULL,不表示增长 0%。完整分层代码见附录 E20。


8. 常见指标

先说清:统计什么范围、每行代表谁、分子分母分别是什么。下面使用附录固定样本,不额外添加月份或注册后的限制。

订单与复购

【ORDER_METRICS】订单表每笔订单一行时:

指标 计算 原样本结果
订单数 COUNT(*) 3
买家数 COUNT(DISTINCT user_id) 2
付款总额 SUM(paid_amount) 300
每笔订单平均金额 总额 ÷ 订单数 100
每位买家平均付款 总额 ÷ 买家数 150

题目写“客单价”时,确认它以订单还是人数为分母。连接明细后,先检查订单金额有没有重复。

【REPEAT_BUYER】本练习把观察范围内至少两笔已付款订单的用户定义为复购买家。先按用户汇总:

用户 订单数 用户付款合计 复购标记
U01 2 200 1
U02 1 100 0

【REPEAT_RATE】复购买家数 ÷ 全部买家数,本例 1 ÷ 2 = 0.5000。每位买家一行时,用标记求和数复购买家;不先筛掉标记为 0 的人。

【REPEAT_PAID】复购买家的全部付款 ÷ 全部付款,本例 200 ÷ 300 = 0.6667。此口径包含复购买家的首笔订单,不等于仅第二笔及以后订单金额。

完整练习:E08~E11。

购买比例与次日留存

【PURCHASE_RATE】购买用户人数 ÷ 全部用户人数。

从 users LEFT JOIN orders;分母数不同 u.user_id,分子数不同 o.user_id。原样本 2 ÷ 4 = 0.5000;不能把连接后的 5 行当作 5 位用户。见附录 E13、E14。

【RETENTION】注册次日留存率 = 某注册日次日回访人数 ÷ 该注册日全部注册人数。

处理顺序:用户编号与注册次日同时匹配 → 每人一个 0/1 标记 → 按注册日汇总。 不提前筛掉未回访者;无人回访的注册日也要保留。

原样本 10 月 1 日注册的 2 人中回访 1 人,留存 0.5000;10 月 2 日注册的 2 人中回访 0 人,留存 0.0000。

一天多条活动先确保每人不被重复计数;尚未观察完次日,不能直接判成未留存。见附录 E15、E16。


9. 数据与数据库对象

数据库、类型与约束

知识点 记住的内容
【DATABASE】库、表、行、列 库放表;一行一条记录;一列一种属性;USE shop 选择库
【QUERY_SAVE】保存查询 保存 SQL 文字与改变数据库数据分别处理;仅编辑未执行不会改变数据
【DATA_TYPE】常见类型 INT 整数;DECIMAL(10,2) 共最多 10 位、其中小数 2 位;CHAR 定长文字;VARCHAR 变长文字;DATE 日期;DATETIME 日期时间
【CONSTRAINT】约束 NOT NULL 禁止 NULL;主键唯一且非空;联合主键要求组合唯一

编号有前导零时可用文字类型保存。关闭查询页不会删除已经成功执行并提交的数据;未提交事务另按提交或回滚处理。

建表与表结构

【CREATE_TABLE】创建表;列定义之间用逗号,最后一项后不加多余逗号。

CREATE TABLE addressbook_demo (
    regist_no INT PRIMARY KEY,
    name VARCHAR(128) NOT NULL,
    address VARCHAR(256) NOT NULL,
    tel_no CHAR(10),
    mail_address VARCHAR(100)
);

【ALTER_TABLE】增加列,例如 ALTER TABLE addressbook_demo ADD COLUMN zip_code CHAR(8) DEFAULT NULL;。DEFAULT 指默认值;给已有数据增加非空列时,要考虑旧行的取值。

【CREATE_TABLE_AS】保存查询结果为新表:

CREATE TABLE clothes_copy AS
SELECT product_id, product_name, sale_price
FROM product
WHERE product_type = '衣服';

它保存当时的数据,之后不自动同步,也不等于完整复制原表的主键和索引。

【DROP_TABLE】DROP TABLE addressbook_demo; 删除整张表及数据。

INSERT:新增行

【INSERT_VALUES】列和值按位置对应;每组括号是一行,多行用逗号隔开。

INSERT INTO actor (actor_id, first_name, last_name, last_update)
VALUES (1, 'PENELOPE', 'GUINESS', '2006-02-15 12:34:33'),
       (2, 'NICK', 'WAHLBERG', '2006-02-15 12:34:33');

【INSERT_IGNORE】在 INSERT 后加 IGNORE,可跳过主键或唯一键冲突的待插入行,保留原有行。冲突判断依靠约束;它还可能把部分其他数据错误转成警告,不能当作只处理重复。详例见附录 M01。

【INSERT_SELECT】从查询结果插入已有表;目标列与 SELECT 输出列按位置对应:

INSERT INTO clothes_copy (product_id, product_name, sale_price)
SELECT product_id, product_name, sale_price
FROM product
WHERE product_type = '衣服';

UPDATE:修改已有行

【UPDATE】SET 列出新值,WHERE 确定修改范围;多列赋值用逗号。

UPDATE titles_test
SET to_date = NULL,
    from_date = '2001-01-01'
WHERE to_date = '9999-01-01';

先按原数据找符合条件的行,再修改这些行的两列。NULL 不加引号;SET to_date = NULL 是赋值,WHERE to_date IS NULL 是判断缺失。没有 WHERE 会更新所有行。

UPDATE 与 REPLACE 组合时:

SET 中的写法 含义
raw_name = REPLACE(raw_name, 'sql', 'SQL') 读取原值,替换匹配片段,再写回;无匹配就保留原值
raw_name = 'SQL' 符合 WHERE 的行都设成固定文字 SQL

REPLACE 按字符串片段处理;数字整体改值通常直接赋值更清楚。原数字例子见附录 M02。

DELETE 与查询去重

【DELETE】DELETE FROM 表名 WHERE 条件; 删除满足条件的原表行;没有 WHERE 会删除所有行。这里是语法提示,独立删除练习仍待完成。

SELECT DISTINCT、SELECT 筛选排名保留行,只改变显示结果。SQL241 查询到 rk = 1,不等于已经删除其他行。

视图与上下合并

知识点 作用与写法
【VIEW】视图 CREATE VIEW v_clothes AS SELECT ...;保存查询定义,读取底表当时的数据
【ALTER_VIEW】改定义 ALTER VIEW v_clothes AS SELECT ...;重写完整查询定义
【VIEW_UPDATE】改数据 可更新视图上的 UPDATE 改变底表数据;分组聚合视图通常不可更新,连接视图须看定义
【VIEW_INSERT】经视图插入 仍需满足底表必填列和约束;是否检查视图筛选条件取决于相关选项
【VIEW_LOOKUP】查看 SELECT * FROM v_clothes 查内容;information_schema.VIEWS 可列出视图名称
【UNION】上下合并去重 两边列数相同,对应类型兼容,按全部输出列判断重复
【UNION_ALL】上下合并保留重复 输出列名取第一条 SELECT;整体 ORDER BY 写在最后

JOIN 按条件配对行;UNION 把结果上下拼接。列名和顺序来自第一条 SELECT 的输出定义,数据按对应位置合并。

索引与触发器

【INDEX】索引用于辅助查找;普通索引不限制重复,唯一索引限制非 NULL 键值重复,主键唯一且非空。

ALTER TABLE product ADD INDEX idx_product_type (product_type);

一张表可有多个普通或唯一索引,只有一个主键;联合主键可以多列。MySQL 唯一索引允许多个 NULL;创建约束前已有数据须符合要求。索引不替代 ORDER BY。

【FORCE_INDEX】FROM product AS p FORCE INDEX (idx_product_type),括号中写已有索引名;主键名为 PRIMARY。它影响索引选择,但不保证更快,也不保证所有情况都避免全表扫描。

【TRIGGER】表发生指定变化时自动执行动作。BEFORE / AFTER 表示操作前后;INSERT / UPDATE / DELETE 表示事件;FOR EACH ROW 表示每处理一行触发一次。

NEW 代表新行,OLD 代表旧行:INSERT 用 NEW,DELETE 用 OLD,UPDATE 可用两者。正式学习和独立练习仍待进行,完整已讲示例放在附录 M03。


10. 检查方法

【SQL_REVIEW】动笔前写四句话:

  1. 哪些数据参与?
  2. 最终每行代表什么?
  3. 输出哪些字段或指标?
  4. 按什么排序,如何处理并列?

【SQL_SYNTAX_CHECK】报错时依次看:英文逗号与引号 → 括号 → 关键字结构 → 表字段拼写 → 别名作用域 → 子查询别名 → 分号位置。

报错 near 指向数据库无法继续解析的位置,错误也可能出在它前面。仅凭报错片段不能确定唯一原因,需要完整 SQL,尤其是 JOIN 条件和 WITH 定义。

【SQL_RESULT_CHECK】能运行但结果不对时:

现象 优先检查
每个商品统计值都一样 ON 是否把字段与自身比较
订单金额变大 连接明细后是否重复累计订单金额
没购买的人消失 INNER JOIN 或 WHERE 是否筛掉未匹配行
比例全部 100% 是否提前删掉分母需要的其他行
同类均价等于自己价格 是否按唯一编号关联
排名或累计不符合预期 排名规则、窗口顺序、范围和筛选层次

把问题分成语法、统计逻辑、输出名称与排序三类。先查看中间结果,每一步说清一行代表什么;文本检查通过不等于已在数据库执行。


维护方式:同一知识点继续修改原条目;完整题目进附录,学习记录进进度。此次整理前的三份原文件已保存到 archive 中的备份压缩包。

posted @ 2026-10-20 15:23  _FFF  阅读(3)  评论(0)    收藏  举报
♫ We Are! · 海贼王
We Are! / ウィーアー!北谷洋 · 海贼王经典主题曲Apple Music 官方原唱试听片段网易云收听 · 官方完整歌曲入口
↑
Theme inspired by Sakura · ONE PIECE
图片 © 尾田荣一郎/集英社・东映动画 · 来源 Wallpaper Abyss / Wallpapers.com