当前位置: 首页 > news >正文

【SQL】刷题记录

文章目录

  • 一、SQL语法、日期函数等
  • 1、语法
    • 1.1. join
      • left join
      • 注意:left join 的其他作用
    • 1.2 as
    • 1.3 using
      • 1. **`USING` 的语法**(注意使用括号)
      • 2. **`USING` 的作用**
    • 1.4 where VS HAVING
    • 1.5 CASE WHEN
    • 1.6 if else
    • 1.7 IFNULL
    • 1.8 OVER 窗口函数
    • 1.9 limit 常常与order by一起使用
    • 1.10 CONCAT
        • 使用示例
    • 1.11 `LIKE` 操作符
        • 功能
        • 语法
        • 使用示例 [1527. 患某种疾病的患者](https://leetcode.cn/problems/patients-with-a-condition/)
    • 1.12 RECURSIVE
        • 举例:生成 **2017 年所有日期**,并统计每周几(星期一到星期日)出现的次数
      • **关键部分解析**
        • **1. `WITH RECURSIVE` 递归 CTE**
        • **2. `SELECT` 主查询**
      • **执行过程**
        • **1. 递归生成日期**
        • **2. 统计星期几**
        • **3. 排序**
      • **示例结果**
      • **注意事项**
      • **总结**
    • 1.13 ORDER BY
      • **排序逻辑总结**
    • 1.14 LEAD
      • 语法
      • 示例
    • 1.15 DENSE_RANK RANK ROW_NUMBER
      • **三种函数的区别总结**
      • **如何选择?**
    • 1.16 尽量使用union all而 不使用union
    • 1.17 not in 遇到null的问题,使用exists改写
  • 2.算术函数
    • (1)count (注意加不加group by )
      • count sum
    • (2)date MySQL 中的日期和时间函数
      • 1. 获取当前日期和时间
      • 2. 提取日期和时间的部分
      • DAYNAME(date)
      • 3. 日期和时间的计算
    • (3)ROUND AVG
    • (4)mod
    • (5) 除法
  • 3.字符串
      • SUBSTRING
      • SUBSTRING_INDEX
  • 4、表操作
    • 4.1 insert into ,replace into
    • 4.2 update
    • 4.3 delete 删除表中的行(的数据)
    • 4.4 truncate table 删除表中数据
    • 4.5 create table
    • 4.6 alter table
    • 4.7 DROP table
    • 4.8 创建索引
    • 4.9 删除索引
  • 5、细节
    • 尽量写具体
  • 二、leetcode 、牛客题目
    • [1280. 学生们参加各科测试的次数](https://leetcode.cn/problems/students-and-examinations/)
    • [1934. 确认率](https://leetcode.cn/problems/confirmation-rate/)
      • 1. **`AVG(c.action='confirmed')`**
      • 2. **`IFNULL(AVG(c.action='confirmed'), 0)`**
    • [1251. 平均售价](https://leetcode.cn/problems/average-selling-price/)
    • [1633. 各赛事的用户注册率](https://leetcode.cn/problems/percentage-of-users-attended-a-contest/)
    • [1211. 查询结果的质量和占比](https://leetcode.cn/problems/queries-quality-and-percentage/)
      • AVG 和 SUM/COUNT
    • [1193. 每月交易 I](https://leetcode.cn/problems/monthly-transactions-i/)
    • [1174. 即时食物配送 II](https://leetcode.cn/problems/immediate-food-delivery-ii/)
    • [1141. 查询近30天活跃用户数](https://leetcode.cn/problems/user-activity-for-the-past-30-days-i/)
    • [619. 只出现一次的最大数字](https://leetcode.cn/problems/biggest-single-number/)
    • [1045. 买下所有产品的客户](https://leetcode.cn/problems/customers-who-bought-all-products/)
    • [1731. 每位经理的下属员工数量](https://leetcode.cn/problems/the-number-of-employees-which-report-to-each-employee/description/)
    • [610. 判断三角形](https://leetcode.cn/problems/triangle-judgement/)
      • case when
      • if else
    • [180. 连续出现的数字](https://leetcode.cn/problems/consecutive-numbers/)
      • 三表连接 自身连接 连续出现
    • [1164. (重要)指定日期的产品价格](https://leetcode.cn/problems/product-price-at-a-given-date/)
    • [1204. 最后一个能进入巴士的人](https://leetcode.cn/problems/last-person-to-fit-in-the-bus/)
        • `SUM(weight) OVER(ORDER BY turn) AS total_weight` 中的 `ORDER BY turn`
        • `ORDER BY turn` 在子查询末尾
    • [1907. 按分类统计薪水](https://leetcode.cn/problems/count-salary-categories/)
      • 错误解法
      • 正确code
      • leetcode官方解法
    • [1978. 上级经理已离职的公司员工](https://leetcode.cn/problems/employees-whose-manager-left-the-company/)
    • [626. 换座位](https://leetcode.cn/problems/exchange-seats/)
    • [1341. 电影评分](https://leetcode.cn/problems/movie-rating/)
    • [1667. 修复表中的名字](https://leetcode.cn/problems/fix-names-in-a-table/)
    • [1527. 患某种疾病的患者](https://leetcode.cn/problems/patients-with-a-condition/)
    • [1321. 餐馆营业额变化增长](https://leetcode.cn/problems/restaurant-growth/)
    • [585. 2016年的投资](https://leetcode.cn/problems/investments-in-2016/)
    • [176. 第二高的薪水](https://leetcode.cn/problems/second-highest-salary/)
    • [184. 部门工资最高的员工](https://leetcode.cn/problems/department-highest-salary/)(每个部门)
        • 错误写法
        • 正确写法
    • [185. 部门工资前三高的所有员工](https://leetcode.cn/problems/department-top-three-salaries/)
        • 注意下面的错误:
    • **SQL125** **得分不小于平均分的最低分**
      • 错误代码
      • 正确代码
    • **SQL124** **统计作答次数**
    • **SQL126** **平均活跃天数和月活人数**
    • **SQL127** **月总刷题数和日均刷题数**
      • 求每月的最大天数
    • **SQL128** (重要)**未完成试卷数大于1的有效用户**
    • [607. 销售员](https://leetcode.cn/problems/sales-person/)
    • [608. 树节点](https://leetcode.cn/problems/tree-node/)
    • [627. 变更性别](https://leetcode.cn/problems/swap-salary/)
    • [1070. 产品销售分析 III](https://leetcode.cn/problems/product-sales-analysis-iii/)
    • [1179. 重新格式化部门表](https://leetcode.cn/problems/reformat-department-table/)
    • **SQL211** **获取每个部门中薪水最高的员工相关信息**
    • **SQL232** **创建一个actor表,包含如下列信息**
    • 关于创建数据表的小提示
    • 在表上创建索引
    • **SQL266** **牛客每个人最近的登录日期(二)**
    • **SQL267** **牛客每个人最近的登录日期(三)**
    • **SQL268** **牛客每个人最近的登录日期(四)**
    • **SQL269** **牛客每个人最近的登录日期(五)**
    • **SQL241** **删除emp_no重复的记录,只保留最小的id对应的**
    • [1393. 股票的资本损益](https://leetcode.cn/problems/capital-gainloss/)
    • 行转列 union all [1795. 每个产品在不同商店的价格](https://leetcode.cn/problems/rearrange-products-table/)
      • code
    • [1965. 丢失信息的雇员](https://leetcode.cn/problems/employees-with-missing-information/)
    • 正则表达式
    • [3465. 查找具有有效序列号的产品](https://leetcode.cn/problems/find-products-with-valid-serial-numbers/)
  • 三、数据库课程笔记
  • 1、 王珊老师数据库
    • 是3NF不是BCNF 的举例
    • 求最小函数依赖集
      • (1)
      • (2)
    • 求候选码的方法
      • (1)L R LR N
      • (2)画有向图
    • 分解到3NF
    • 例题:
    • 分解到BCNF

在这里插入图片描述

一、SQL语法、日期函数等

1、语法

https://leetcode.cn/studyplan/sql-free-50/

# Write your MySQL query statement below
select name 
from Customer 
where referee_id <> '2' or referee_id is null
  • 不等于 <>
  • null 使用is 不使用=
select distinct author_id as id
from Views 
where author_id =viewer_id 
order by id
  • 消除取值重复的行 distinct
  • 统计varchar 类型长度 length(content)>15

1.1. join

(1)left join (当某个表有多余的值需要使用,需要使用外连接,不忽略悬浮元组)

展示每位用户的 唯一标识码(unique ID );如果某位员工没有唯一标识码,使用 null 填充即可。

image-20250216155031788

左连接,保留左侧表的全部行

# Write your MySQL query statement below
select Employees.name,EmployeeUNI.unique_id 
from Employees left join EmployeeUNI 
on Employees.id = EmployeeUNI.id

(2)显式内连接join ,隐式内连接where p.product_id = s.product_id;

隐式内连接

是一种比较早期的写法。(书上就有 可以使用)

image-20250216173908543

image-20250216173728356

select product_name, year, price
from Product p, Sales s
where p.product_id = s.product_id;

等价的显式内连接写法

select product_name, year, price
from Product p
join Sales s on p.product_id = s.product_id;

left join

当筛选条件在外连接中的"大表(含有悬浮元祖)"的时候,放在where中

https://www.nowcoder.com/practice/53235096538a456b9220fce120c062b3?tpId=199&tags=&title=&difficulty=0&judgeStatus=0&rp=0&sourceUrl=%2Fexam%2Foj%3FquestionJobId%3D10%26subTabName%3Donline_coding_page

image-20250302123201078

image-20250302123235638

image-20250302123309040

注意:left join 的其他作用

image-20250309224956174

1.2 as

AS 关键字主要用于为表或者列指定别名,

对于别名而言,AS 关键字是可选的,

不过对于别名,虽然大多数数据库系统里 AS 也是可选的,但为了增强代码的可读性,建议在指定列别名时使用 AS 关键字。

1.3 using

在 SQL 中,USING 是用于简化 JOIN 操作的关键字。当两个表在连接时使用相同名称的列作为连接条件时,可以使用 USING 来替代 ON,从而使代码更简洁。


1. USING 的语法(注意使用括号)

SELECT columns
FROM table1
JOIN table2
USING (column_name);

2. USING 的作用

  • USING 会自动根据指定的列名在两个表中查找匹配的列,并将其作为连接条件。
  • 使用 USING 时,查询结果中只会保留一个连接列(而不是像 ON 那样保留两个表的列)。

1.4 where VS HAVING

image-20250219143011435

注意,WHERE 子句中是不能用聚集函数作为条件表达式的。 聚集函数只能用于SELECT 子句和 GROUP BY 中的 HAVING 子句。

  1. HAVING vs WHERE:
    • WHERE 用于过滤行,
    • HAVING 用于在分组操作之后,对分组进行过滤,选出合适的组,
    • 在这个查询中,COUNT 是聚合函数,必须与 HAVING 一起使用。

举例:

image-20250216190118262

image-20250216190216803

1.5 CASE WHEN

输入: 
Triangle 表:
+----+----+----+
| x  | y  | z  |
+----+----+----+
| 13 | 15 | 30 |
| 10 | 20 | 15 |
+----+----+----+
输出: 
+----+----+----+----------+
| x  | y  | z  | triangle |
+----+----+----+----------+
| 13 | 15 | 30 | No       |
| 10 | 20 | 15 | Yes      |
+----+----+----+----------+

对每三个线段报告它们是否可以形成一个三角形。

SELECT 
    x,
    y,
    z,
    CASE
        WHEN x + y > z AND x + z > y AND y + z > x THEN 'Yes'
        ELSE 'No'
    END AS 'triangle'
FROM
    triangle
;

1.6 if else

IF(condition, value_if_true, value_if_false)

# Write your MySQL query statement below
# 使用IF函数嵌套and条件,判断任意两边之和大于第三边:IF( expr1 AND expr2 AND expr3, trueResult, falseResult)
SELECT
	x,
	y,
	z,
IF
	( x + y > z AND x + z > y AND y + z > x, 'Yes', 'No' ) AS triangle 
FROM
	Triangle;

1.7 IFNULL

IFNULL(expression, replacement_value)---如果expression为null 就是replacement_value
  • expression:需要进行检查的表达式或列名。该表达式可能返回 NULL 值。
  • replacement_value:当 expression 的结果为 NULL 时,要替换成的值。这个值的数据类型应该和 expression 结果的数据类型兼容。

1.8 OVER 窗口函数

person_nameweightturn
Alice501
Bob602
Charlie703

在计算 SUM(weight) OVER(ORDER BY turn) 时,会按照 turn 从小到大的顺序依次计算累计重量。

  • 对于 turn 为 1 的行,累计重量就是该行的 weight(50);
  • 对于 turn 为 2 的行,累计重量是前两行 weight 的和(50 + 60 = 110);
  • 对于 turn 为 3 的行,累计重量是前三行 weight 的和(50 + 60 + 70 = 180)。

1.9 limit 常常与order by一起使用

SELECT column1, column2, ...
FROM table_name
WHERE condition
LIMIT number;

其中 number 表示要返回的行数,当 number 为 1 时,即 LIMIT 1,表示只返回一行记录。

LIMIT 1 常用于以下几种场景:

  • 获取单条记录:当你只需要查询结果中的一条记录时,例如获取表中的第一条记录(需要使用 ORDER BY 子句)、最新的一条记录(照某个表示时间的列(如 create_timeupdate_time 等)降序排列后的第一条记录)或者满足特定条件的某一条记录

1.10 CONCAT

CONCAT 函数用于将多个字符串连接成一个字符串。

在大多数数据库(如 MySQL、PostgreSQL 等)中,CONCAT 函数的基本语法如下:

CONCAT(string1, string2, ..., string_n);

其中 string1string_n 是要连接的字符串,可以是列名、常量字符串或其他表达式。

使用示例

假设我们有一个 employees 表,包含 first_namelast_name 两列,我们可以使用 CONCAT 函数将员工的名字和姓氏连接起来:

-- 创建示例表
CREATE TABLE employees (
    first_name VARCHAR(50),
    last_name VARCHAR(50)
);

-- 插入示例数据
INSERT INTO employees (first_name, last_name) VALUES ('John', 'Doe');
INSERT INTO employees (first_name, last_name) VALUES ('Jane', 'Smith');

-- 使用 CONCAT 函数连接名字和姓氏
SELECT CONCAT(first_name, ' ', last_name) AS full_name
FROM employees;

上述代码中,CONCAT 函数将 first_name、一个空格和 last_name 连接成一个完整的姓名。

1.11 LIKE 操作符

功能

LIKE 操作符用于在 WHERE 子句中搜索列中的指定模式。它通常与通配符一起使用,以实现模糊匹配。

语法

LIKE 操作符的基本语法如下:

column_name LIKE pattern;

其中 column_name 是要搜索的列名,pattern 是要匹配的模式。常用的通配符有:

  • %:匹配任意数量(包括零个)的任意字符。
  • _:匹配单个任意字符。
使用示例 1527. 患某种疾病的患者
SELECT patient_id, patient_name, conditions
FROM Patients
WHERE conditions LIKE 'DIAB1%' OR conditions LIKE '% DIAB1%';

1.12 RECURSIVE

RECURSIVE是用于处理递归查询的关键字,从 MySQL 8.0.1 递归查询功能被引入

WITH RECURSIVE cte_name AS (
    -- 初始查询部分,定义递归的基础条件
    initial_query
    UNION [ALL]
    -- 递归查询部分,定义递归的规则
    recursive_query
)
SELECT * FROM cte_name;
举例:生成 2017 年所有日期,并统计每周几(星期一到星期日)出现的次数
WITH RECURSIVE dates_2017 AS (
    SELECT '2017-01-01' AS date
    UNION ALL
    SELECT DATE_ADD(date, INTERVAL 1 DAY)
    FROM dates_2017
    WHERE date < '2017-12-31'
)
SELECT 
    DAYNAME(date) AS weekday,
    COUNT(*) AS count
FROM dates_2017
GROUP BY weekday
ORDER BY count DESC;

关键部分解析

1. WITH RECURSIVE 递归 CTE
  • WITH RECURSIVE:定义一个递归的公共表表达式(CTE),用于生成 2017 年所有日期。

  • 初始查询

    SELECT '2017-01-01' AS date
    

    生成初始日期 2017-01-01

  • 递归查询

    SELECT DATE_ADD(date, INTERVAL 1 DAY)
    FROM dates_2017
    WHERE date < '2017-12-31'
    
    • 每次递归调用,将当前日期加 1 天(DATE_ADD(date, INTERVAL 1 DAY))。
    • 递归终止条件是日期达到 2017-12-31WHERE date < '2017-12-31')。
2. SELECT 主查询
  • DAYNAME(date):获取日期的星期几名称(如 MondayTuesday 等)。
  • COUNT(*):统计每个星期几出现的次数。
  • GROUP BY weekday:按星期几分组。
  • ORDER BY count DESC:按次数从高到低排序。

执行过程

1. 递归生成日期

递归 CTE 会生成 2017 年所有日期,结果如下:

date
2017-01-01
2017-01-02
2017-01-03
2017-12-31
2. 统计星期几

对生成的日期表,使用 DAYNAME(date) 获取星期几,并统计次数:

weekdaycount
Monday52
Tuesday52
Wednesday52
Thursday52
Friday52
Saturday52
Sunday52
3. 排序

count 从高到低排序(本例中所有星期几的次数相同,排序结果不变)。


示例结果

weekdaycount
Monday52
Tuesday52
Wednesday52
Thursday52
Friday52
Saturday52
Sunday52

注意事项

  1. 递归深度

    • 递归 CTE 的默认深度限制可能较低(如 MySQL 默认 1000 次递归)。
    • 如果递归次数超过限制,需要调整数据库配置:
      SET SESSION cte_max_recursion_depth = 10000;
      
  2. 日期范围

    • 确保初始日期和终止日期正确,否则可能生成错误日期范围。
  3. 星期几名称

    • DAYNAME(date) 返回的星期几名称是英文(如 Monday),如需中文需额外处理。
  4. 性能优化

    • 递归 CTE 适用于小规模日期生成(如一年)。
    • 如果需要生成更长时间的日期,建议使用预先生成的日期表。

总结

  • 该查询通过递归 CTE 生成 2017 年所有日期,并统计每周几出现的次数。
  • 递归 CTE 是生成序列数据(如日期、数字)的强大工具。
  • 结果按次数排序,适合分析日期分布规律。

1.13 ORDER BY

ORDER BY HIREDATE, EMPNO 是按照什么排序的

排序逻辑总结

  1. 首先按 HIREDATE 排序,确保员工按入职日期从早到晚排列。
  2. 如果多个员工的 HIREDATE 相同,则按 EMPNO 排序,确保顺序稳定且唯一。

1.14 LEAD

在MySQL中,LEAD是一个窗口函数,用于在结果集中访问当前行之后的行数据,以下是其详细介绍:

语法

LEAD(expr[, offset[, default]]) OVER (PARTITION BY partition_expression ORDER BY order_expression)

示例

假设有一个sales表,包含yearproductsales_amount列,记录了不同年份各产品的销售金额,以下是一些使用LEAD函数的示例:

  • 获取下一年的销售金额
SELECT
    year,
    product,
    sales_amount,
    LEAD(sales_amount) OVER (PARTITION BY product ORDER BY year) AS next_year_sales
FROM 
    sales;

在这个查询中,LEAD(sales_amount)用于获取每个产品下一年的销售金额。通过PARTITION BY product按照产品进行分区,确保在每个产品内进行偏移操作。ORDER BY year按照年份对每个分区内的数据进行排序,以便正确获取下一年的数据。

  • 获取间隔两行后的销售金额
SELECT
    year,
    product,
    sales_amount,
    LEAD(sales_amount, 2) OVER (PARTITION BY product ORDER BY year) AS next_two_years_sales
FROM
    sales;

这里使用LEAD(sales_amount, 2)获取每个产品间隔两行后的销售金额,即间隔两年后的销售数据。

  • 指定默认值
SELECT
    year,
    product,
    sales_amount,
    LEAD(sales_amount, 2, 0) OVER (PARTITION BY product ORDER BY year) AS next_two_years_sales
FROM
    sales;

当偏移超出结果集范围时,将返回指定的默认值0,而不是NULL

通过使用LEAD函数,可以方便地在查询结果中获取当前行之后的数据,进行数据比较、分析等操作,为数据分析和处理提供了很大的便利。

1.15 DENSE_RANK RANK ROW_NUMBER

三种函数的区别总结

函数相同值的处理是否跳过后续序号示例结果(输入:4, 4, 3.85, 3.65, 3.65, 3.5)
ROW_NUMBER()分配不同的序号1, 2, 3, 4, 5, 6
RANK()分配相同的排名1, 1, 3, 4, 4, 6
DENSE_RANK()分配相同的排名1, 1, 2, 3, 3, 4

如何选择?

  1. 如果需要为每一行分配唯一的序号,使用 ROW_NUMBER()
  2. 如果需要为相同的值分配相同的排名,并且**允许跳过后续序号,**使用 RANK()
  3. 如果需要为相同的值分配相同的排名,并且不允许跳过后续序号,使用 DENSE_RANK()
# Write your MySQL query statement below
#  https://leetcode.cn/problems/rank-scores/
select score,	
    DENSE_RANK() OVER (ORDER BY score desc) AS 'rank'
from Scores 

1.16 尽量使用union all而 不使用union

尽量使用union all而 不使用union,

union 相当于在union all基础上distinct(含有排序)

1.17 not in 遇到null的问题,使用exists改写

EXISTS 并不一定需要关联嵌套查询。EXISTS 的核心作用是检查子查询是否返回至少一行结果,而子查询可以是关联的(Correlated Subquery)或非关联的(Non-Correlated Subquery)

image-20250309224652181

2.算术函数

(1)count (注意加不加group by )

https://leetcode.cn/problems/customer-who-visited-but-did-not-make-any-transactions/?envType=study-plan-v2&envId=sql-free-50

image-20250216161724148

这里 customer_id 只显示筛选结果集中的某一个 customer_id 值(通常是第一条记录的 customer_id),而 count_no_trans 是所有满足条件的记录总数,无法体现每个顾客的具体情况。

综上所述,GROUP BY customer_id 对于实现按顾客统计未交易访问次数的功能是必不可少的。

count sum

  • 概念:COUNT 用于统计行数,SUM 用于计算数值列的总和

    • COUNTCOUNT 函数主要用于统计满足特定条件的行数。

    • SUMSUM 函数的作用是对某列中的数值进行求和操作,它只适用于数值类型的列。

  • 举例:

    • **count(age > 20 or null)这个语句,里面or null必须加,否则就等价于count(*)了,要么就是写作sum(age > 20)**也可以。

    • 我猜测是因为**age > 20返回的是0或者1,而count对于不管是0还是1,都是会计数一次的,只有Null不会被计数。**sum对于1会计数,对于0,加上0 等于没加。所以这个age > 20 or null表达的是不大于20就转换为null,这样就不会被count计数

  • COUNTCOUNT(*) 会统计所有行,包含空值;而 COUNT(column_name) 只统计该列中非空值的数量。

  • SUMSUM 函数只能用于数值类型的列,如果列中包含非数值类型的值,这些值会被忽略。

# Write your MySQL query statement below

SELECT p.product_id,p.product_name   
FROM Product p,Sales  s
WHERE p.product_id = s.product_id 
GROUP BY product_id  
HAVING COUNT(sale_date BETWEEN '2019-01-01' AND '2019-03-31' OR null) =COUNT(*)
# Write your MySQL query statement below

SELECT p.product_id,p.product_name   
FROM Product p,Sales  s
WHERE p.product_id = s.product_id 
GROUP BY product_id  
HAVING SUM(sale_date BETWEEN '2019-01-01' AND '2019-03-31') =COUNT(*)

上述代码等价

  • 注意

    • count(某一列)返回指定列中,不为NULL的数量。不会计算NULL值。

    • count(*)count(0)count(1)返回表格的数据量。会计算NULL值。

(2)date MySQL 中的日期和时间函数

1. 获取当前日期和时间

  • CURDATE()CURRENT_DATE(): 返回当前日期(YYYY-MM-DD)。
  • CURTIME()CURRENT_TIME(): 返回当前时间(HH:MM:SS)。

2. 提取日期和时间的部分

  • YEAR(date): 返回日期的年份。

  • MONTH(date): 返回日期的月份(1-12)。

  • DAY(date)DAYOFMONTH(date): 返回日期的天数(1-31)。

  • HOUR(time): 返回时间的小时部分(0-23)。

  • MINUTE(time): 返回时间的分钟部分(0-59)。

  • SECOND(time): 返回时间的秒数部分(0-59)。

  • DAYOFWEEK(date): 返回日期对应的星期几(1=周日,2=周一,…,7=周六)。

    • SELECT DAYOFWEEK('2025-02-23');    返回结果为`7`,因为 2025223 日是星期六。
      
  • DAYNAME(date)

    • SELECT DAYNAME('2025-02-23');    返回结果为Saturday。
      

3. 日期和时间的计算

  • DATE_ADD(date, INTERVAL expr unit): 在日期上添加一个时间间隔。
    • 例如:DATE_ADD('2023-10-01', INTERVAL 1 DAY) 返回 2023-10-02
  • DATE_SUB(date, INTERVAL expr unit): 从日期中减去一个时间间隔。
    • 例如:DATE_SUB('2023-10-01', INTERVAL 1 MONTH) 返回 2023-09-01
  • DATEDIFF(date1, date2): 返回两个日期之间的天数差**(date1 - date2)。**
  • TIMEDIFF(time1, time2): 返回两个时间之间的差值**(time1 - time2)。**
  • (重要)TIMESTAMPDIFF(unit, datetime1, datetime2): 返回两个日期时间之间的差值,单位可以是 YEAR, MONTH, DAY, HOUR, MINUTE, SECOND 等。 datetime2 - datetime1
TIMESTAMPDIFF(unit, start_date, end_date)     start_date起始日期  end_date:结束日期或时间,
  1. 日期和时间的格式化
  • DATE_FORMAT(date, format): 格式化日期为指定的字符串格式。
    • 例如:DATE_FORMAT('2023-10-01', '%Y/%m/%d') 返回 2023/10/01必须是大写的Y
  • TIME_FORMAT(time, format): 格式化时间为指定的字符串格式。
    • 例如:TIME_FORMAT('14:30:00', '%H:%i') 返回 14:30

日期在…中间 使用between and

select w2.id
from Weather w1, Weather w2
where datediff(w2.recordDate, w1.recordDate) = 1 and w2.Temperature > w1.Temperature

(3)ROUND AVG

在 SQL 中,ROUND 函数用于对数值进行四舍五入。它可以对小数或整数进行四舍五入,并允许指定保留的小数位数。以下是 ROUND 函数的详细说明和用法:

函数的基本语法

ROUND(numeric_expression, [decimal_places])
  • numeric_expression: 需要进行四舍五入的数值(可以是列名、常量或表达式)。
  • decimal_places: (可选)指定保留的小数位数。如果省略,则默认为 0,即四舍五入到整数。

注意下面这里select要写明 t1.machine_id 不能只写 machine_id

select t1.machine_id , round(avg(t2.timestamp - t1.timestamp),3) processing_time 
from Activity t1,Activity t2
where t1.machine_id =t2.machine_id 
and t1.process_id =t2.process_id 
and t1.activity_type ='start'
and t2.activity_type ='end'
group by machine_id

(4)mod

int计算奇数偶数

mod(id, 2) = 1

(5) 除法

mysql中 5/2得出的是2.5 不是2

求中位数出现的位置:AVG( floor((cnt+1)/2) , ceil((cnt+1)/2) )

5+1 /2=3 floor 3 ceil 3

4+1 /2 =2.5 floor 2 ceil 3

3.字符串

SUBSTRING

  • 在 MySQL 中,SUBSTRING 的起始位置是从 1 开始,而不是 0。

SUBSTRING_INDEX

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

https://www.nowcoder.com/practice/f04189f92f8d4f6fa0f383d413af7cb8?tpId=199&tqId=1975679&sourceUrl=%2Fexam%2Foj%3FquestionJobId%3D10%26subTabName%3Donline_coding_page

SELECT SUBSTRING_INDEX(profile,",",-1) as gender,
    COUNT(*) as number
FROM user_submit 
GROUP BY gender;

4、表操作

4.1 insert into ,replace into

自增id可以忽略,他会自动加https://www.nowcoder.com/practice/978bcee6530a430fb0be716423d84082?tpId=240&tqId=2223554&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3DSQL%25E7%25AF%2587%26topicId%3D298 不管该ID试卷是否存在,都要插入成功,请尝试插入它。

image-20250306201459187

insert into examination_info(exam_id,tag,difficulty,duration,release_time)
values (9003,'SQL','hard',90,'2021-01-01 00:00:00');
replace into examination_info(exam_id,tag,difficulty,duration,release_time)
values (9003,'SQL','hard',90,'2021-01-01 00:00:00');

replace into 跟 insert into功能类似,不同点在于:replace into 首先尝试插入数据到表中,

  1. 如果发现表中已经有此行数据(根据主键或者唯一索引判断)则先删除此行数据,然后插入新的数据;
  2. 否则,直接插入新数据。

要注意的是:插入数据的表必须有主键或者是唯一索引!否则的话,replace into 会直接插入数据,这将导致表中出现重复的数据。

4.2 update

请把examination_info表中tag为PYTHON的tag字段全部修改为Python。

image-20250306201923426

update examination_info
set tag='Python'
where tag='PYTHON'
  • 修改多个元素 使用逗号
  • ]image-20250306202245543

4.3 delete 删除表中的行(的数据)

delete from
    exam_record
where
    TIMESTAMPDIFF (MINUTE, start_time, submit_time) < 5
    and score < 60;  --注意加分号

https://www.nowcoder.com/practice/964c9f7fffbb4ab18b507cfed4111b4a?tpId=240&tqId=2223554&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3DSQL%25E7%25AF%2587%26topicId%3D298

delete from
    exam_record
where
    submit_time is null
    or timestampdiff (minute, start_time, submit_time) < 5
order by
    start_time asc
limit
    3

4.4 truncate table 删除表中数据

请删除nowcoder_exam_record表中所有记录,并重置自增主键。

delete from nowcoder_exam_record; 
alter table nowcoder_exam_record drop id; 

等价于

truncate table nowcoder_exam_record
  1. 高效性
    • TRUNCATE 直接释放数据页,而不是逐行删除,因此速度更快,尤其是在大表上。
  2. 自动重置自增列
    • 如果表有自增列(如 AUTO_INCREMENT),TRUNCATE将其重置为初始值(通常是 1)。
  3. 不可回滚
    • TRUNCATE 是一个 DDL(数据定义语言)操作,执行后无法通过 ROLLBACK 回滚。如果需要回滚,应使用 DELETE
  4. 不触发触发器
    • TRUNCATE 不会触发与表相关的 DELETE 触发器。
  5. 需要权限
    • 执行 TRUNCATE 需要对该表有 DROP 权限。

4.5 create table

create table if not exists 
    user_info_vip (
        id int (11) not null primary key auto_increment comment '自增ID',
        uid int (11) not null unique comment '用户ID',
        nick_name varchar(64) comment '昵称',
        achievement int (11) default 0 comment '成就值',
        level int(11) comment '用户等级',
        job varchar(32) comment '职业方向',
        register_time datetime default CURRENT_TIMESTAMP comment '注册时间'   #这里没有逗号
    ) default charset utf8;

image-20250306211425602

4.6 alter table


alter table user_info add school varchar(15) after level;
# 增加列在某列之后
# alter table 增加的表格 add 增加列的名称 数据类型 位置(after level 在level 之后)

alter table user_info change job profession varchar(10);
# 更换列的名称及数据类型
# alter table user_info change 原列名 修改列名 修改数据类型

alter table user_info modify achievement int(11) default 0;
# 更改数据类型
# alter table 表名 modify 修改列名称 数据类型 默认值等

不可缺失 https://www.nowcoder.com/practice/d08209df6f464cebafda5dfd5de03fce?tpId=240&tqId=2223567&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3DSQL%25E7%25AF%2587%26topicId%3D240

image-20250307101655126

4.7 DROP table

-- 删除流水账单表(先删子表)
DROP TABLE IF EXISTS bill;

-- 删除商品表(外键关联已被删除)
DROP TABLE IF EXISTS commodity;

-- 删除销售员表(外键关联已被删除)
DROP TABLE IF EXISTS saler;

-- 删除店铺表(最后删父表)
DROP TABLE IF EXISTS store;

DROP TABLE store;
DROP TABLE IF EXISTS exam_record_2011, exam_record_2012, exam_record_2013, exam_record_2014;

4.8 创建索引

create [unique|fulltext] index 索引名 on 表名 (列名);
alter table examination_info add [索引类型] index 索引名(列名);

#举例
create index  idx_duration on examination_info (duration);
create unique  index  uniq_idx_exam_id on examination_info (exam_id);
create full index  full_idx_tag on examination_info (tag);

空格

image-20250307102637008

4.9 删除索引

drop index uniq_idx_exam_id on  examination_info;
drop index full_idx_tag on  examination_info;

image-20250307102924702

5、细节

尽量写具体

下面的student不能写* ,可能会超时;

  • COUNT(*):它会统计查询结果集中的所有行数,不管列值是否为 NULL,也就是统计分组后的记录总数。在本题的语境下,它会统计每个课程对应的所有记录数量,不管 student 列的值是不是null。

image-20250219152406810

二、leetcode 、牛客题目

1280. 学生们参加各科测试的次数

https://leetcode.cn/problems/students-and-examinations/description/?envType=study-plan-v2&envId=sql-free-50

  • 注意使用笛卡尔积
  • 左连接**(因为有些人在左表中 不在右表中,但是最后要统计这个人的参加测试的次数为0)**
  • image-20250216185107163
  • 结果表中 主码是(student_id, subject_name ) ,group by和 order by 后面都是两个属性
# Write your MySQL query statement below
select student_id,
    student_name,
    subject_name,
    count(Examinations.subject_name) as attended_exams 
from Students cross join Subjects
left join Examinations USING (student_id, subject_name)
GROUP BY 
    student_id, 
    subject_name 
ORDER BY 
    student_id ASC, 
    subject_name ASC;

1934. 确认率

https://leetcode.cn/problems/confirmation-rate/description/?envType=study-plan-v2&envId=sql-free-50

ROUND(IFNULL(AVG(c.action='confirmed'), 0), 2) AS confirmation_rate 作用是计算名为 confirmation_rate 的字段,表示确认率。


1. AVG(c.action='confirmed')

  • 作用:计算 c.action='confirmed' 的平均值。

  • 解释

    • AVG 函数会对所有行的 c.action='confirmed' 的结果(即 10)求平均值。
    • 由于 1 表示确认,0 表示未确认,因此 AVG 的结果就是确认的比例(即确认率)。
  • 示例

    • 如果有 4 行数据,其中 3 行的 c.action'confirmed',1 行不是,则:
      AVG(c.action='confirmed') = (1 + 1 + 1 + 0) / 4 = 0.75
      

2. IFNULL(AVG(c.action='confirmed'), 0)

  • 作用:处理 AVG 结果为 NULL 的情况。

  • 解释

    • 如果 AVG(c.action='confirmed') 的结果是 NULL(例如,表中没有数据),则 IFNULL 会将其替换为 0
    • 这样可以避免在计算确认率时出现 NULL 值。
  • 示例

    • 如果表中没有数据,AVG(c.action='confirmed') 返回 NULL,则:
      IFNULL(NULL, 0) = 0
      
  1. 判断 c.action 是否等于 'confirmed',返回 10
  2. 对所有行的结果求平均值,得到确认率。
  3. 如果确认率为 NULL,则替换为 0
  4. 将确认率四舍五入到小数点后两位。
  5. 将结果命名为 confirmation_rate
SELECT
    s.user_id,
    ROUND(IFNULL(AVG(c.action='confirmed'), 0), 2) AS confirmation_rate
FROM
    Signups AS s
LEFT JOIN
    Confirmations AS c using (user_id)
GROUP BY
    user_id

1251. 平均售价

image-20250217105431047

image-20250217105437831

  • 只有ON的情况,会筛选出两条记录

image-20250217105403155

  • (1) 再WHERE就没有记录了

image-20250217105225549

--- 错误
SELECT p.product_id,
	ROUND(IFNULL(SUM(p.price * u.units) / SUM(u.units), 0), 2) AS average_price
FROM Prices as p
    LEFT JOIN UnitsSold as u 
    ON p.product_id = u.product_id
WHERE u.purchase_date BETWEEN p.start_date AND p.end_date
GROUP BY p.product_id
  • (2) AND会得出两条记录

image-20250217105308785

--- 正确
SELECT p.product_id,
    ROUND(IFNULL(SUM(p.price * u.units) / SUM(u.units), 0), 2) AS average_price
FROM Prices as p
    LEFT JOIN UnitsSold as u 
    ON p.product_id = u.product_id
AND u.purchase_date BETWEEN p.start_date AND p.end_date
GROUP BY p.product_id
  • 结果集差异:第一个查询可能会排除 Prices 表中没有在 UnitsSold 表找到匹配且满足日期范围条件的记录,结果集可能会比第二个查询少;第二个查询会保留 Prices 表的所有记录,更符合 LEFT JOIN 的语义。

image-20250216222031458

注意:

左侧: on 先左连接(注意这里会有多的null值,左侧表右悬浮元组),之后where挑选,

右侧:on 和and作为左连接的条件

  • left join时候,

    • 右 边表某属性的筛选条件建议可以在ON AND 中给出,例如本题中的u.purchase_date

    • 左 边表某属性的筛选条件建议可以在 WHERE 中给出

1633. 各赛事的用户注册率

SELECT
	contest_id,
	ROUND( COUNT( r.user_id )/ ( SELECT COUNT(*) FROM Users )* 100, 2 ) AS percentage 
FROM
	Users u
	RIGHT JOIN Register r ON u.user_id = r.user_id
    
GROUP BY
	contest_id 
ORDER BY
	percentage DESC,
	contest_id ASC

1211. 查询结果的质量和占比

AVG 和 SUM/COUNT

# Write your MySQL query statement below
SELECT query_name , 
    ROUND(AVG(rating/position),2) as quality ,
    ROUND(SUM(rating<3)/COUNT(rating)*100,2) as poor_query_percentage 
FROM Queries 
GROUP BY query_name
# Write your MySQL query statement below
SELECT query_name , 
    ROUND(SUM(rating/position)/COUNT(rating),2) as quality ,
    ROUND(SUM(rating<3)/COUNT(rating)*100,2) as poor_query_percentage 
FROM Queries 
GROUP BY query_name

1193. 每月交易 I

错误:

image-20250307105711694

  • 在 MySQL 中,SUBSTRING 的起始位置是从 1 开始,而不是 0。
  • SUM 函数不能直接嵌套子查询。
SELECT 
    SUBSTRING(trans_date, 1, 7) AS month, -- 截取年月
    country, -- 国家
    COUNT(*) AS trans_count, -- 总交易数
    SUM(state = 'approved') AS approved_count, -- 已批准交易数
    SUM(amount) AS trans_total_amount, -- 总交易金额
    SUM(IF(state = 'approved', amount, 0)) AS approved_total_amount -- 已批准交易金额
FROM 
    Transactions
GROUP BY 
    month, country; -- 按年月和国家分组

1174. 即时食物配送 II

  1. 找出每个用户的首次订单:使用 MIN() 函数结合 GROUP BY 子句找出每个用户的最早订单日期。

        (customer_id, order_date) IN (
            -- 子查询找出每个用户的首次订单
            SELECT 
                customer_id, MIN(order_date)
            FROM 
                Delivery
            GROUP BY 
                customer_id
        );
    
  2. 筛选出首次订单中的即时订单:根据首次订单日期筛选出那些期望配送日期和订单日期相同的订单。

    ROUND(SUM(IF(order_date = customer_pref_delivery_date,1,0)) 
    
  3. 计算比例:用即时订单的数量除以首次订单的总数量,得到比例。

ROUND(SUM(IF(order_date = customer_pref_delivery_date,1,0)) / COUNT(*) * 100, 2) 

这里的COUNT(*) 是WHERE筛选之后的“每个用户的首次订单”的总和

-- 计算即时订单在所有用户首次订单中的比例
SELECT 
    -- 保留两位小数
    ROUND(SUM(IF(order_date = customer_pref_delivery_date,1,0)) / COUNT(*) * 100, 2) AS immediate_percentage
FROM 
    Delivery
WHERE 
    (customer_id, order_date) IN (
        -- 子查询找出每个用户的首次订单
        SELECT 
            customer_id, MIN(order_date)
        FROM 
            Delivery
        GROUP BY 
            customer_id
    );

1141. 查询近30天活跃用户数

# 正确代码
SELECT 
    activity_date AS day,
    COUNT(DISTINCT user_id) AS active_users
FROM 
    Activity
WHERE 
    activity_date BETWEEN '2019-06-28' AND '2019-07-27'
GROUP BY 
    activity_date;
# 错误代码
# Write your MySQL query statement below
SELECT activity_date BETWEEN 2019-06-27 AND 2019-07-27 as day, COUNT(DISTINCT user_id) as active_users 
FROM Activity 
GROUP BY activity_date 
  • 日期需要用单引号括起来,像 '2019-06-27''2019-07-27' 这种形式
  • BETWEEN 通常要和 WHERE 子句一起使用,以此筛选出符合日期范围的记录。
  • 直接把 BETWEEN 放在 SELECT 语句中,这不符合 SQL 语法规则。

619. 只出现一次的最大数字

  • 注意子查询必须有别名
  • MAX(num) 如果num为null 会自动返回null ,不需要额外判断
  • MAX 函数用于返回一组值中的最大值
# Write your MySQL query statement below
SELECT MAX(num)  as num
FROM (
    SELECT num
    FROM MyNumbers 
    GROUP BY num 
    HAVING COUNT(*) =1
) as p

1045. 买下所有产品的客户

  • 不用连接
  • 查询只与第二个表的product_key 的数量有关系
  • 对每个分组进行筛选,选出哪个组的COUNT 满足组的“COUNT()=COUNT()”的组
# Write your MySQL query statement below
SELECT c.customer_id 
FROM Customer c
GROUP BY c.customer_id
HAVING COUNT(DISTINCT  product_key )=(SELECT COUNT(DISTINCT product_key) product_key FROM Product  )

1731. 每位经理的下属员工数量

  • ROUND(number, decimal_places) 当不加第二个参数的时候,第二个参数默认为0,相当于取整(向下)
  • 注意每个列名字是e1 还是 e2
# Write your MySQL query statement below
SELECT
	e2.employee_id,
	e2.NAME,
	COUNT( e1.employee_id ) AS reports_count,
	ROUND( AVG( e1.age ) ) AS average_age 
FROM
	Employees e1
    join Employees e2 on e1.reports_to = e2.employee_id 
GROUP BY
	e2.employee_id
ORDER BY
    e2.employee_id 

610. 判断三角形

case when

CASE expression
    WHEN value1 THEN result1
    WHEN value2 THEN result2
    ...
    ELSE default_result
END
SELECT 
    x,
    y,
    z,
    CASE
        WHEN x + y > z AND x + z > y AND y + z > x THEN 'Yes'
        ELSE 'No'
    END AS 'triangle'
FROM
    triangle
;

if else

IF(condition, value_if_true, value_if_false)

# Write your MySQL query statement below
# 使用IF函数嵌套and条件,判断任意两边之和大于第三边:IF( expr1 AND expr2 AND expr3, trueResult, falseResult)
SELECT
	x,
	y,
	z,
IF
	( x + y > z AND x + z > y AND y + z > x, 'Yes', 'No' ) AS triangle 
FROM
	Triangle;

180. 连续出现的数字

三表连接 自身连接 连续出现

# Write your MySQL query statement below
SELECT  DISTINCT l1.num as ConsecutiveNums 
FROM Logs l1
    join Logs l2 on  l1.num=l2.num and   l1.id=l2.id-1
    join Logs l3 on  l2.num=l3.num and l2.id=l3.id-1

1164. (重要)指定日期的产品价格

https://leetcode.cn/problems/product-price-at-a-given-date/solutions/178481/zhi-ding-ri-qi-de-chan-pin-jie-ge-by-leetcode-solu/?envType=study-plan-v2&envId=sql-free-50

  1. 找出所有的产品:
  2. 找到2019-08-16前所有有改动的产品的最新价格。
  3. 上面两步已经找到了所有的产品和已经修改过价格的产品。使用left join得到所有产品的最新价格,如果没有设置为10
select p1.product_id, ifnull(p2.new_price, 10) as price
from (
    select distinct product_id
    from products
) as p1 -- 所有的产品
left join (
    select product_id, new_price 
    from products
    where (product_id, change_date) in (
        select product_id, max(change_date)
        from products
        where change_date <= '2019-08-16'
        group by product_id  -- 这里GROUP必须在WHERE之后
    )
) as p2 -- 在 2019-08-16 之前有过修改的产品和最新的价格
on p1.product_id = p2.product_id

取出某个表的最后一行的元素 ?先找最后一行对应的某个属性的值 ,然后按照这个属性连接

1204. 最后一个能进入巴士的人

# Write your MySQL query statement below
select person_name
from(
    select person_name,sum(weight) over(order by turn) as totalweight
    from Queue
    order by turn
) as t1
where totalweight<=1000
order by totalweight desc
limit 1 -- 返回第一行
  • 在这段 SQL 代码中,ORDER BY turn 出现了两次,但它们的作用和含义是不同的,

  • SUM(weight) OVER(ORDER BY turn) AS total_weight 中的 ORDER BY turn
    • 这里的 ORDER BY turn 是窗口函数 SUM() 的窗口规范的一部分,它用于定义窗口函数的计算顺序。窗口函数 SUM(weight) OVER(ORDER BY turn) 会根据 turn 列的值对结果集进行排序,然后逐行计算 weight 列的累计总和。
  • ORDER BY turn 在子查询末尾
    • 普通的 ORDER BY 子句,用于对整个子查询的结果集进行排序。
    • 它的作用是确保子查询返回的结果按照 turn 列的值从小到大排序。

1907. 按分类统计薪水

错误解法

SELECT 
    CASE
        WHEN income< 20000 THEN 'Low Salary'
        WHEN income between 20000 and 50000 THEN 'Average Salary'
        ELSE 'High Salary'
    END AS 'category' ,
    count(account_id ) as accounts_count 
FROM
    Accounts 
group by category

image-20250220114011236

问题在于:如果某个类别在表中没有对应的记录,GROUP BY会忽略该类别,导致结果中缺少该行。

解决方案需要确保**:即使某个类别没有数据,也会显示该类别并计数为零**。

正确code

实现方法:可以使用一个包含所有三个类别的临时表或CTE,然后左连接原表的数据,这样即使没有对应的记录,也会显示类别并填充零。

  • 建立一张含有所有类的表
  • 建立一张统计“accounts_count不为0的类” 的表
  • 左连接

image-20250220121338607

WITH Categories AS ( -- 表名
    SELECT 'Low Salary' AS category
    UNION ALL
    SELECT 'Average Salary'
    UNION ALL
    SELECT 'High Salary'
)
SELECT 
    c.category, -- 全部的类别
    IFNULL(a.accounts_count, 0) AS accounts_count  -- 哪些类别在左连接之后accounts_count为null 就赋值为0
FROM Categories c
LEFT JOIN (
    SELECT 
        CASE
            WHEN income < 20000 THEN 'Low Salary'
            WHEN income BETWEEN 20000 AND 50000 THEN 'Average Salary'
            ELSE 'High Salary'
        END AS category,
        COUNT(account_id) AS accounts_count
    FROM Accounts
    GROUP BY category
) a ON c.category = a.category;

LEFT JOIN后面的一个子查询就是"错误解法"中的代码

leetcode官方解法

SELECT
	'Low Salary' AS category,
	SUM( CASE WHEN income < 20000 THEN 1 ELSE 0 END ) AS accounts_count 
FROM
	Accounts 
	
UNION

SELECT
	'Average Salary' category,
	SUM( CASE WHEN income >= 20000 AND income <= 50000 THEN 1 ELSE 0 END ) AS accounts_count 
FROM
	Accounts 

UNION

SELECT
	'High Salary' category,
	SUM( CASE WHEN income > 50000 THEN 1 ELSE 0 END ) AS accounts_count 
FROM
	Accounts

或者

select 'Low Salary' as category ,sum(income < 20000) as accounts_count 
from Accounts 

union

select 'Average Salary' as category ,sum(income between  20000 and 50000) as accounts_count 
from Accounts 

union

select 'High Salary' as category ,sum(income > 50000) as accounts_count 
from Accounts 

1978. 上级经理已离职的公司员工

image-20250220131155915

# Write your MySQL query statement below
select employee_id 
from Employees 
where salary < 30000 and manager_id not in(
    select employee_id
    from Employees 
)
order by ememployee_id
  • 这里的employee_id就是先选出行,再选出这行当中的一个属性employee_id

626. 换座位

select (
    case 
        when id%2=1 and id = (select count(distinct id) from Seat) then id
        when id%2=0 then id-1
        else id+1
    end
) as id ,
student 
from Seat
order by id

修改某个属性的值 = 实际上是在原有的基础上错位重新赋值一下 使用case when

1341. 电影评分

  • 注意使用UNION all 合并的时候 用()括起来
  • UNION 的每个部分必须是一个完整的 SELECT 语句,且不能在 UNION 的某个部分中使用 ORDER BY 和 LIMIT,除非将整个 UNION 查询包装在子查询中。
-- 第一个查询:找出评论电影数量最多的用户
(
    select name as results
    from 
    (-- 所有用户评论电影的数量
        select user_id ,count(movie_id) as cnt
        from MovieRating 
        group by user_id      
    ) as t1
    join Users u
    on u.user_id=t1.user_id
    order by t1.cnt desc,name -- 以 所有用户评论电影的数量 排序
    limit 1 -- 取第一行的数据
)
UNION all
-- 第二个查询:找出 2020 年 2 月平均评分最高的电影
(
    select title as results
    from 
    ( -- 所有电影的平均得分
        select movie_id, AVG(rating) as ratingAvg
        from MovieRating 
        where created_at  between '2020-02-01' and '2020-02-29'
        group by movie_id          
    ) as t1
    join Movies  m
    on m.movie_id = t1.movie_id    
    order by t1.ratingAvg desc,title -- 按照 所有电影的平均得分 排序
    limit 1
);

不要使用union
image.png

1667. 修复表中的名字

SELECT user_id, CONCAT(UPPER(SUBSTRING(name, 1, 1)), LOWER(SUBSTRING(name, 2))) AS name
FROM Users
ORDER BY user_id;

这段 SQL 查询的作用是从 Users 表中提取 user_id 和格式化后的 name,并按 user_id 排序。以下是逐行解释:

1. SELECT user_id, CONCAT(UPPER(SUBSTRING(name, 1, 1)), LOWER(SUBSTRING(name, 2))) AS name

  • CONCAT(...) AS name: 对 name 列进行格式化处理,并将结果命名为 name
    • UPPER(SUBSTRING(name, 1, 1)):
      • SUBSTRING(name, 1, 1): 从 name 列的第一个字符开始,提取 1 个字符(即首字母)。
      • UPPER(...): 将提取的首字母转换为大写。
    • LOWER(SUBSTRING(name, 2)):
      • SUBSTRING(name, 2): 从 name 列的第二个字符开始,提取剩余部分。
      • LOWER(...): 将剩余部分转换为小写。
    • CONCAT(...): 将大写首字母和小写剩余部分拼接起来,形成格式化后的 name

1527. 患某种疾病的患者

SELECT patient_id, patient_name, conditions
FROM Patients
WHERE conditions LIKE 'DIAB1%' OR conditions LIKE '% DIAB1%';

1321. 餐馆营业额变化增长

  • 在 ORDER BY 子句中不明确指定 ASC(升序)或 DESC(降序)时,默认按照升序排列结果

  • ROWS 6 PRECEDING :于行的偏移来定义窗口范围的。它表示窗口从当前行开始,往前取 6 行数据,再加上当前行本身,一共包含 7 行数据参与窗口函数的聚合计算。(共7行)

  • RANGE INTERVAL 6 DAY PRECEDING : 是基于值的范围来定义窗口范围的,通常用于日期或时间类型的列。从当前行的 visited_date 往前推 6 天(共7天)

select distinct visited_on,
amount,
round(amount/7,2) as average_amount 
from(
    select visited_on,  
    sum(amount) over(order by visited_on range interval 6 day preceding ) as amount
    from Customer 
) as t
where datediff(visited_on,(select min(visited_on) from Customer))>=6

585. 2016年的投资

  • 选出某列的独一无二的行。使用GROUP BY 行名 和COUNT=1
    • ( lat, lon ) IN ( SELECT lat, lon FROM Insurance GROUP BY lat, lon HAVING COUNT(*) = 1 );
  • 选出某列的行,此行至少跟其他一个行相同。使用GROUP BY 行名 和COUNT > 1
    • tiv_2015 IN ( SELECT tiv_2015 FROM Insurance GROUP BY tiv_2015 HAVING COUNT(*) > 1 )
SELECT
	ROUND( SUM( tiv_2016 ), 2 ) AS tiv_2016 
FROM
	Insurance 
	WHERE-- 筛选出在 2015 年有其他投保人也有相同总投资价值的记录
	tiv_2015 IN ( SELECT tiv_2015 FROM Insurance GROUP BY tiv_2015 HAVING COUNT(*) > 1 ) 
	AND -- 筛选出所在地点是独一无二的记录
	( lat, lon ) IN ( SELECT lat, lon FROM Insurance GROUP BY lat, lon HAVING COUNT(*) = 1 );

176. 第二高的薪水

SELECT DISTINCT Salary 
FROM Employee 
ORDER BY Salary DESC 
LIMIT 1 OFFSET 1

LIMIT OFFSET在无数据时会返回空集,但题目要求无结果时必须返回null

空结果集 不是 null

SELECT (
    SELECT DISTINCT Salary 
    FROM Employee 
    ORDER BY Salary DESC 
    LIMIT 1 OFFSET 1
) AS SecondHighestSalary;

184. 部门工资最高的员工(每个部门)

+--------------+---------+
| 列名          | 类型    |
+--------------+---------+
| id           | int     |
| name         | varchar |
| salary       | int     |
| departmentId | int     |
+--------------+---------+
在 SQL 中,id是此表的主键。
departmentId 是 Department 表中 id 的外键(在 Pandas 中称为 join key)。
此表的每一行都表示员工的 id、姓名和工资。它还包含他们所在部门的 id。
错误写法

image-20250226204102220

# Write your MySQL query statement below
select d.name as Department ,
    e.name as Employee  ,
    e.Salary 
from Employee e
    join Department d
    on e.departmentId =d.id
group by e.departmentId 
order by salary desc
正确写法

注意:新生成的列名Department 由于和表名相同,所以使用单引号

# Write your MySQL query statement below
select d.name as 'Department' ,
    e.name as 'Employee'  ,
    e.Salary 
from Employee e
    join Department d
    on e.departmentId =d.id
where (e.departmentId ,e.salary ) in   # 找出最大薪资 和  最大薪资对应的部门id,再由此推出  e.Salary   d.name
    (   
        select departmentId, max(salary)  # 可能返回两个最大的薪资 如果相同
        from  Employee 
        group by departmentId
    )

185. 部门工资前三高的所有员工

-- 从 Department 表和子查询结果中选取所需列
SELECT 
    d.name AS 'Department',
    e.name AS 'Employee',
    e.Salary
FROM (
    -- 子查询,对 Employee 表进行处理
    SELECT 
        name,
        salary,
        departmentId,
        -- 使用 DENSE_RANK() 函数对每个部门内的员工按薪资降序排名
        DENSE_RANK() OVER (PARTITION BY departmentId ORDER BY salary DESC) AS ranked
    FROM 
        Employee
) AS e
-- 通过部门 ID 关联 Employee 表和 Department 表
JOIN Department d ON e.departmentId = d.id
-- 筛选出每个部门中薪资排名前三的员工
WHERE ranked <= 3
注意下面的错误:

image-20250226230429544

在 SQL 中,**WHERE 子句不能直接使用 SELECT 子句中定义的别名(如 ranked),**因为 WHERE 子句是在 SELECT 子句之前执行的。因此,ranked 这个别名在 WHERE 子句中是不可见的。

类似好题目:

https://www.nowcoder.com/practice/255aa1863fe14aa88694c09ebbc1dbca?tpId=240&tqId=2183404&sourceUrl=%2Fexam%2Foj%3FquestionJobId%3D10%26subTabName%3Donline_coding_page

每类试卷得分前3名

image-20250303152201355

SELECT tag,uid,ranking
FROM (
    SELECT b.tag,a.uid,
    ROW_NUMBER()OVER(PARTITION BY tag ORDER BY max(a.score) DESC,min(a.score) DESC,a.uid DESC) ranking
        /*先按照最高分,再按照最低分排,最后按照uid排序 */
    FROM exam_record a 
    LEFT JOIN examination_info b ON a.exam_id=b.exam_id
    GROUP BY b.tag,a.uid)t1
WHERE ranking<=3

SQL125 得分不小于平均分的最低分

错误代码

在这里插入图片描述

WHERE 子句中不能使用聚合函数 AVG

这是因为 SQL 语句的执行顺序是先执行 FROMJOINWHERE 等子句来筛选和连接数据,之后才执行聚合函数(如 AVGSUMMIN 等)进行分组计算。

**而 WHERE 子句在聚合操作之前执行,**所以不能直接在 WHERE 子句中使用聚合函数。

正确代码

select min(e1.score) as min_score_over_avg
from exam_record e1
    join examination_info e2
    on e1.exam_id=e2.exam_id and e2.tag='SQL'
where e1.score>=(
    SELECT AVG(score)
    from exam_record e1
        join examination_info e2
        on e1.exam_id=e2.exam_id and e2.tag='SQL'
)

SQL124 统计作答次数

https://www.nowcoder.com/practice/45a87639110841b6950ef6a12d20175f?tpId=240&tqId=2180960&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3DSQL%25E7%25AF%2587%26topicId%3D240

select
    count(id) as total_pv,
    count(submit_time) as complete_pv,
    count(distinct exam_id and score is not null) as complete_exam_cnt
from exam_record

SQL126 平均活跃天数和月活人数

https://www.nowcoder.com/practice/9e2fb674b58b4f60ac765b7a37dde1b9?tpId=240&tags=&title=&difficulty=0&judgeStatus=0&rp=0&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3DSQL%25E7%25AF%2587%26topicId%3D240

202107活跃用户数
    count(distinct uid)
202107活跃天数(按照用户和时间两个约束确定天数)
    count(distinct uid,date_format(submit_time,'%y%m%d'
select date_format(submit_time, '%Y%m') as month,
       round((count(distinct uid, date_format(submit_time, '%y%m%d'))) / count(distinct uid), 2) as avg_active_days,
       count(distinct uid) as mau
from exam_record
where submit_time is not null and year(submit_time) = 2021
group by month

SQL127 月总刷题数和日均刷题数

https://www.nowcoder.com/practice/f6b4770f453d4163acc419e3d19e6746?tpId=240&tags=&title=&difficulty=0&judgeStatus=0&rp=0&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3DSQL%25E7%25AF%2587%26topicId%3D240

求每月的最大天数

先用last_day()求出当月最后一天日期,再用day()取天数

  • 因为day(last_day(submit_time)运算结果还是跟submit_time同样的一串数列,只有加上avg(),min()或max()运算才变成了一个数值作为分母使用
  • 否则会导致错误 SQL_ERROR_INFO: “Expression #3 of SELECT list is not in GROUP BY clause and contains nonaggregated column ‘practice_record.submit_time’ which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by”
  • SQL 错误信息:“SELECT 列表中的表达式 #3 不在 GROUP BY 子句中,并且包含了非聚合列‘practice_record.submit_time’,该列在功能上并不依赖于 GROUP BY 子句中的列;这与 sql_mode=only_full_group_by 不兼容。”
select 
    date_format(submit_time,'%Y%m') as submit_month,
    count(question_id) as month_q_cnt,
    round(count(question_id)/avg(day(last_day(submit_time))),3) as avg_day_q_cnt
from practice_record
where date_format(submit_time,'%Y')=2021
group by submit_month

union all

select  '2021汇总' as submit_month,
    count(question_id) as month_q_cnt,
    round(count(question_id)/31,3) as avg_day_q_cnt
from practice_record
where date_format(submit_time,'%Y')=2021
order by submit_month;

image-20250308161652324

SQL128 (重要)未完成试卷数大于1的有效用户

  • 统计为空的用户 sum(if(submit_time is null, 1, 0) 不要使用count
  • 统计不为空的用户 sum(if(submit_time is null, 0, 1))
select uid, 
	sum(if(submit_time is null, 1, 0)) as incomplete_cnt,
    sum(if(submit_time is null, 0, 1)) as complete_cnt,
    group_concat(distinct CONCAT(DATE_FORMAT(start_time, '%Y-%m-%d'),':',tag) separator ';') as detail
from exam_record er join examination_info ei on er.exam_id = ei.exam_id
where YEAR(start_time) = 2021 
group by uid
having incomplete_cnt>1 and incomplete_cnt<5 and complete_cnt >= 1
order by incomplete_cnt desc

image-20250308164508078

或者 https://blog.nowcoder.net/n/295c2f9b67cd4bff87e636a855698a30?f=comment

SELECT uid, count(incomplete) as incomplete_cnt,
    count(complete) as complete_cnt,
    group_concat(distinct concat_ws(':', date(start_time), tag) SEPARATOR ';') as detail
from (
    SELECT uid, tag, start_time,
        if(submit_time is null, 1, null) as incomplete,
        if(submit_time is null, null, 1) as complete
    from exam_record 
    left join examination_info using(exam_id)
    where year(start_time)=2021
) as exam_complete_rec
group by uid
having complete_cnt >= 1 and incomplete_cnt BETWEEN 2 and 4
order by incomplete_cnt DESC

607. 销售员

# Write your MySQL query statement below
select name
from SalesPerson
where sales_id not in(
    select o.sales_id
    from  Orders o
        join Company c on o.com_id  =c.com_id  and c.name='RED'
)

select name
from SalesPerson
where not exists (
    -- 子查询找出与RED公司有订单的销售员的ID
    select o.sales_id
    from Orders o
             join Company c on o.com_id = c.com_id and c.name = 'RED'
    -- 关联条件,判断当前SalesPerson表中的销售员ID是否在子查询结果中
    and SalesPerson.sales_id = o.sales_id
);

错 (这里要关联才对)

注意:exists筛选的是行

where not exists ( 对

where sales_id not exists( 错

# Write your MySQL query statement below
select name
from SalesPerson
where sales_id not exists(
    select o.sales_id
    from  Orders o
        join Company c on o.com_id  =c.com_id  and c.name='RED'
)

608. 树节点

select t1.id,'Root' as type
from Tree t1
where t1.p_id is null       # t1的父节点 为 空
union
select t1.id,'Inner' as type
from Tree t1
where t1.p_id is not null and t1.id in       # t1的父节点不空 且 是父节点(id在非空的p_id里)
(select p_id from Tree where p_id is not null)
union
select t1.id,'Leaf' as type
from Tree t1
where t1.p_id is not null and t1.id not in   # t1的父节点不空 且 不是父节点(id不在非空的p_id里)
(select p_id from Tree where p_id is not null);

627. 变更性别

  • update 没有on
  • case后面加sex
# Write your MySQL query statement below
update Salary 
set sex = (case sex
                when  'm' then 'f'
                else 'm'
            end)

1070. 产品销售分析 III

# Write your MySQL query statement below
select  product_id , year as first_year , quantity , price 
from Sales 
where (product_id,year) in (
    select product_id,min(year)
    from Sales
    group by product_id
)

image-20250310194954562

[坑] 原因是 group by 作用的只有分组列和聚合函数列,其他列不管用,返回的其他列只有对应的第一行

Sales 表:
+---------+------------+------+----------+-------+
| sale_id | product_id | year | quantity | price |
+---------+------------+------+----------+-------+ 
| 1       | 100        | 2008 | 10       | 5000  |
| 2       | 100        | 2009 | 12       | 5000  |
| 7       | 200        | 2011 | 15       | 9000  |
+---------+------------+------+----------+-------+

select 之后
+---------+------------+------+----------+-------+
| sale_id | product_id | year | quantity | price |
+---------+------------+------+----------+-------+ 
| 1       | 100        | 2008 | 10       | 5000  |
| 2       | 100        | 2008 | 12       | 5000  |
| 7       | 200        | 2011 | 15       | 9000  |
+---------+------------+------+----------+-------+

1179. 重新格式化部门表

https://leetcode.cn/problems/reformat-department-table/solutions/535179/group-byben-zhi-lun-by-loverxp-7mgy/

sum(case month when 'Dec' then revenue end) 这个表达式中,当 month 不等于 'Dec' 时,CASE 表达式会返回 NULL

这是因为在 CASE 语句中,如果没有匹配到 WHEN 条件,且没有 ELSE 子句时,默认返回值就是 NULLSUM 函数在计算时会忽略 NULL 值,不会将其纳入求和计算。

select id,
    sum(case month when 'Jan' then revenue end) as Jan_Revenue,
    sum(case month when 'Feb' then revenue end) as Feb_Revenue,
    sum(case month when 'Mar' then revenue end) as Mar_Revenue,
    sum(case month when 'Apr' then revenue end) as Apr_Revenue,
    sum(case month when 'May' then revenue end) as May_Revenue,
    sum(case month when 'Jun' then revenue end) as Jun_Revenue,
    sum(case month when 'Jul' then revenue end) as Jul_Revenue,
    sum(case month when 'Aug' then revenue end) as Aug_Revenue,
    sum(case month when 'Sep' then revenue end) as Sep_Revenue,
    sum(case month when 'Oct' then revenue end) as Oct_Revenue,
    sum(case month when 'Nov' then revenue end) as Nov_Revenue,
    sum(case month when 'Dec' then revenue end) as Dec_Revenue
from Department
group by id

输出的 表格中如果没有填充的数据自动为null

image-20250310201014707

SQL211 获取每个部门中薪水最高的员工相关信息

https://www.nowcoder.com/practice/4a052e3e1df5435880d4353eb18a91c6?tpId=82&tags=&title=&difficulty=0&judgeStatus=0&rp=1&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3DSQL%25E7%25AF%2587%26topicId%3D240

rank() over(partition by de.dept_no order by s.salary desc) 是在每个分组内部排序,t.rk=1每个分组内部的第一名

select t.dept_no,t.emp_no,t.salary from
(
    select 
    de.dept_no,de.emp_no,s.salary,
    rank() over(partition by de.dept_no order by s.salary desc) as rk
    from (
        select * from dept_emp where to_date='9999-01-01') de
    join
    (select * from salaries where to_date='9999-01-01') s on de.emp_no=s.emp_no
)t
where t.rk=1;

SQL232 创建一个actor表,包含如下列信息

关于创建数据表的小提示

创建数据表时,表名和字段名不需要用引号括起来。因此,下面的代码是错误的:

CREATE TABLE 'actor'(
'actor_id' smallint(5) primary key,
'first_name' varchar(45) not null,
'last_name' varchar(45) not null,
'last_update' date not null);

正确示例:

CREATE TABLE actor(
actor_id smallint(5) primary key,
first_name varchar(45) not null,
last_name varchar(45) not null,
last_update date not null);

在表上创建索引

# alter table actor add unique index uniq_idx_firstname(first_name);
# alter table actor add index idx_lastname(last_name);


或者  (注意两个索引分别创建)
    
CREATE UNIQUE INDEX uniq_idx_firstname on actor (first_name);
CREATE INDEX idx_lastname ON actor (last_name);

SQL266 牛客每个人最近的登录日期(二)

select u.name as u_n,	c.name as  c_n,	t.date
from login l
    join user u on l.user_id=u.id
    join client c on l.client_id=c.id
    join (
        select user_id,max(date) as date
        from login
        group by user_id
    ) as t on l.user_id=t.user_id and l.date=t.date
order by u_n

image-20250316152003309

SQL267 牛客每个人最近的登录日期(三)

select round(count(l2.user_id)/count( l1.user_id),3) as p
from (select user_id,min(date) first_date from login group by user_id) l1
    left join login l2  on l1.user_id=l2.user_id and DATEDIFF(l2.date,l1.first_date)=1

SQL268 牛客每个人最近的登录日期(四)

SELECT
	a.date,
	count( b.user_id ) new 
FROM
	( SELECT DISTINCT date FROM login ) a
	LEFT JOIN ( SELECT user_id, min( date ) first_date FROM login GROUP BY user_id ) b ON a.date = b.first_date 
GROUP BY
	a.date 
ORDER BY
	a.date

SQL269 牛客每个人最近的登录日期(五)

SELECT
	t0.date,
	ifnull( round( count( DISTINCT t2.user_id )/( count( t1.user_id )), 3 ), 0 ) 
FROM
	( SELECT date FROM login GROUP BY date ) t0
	LEFT JOIN ( SELECT user_id, min( date ) AS date FROM login GROUP BY user_id ) t1 ON t0.date = t1.date
	LEFT JOIN login AS t2 ON t1.user_id = t2.user_id AND datediff( t2.date, t1.date )= 1 
GROUP BY
	t0.date

image-20250316155817659

此处的group by可以避免最外侧使用distinct l.

SQL241 删除emp_no重复的记录,只保留最小的id对应的

DELETE FROM
    titles_test
WHERE
    id NOT IN (
        SELECT
            *
        FROM
            (
                SELECT
                    MIN(id)
                FROM
                    titles_test
                GROUP BY
                    emp_no
            ) a
    );

 -- 把得出的表重命名那就不是原表了(机智.jpg//  MySQL中不允许在子查询的同时删除表数据(不能一边查一边把查的表删了)

1393. 股票的资本损益

image-20250318151120538

select
    stock_name,
    sum(
        case
            when operation = 'Buy' then -price
            when operation = 'Sell' then price
        end
    ) as capital_gain_loss
from stocks
group by stock_name

行转列 union all 1795. 每个产品在不同商店的价格

表:Products

+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| product_id  | int     |
| store1      | int     |
| store2      | int     |
| store3      | int     |
+-------------+---------+
在 SQL 中,这张表的主键是 product_id(产品Id)。
每行存储了这一产品在不同商店 store1, store2, store3 的价格。
如果这一产品在商店里没有出售,则值将为 null。

请你重构 Products 表,查询每个产品在不同商店的价格,使得输出的格式变为(product_id, store, price) 。如果这一产品在商店里没有出售,则不输出这一行。

输出结果表中的 顺序不作要求

code

SELECT product_id, 'store1' AS store, store1 AS price 
FROM Products 
WHERE store1 IS NOT NULL

UNION all

SELECT product_id, 'store2' AS store, store2 AS price 
FROM Products 
WHERE store2 IS NOT NULL

UNION all

SELECT product_id, 'store3' AS store, store3 AS price 
FROM Products 
WHERE store3 IS NOT NULL

1965. 丢失信息的雇员

  • ORDER BY employee_id 是对整个 UNION 操作的结果进行排序。
# Write your MySQL query statement below
# mysql不支持满外联结,所以我们考虑用union分别查询之后拼在一起
#上面这个是缺薪水的
select e.employee_id
from employees as e left outer join salaries as s
on e.employee_id = s.employee_id
where s.salary is null


union #Union可以去重

#下面这个是缺名字的
select s.employee_id
from employees as e right outer join salaries as s
on e.employee_id = s.employee_id
where e.name is null


order by employee_id;

正则表达式

3465. 查找具有有效序列号的产品

SELECT product_id, product_name, description
FROM products
WHERE description REGEXP 'SN[0-9]{4}-[0-9]{4}([^0-9]|$)'
ORDER BY product_id ASC;
^ 表示以后面的字符为开头
[] 表示括号内任意字符
- 表示连续
* 表示重复前面任意字符任意次数
\ 用来转义后面的特殊字符,以表示字符原本的样子,而不是将其作为特殊字符使用
$ 表示以前面的字符为结尾

三、数据库课程笔记

1、 王珊老师数据库

是3NF不是BCNF 的举例

image-20250215221722736

求最小函数依赖集

(1)

https://www.bilibili.com/video/BV1sv411H7Xp/?spm_id_from=…search-card.all.click&vd_source=134104876aaac3662b320f1ea1286a16

image-20250304194058137

https://www.bilibili.com/video/BV1pE411P7ab/?spm_id_from=…search-card.all.click&vd_source=134104876aaac3662b320f1ea1286a16QQ_1741089707767

1、注意:右侧有冗余的也要删除,

比如:

  • ABD->AC,只保留ABD->C,因为ADB->A 是废话,

  • C->BE 拆分 C->B C->E ,若删除C->E 那么由C->B B->E 可以直接推出C->E

2、试着删除某个函数依赖,

比如试着删除BC->E 看是否能从其他的依赖集中推出这个关系

3、接着试着看看 BC->E 是否能分解成B->E 或者 C->E

(左边属性不为单属性的都试着拆一下)

4、好像最后还要合并试试

image-20250309190837322

(2)

image-20250309190602526

求候选码的方法

(1)L R LR N

求候选码的方法有很多,我用的是:

1.L类属性:如果属性A只在F中各个函数依赖的左端出现,则A一定是候选码中的属性。

2.N类属性:如果属性A在F的各个函数依赖中都不出现,则A一定是候选码中的属性。

3.R类属性:如果属性A只在F中各个函数依赖的右端出现,则A一定不是候选码中的属性。

4.LR类属性:如果属性A在F中各个函数依赖的左右端都出现,则A不确定是不是候选码中的属性。

先根据 函数依赖集F 和 全集U 把所有属性归为以上四类,然后先验证 L类和N类 中的单个属性,若单个属性无法决定R。再验证 L类和N类 中的组合属性,若还是无法决定R。再用L类或N类中的 属性LR类中的属性组合,直至能决定R为止。

https://blog.csdn.net/naozibuok/article/details/139326837

image-20250304150106069

image-20250304150533368

image-20250304150543987

(2)画有向图

image-20250309185748000

分解到3NF

将每个函数依赖写成每个R集合,之后检查结合当中是否含有候选码,如果没有,那么单独加上一个R (里面是候选码的属性)R6

  • 注意:左侧相同的要合并 (A->B A->C 那么R(A,B,C))
  • 注意:如果集合里面没有公刚刚求出的候选码,那么加上由候选码组成的 集合

image-20250304152402324

例题:

image-20250309192953136

image-20250309193002378

分解到BCNF

BCNF:如果BC->D ,那么箭头左边的一定包含码

https://www.bilibili.com/video/BV1eE411a79r/?spm_id_from=…search-card.all.click&vd_source=134104876aaac3662b320f1ea1286a16

image-20250304152458689

https://www.bilibili.com/video/BV1bq4y147uo/?spm_id_from=333.788.recommend_more_video.1&vd_source=134104876aaac3662b320f1ea1286a16

image-20250304160206109

无损连接:子集的至少一个集合R中含有候选码

image-20250304160144546

文章目录

  • 一、SQL语法、日期函数等
  • 1、语法
    • 1.1. join
      • left join
      • 注意:left join 的其他作用
    • 1.2 as
    • 1.3 using
      • 1. **`USING` 的语法**(注意使用括号)
      • 2. **`USING` 的作用**
    • 1.4 where VS HAVING
    • 1.5 CASE WHEN
    • 1.6 if else
    • 1.7 IFNULL
    • 1.8 OVER 窗口函数
    • 1.9 limit 常常与order by一起使用
    • 1.10 CONCAT
        • 使用示例
    • 1.11 `LIKE` 操作符
        • 功能
        • 语法
        • 使用示例 [1527. 患某种疾病的患者](https://leetcode.cn/problems/patients-with-a-condition/)
    • 1.12 RECURSIVE
        • 举例:生成 **2017 年所有日期**,并统计每周几(星期一到星期日)出现的次数
      • **关键部分解析**
        • **1. `WITH RECURSIVE` 递归 CTE**
        • **2. `SELECT` 主查询**
      • **执行过程**
        • **1. 递归生成日期**
        • **2. 统计星期几**
        • **3. 排序**
      • **示例结果**
      • **注意事项**
      • **总结**
    • 1.13 ORDER BY
      • **排序逻辑总结**
    • 1.14 LEAD
      • 语法
      • 示例
    • 1.15 DENSE_RANK RANK ROW_NUMBER
      • **三种函数的区别总结**
      • **如何选择?**
    • 1.16 尽量使用union all而 不使用union
    • 1.17 not in 遇到null的问题,使用exists改写
  • 2.算术函数
    • (1)count (注意加不加group by )
      • count sum
    • (2)date MySQL 中的日期和时间函数
      • 1. 获取当前日期和时间
      • 2. 提取日期和时间的部分
      • DAYNAME(date)
      • 3. 日期和时间的计算
    • (3)ROUND AVG
    • (4)mod
    • (5) 除法
  • 3.字符串
      • SUBSTRING
      • SUBSTRING_INDEX
  • 4、表操作
    • 4.1 insert into ,replace into
    • 4.2 update
    • 4.3 delete 删除表中的行(的数据)
    • 4.4 truncate table 删除表中数据
    • 4.5 create table
    • 4.6 alter table
    • 4.7 DROP table
    • 4.8 创建索引
    • 4.9 删除索引
  • 5、细节
    • 尽量写具体
  • 二、leetcode 、牛客题目
    • [1280. 学生们参加各科测试的次数](https://leetcode.cn/problems/students-and-examinations/)
    • [1934. 确认率](https://leetcode.cn/problems/confirmation-rate/)
      • 1. **`AVG(c.action='confirmed')`**
      • 2. **`IFNULL(AVG(c.action='confirmed'), 0)`**
    • [1251. 平均售价](https://leetcode.cn/problems/average-selling-price/)
    • [1633. 各赛事的用户注册率](https://leetcode.cn/problems/percentage-of-users-attended-a-contest/)
    • [1211. 查询结果的质量和占比](https://leetcode.cn/problems/queries-quality-and-percentage/)
      • AVG 和 SUM/COUNT
    • [1193. 每月交易 I](https://leetcode.cn/problems/monthly-transactions-i/)
    • [1174. 即时食物配送 II](https://leetcode.cn/problems/immediate-food-delivery-ii/)
    • [1141. 查询近30天活跃用户数](https://leetcode.cn/problems/user-activity-for-the-past-30-days-i/)
    • [619. 只出现一次的最大数字](https://leetcode.cn/problems/biggest-single-number/)
    • [1045. 买下所有产品的客户](https://leetcode.cn/problems/customers-who-bought-all-products/)
    • [1731. 每位经理的下属员工数量](https://leetcode.cn/problems/the-number-of-employees-which-report-to-each-employee/description/)
    • [610. 判断三角形](https://leetcode.cn/problems/triangle-judgement/)
      • case when
      • if else
    • [180. 连续出现的数字](https://leetcode.cn/problems/consecutive-numbers/)
      • 三表连接 自身连接 连续出现
    • [1164. (重要)指定日期的产品价格](https://leetcode.cn/problems/product-price-at-a-given-date/)
    • [1204. 最后一个能进入巴士的人](https://leetcode.cn/problems/last-person-to-fit-in-the-bus/)
        • `SUM(weight) OVER(ORDER BY turn) AS total_weight` 中的 `ORDER BY turn`
        • `ORDER BY turn` 在子查询末尾
    • [1907. 按分类统计薪水](https://leetcode.cn/problems/count-salary-categories/)
      • 错误解法
      • 正确code
      • leetcode官方解法
    • [1978. 上级经理已离职的公司员工](https://leetcode.cn/problems/employees-whose-manager-left-the-company/)
    • [626. 换座位](https://leetcode.cn/problems/exchange-seats/)
    • [1341. 电影评分](https://leetcode.cn/problems/movie-rating/)
    • [1667. 修复表中的名字](https://leetcode.cn/problems/fix-names-in-a-table/)
    • [1527. 患某种疾病的患者](https://leetcode.cn/problems/patients-with-a-condition/)
    • [1321. 餐馆营业额变化增长](https://leetcode.cn/problems/restaurant-growth/)
    • [585. 2016年的投资](https://leetcode.cn/problems/investments-in-2016/)
    • [176. 第二高的薪水](https://leetcode.cn/problems/second-highest-salary/)
    • [184. 部门工资最高的员工](https://leetcode.cn/problems/department-highest-salary/)(每个部门)
        • 错误写法
        • 正确写法
    • [185. 部门工资前三高的所有员工](https://leetcode.cn/problems/department-top-three-salaries/)
        • 注意下面的错误:
    • **SQL125** **得分不小于平均分的最低分**
      • 错误代码
      • 正确代码
    • **SQL124** **统计作答次数**
    • **SQL126** **平均活跃天数和月活人数**
    • **SQL127** **月总刷题数和日均刷题数**
      • 求每月的最大天数
    • **SQL128** (重要)**未完成试卷数大于1的有效用户**
    • [607. 销售员](https://leetcode.cn/problems/sales-person/)
    • [608. 树节点](https://leetcode.cn/problems/tree-node/)
    • [627. 变更性别](https://leetcode.cn/problems/swap-salary/)
    • [1070. 产品销售分析 III](https://leetcode.cn/problems/product-sales-analysis-iii/)
    • [1179. 重新格式化部门表](https://leetcode.cn/problems/reformat-department-table/)
    • **SQL211** **获取每个部门中薪水最高的员工相关信息**
    • **SQL232** **创建一个actor表,包含如下列信息**
    • 关于创建数据表的小提示
    • 在表上创建索引
    • **SQL266** **牛客每个人最近的登录日期(二)**
    • **SQL267** **牛客每个人最近的登录日期(三)**
    • **SQL268** **牛客每个人最近的登录日期(四)**
    • **SQL269** **牛客每个人最近的登录日期(五)**
    • **SQL241** **删除emp_no重复的记录,只保留最小的id对应的**
    • [1393. 股票的资本损益](https://leetcode.cn/problems/capital-gainloss/)
    • 行转列 union all [1795. 每个产品在不同商店的价格](https://leetcode.cn/problems/rearrange-products-table/)
      • code
    • [1965. 丢失信息的雇员](https://leetcode.cn/problems/employees-with-missing-information/)
    • 正则表达式
    • [3465. 查找具有有效序列号的产品](https://leetcode.cn/problems/find-products-with-valid-serial-numbers/)
  • 三、数据库课程笔记
  • 1、 王珊老师数据库
    • 是3NF不是BCNF 的举例
    • 求最小函数依赖集
      • (1)
      • (2)
    • 求候选码的方法
      • (1)L R LR N
      • (2)画有向图
    • 分解到3NF
    • 例题:
    • 分解到BCNF

http://www.dtcms.com/a/99747.html

相关文章:

  • Python 查找PDF中的指定文本并高亮显示
  • leetcode刷题日记——跳跃游戏
  • vLLM 部署 InternVL2_5
  • Gogs 精简备份与恢复方案(仅SQLite数据库和配置)
  • 逻辑回归(Logistic Regression)模型的概率预测函数
  • Gateway实战(一)、网关基本了解、配置路由案例实操
  • YOLOv8环境配置及依赖安装过程记录
  • 爬虫工程师分享自动批量化获取商品评论数据的方法有哪些?
  • Linux中CP到一半不小心kill了能恢复吗?
  • SAP SD学习笔记36 - BackOrder(延期交货订单处理)
  • Stable Diffusion太慢?国内Midjourney平替方案—商用合规部署
  • AI Agent开发大全第十四课-零售智能导购智能体的RAG开发理论部分
  • 从分布式系统架构看LPL饭圈生态:开发者视角下的博弈平衡与演化逻辑
  • 【vue】聊一聊拖拽改变DOM大小的实现
  • Redis场景问题2:缓存击穿
  • VMware笔记之windows的vmware17pro中的ubuntu22.04调整系统空间大小
  • C#里实现C#脚本单步执行的信息提示
  • 算法 之 求解有向图和无向图的环的长度
  • CSS学习笔记4——盒子模型
  • IO模型之于并发编程模型、并发模型之于架构模式
  • 破界·共生:生成式人工智能(GAI)认证重构普通人的AI进化图谱
  • SpringCould微服务架构之Docker(6)
  • 【C#】C#字符串拼接的6种方式及其性能分析对比
  • Axure项目实战:智慧运输平台后台管理端-货主管理(中继器)
  • 21 python __name__ 与 __main__
  • Java中的String类
  • 智能巡检机器人:2025年企业安全运维的“数字哨兵“
  • Vue 3 中 slot插槽的使用方法
  • 最大子序和 买股票的最佳时机|| 跳跃游戏
  • 【计算机网络】深入解析TCP/IP参考模型:从四层架构到数据封装,全面对比OSI