1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-06-16 18:10:13 +08:00
Java-Interview-Guide/docs/database/sql/sql-questions-02.md
2023-10-27 06:44:02 +08:00

451 lines
23 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: SQL常见面试题总结2
category: 数据库
tag:
- 数据库基础
- SQL
---
> 题目来源于:[牛客题霸 - SQL 进阶挑战](https://www.nowcoder.com/exam/oj?page=1&tab=SQL%E7%AF%87&topicId=240)
## 增删改操作
SQL 插入记录的方式汇总:
- **普通插入(全字段)** `INSERT INTO table_name VALUES (value1, value2, ...)`
- **普通插入(限定字段)** `INSERT INTO table_name (column1, column2, ...) VALUES (value1, value2, ...)`
- **多条一次性插入** `INSERT INTO table_name (column1, column2, ...) VALUES (value1_1, value1_2, ...), (value2_1, value2_2, ...), ...`
- **从另一个表导入** `INSERT INTO table_name SELECT * FROM table_name2 [WHERE key=value]`
- **带更新的插入** `REPLACE INTO table_name VALUES (value1, value2, ...)`(注意这种原理是检测到主键或唯一性索引键重复就删除原记录后重新插入)
### 插入记录(一)
**描述**:牛客后台会记录每个用户的试卷作答记录到 `exam_record` 表,现在有两个用户的作答记录详情如下:
- 用户 1001 在 2021 年 9 月 1 日晚上 10 点 11 分 12 秒开始作答试卷 9001并在 50 分钟后提交,得了 90 分;
- 用户 1002 在 2021 年 9 月 4 日上午 7 点 1 分 2 秒开始作答试卷 9002并在 10 分钟后退出了平台。
试卷作答记录表`exam_record`中,表已建好,其结构如下,请用一条语句将这两条记录插入表中。
| Filed | Type | Null | Key | Extra | Default | Comment |
| ----------- | ---------- | ---- | --- | -------------- | ------- | -------- |
| id | int(11) | NO | PRI | auto_increment | (NULL) | 自增 ID |
| uid | int(11) | NO | | | (NULL) | 用户 ID |
| exam_id | int(11) | NO | | | (NULL) | 试卷 ID |
| start_time | datetime | NO | | | (NULL) | 开始时间 |
| submit_time | datetime | YES | | | (NULL) | 提交时间 |
| score | tinyint(4) | YES | | | (NULL) | 得分 |
**答案**
```sql
// 存在自增主键,无需手动赋值
INSERT INTO exam_record (uid, exam_id, start_time, submit_time, score) VALUES
(1001, 9001, '2021-09-01 22:11:12', '2021-09-01 23:01:12', 90),
(1002, 9002, '2021-09-04 07:01:02', NULL, NULL);
```
### 插入记录(二)
**描述**:现有一张试卷作答记录表`exam_record`,结构如下表,其中包含多年来的用户作答试卷记录,由于数据越来越多,维护难度越来越大,需要对数据表内容做精简,历史数据做备份。
`exam_record`
| Filed | Type | Null | Key | Extra | Default | Comment |
| ----------- | ---------- | ---- | --- | -------------- | ------- | -------- |
| id | int(11) | NO | PRI | auto_increment | (NULL) | 自增 ID |
| uid | int(11) | NO | | | (NULL) | 用户 ID |
| exam_id | int(11) | NO | | | (NULL) | 试卷 ID |
| start_time | datetime | NO | | | (NULL) | 开始时间 |
| submit_time | datetime | YES | | | (NULL) | 提交时间 |
| score | tinyint(4) | YES | | | (NULL) | 得分 |
我们已经创建了一张新表`exam_record_before_2021`用来备份 2021 年之前的试题作答记录,结构和`exam_record`表一致,请将 2021 年之前的已完成了的试题作答纪录导入到该表。
**答案**
```sql
INSERT INTO exam_record_before_2021 (uid, exam_id, start_time, submit_time, score)
SELECT uid,exam_id,start_time,submit_time,score
FROM exam_record
WHERE YEAR(submit_time) < 2021;
```
### 插入记录(三)
**描述**:现在有一套 ID 为 9003 的高难度 SQL 试卷,时长为一个半小时,请你将 2021-01-01 00:00:00 作为发布时间插入到试题信息表`examination_info`,不管该 ID 试卷是否存在,都要插入成功,请尝试插入它。
试题信息表`examination_info`
| Filed | Type | Null | Key | Extra | Default | Comment |
| ------------ | ----------- | ---- | --- | -------------- | ------- | ------------ |
| id | int(11) | NO | PRI | auto_increment | (NULL) | 自增 ID |
| exam_id | int(11) | NO | UNI | | (NULL) | 试卷 ID |
| tag | varchar(32) | YES | | | (NULL) | 类别标签 |
| difficulty | varchar(8) | YES | | | (NULL) | 难度 |
| duration | int(11) | NO | | | (NULL) | 时长(分钟数) |
| release_time | datetime | YES | | | (NULL) | 发布时间 |
**答案**
```sql
REPLACE INTO examination_info VALUES
(NULL, 9003, "SQL", "hard", 90, "2021-01-01 00:00:00");
```
### 更新记录(一)
**描述**:现在有一张试卷信息表 `examination_info`, 表结构如下图所示:
| Filed | Type | Null | Key | Extra | Default | Comment |
| ------------ | -------- | ---- | --- | -------------- | ------- | -------- |
| id | int(11) | NO | PRI | auto_increment | (NULL) | 自增 ID |
| exam_id | int(11) | NO | UNI | | (NULL) | 试卷 ID |
| tag | char(32) | YES | | | (NULL) | 类别标签 |
| difficulty | char(8) | YES | | | (NULL) | 难度 |
| duration | int(11) | NO | | | (NULL) | 时长 |
| release_time | datetime | YES | | | (NULL) | 发布时间 |
请把**examination_info**表中`tag``PYTHON``tag`字段全部修改为`Python`
**思路**:这题有两种解题思路,最容易想到的是直接`update + where`来指定条件更新,第二种就是根据要修改的字段进行查找替换
**答案一**
```sql
UPDATE examination_info SET tag = 'Python' WHERE tag='PYTHON'
```
**答案二**
```sql
UPDATE examination_info
SET tag = REPLACE(tag,'PYTHON','Python')
# REPLACE (目标字段,"查找内容","替换内容")
```
### 更新记录(二)
**描述**:现有一张试卷作答记录表 exam_record其中包含多年来的用户作答试卷记录结构如下表作答记录表 `exam_record` **`submit_time`** 为 完成时间 (注意这句话)
| Filed | Type | Null | Key | Extra | Default | Comment |
| ----------- | ---------- | ---- | --- | -------------- | ------- | -------- |
| id | int(11) | NO | PRI | auto_increment | (NULL) | 自增 ID |
| uid | int(11) | NO | | | (NULL) | 用户 ID |
| exam_id | int(11) | NO | | | (NULL) | 试卷 ID |
| start_time | datetime | NO | | | (NULL) | 开始时间 |
| submit_time | datetime | YES | | | (NULL) | 提交时间 |
| score | tinyint(4) | YES | | | (NULL) | 得分 |
**题目要求**:请把 `exam_record` 表中 2021 年 9 月 1 日==之前==开始作答的==未完成==记录全部改为被动完成,即:将完成时间改为'2099-01-01 00:00:00',分数改为 0。
**思路**:注意题干中的关键字(已经高亮) `" xxx 时间 "`之前这个条件, 那么这里马上就要想到要进行时间的比较 可以直接 `xxx_time < "2021-09-01 00:00:00",` 也可以采用`date()`函数来进行比较;第二个条件就是 `"未完成"` 即完成时间为 NULL也就是题目中的提交时间 ----- `submit_time 为 NULL`
**答案**
```sql
UPDATE exam_record SET submit_time = '2099-01-01 00:00:00', score = 0 WHERE DATE(start_time) < "2021-09-01" AND submit_time IS null
```
### 删除记录(一)
**描述**:现有一张试卷作答记录表 `exam_record`,其中包含多年来的用户作答试卷记录,结构如下表:
作答记录表`exam_record` **`start_time`** 是试卷开始时间`submit_time` 是交卷,即结束时间。
| Filed | Type | Null | Key | Extra | Default | Comment |
| ----------- | ---------- | ---- | --- | -------------- | ------- | -------- |
| id | int(11) | NO | PRI | auto_increment | (NULL) | 自增 ID |
| uid | int(11) | NO | | | (NULL) | 用户 ID |
| exam_id | int(11) | NO | | | (NULL) | 试卷 ID |
| start_time | datetime | NO | | | (NULL) | 开始时间 |
| submit_time | datetime | YES | | | (NULL) | 提交时间 |
| score | tinyint(4) | YES | | | (NULL) | 得分 |
**要求**:请删除`exam_record`表中作答时间小于 5 分钟整且分数不及格(及格线为 60 分)的记录;
**思路**:这一题虽然是练习删除,仔细看确是考察对时间函数的用法,这里提及的分钟数比较,常用的函数有 **`TIMEDIFF`**和**`TIMESTAMPDIFF`** ,两者用法稍有区别,后者更为灵活,这都是看个人习惯。
1.  `TIMEDIFF`:两个时间之间的差值
```sql
TIMEDIFF(time1, time2)
```
两者参数都是必须的,都是一个时间或者日期时间表达式。如果指定的参数不合法或者是 NULL那么函数将返回 NULL。
对于这题而言,可以用在 minute 函数里面,因为 TIMEDIFF 计算出来的是时间的差值,在外面套一个 MINUTE 函数,计算出来的就是分钟数。
2. `TIMESTAMPDIFF`:用于计算两个日期的时间差
```sql
TIMESTAMPDIFF(unit,datetime_expr1,datetime_expr2)
# 参数说明
#unit: 日期比较返回的时间差单位,常用可选值如下:
SECOND:秒
MINUTE:分钟
HOUR:小时
DAY:天
WEEK:星期
MONTH:月
QUARTER:季度
YEAR:年
# TIMESTAMPDIFF函数返回datetime_expr2 - datetime_expr1的结果(人话: 后面的 - 前面的 2-1),其中datetime_expr1和datetime_expr2可以是DATE或DATETIME类型值(人话:可以是“2023-01-01”, 也可以是“2023-01-01- 00:00:00”)
```
这题需要进行分钟的比较,那么就是 TIMESTAMPDIFF(MINUTE, 开始时间, 结束时间) < 5
**答案**
```sql
DELETE FROM exam_record WHERE MINUTE (TIMEDIFF(submit_time , start_time)) < 5 AND score < 60
```
```sql
DELETE FROM exam_record WHERE TIMESTAMPDIFF(MINUTE, start_time, submit_time) < 5 AND score < 60
```
### 删除记录(二)
**描述**现有一张试卷作答记录表`exam_record`其中包含多年来的用户作答试卷记录结构如下表
作答记录表`exam_record``start_time` 是试卷开始时间`submit_time` 是交卷时间即结束时间如果未完成的话则为空
| Filed | Type | Null | Key | Extra | Default | Comment |
| ----------- | ---------- | :--: | --- | -------------- | ------- | -------- |
| id | int(11) | NO | PRI | auto_increment | (NULL) | 自增 ID |
| uid | int(11) | NO | | | (NULL) | 用户 ID |
| exam_id | int(11) | NO | | | (NULL) | 试卷 ID |
| start_time | datetime | NO | | | (NULL) | 开始时间 |
| submit_time | datetime | YES | | | (NULL) | 提交时间 |
| score | tinyint(4) | YES | | | (NULL) | 分数 |
**要求**请删除`exam_record`表中未完成作答==或==作答时间小于 5 分钟整的记录中开始作答时间最早的 3 条记录
**思路**这题比较简单但是要注意题干中给出的信息结束时间如果未完成的话则为空这个其实就是一个条件
还有一个条件就是小于 5 分钟跟上题类似但是这里是****即两个条件满足一个就行另外就是稍微考察到了排序和 limit 的用法
**答案**
```sql
DELETE FROM exam_record WHERE submit_time IS null OR TIMESTAMPDIFF(MINUTE, start_time, submit_time) < 5
ORDER BY start_time
LIMIT 3
# 默认就是asc desc是降序排列
```
### 删除记录(三)
**描述**现有一张试卷作答记录表 exam_record其中包含多年来的用户作答试卷记录结构如下表
| Filed | Type | Null | Key | Extra | Default | Comment |
| ----------- | ---------- | :--: | --- | -------------- | ------- | -------- |
| id | int(11) | NO | PRI | auto_increment | (NULL) | 自增 ID |
| uid | int(11) | NO | | | (NULL) | 用户 ID |
| exam_id | int(11) | NO | | | (NULL) | 试卷 ID |
| start_time | datetime | NO | | | (NULL) | 开始时间 |
| submit_time | datetime | YES | | | (NULL) | 提交时间 |
| score | tinyint(4) | YES | | | (NULL) | 分数 |
**要求**请删除`exam_record`表中所有记录==并重置自增主键==
**思路**这题考察对三种删除语句的区别注意高亮部分要求重置主键
- `DROP`: 清空表删除表结构不可逆
- `TRUNCATE`: 格式化表不删除表结构不可逆
- `DELETE`删除数据可逆
这里选用`TRUNCATE`的原因是TRUNCATE 只能作用于表`TRUNCATE`会清空表中的所有行但表结构及其约束索引等保持不变`TRUNCATE`会重置表的自增值使用`TRUNCATE`后会使表和索引所占用的空间会恢复到初始大小
这题也可以采用`DELETE`来做但是在删除后还需要手动`ALTER`表结构来设置主键初始值
同理也可以采用`DROP`来做直接删除整张表包括表结构然后再新建表即可
**答案**
```sql
TRUNCATE exam_record;
```
## 表与索引操作
### 创建一张新表
**描述**现有一张用户信息表其中包含多年来在平台注册过的用户信息随着牛客平台的不断壮大用户量飞速增长为了高效地为高活跃用户提供服务现需要将部分用户拆分出一张新表
原来的用户信息表
| Filed | Type | Null | Key | Default | Extra | Comment |
| ------------- | ----------- | ---- | --- | ----------------- | -------------- | -------- |
| id | int(11) | NO | PRI | (NULL) | auto_increment | 自增 ID |
| uid | int(11) | NO | UNI | (NULL) | | 用户 ID |
| nick_name | varchar(64) | YES | | (NULL) | | 昵称 |
| achievement | int(11) | YES | | 0 | | 成就值 |
| level | int(11) | YES | | (NULL) | | 用户等级 |
| job | varchar(32) | YES | | (NULL) | | 职业方向 |
| register_time | datetime | YES | | CURRENT_TIMESTAMP | | 注册时间 |
作为数据分析师**创建一张优质用户信息表 user_info_vip**表结构和用户信息表一致
你应该返回的输出如下表格所示请写出建表语句将表格中所有限制和说明记录到表里
| Filed | Type | Null | Key | Default | Extra | Comment |
| ------------- | ----------- | ---- | --- | ----------------- | -------------- | -------- |
| id | int(11) | NO | PRI | (NULL) | auto_increment | 自增 ID |
| uid | int(11) | NO | UNI | (NULL) | | 用户 ID |
| nick_name | varchar(64) | YES | | (NULL) | | 昵称 |
| achievement | int(11) | YES | | 0 | | 成就值 |
| level | int(11) | YES | | (NULL) | | 用户等级 |
| job | varchar(32) | YES | | (NULL) | | 职业方向 |
| register_time | datetime | YES | | CURRENT_TIMESTAMP | | 注册时间 |
**思路**如果这题给出了旧表的名称可直接`create table 新表 as select * from 旧表;` 但是这题并没有给出旧表名称所以需要自己创建注意默认值和键的创建即可比较简单。(注意如果是在牛客网上面执行请注意 comment 中要和题目中的 comment 保持一致包括大小写否则不通过还有字符也要设置
答案
```sql
CREATE TABLE IF NOT EXISTS user_info_vip(
id INT(11) PRIMARY KEY AUTO_INCREMENT COMMENT'自增ID',
uid INT(11) UNIQUE NOT NULL 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 '注册时间'
)CHARACTER SET UTF8
```
### 修改表
**描述** 现有一张用户信息表`user_info`其中包含多年来在平台注册过的用户信息
**用户信息表 `user_info`**
| Filed | Type | Null | Key | Default | Extra | Comment |
| ------------- | ----------- | ---- | --- | ----------------- | -------------- | -------- |
| id | int(11) | NO | PRI | (NULL) | auto_increment | 自增 ID |
| uid | int(11) | NO | UNI | (NULL) | | 用户 ID |
| nick_name | varchar(64) | YES | | (NULL) | | 昵称 |
| achievement | int(11) | YES | | 0 | | 成就值 |
| level | int(11) | YES | | (NULL) | | 用户等级 |
| job | varchar(32) | YES | | (NULL) | | 职业方向 |
| register_time | datetime | YES | | CURRENT_TIMESTAMP | | 注册时间 |
**要求**请在用户信息表字段 `level` 的后面增加一列最多可保存 15 个汉字的字段 `school`并将表中 `job` 列名改为 `profession`同时 `varchar` 字段长度变为 10`achievement` 的默认值设置为 0
**思路**首先做这题之前需要了解 ALTER 语句的基本用法
- 添加一列`ALTER TABLE 表名 ADD COLUMN 列名 类型 【first | after 字段名】;`first 在某列之前添加after 反之
- 修改列的类型或约束`ALTER TABLE 表名 MODIFY COLUMN 列名 新类型 【新约束】;`
- 修改列名`ALTER TABLE 表名 change COLUMN 旧列名 新列名 类型;`
- 删除列`ALTER TABLE 表名 drop COLUMN 列名;`
- 修改表名`ALTER TABLE 表名 rename 【to】 新表名;`
- 将某一列放到第一列`ALTER TABLE 表名 MODIFY COLUMN 列名 类型 first;`
`COLUMN` 关键字其实可以省略不写这里基于规范还是罗列出来了
在修改时如果有多个修改项可以写到一起但要注意格式
**答案**
```sql
ALTER TABLE user_info
ADD school VARCHAR(15) AFTER level,
CHANGE job profession VARCHAR(10),
MODIFY achievement INT(11) DEFAULT 0;
```
### 删除表
**描述**现有一张试卷作答记录表 `exam_record`其中包含多年来的用户作答试卷记录一般每年都会为 `exam_record` 表建立一张备份表 `exam_record_{YEAR}{YEAR}` 为对应年份
现在随着数据越来越多存储告急请你把很久前的2011 2014 备份表都删掉如果存在的话)。
**思路**这题很简单直接删就行如果嫌麻烦可以将要删除的表用逗号隔开写到一行这里肯定会有小伙伴问如果要删除很多张表呢放心如果要删除很多张表可以写脚本来进行删除
**答案**
```sql
DROP TABLE IF EXISTS exam_record_2011;
DROP TABLE IF EXISTS exam_record_2012;
DROP TABLE IF EXISTS exam_record_2013;
DROP TABLE IF EXISTS exam_record_2014;
```
### 创建索引
**描述**现有一张试卷信息表 `examination_info`其中包含各种类型试卷的信息为了对表更方便快捷地查询需要在 `examination_info` 表创建以下索引
规则如下 `duration` 列创建普通索引 `idx_duration` `exam_id` 列创建唯一性索引 `uniq_idx_exam_id` `tag` 列创建全文索引 `full_idx_tag`
根据题意将返回如下结果
| examination_info | 0 | PRIMARY | 1 | id | A | 0 | | | | BTREE |
| ---------------- | --- | ---------------- | --- | -------- | --- | --- | --- | --- | --- | -------- |
| examination_info | 0 | uniq_idx_exam_id | 1 | exam_id | A | 0 | | | YES | BTREE |
| examination_info | 1 | idx_duration | 1 | duration | A | 0 | | | | BTREE |
| examination_info | 1 | full_idx_tag | 1 | tag | | 0 | | | YES | FULLTEXT |
备注后台会通过 `SHOW INDEX FROM examination_info` 语句来对比输出结果
**思路**做这题首先需要了解常见的索引类型
- B-Tree 索引B-Tree或称为平衡树索引是最常见和默认的索引类型它适用于各种查询条件可以快速定位到符合条件的数据B-Tree 索引适用于普通的查找操作支持等值查询范围查询和排序
- 唯一索引唯一索引与普通的 B-Tree 索引类似不同之处在于它要求被索引的列的值是唯一的这意味着在插入或更新数据时MySQL 会验证索引列的唯一性
- 主键索引主键索引是一种特殊的唯一索引它用于唯一标识表中的每一行数据每个表只能有一个主键索引它可以帮助提高数据的访问速度和数据完整性
- 全文索引全文索引用于在文本数据中进行全文搜索它支持在文本字段中进行关键字搜索而不仅仅是简单的等值或范围查找全文索引适用于需要进行全文搜索的应用场景
```sql
-- 示例:
-- 添加B-Tree索引
CREATE INDEX idx_name(索引名) ON 表名 (字段名); -- idx_name为索引名以下都是
-- 创建唯一索引:
CREATE UNIQUE INDEX idx_name ON 表名 (字段名);
-- 创建一个主键索引:
ALTER TABLE 表名 ADD PRIMARY KEY (字段名);
-- 创建一个全文索引
ALTER TABLE 表名 ADD FULLTEXT INDEX idx_name (字段名);
-- 通过以上示例可以看出create 和 alter 都可以添加索引
```
有了以上的基础知识之后该题答案也就浮出水面了
**答案**
```sql
ALTER TABLE examination_info
ADD INDEX idx_duration(duration),
ADD UNIQUE INDEX uniq_idx_exam_id(exam_id),
ADD FULLTEXT INDEX full_idx_tag(tag);
```
### 删除索引
**描述**请删除`examination_info`表上的唯一索引 uniq_idx_exam_id 和全文索引 full_idx_tag
**思路**该题考察删除索引的基本语法
```sql
-- 使用 DROP INDEX 删除索引
DROP INDEX idx_name ON 表名;
-- 使用 ALTER TABLE 删除索引
ALTER TABLE employees DROP INDEX idx_email;
```
这里需要注意的是 MySQL 一次删除多个索引的操作是不支持的每次删除索引时只能指定一个索引名称进行删除
而且 **DROP** 命令需要慎用
**答案**
```sql
DROP INDEX uniq_idx_exam_id ON examination_info;
DROP INDEX full_idx_tag ON examination_info;
```
<!-- @include: @article-footer.snippet.md -->