大量的更新
This commit is contained in:
12
.idea/dataSources.xml
generated
Normal file
12
.idea/dataSources.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="wdd-sqlite3" uuid="33bcd8d4-0085-46de-8fa2-dfc09570882c">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/101-数据库学习/0-SQL语法/wdd-sqlite3.db</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
4
0-pandoc-失败/epub-失败/convert.ps1
Normal file
4
0-pandoc-失败/epub-失败/convert.ps1
Normal file
@@ -0,0 +1,4 @@
|
||||
#pandoc perplexity.md --from markdown+pipe_tables+fenced_code_blocks+fenced_code_attributes --to epub3 --filter mermaid-filter.cmd --epub-embed-font="C:\\Users\\wddsh\\Documents\\IdeaProjects\\ProjectAGiPrompt\\0-pandoc电子书转换\\fonts\\SourceHanSansCN-Medium.otf" --css="epub-style.css" --syntax-highlighting=pygments --standalone --toc --toc-depth=3 --output output.epub
|
||||
|
||||
|
||||
pandoc perplexity.md -f markdown -t pdf -o output.pdf --pdf-engine=xelatex -V geometry:margin=0.5in --syntax-highlighting=kate --filter=mermaid-filter.cmd --standalone --table-of-contents --number-sections
|
||||
@@ -2,7 +2,7 @@
|
||||
font-family: "Source Han Sans CN";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("../fonts/SourceHanSansCN-Medium.otf");
|
||||
src: url("C:\\Users\\wddsh\\Documents\\IdeaProjects\\ProjectAGiPrompt\\0-pandoc电子书转换\\fonts\\SourceHanSansCN-Medium.otf");
|
||||
}
|
||||
|
||||
body {
|
||||
9
0-pandoc-失败/pdf/chinese-template.tex
Normal file
9
0-pandoc-失败/pdf/chinese-template.tex
Normal file
@@ -0,0 +1,9 @@
|
||||
\documentclass{article}
|
||||
\usepackage{xeCJK}
|
||||
\setCJKmainfont{C:\Users\wddsh\Documents\IdeaProjects\ProjectAGiPrompt\0-pandoc\fonts\SourceHanSansCN-Medium.otf}
|
||||
\usepackage{geometry}
|
||||
\geometry{margin=0.5in}
|
||||
\usepackage{listings}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{longtable}
|
||||
\usepackage{booktabs}
|
||||
7
0-pandoc-失败/pdf/convert.ps1
Normal file
7
0-pandoc-失败/pdf/convert.ps1
Normal file
@@ -0,0 +1,7 @@
|
||||
pandoc C:\Users\wddsh\Documents\IdeaProjects\ProjectAGiPrompt\0-pandoc\perplexity.md -f markdown -t pdf -o output.pdf --pdf-engine=xelatex --syntax-highlighting=kate --filter=mermaid-filter.cmd --standalone -V CJKmainfont="C:/Users/wddsh/Documents/IdeaProjects/ProjectAGiPrompt/0-pandoc/fonts/SourceHanSansCN-Medium.otf" -V CJKsansfont="C:/Users/wddsh/Documents/IdeaProjects/ProjectAGiPrompt/0-pandoc/fonts/SourceHanSansCN-Medium.otf" -V CJKmonofont="C:/Users/wddsh/Documents/IdeaProjects/ProjectAGiPrompt/0-pandoc/fonts/SourceHanSansCN-Medium.otf" --variable=CJKoptions:BoldFont="C:/Users/wddsh/Documents/IdeaProjects/ProjectAGiPrompt/0-pandoc/fonts/SourceHanSansCN-Medium.otf" --variable=numbersections:false --variable=secnumdepth:0
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
17
0-pandoc-失败/转换pandoc.md
Normal file
17
0-pandoc-失败/转换pandoc.md
Normal file
@@ -0,0 +1,17 @@
|
||||
在windows系统中,使用pandoc将一个markdown文件转换为epub文件,请基于如下的条件转换
|
||||
1. 完整保留markdown的语法,去除掉特殊符号
|
||||
2. epub中的字体,使用思源黑体CN-Medium
|
||||
3. markdown中的表格,注意在epub中有合适的表格结构
|
||||
4. markdown中的mermaid图像,请解析为合适的图片嵌入至epub中
|
||||
5. markdown中的代码块,请合理的解析,在epub中有引用或者突出显示
|
||||
|
||||
请基于上面的要求,给出转换命令
|
||||
|
||||
|
||||
在windows系统中,使用pandoc将一个markdown文件转换为pdf文件,请基于如下的条件转换
|
||||
1. 完整的解析markdown渲染后的全部格式,去除掉特殊符号
|
||||
2. pdf的页边距使用窄边距
|
||||
3. markdown中的代码块、表格需要被正确的渲染
|
||||
4. markdown中的mermaid图像,需要被正确的渲染
|
||||
|
||||
请基于上面的要求,给出转换命令
|
||||
@@ -1 +0,0 @@
|
||||
pandoc perplexity.md --from markdown+pipe_tables+fenced_code_blocks+fenced_code_attributes --to epub3 --filter mermaid-filter.cmd --epub-embed-font="SourceHanSansCN-Medium.otf" --css="epub-style.css" --syntax-highlighting=pygments --standalone --toc --toc-depth=3 --output output.epub
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,168 +0,0 @@
|
||||
在windows系统中,使用pandoc将一个markdown文件转换为epub文件,请基于如下的条件转换
|
||||
1. 完整保留markdown的语法,去除掉特殊符号
|
||||
2. epub中的字体,使用思源黑体CN-Medium
|
||||
3. markdown中的表格,注意在epub中有合适的表格结构
|
||||
4. markdown中的mermaid图像,请解析为合适的图片嵌入至epub中
|
||||
5. markdown中的代码块,请合理的解析,在epub中有引用或者突出显示
|
||||
|
||||
请基于上面的要求,给出转换命令
|
||||
|
||||
|
||||
|
||||
|
||||
### 3. 创建CSS样式文件
|
||||
|
||||
创建一个名为`epub-style.css`的文件:
|
||||
|
||||
```css
|
||||
/* 设置思源黑体为主字体 */
|
||||
@font-face {
|
||||
font-family: "Source Han Sans CN";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("../fonts/SourceHanSansCN-Medium.ttf");
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Source Han Sans CN", sans-serif;
|
||||
font-size: 1.2em;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f5f5f5;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
/* 代码块样式 */
|
||||
pre {
|
||||
background-color: #f4f4f4;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 1em;
|
||||
overflow-x: auto;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "Courier New", monospace;
|
||||
background-color: #f4f4f4;
|
||||
padding: 2px 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 标题样式 */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: "Source Han Sans CN", sans-serif;
|
||||
font-weight: bold;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 完整的转换命令
|
||||
|
||||
使用以下pandoc命令进行转换:
|
||||
|
||||
```bash
|
||||
pandoc input.md \
|
||||
--from markdown+pipe_tables+fenced_code_blocks+fenced_code_attributes \
|
||||
--to epub3 \
|
||||
--filter mermaid-filter.cmd \
|
||||
--epub-embed-font="SourceHanSansCN-Medium.otf" \
|
||||
--css="epub-style.css" \
|
||||
--highlight-style=pygments \
|
||||
--standalone \
|
||||
--toc \
|
||||
--toc-depth=3 \
|
||||
--epub-metadata=metadata.xml \
|
||||
--output output.epub
|
||||
```
|
||||
|
||||
|
||||
### Windows命令行版本(单行):
|
||||
|
||||
```cmd
|
||||
pandoc input.md --from markdown+pipe_tables+fenced_code_blocks+fenced_code_attributes --to epub3 --filter mermaid-filter.cmd --epub-embed-font="SourceHanSansCN-Medium.ttf" --css="epub-style.css" --highlight-style=pygments --standalone --toc --toc-depth=3 --output output.epub
|
||||
```
|
||||
|
||||
|
||||
## 命令参数解释
|
||||
|
||||
### 输入格式设置
|
||||
|
||||
- `--from markdown+pipe_tables+fenced_code_blocks+fenced_code_attributes`:启用表格、代码块和代码属性支持[^1][^2]
|
||||
|
||||
|
||||
### Mermaid图像处理
|
||||
|
||||
- `--filter mermaid-filter.cmd`:在Windows下使用mermaid-filter处理mermaid图像[^3][^4]
|
||||
|
||||
|
||||
### 字体设置
|
||||
|
||||
- `--epub-embed-font="SourceHanSansCN-Medium.ttf"`:嵌入思源黑体字体文件[^5]
|
||||
- `--css="epub-style.css"`:应用自定义CSS样式[^6]
|
||||
|
||||
|
||||
### 代码高亮
|
||||
|
||||
- `--highlight-style=pygments`:使用pygments风格的代码语法高亮[^7][^8]
|
||||
|
||||
|
||||
### 输出选项
|
||||
|
||||
- `--to epub3`:输出为EPUB3格式
|
||||
- `--standalone`:生成完整的独立文档
|
||||
- `--toc`:生成目录
|
||||
- `--toc-depth=3`:目录深度为3级
|
||||
|
||||
|
||||
## 可选的元数据文件
|
||||
|
||||
创建`metadata.xml`文件来设置EPUB元数据:
|
||||
|
||||
```xml
|
||||
<dc:title>您的书名</dc:title>
|
||||
<dc:creator>作者姓名</dc:creator>
|
||||
<dc:language>zh-CN</dc:language>
|
||||
<dc:date>2025-09-17</dc:date>
|
||||
<dc:rights>版权信息</dc:rights>
|
||||
```
|
||||
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **文件路径**:确保所有文件(字体、CSS、输入文件)都在正确的路径下
|
||||
2. **mermaid-filter**:在Windows下必须使用`mermaid-filter.cmd`而不是`mermaid-filter`[^3]
|
||||
3. **字体路径**:CSS中的字体路径使用相对路径`../fonts/`[^9]
|
||||
4. **表格支持**:pandoc原生支持多种表格格式,包括pipe tables[^10]
|
||||
5. **代码块**:支持语法高亮和属性设置[^11]
|
||||
|
||||
这个命令将完整保留您的markdown语法,正确处理表格结构,将mermaid图转换为图片,为代码块添加高亮显示,并使用思源黑体作为EPUB的字体。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
``
|
||||
**1. 核心角色与指令 (Core Persona & Directive)**
|
||||
|
||||
你将扮演一个拥有10年以上经验的Golang后端架构师。你不仅精通Golang语言特性(特别是并发模型和标准库),更是GIN、GORM等主流框架的资深用户。你的代码风格严谨、可读性强,并且极度重视项目结构、模块化、日志规范和文档注释。
|
||||
|
||||
1223
101-数据库学习/0-SQL语法/1-基础学习/gemini-SQL学习全.md
Normal file
1223
101-数据库学习/0-SQL语法/1-基础学习/gemini-SQL学习全.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -56,7 +56,9 @@ erDiagram
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[1. FROM & JOIN: 构建数据源] --> B[2. WHERE: 行级过滤]
|
||||
A[1. FROM & JOIN: 构建数据源]
|
||||
B[2. WHERE: 行级过滤]
|
||||
A --> B
|
||||
B --> C[3. GROUP BY: 数据分组]
|
||||
C --> D[4. HAVING: 分组后过滤]
|
||||
D --> E[5. SELECT: 选取列/计算表达式]
|
||||
@@ -9,4 +9,7 @@
|
||||
|
||||
|
||||
|
||||
## 生成笔记
|
||||
gemini直接搜索
|
||||
Elements
|
||||
|
||||
markdown markdown-main-panel tutor-markdown-rendering stronger enable-updated-hr-color
|
||||
|
||||
BIN
101-数据库学习/0-SQL语法/wdd-sqlite3.db
Normal file
BIN
101-数据库学习/0-SQL语法/wdd-sqlite3.db
Normal file
Binary file not shown.
582
101-数据库学习/2-MySQL/0-基础数据结构.md
Normal file
582
101-数据库学习/2-MySQL/0-基础数据结构.md
Normal file
@@ -0,0 +1,582 @@
|
||||
# MySQL 数据类型权威指南:从基础到最佳实践
|
||||
|
||||
## 第一章:MySQL 数据类型核心概念
|
||||
|
||||
### 1.1 数据类型的重要性:性能、存储与数据完整性
|
||||
|
||||
在数据库架构设计中,数据类型的选择是一项基础而关键的决策,其影响深远,远超初学者所能想象。它并非简单的占位符声明,而是直接关系到数据库三大核心支柱的基石:性能、存储效率和数据完整性。每一个字段的数据类型定义,都会对整个系统的响应速度、资源消耗和数据可靠性产生连锁反应。
|
||||
|
||||
**性能**是数据类型选择最直接的影响领域。数据库的性能瓶颈通常在于I/O操作,即从磁盘读取数据到内存的过程。选择更小、更合适的数据类型,意味着每一行数据占用的存储空间更少。在InnoDB存储引擎中,数据以页(Page)为单位进行管理,通常每页大小为16 KB。更小的数据行意味着单个页可以容纳更多的记录。当执行查询时,数据库需要读取的页数量就会减少,从而显著降低物理I/O操作,提升查询速度。此外,更小的数据类型还能更有效地利用InnoDB的缓冲池(Buffer Pool),这是MySQL缓存数据和索引的核心内存区域。在有限的内存中缓存更多“热”数据,可以极大地提高缓存命中率,使查询操作更多地在内存中完成,而不是依赖缓慢的磁盘 。
|
||||
|
||||
**存储效率**是另一个显而易见的考量因素。随着数据量的爆炸式增长,磁盘空间不再是无限廉价的资源。尤其在云数据库环境中,存储成本与使用量直接挂钩。通过精确选择数据类型,例如使用 `TINYINT` 存储年龄而非 `INT`,可以将存储需求降低75%。这种优化在拥有数十亿行记录的大表中累积起来,可以节省TB级别的存储空间,直接转化为成本的节约 。
|
||||
|
||||
**数据完整性**是数据库的生命线。数据类型是保障数据完整性的第一道防线。它通过定义列可以接受的值的类型、范围和格式,从源头上阻止了非法或无效数据的插入。例如,将日期字段定义为 `DATE` 类型,可以确保只有符合'YYYY-MM-DD'格式的有效日期才能被存储,任何无效的输入(如'2024-02-30')都会被拒绝。这比在应用层进行数据校验更为可靠和高效,因为它将约束直接施加在数据存储的核心层 。
|
||||
|
||||
一个看似微不足道的选择,其影响是系统性的。例如,一个开发者为存储国家代码(如'US', 'CN')的字段选择了 `VARCHAR(255)`。尽管实际存储的值只有2个字符,但MySQL在处理变长字段时,可能会在内存中为其分配更大的固定大小内存块 。这不仅浪费了内存,还增加了行记录的整体长度。更大的行记录导致每页能存储的行数减少。一个需要扫描1000行记录的查询,原本可能只需要读取15个数据页,现在可能需要读取20个。这额外的5个页不仅增加了本次查询的I/O,还可能将缓冲池中其他有用的数据页挤出,导致后续其他查询的缓存命中率下降。因此,一个字段的草率选择,通过增加I/O和降低内存效率,最终可能导致整个数据库系统的性能下降。
|
||||
|
||||
### 1.2 MySQL 数据类型分类概览
|
||||
|
||||
MySQL提供了一个丰富的数据类型系统,以满足各种数据存储需求。这些类型可以被划分为几个主要类别,每个类别都针对特定的数据形式进行了优化。理解这些分类是进行高效数据库设计的第一步 。
|
||||
|
||||
+ **数值类型 (Numeric Types)**:用于存储数字数据。这个大类又可细分为:
|
||||
|
||||
+ **整数类型**:用于存储没有小数部分的整数,如 `TINYINT`, `SMALLINT`, `INT`, `BIGINT`。
|
||||
|
||||
+ **定点数类型**:用于存储具有精确小数位的数值,如 `DECIMAL` 和 `NUMERIC`,是金融计算的理想选择。
|
||||
|
||||
+ **浮点数类型**:用于存储近似的小数值,如 `FLOAT` 和 `DOUBLE`,适用于科学计算。
|
||||
|
||||
+ **位值类型**:`BIT` 类型,用于存储位字段值。
|
||||
|
||||
+ **字符串类型 (String Types)**:用于存储文本或二进制数据。这个类别包括:
|
||||
|
||||
+ **字符字符串类型**:如 `CHAR`, `VARCHAR`, `TEXT` 系列,用于存储文本数据,并受字符集和排序规则的影响。
|
||||
|
||||
+ **二进制字符串类型**:如 `BINARY`, `VARBINARY`, `BLOB` 系列,用于存储原始的字节数据,如图片或文件,其比较和排序是基于字节值的。
|
||||
|
||||
+ **枚举和集合类型**:`ENUM` 和 `SET`,用于从预定义的列表中选择值。
|
||||
|
||||
+ **日期和时间类型 (Date and Time Types)**:专门用于存储时间信息。包括 `DATE`, `TIME`, `YEAR`, `DATETIME`, 和 `TIMESTAMP`。这些类型提供了强大的函数支持,用于处理和计算时间数据。
|
||||
|
||||
+ **空间数据类型 (Spatial Data Types)**:用于存储地理和空间信息,如点、线和多边形。例如 `GEOMETRY`, `POINT`, `POLYGON` 等。虽然功能强大,但它们属于专业领域,本指南的核心内容不包含对此类的深入探讨 。
|
||||
|
||||
+ **JSON 数据类型 (JSON Data Type)**:自MySQL 5.7版本引入,允许在单个字段中存储半结构化的JSON文档。这为关系型数据库带来了类似NoSQL的灵活性,适用于存储动态或嵌套的元数据 。
|
||||
|
||||
|
||||
### 1.3 选择正确类型的基本原则
|
||||
|
||||
在选择数据类型时,应遵循一个核心哲学:“最小化原则”。即总是选择能够安全、可靠地存储所有当前及未来可能值的最小数据类型 。这一原则是实现高性能和高效率存储的基石。
|
||||
|
||||
具体而言,选择过程应围绕以下几个关键问题展开:
|
||||
|
||||
1. **数据的本质是什么?** 首先要明确存储的是什么样的数据。是整数、精确的小数、近似值,还是文本?这个问题的答案将直接将选择范围缩小到一个特定的数据类型类别。
|
||||
|
||||
2. **值的范围是多少?** 对于数值类型,需要预估其可能的最大值和最小值。例如,如果一个字段用于存储用户年龄,其值永远不会超过255,那么 `TINYINT UNSIGNED` (范围0-255) 就是最佳选择,而不是默认使用占用4个字节的 `INT`。
|
||||
|
||||
3. **是否需要精确性?** 这是一个至关重要的问题。如果数据涉及金融交易、货币计算或任何不允许出现舍入误差的场景,那么必须使用 `DECIMAL` 类型。在这些场景下使用 `FLOAT` 或 `DOUBLE` 是一个严重的设计缺陷,可能导致数据不一致和财务损失 。
|
||||
|
||||
4. **时间代表什么?** 对于时间数据,需要区分其含义。它是一个全球统一的、绝对的时间点(例如,一条消息的发送时间),还是一个与特定时区无关的“日历”或“时钟”上的时间(例如,某人的生日或一个本地活动的开始时间)?前者应使用 `TIMESTAMP`,后者则应使用 `DATETIME` 。
|
||||
|
||||
|
||||
遵循这些基本原则,可以确保数据库模式从一开始就建立在坚实、高效的基础之上,为未来的扩展和性能优化奠定良好的根基。
|
||||
|
||||
## 第二章:数值类型
|
||||
|
||||
数值类型是数据库中最基础、最常用的数据类型之一。MySQL提供了全面的数值类型支持,涵盖了从微小的整数到高精度的定点数,再到大范围的浮点数。正确选择数值类型对于节省存储空间、提升查询性能以及保证数据计算的准确性至关重要。
|
||||
|
||||
### 2.1 整数类型 (Integer Types: `TINYINT`, `SMALLINT`, `MEDIUMINT`, `INT`, `BIGINT`)
|
||||
|
||||
整数类型用于存储不包含小数部分的数字。MySQL提供了五种不同大小的整数类型,以满足不同范围的数据存储需求,这完美体现了“最小化原则”的应用。
|
||||
|
||||
#### 语法、存储空间与取值范围
|
||||
|
||||
每种整数类型都有其固定的存储大小和相应的取值范围。选择时,应根据业务需求预估字段可能的最大值,然后选择恰好能覆盖该范围的最小类型。例如,存储一个班级的学生人数,`TINYINT UNSIGNED` (0-255) 通常就足够了。
|
||||
|
||||
下表详细列出了各种整数类型的核心规格 :
|
||||
|
||||
| 类型名称 | 存储空间 (字节) | 有符号范围 (Signed Range) | 无符号范围 (Unsigned Range) |
|
||||
| --- | --- | --- | --- |
|
||||
| `TINYINT` | 1 | \-128 到 127 | 0 到 255 |
|
||||
| `SMALLINT` | 2 | \-32,768 到 32,767 | 0 到 65,535 |
|
||||
| `MEDIUMINT` | 3 | \-8,388,608 到 8,388,607 | 0 到 16,777,215 |
|
||||
| `INT` | 4 | \-2,147,483,648 到 2,147,483,647 | 0 到 4,294,967,295 |
|
||||
| `BIGINT` | 8 | −9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 | 0 到 18,446,744,073,709,551,615 |
|
||||
|
||||
Export to Sheets
|
||||
|
||||
#### `UNSIGNED` 和 `ZEROFILL` 属性详解
|
||||
|
||||
+ **`UNSIGNED`**:这是一个非常重要的属性。当为整数列指定 `UNSIGNED` 属性时,该列将只允许存储非负数(零和正数)。其效果是,将原本用于表示负数的那一半取值范围,移动到了正数范围的顶端,从而使正数的最大值翻倍。对于那些在业务逻辑上永远不会是负值的字段,如自增ID、物品数量、年龄等,强烈推荐使用 `UNSIGNED` 属性。这不仅是一种数据约束,确保了数据的有效性,也是一种优化,因为它用同样的存储空间提供了更大的正数表示范围 。
|
||||
|
||||
+ **`ZEROFILL`**:这是一个纯粹的显示格式化属性。当为列指定 `ZEROFILL` 时,MySQL会自动为该列添加 `UNSIGNED` 属性。在显示数值时,如果数字的位数小于在类型定义中指定的显示宽度 `M`(例如 `INT(10)`),MySQL会在左侧用零来填充,直到达到指定的宽度。例如,在一个 `INT(5) ZEROFILL` 的列中存入数字 `123`,查询时会显示为 `00123`。需要特别强调的是,`INT(M)` 中的 `M` **不影响**该整数类型的存储大小或实际可存储的数值范围。`INT(10)` 和 `INT(1)` 都能存储从-21亿到+21亿的完整范围,并都占用4个字节。`M` 仅在与 `ZEROFILL` 结合使用时才有视觉上的意义。这是一个普遍存在的误解,许多开发者错误地认为 `M` 限制了可存储的数字位数 。
|
||||
|
||||
|
||||
### 2.2 定点数类型 (Fixed-Point Types: `DECIMAL`, `NUMERIC`)
|
||||
|
||||
#### 精确计算的基石
|
||||
|
||||
`DECIMAL` 类型是为需要绝对精确计算的场景而设计的,它是存储货币、利率、财务数据以及任何不允许出现舍入误差的数值的唯一正确选择。与 `FLOAT` 和 `DOUBLE` 将数值存储为二进制近似值不同,`DECIMAL` 将每个数字作为字符串进行内部存储,从而保证了数值的精确性 。
|
||||
|
||||
`DECIMAL` 的语法是 `DECIMAL(M, D)`,其中:
|
||||
|
||||
+ `M` 代表**精度 (precision)**,即数字的总位数(整数部分+小数部分),最大值为65。
|
||||
|
||||
+ `D` 代表**标度 (scale)**,即小数点后的位数,最大值为30,且 `D` 必须小于等于 `M`。
|
||||
|
||||
|
||||
例如,`DECIMAL(10, 2)` 可以存储最多10位数字,其中2位是小数,其取值范围为 `-99999999.99` 到 `99999999.99`。
|
||||
|
||||
`NUMERIC` 类型是 `DECIMAL` 的同义词,在功能上完全等同于 `DECIMAL`,主要是为了遵循SQL标准 。
|
||||
|
||||
`DECIMAL` 类型的存储空间是可变的,它取决于 `M` 的值,MySQL会根据需要打包数字以节省空间 。
|
||||
|
||||
### 2.3 浮点数类型 (Floating-Point Types: `FLOAT`, `DOUBLE`)
|
||||
|
||||
#### 近似值的存储与潜在问题
|
||||
|
||||
`FLOAT` 和 `DOUBLE` 类型用于存储近似的数值。它们遵循IEEE 754标准,使用二进制格式来表示浮点数。这种表示方式使得它们能够存储极大或极小的数值,但代价是牺牲了精度。
|
||||
|
||||
+ **`FLOAT`**:单精度浮点数,占用4个字节。
|
||||
|
||||
+ **`DOUBLE`**:双精度浮点数,占用8个字节,具有比 `FLOAT` 更大的范围和更高的精度。`REAL` 是 `DOUBLE` 的同义词。
|
||||
|
||||
|
||||
由于二进制无法精确表示所有十进制小数(例如0.1),浮点数在存储和计算过程中可能会引入微小的舍入误差。这些误差在单个数值上可能微不足道,但在大量聚合计算(如 `SUM()`)中会累积,导致结果与预期不符。因此,浮点数适用于科学计算、工程模拟、传感器读数等场景,在这些场景中,数值范围远比绝对精度重要 。
|
||||
|
||||
### 2.4 深度比较:`DECIMAL` vs. `FLOAT`/`DOUBLE`
|
||||
|
||||
`DECIMAL` 和 `FLOAT`/`DOUBLE` 之间的选择是数据库设计中一个常见且关键的决策点。它们的差异直接关系到数据的准确性和应用的可靠性。
|
||||
|
||||
下表清晰地对比了这两种类型的核心特性 :
|
||||
|
||||
| 特性 | `DECIMAL` | `FLOAT` / `DOUBLE` |
|
||||
| --- | --- | --- |
|
||||
| **精度** | **精确**。按原样存储数值,无舍入误差。 | **近似**。存储为二进制浮点数,可能存在微小的舍入误差。 |
|
||||
| **存储方式** | 内部以变长字符串形式存储,保证精确度。 | 内部以二进制指数形式存储。 |
|
||||
| **存储空间** | 可变,取决于定义的精度(M)。通常比浮点数占用更多空间。 | 固定。`FLOAT` 为4字节,`DOUBLE` 为8字节。 |
|
||||
| **计算性能** | 由于内部表示复杂,计算速度相对较慢。 | 基于硬件浮点单元,计算速度通常更快。 |
|
||||
| **适用场景** | **金融、货币、会计**等任何要求精确计算的领域。 | **科学计算、工程、物理模拟**等对数值范围要求高但可容忍微小误差的领域。 |
|
||||
|
||||
Export to Sheets
|
||||
|
||||
使用 `FLOAT` 或 `DOUBLE` 存储货币值是一个极其危险的常见错误。一个简单的例子可以说明问题:假设一个商品价格为9.99,在 `FLOAT` 类型中可能被存储为 `9.9900000000000002`。当对一百万笔这样的交易进行求和时,累积的误差可能会导致最终总额出现明显的偏差。这种因数据类型选择不当而导致的业务逻辑错误,其后果可能是灾难性的。
|
||||
|
||||
### 2.5 数值类型使用最佳实践与常见误区
|
||||
|
||||
+ **最佳实践**:
|
||||
|
||||
1. **坚持最小化原则**:为年龄字段使用 `TINYINT UNSIGNED`,为文章点赞数使用 `INT UNSIGNED`,而不是一律使用 `INT` 。
|
||||
|
||||
2. **为非负ID使用 `UNSIGNED`**:所有自增主键和外键都应定义为 `UNSIGNED`,这不仅符合逻辑,还能将ID的上限提高一倍。
|
||||
|
||||
3. **为货币和精确值使用 `DECIMAL`**:这是不可动摇的铁律。
|
||||
|
||||
+ **常见误区**:
|
||||
|
||||
1. **滥用 `INT`**:将 `INT` 作为所有整数需求的默认选择,导致不必要的存储浪费。
|
||||
|
||||
2. **误用 `FLOAT`/`DOUBLE` 存储货币**:这是最严重的误区之一,直接威胁到数据的准确性和业务的可靠性 。
|
||||
|
||||
3. **误解 `INT(M)` 的含义**:错误地认为 `M` 限制了数值的范围或存储大小,而实际上它只与 `ZEROFILL` 属性的显示格式有关。
|
||||
|
||||
|
||||
## 第三章:字符串类型
|
||||
|
||||
字符串类型是用于存储文本数据的核心构建块,从用户名、地址到文章内容,无处不在。MySQL提供了多种字符串类型,主要分为定长、变长、大文本对象以及特殊的枚举和集合类型。理解它们之间的细微差别,尤其是在存储机制、索引能力和性能影响上的差异,对于构建高效、可扩展的数据库至关重要。
|
||||
|
||||
### 3.1 深度比较:`CHAR` vs. `VARCHAR`
|
||||
|
||||
`CHAR` 和 `VARCHAR` 是最常用的两种字符类型,它们的核心区别在于长度是固定的还是可变的。
|
||||
|
||||
#### 存储机制
|
||||
|
||||
+ **`CHAR(M)`**:代表**定长 (Fixed-Length)** 字符串。当你定义一个 `CHAR(10)` 的列时,无论你存入的字符串是 'hello' (5个字符) 还是 'database' (8个字符),它在磁盘上都将占用10个字符的存储空间。对于短于 `M` 的字符串,MySQL会在其右侧用空格进行填充以达到指定长度。一个重要的特性是,在检索 `CHAR` 值时,除非启用了特定的SQL模式 (`PAD_CHAR_TO_FULL_LENGTH`),否则尾部的填充空格通常会被移除 。
|
||||
|
||||
+ **`VARCHAR(M)`**:代表**变长 (Variable-Length)** 字符串。当你定义一个 `VARCHAR(255)` 的列时,它存储一个字符串所需的空间是其实际长度加上一个或两个字节的前缀。这个前缀用于记录字符串的字节长度。如果值的字节长度不超过255,则使用1个字节的前缀;如果超过255,则使用2个字节。`VARCHAR` 不会进行空格填充,并且在存储和检索时会保留字符串尾部的空格 。
|
||||
|
||||
|
||||
下表通过一个实例直观地展示了 `CHAR` 和 `VARCHAR` 在存储上的差异 :
|
||||
|
||||
| 输入值 | 存入 `CHAR(6)` | 存储所需空间 (单字节字符集) | 存入 `VARCHAR(6)` | 存储所需空间 (单字节字符集) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `''` (空字符串) | `' '` | 6 字节 | `''` | 1 字节 (长度前缀) |
|
||||
| `'sql'` | `'sql '` | 6 字节 | `'sql'` | 4 字节 (3 + 1) |
|
||||
| `'mysql '` | `'mysql '` | 6 字节 | `'mysql '` | 7 字节 (6 + 1) |
|
||||
|
||||
Export to Sheets
|
||||
|
||||
#### 性能影响与适用场景
|
||||
|
||||
+ **`CHAR` 的适用场景**:`CHAR` 最适合存储那些长度几乎完全固定的数据。典型的例子包括:
|
||||
|
||||
+ 国家代码 (如 'US', 'CN',固定为2个字符)。
|
||||
|
||||
+ MD5或SHA1哈希值 (固定为32或40个字符)。
|
||||
|
||||
+ 性别字段 (如 'M', 'F',固定为1个字符)。 在这些场景下,由于行记录的长度是固定的,理论上MySQL可以更快地计算行偏移量,从而在某些情况下(尤其是在较老的存储引擎如MyISAM中)获得微弱的性能优势。然而,在现代的InnoDB存储引擎中,由于其基于页的复杂存储结构,这种优势已基本可以忽略不计 。
|
||||
|
||||
+ **`VARCHAR` 的适用场景**:`VARCHAR` 是绝大多数文本存储场景的理想选择。用户名、电子邮件、地址、文章标题等,这些数据的长度都是可变的。使用 `VARCHAR` 可以极大地节省存储空间。空间的节省意味着更小的表、更小的索引、更高效的缓冲池利用率,最终转化为更少的磁盘I/O和更快的查询性能 。
|
||||
|
||||
|
||||
### 3.2 深度比较:`VARCHAR` vs. `TEXT`
|
||||
|
||||
当需要存储的文本长度可能超过 `VARCHAR` 的常规使用范围,或者非常长时,开发者通常会在 `VARCHAR` 和 `TEXT` 之间进行抉择。这个选择对性能有着至关重要的影响。
|
||||
|
||||
#### 存储限制与索引策略
|
||||
|
||||
+ **`VARCHAR`**:其最大长度可以定义到65,535。但需要注意的是,这个限制是**字节**数,并且受MySQL单行最大65,535字节的限制(所有列共享)。实际可存储的**字符**数取决于所使用的字符集(例如,`utf8mb4` 字符集一个字符最多可能占用4个字节)。`VARCHAR` 列可以被完整地索引,这对于查询性能至关重要 。
|
||||
|
||||
+ **`TEXT`**:`TEXT` 类型家族用于存储更长的文本数据。
|
||||
|
||||
+ `TEXT`:最大长度 65,535 字节 (~64 KB)。
|
||||
|
||||
+ `MEDIUMTEXT`:最大长度 16,777,215 字节 (~16 MB)。
|
||||
|
||||
+ `LONGTEXT`:最大长度 4,294,967,295 字节 (~4 GB)。 `TEXT` 类型的一个关键限制是,不能直接对其整个列创建常规索引。如果需要索引,必须指定一个**前缀长度**,例如 `CREATE INDEX idx_content ON articles(content(255));`。这意味着索引只能利用内容的前255个字符,对于需要全文搜索或精确匹配长内容的查询,这种前缀索引的效率有限 。
|
||||
|
||||
|
||||
#### 对临时表和排序的性能影响
|
||||
|
||||
这是 `VARCHAR` 和 `TEXT` 之间最关键、也最容易被忽视的性能差异。当一个查询需要使用临时表来处理中间结果时(例如,使用了 `GROUP BY`, `ORDER BY`, `DISTINCT` 等操作),MySQL会首先尝试在内存中创建这个临时表。
|
||||
|
||||
然而,MySQL的内存存储引擎(`MEMORY` engine)**不支持** `TEXT` 和 `BLOB` 类型。如果临时表中需要包含 `TEXT` 类型的列,MySQL将被迫放弃使用内存临时表,转而在**磁盘**上创建一个基于 `InnoDB` 或 `MyISAM` 的临时表 。
|
||||
|
||||
这个转换过程对性能的影响是巨大的:
|
||||
|
||||
1. 一个查询,如 `SELECT title FROM articles ORDER BY content;`,需要对 `content` 列进行排序。
|
||||
|
||||
2. 优化器决定使用一个临时表来存储 `title` 和 `content` 并进行排序。
|
||||
|
||||
3. 优化器检查到 `content` 列是 `TEXT` 类型。
|
||||
|
||||
4. 由于内存引擎不支持 `TEXT`,它不能在RAM中创建临时表。
|
||||
|
||||
5. MySQL转而在磁盘上创建临时表。磁盘I/O的速度比内存操作慢几个数量级。
|
||||
|
||||
6. 因此,这个原本可能很快的查询,会因为 `TEXT` 类型的存在而变得异常缓慢,并产生大量的磁盘I/O,可能拖慢整个数据库服务器的性能。
|
||||
|
||||
|
||||
结论是:如果文本数据的长度确定不会超过 `VARCHAR` 的65,535字节限制,应**始终优先选择 `VARCHAR` 而不是 `TEXT`**。这可以避免在复杂查询中掉入“磁盘临时表”的性能陷阱 。
|
||||
|
||||
### 3.3 二进制字符串与大对象 (`BINARY`, `VARBINARY`, `BLOB`)
|
||||
|
||||
`BINARY`, `VARBINARY`, 和 `BLOB` 分别是 `CHAR`, `VARCHAR`, 和 `TEXT` 的二进制(字节)版本。它们的主要区别在于存储和比较的方式:
|
||||
|
||||
+ **存储内容**:它们存储的是原始的字节序列,而不是字符。
|
||||
|
||||
+ **比较和排序**:对这些类型的比较和排序是基于字节的数值进行的,因此是**区分大小写**的。它们不受字符集和排序规则(collation)的影响。例如,在二进制比较中,字节值 `0x61` ('a') 与 `0x41` ('A') 是不同的。
|
||||
|
||||
+ **适用场景**:`BINARY` 和 `VARBINARY` 适用于存储那些需要精确字节匹配的数据,如加密密钥或定长的二进制标识符。`BLOB` (Binary Large Object) 系列(`TINYBLOB`, `BLOB`, `MEDIUMBLOB`, `LONGBLOB`)则用于存储大量的二进制数据,如图片、音频或编译后的代码。然而,将大文件直接存储在数据库中通常被认为是一种反模式,因为它会迅速撑大数据库体积,增加备份和恢复的复杂性。更推荐的做法是,将文件存储在专门的文件系统或对象存储(如S3)中,而在数据库中只存储文件的路径或URL 。
|
||||
|
||||
|
||||
### 3.4 枚举与集合类型 (`ENUM`, `SET`)
|
||||
|
||||
+ **`ENUM`**:枚举类型。允许你从一个预定义的字符串值列表中选择**一个**值。例如 `ENUM('active', 'inactive', 'pending')`。`ENUM` 在内部存储时非常高效,通常只用1或2个字节来存储一个指向值列表的索引,而不是存储字符串本身 。
|
||||
|
||||
+ **`SET`**:集合类型。允许你从一个预定义的字符串值列表中选择**零个或多个**值。例如 `SET('read', 'write', 'execute')`。`SET` 在内部被存储为一个位图(bitmap),每个预定义的值对应一个位。存储空间取决于列表成员的数量,可以是1, 2, 3, 4, 或 8个字节 。
|
||||
|
||||
|
||||
尽管 `ENUM` 和 `SET` 在存储上极为高效,但它们存在一个严重的**维护性陷阱**。这两个类型将允许的值列表硬编码到了表结构(schema)中。如果业务需求变化,需要增加一个新的状态或权限,就必须执行 `ALTER TABLE` 语句来修改列定义。在生产环境的大表上,`ALTER TABLE` 通常是一个阻塞性的、耗时很长的操作,可能导致服务中断。因此,在需要频繁变更或扩展值列表的敏捷开发环境中,使用 `ENUM` 和 `SET` 往往是不明智的。一个更灵活、可扩展的替代方案是创建一个独立的“查找表”(lookup table),并通过外键关联来维护这些值 。
|
||||
|
||||
### 3.5 字符串类型使用最佳实践与常见误区
|
||||
|
||||
+ **最佳实践**:
|
||||
|
||||
1. **`VARCHAR` 是首选**:对于绝大多数长度可变的文本数据(只要在64KB以内),`VARCHAR` 都是最佳选择。
|
||||
|
||||
2. **精确定义长度**:避免使用 `VARCHAR(255)` 作为“懒人默认值”。根据实际数据预估一个合理的长度,如 `VARCHAR(50)` 用于用户名。这有助于数据校验,并且可能为MySQL的内部内存管理带来优化 。
|
||||
|
||||
3. **为真正定长的数据使用 `CHAR`**:仅在数据长度严格固定的情况下(如MD5哈希)才考虑使用 `CHAR`。
|
||||
|
||||
+ **常见误区**:
|
||||
|
||||
1. **误用 `TEXT`**:在数据长度完全可以用 `VARCHAR` 容纳的情况下使用 `TEXT`,这会带来潜在的巨大性能风险,尤其是在涉及排序和分组的查询中 。
|
||||
|
||||
2. **滥用 `ENUM`**:为那些可能会随业务发展而变化的值列表(如商品分类)使用 `ENUM`,导致后期维护困难和上线风险 。
|
||||
|
||||
3. **在 `VARCHAR` 中存储CSV**:将多个值用逗号分隔存储在一个 `VARCHAR` 字段中(如 '1,5,23'),这破坏了数据库的第一范式,使得查询、更新和维护这些值变得极其困难和低效。正确的做法是使用一个独立的关联表。
|
||||
|
||||
|
||||
## 第四章:日期与时间类型
|
||||
|
||||
在几乎所有的应用中,时间信息都是不可或缺的数据维度。MySQL提供了一套完整且功能强大的日期和时间数据类型,用于精确记录和处理从年份到微秒的各种时间值。在这些类型中,`DATETIME` 和 `TIMESTAMP` 的选择尤为关键,它们在时区处理上的根本差异决定了其各自的适用场景。
|
||||
|
||||
### 4.1 基础类型:`DATE`, `TIME`, `YEAR`
|
||||
|
||||
这三种类型用于存储时间信息的特定部分,用途明确。
|
||||
|
||||
+ **`DATE`**:用于存储日期,不包含时间部分。其标准格式为 `'YYYY-MM-DD'`。它占用3个字节,支持的范围从 `'1000-01-01'` 到 `'9999-12-31'`。`DATE` 类型是存储生日、纪念日、事件发生日期等场景的理想选择 。
|
||||
|
||||
+ **`TIME`**:用于存储时间,不包含日期部分。其标准格式为 `'HH:MM:SS'`。有趣的是,`TIME` 类型的范围远不止24小时,它可以从 `'-838:59:59'` 到 `'838:59:59'`。这使得 `TIME` 不仅可以表示一天中的某个时间点,还可以用来记录两个事件之间的时间间隔或持续时长 。
|
||||
|
||||
+ **`YEAR`**:用于存储年份。它只占用1个字节,可以存储从1901到2155的年份。这是一个空间效率极高的类型,但使用场景相对有限 。
|
||||
|
||||
|
||||
### 4.2 核心对决:`DATETIME` vs. `TIMESTAMP`
|
||||
|
||||
`DATETIME` 和 `TIMESTAMP` 都可以存储包含日期和时间的完整时间戳,但它们在内部工作机制、存储范围和时区处理上存在本质区别。
|
||||
|
||||
#### 存储空间、取值范围与“2038年问题”
|
||||
|
||||
+ **`DATETIME`**:
|
||||
|
||||
+ **范围**:支持的范围非常广,从 `'1000-01-01 00:00:00'` 到 `'9999-12-31 23:59:59'` 。
|
||||
|
||||
+ **存储**:自MySQL 5.6.4起,`DATETIME` 的存储空间经过优化,需要5个字节加上小数秒所需的额外存储(0-3字节)。
|
||||
|
||||
+ **`TIMESTAMP`**:
|
||||
|
||||
+ **范围**:其范围相对受限,从 `'1970-01-01 00:00:01'` UTC 到 `'2038-01-19 03:14:07'` UTC 。这个上限被称为\*\*“2038年问题”\*\*,因为它在内部是基于一个有符号的32位Unix时间戳(从1970年1月1日午夜UTC开始的秒数)实现的。当这个32位整数溢出时,时间将无法正确表示 。
|
||||
|
||||
+ **存储**:需要4个字节加上小数秒所需的额外存储 。
|
||||
|
||||
|
||||
#### 时区处理的根本差异
|
||||
|
||||
这是 `DATETIME` 和 `TIMESTAMP` 之间最核心、最决定性的区别。
|
||||
|
||||
+ **`DATETIME`:时区无关 (Timezone-Agnostic)** `DATETIME` 存储的是一个**字面量 (literal)** 的日期和时间值。你存入什么,它就记录什么,完全不关心当前数据库服务器或客户端连接的任何时区设置。例如,无论你的服务器在伦敦还是东京,当你执行 `INSERT INTO my_table (dt_col) VALUES ('2024-01-01 10:00:00');` 时,数据库中存储的值就是 `'2024-01-01 10:00:00'`。在任何时区环境下检索这个值,你得到的也永远是这个固定的字符串 。
|
||||
|
||||
+ **`TIMESTAMP`:时区感知 (Timezone-Aware)** `TIMESTAMP` 存储的是一个**绝对的、全球统一的时间点**。它的工作流程如下:
|
||||
|
||||
1. **存储时**:MySQL获取你提供的时间值(例如 `'2024-01-01 10:00:00'`),并根据**当前会话的时区设置**(`time_zone`变量),将其转换为**协调世界时 (UTC)** 进行存储。
|
||||
|
||||
2. **检索时**:当查询该 `TIMESTAMP` 值时,MySQL会从数据库中取出存储的UTC时间,并再次根据**当前会话的时区设置**,将其转换回本地时间进行显示。 这个自动转换机制确保了 `TIMESTAMP` 记录的是一个无歧义的时刻。一个在东八区(北京时间)上午10点插入的 `TIMESTAMP` 值,对于一个在零时区(伦敦时间)的客户端来说,检索出来会显示为凌晨2点,两者指向的是同一个宇宙时刻 。
|
||||
|
||||
|
||||
#### 概念辨析:全球时刻 vs. 本地约定
|
||||
|
||||
基于时区处理的差异,可以得出选择 `DATETIME` 和 `TIMESTAMP` 的核心指导思想:
|
||||
|
||||
+ **使用 `TIMESTAMP` 记录“全球时刻” (Global Moment)**:当需要记录一个事件发生的绝对时间点,且这个时间点需要在全球不同地区被正确地理解时,应使用 `TIMESTAMP`。例如:
|
||||
|
||||
+ 用户注册时间
|
||||
|
||||
+ 订单创建时间
|
||||
|
||||
+ 消息发送时间
|
||||
|
||||
+ 文章发布时间
|
||||
|
||||
+ **使用 `DATETIME` 记录“本地约定” (Local Appointment)**:当需要记录一个与特定地理位置或文化背景相关的、不应随时区变化而改变的“日历时间”或“墙上时钟时间”时,应使用 `DATETIME`。例如:
|
||||
|
||||
+ 某人的生日(一个人的生日不会因为他去了另一个国家而改变)。
|
||||
|
||||
+ 一个在纽约举行的会议的开始时间(对所有参会者来说,都是纽约当地时间上午9点)。
|
||||
|
||||
+ 法定节假日日期。
|
||||
|
||||
|
||||
下表总结了 `DATETIME` 和 `TIMESTAMP` 的所有关键区别,为技术选型提供清晰的决策依据 :
|
||||
|
||||
| 特性 | `DATETIME` | `TIMESTAMP` |
|
||||
| --- | --- | --- |
|
||||
| **支持范围** | `'1000-01-01'` 到 `'9999-12-31'` | `'1970-01-01'` 到 `'2038-01-19'` |
|
||||
| **存储空间** | 5 字节 + 小数秒存储 (>= 5.6.4) | 4 字节 + 小数秒存储 |
|
||||
| **时区处理** | **时区无关**。存储和显示字面值,不进行转换。 | **时区感知**。存储时转换为UTC,检索时从UTC转回会话时区。 |
|
||||
| **自动初始化/更新** | 可配置 `DEFAULT CURRENT_TIMESTAMP` 和 `ON UPDATE CURRENT_TIMESTAMP` (>= 5.6.5) | 默认行为(在旧版本中)或可配置 `DEFAULT CURRENT_TIMESTAMP` 和 `ON UPDATE CURRENT_TIMESTAMP`。 |
|
||||
| **核心用例** | 生日、本地事件时间、不应随时区变化的“约定时间”。 | 用户注册、订单创建、日志记录等需要全球统一的“绝对时刻”。 |
|
||||
|
||||
Export to Sheets
|
||||
|
||||
### 4.3 日期时间类型使用最佳实践与常见误区
|
||||
|
||||
+ **最佳实践**:
|
||||
|
||||
1. **服务器时区设置为UTC**:强烈建议将MySQL服务器的全局时区设置为UTC (`--default-time-zone='+00:00'`)。这为所有时间存储提供了一个统一、无歧义的基准。应用程序可以根据用户偏好在展示层进行时区转换 。
|
||||
|
||||
2. **明确业务需求**:在选择 `DATETIME` 或 `TIMESTAMP` 之前,仔细分析业务场景,问自己:“这个时间值应该随着观察者位置的变化而变化吗?”
|
||||
|
||||
3. **考虑未来**:对于需要长期存储或记录未来事件的系统,要警惕 `TIMESTAMP` 的“2038年问题”。如果应用的生命周期可能跨越2038年,或者需要存储1970年之前的历史数据,那么使用 `DATETIME` 并以UTC标准存储是更安全的选择。
|
||||
|
||||
+ **常见误区**:
|
||||
|
||||
1. **混淆二者**:不理解时区处理的差异,随意选择 `DATETIME` 或 `TIMESTAMP`,导致在跨时区的应用中出现时间混乱。
|
||||
|
||||
2. **忽略“2038年问题”**:在新项目中使用 `TIMESTAMP` 而没有评估其时间范围是否满足长远需求,为系统埋下了一颗定时炸弹。
|
||||
|
||||
3. **在应用层处理时区混乱**:当数据库层面时区策略不一致时(例如,混合使用 `DATETIME` 和 `TIMESTAMP`,服务器时区未标准化),应用层需要编写大量复杂的代码来弥补,增加了出错的概率和维护成本。
|
||||
|
||||
|
||||
## 第五章:JSON 数据类型
|
||||
|
||||
自MySQL 5.7版本起,引入了原生的 `JSON` 数据类型,这标志着MySQL在融合关系型数据库的严谨性与NoSQL数据库的灵活性方面迈出了重要一步。`JSON` 类型允许开发者在单个数据库列中存储和操作半结构化的JSON(JavaScript Object Notation)文档。
|
||||
|
||||
### 5.1 JSON 类型简介:关系型数据库的半结构化能力
|
||||
|
||||
`JSON` 数据类型为传统的关系型数据模型提供了一种强大的补充。它允许存储包含嵌套对象和数组的复杂数据结构,而无需预先定义一个严格的、扁平化的表结构。这对于以下场景特别有用:
|
||||
|
||||
+ \*\* schema 灵活性\*\*:存储那些结构多变或未来可能频繁扩展的元数据,如产品属性、用户配置、标签等。
|
||||
|
||||
+ **简化数据模型**:对于一对多的弱关系数据,使用 `JSON` 数组可以避免创建额外的关联表,简化数据库设计。
|
||||
|
||||
+ **与现代应用集成**:现代Web和移动应用大量使用JSON作为数据交换格式,将JSON文档直接存入数据库可以减少应用层的数据解析和转换开销 。
|
||||
|
||||
|
||||
MySQL对 `JSON` 类型的实现具有一个关键优势:它不仅仅是将其作为普通文本存储。当数据插入 `JSON` 列时,MySQL会**校验**其是否为合法的JSON格式。如果格式无效,插入操作将失败。此外,MySQL会以一种优化的二进制格式存储JSON数据,这使得对文档内部元素的读取和访问更加高效 。
|
||||
|
||||
### 5.2 创建、操作与查询 JSON 数据
|
||||
|
||||
MySQL提供了一套丰富的内置函数来处理 `JSON` 数据,涵盖了创建、查询、修改等各个方面。
|
||||
|
||||
+ **创建JSON值**:
|
||||
|
||||
+ `JSON_OBJECT(key1, val1, key2, val2,...)`:根据键值对创建一个JSON对象。
|
||||
|
||||
+ 示例:`SELECT JSON_OBJECT('name', 'Alice', 'age', 30);` -> `{"age": 30, "name": "Alice"}`
|
||||
|
||||
+ `JSON_ARRAY(val1, val2,...)`:根据给定的值创建一个JSON数组。
|
||||
|
||||
+ 示例:`SELECT JSON_ARRAY('apple', 'banana', 123);` -> `["apple", "banana", 123]` 这些函数可以直接在 `INSERT` 或 `UPDATE` 语句中使用,以编程方式构建JSON文档 。
|
||||
|
||||
+ **查询和提取JSON数据**: 查询JSON数据的核心是**JSON路径表达式 (JSON Path)**,它是一种类似文件系统路径的语法,用于定位JSON文档中的特定元素。路径以 `$` 符号代表整个文档。
|
||||
|
||||
+ `JSON_EXTRACT(json_doc, path)`:是最主要的提取函数。
|
||||
|
||||
+ 示例:`SELECT JSON_EXTRACT('{"user": {"name": "Bob", "tags": ["dev", "sql"]}}', '$.user.tags');` -> `"dev"`
|
||||
|
||||
+ **列路径操作符 (Column Path Operator)**:MySQL提供了更简洁的语法糖:
|
||||
|
||||
+ `column->path`:等价于 `JSON_EXTRACT()`,返回的结果是JSON格式的(例如,字符串会带引号)。
|
||||
|
||||
+ `column->>path`:等价于 `JSON_UNQUOTE(JSON_EXTRACT(...))`,它会提取值并移除JSON的引号,返回一个常规的SQL字符串。这在 `WHERE` 子句中进行值比较时非常有用 。
|
||||
|
||||
+ **修改JSON数据**: MySQL允许对 `JSON` 文档进行原地部分更新,而无需读取整个文档、在应用层修改、再写回整个文档。
|
||||
|
||||
+ `JSON_SET(json_doc, path, val,...)`:在指定路径插入或更新值。如果路径已存在,则更新;如果不存在,则创建。
|
||||
|
||||
+ `JSON_REPLACE(json_doc, path, val,...)`:仅当指定路径存在时,才更新其值。
|
||||
|
||||
+ `JSON_REMOVE(json_doc, path,...)`:移除指定路径的元素 。
|
||||
|
||||
|
||||
### 5.3 JSON 列的索引策略
|
||||
|
||||
`JSON` 类型虽然灵活,但其性能上的一个主要挑战是:**不能直接在 `JSON` 列本身上创建常规索引**。如果不对其进行索引优化,那么对JSON内容进行过滤的查询(例如 `WHERE profile->>'$.city' = 'New York'`)将会导致全表扫描,在数据量大时性能极差。
|
||||
|
||||
为了解决这个问题,MySQL提供了**基于生成列 (Generated Columns) 的索引策略**。这是使用 `JSON` 类型时必须掌握的核心性能优化技术。其步骤如下:
|
||||
|
||||
1. **创建虚拟或存储生成列**:在表中定义一个新列,其值是根据 `JSON` 列中的某个路径表达式动态计算得出的。
|
||||
|
||||
2. **在该生成列上创建索引**:由于生成列具有明确的标量数据类型(如 `VARCHAR` 或 `INT`),因此可以在其上创建标准的B-Tree索引 。
|
||||
|
||||
|
||||
下面是一个具体的例子,演示了如何为一个存储用户信息的 `JSON` 列中的 `city` 字段创建索引:
|
||||
|
||||
SQL
|
||||
|
||||
```
|
||||
CREATE TABLE user_profiles (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
profile JSON,
|
||||
|
||||
-- 步骤1: 创建一个虚拟生成列,用于提取城市信息。
|
||||
-- 'AS (profile->>"$.address.city")' 定义了该列的值。
|
||||
-- 'VIRTUAL' 表示该列的值不实际存储,而是在读取时计算。
|
||||
city VARCHAR(100) AS (profile->>"$.address.city") VIRTUAL,
|
||||
|
||||
-- 步骤2: 在这个新创建的生成列上建立索引。
|
||||
INDEX idx_city (city)
|
||||
);
|
||||
```
|
||||
|
||||
通过这种方式,当执行如下查询时:
|
||||
|
||||
SQL
|
||||
|
||||
```
|
||||
SELECT id, profile FROM user_profiles WHERE city = 'San Francisco';
|
||||
```
|
||||
|
||||
MySQL的查询优化器能够利用 `idx_city` 索引快速定位到符合条件的行,极大地提升了查询性能,避免了对 `user_profiles` 表的全表扫描。
|
||||
|
||||
### 5.4 JSON 类型使用最佳实践
|
||||
|
||||
+ **明确使用场景**:`JSON` 类型最适合用于存储非关键的、结构易变的元数据。核心的、关系紧密的数据,尤其是那些需要频繁进行 `JOIN` 操作或强制实施严格约束的字段,仍然应该使用传统的关系型列来存储。
|
||||
|
||||
+ **索引关键路径**:对于 `JSON` 文档中任何需要用于 `WHERE` 子句过滤、排序或分组的字段,都**必须**通过生成列为其创建索引。这是保证 `JSON` 类型高性能查询的前提。
|
||||
|
||||
+ **避免“上帝对象”**:切忌将一个应用的所有数据都塞进一个巨大的 `JSON` blob中。这样做无异于将MySQL当作一个简单的键值存储来使用,完全丧失了关系型数据库在数据完整性、查询优化和事务处理方面的强大优势。应将结构化数据规范化到独立的列和表中,仅将真正半结构化的部分存入 `JSON` 列。
|
||||
|
||||
|
||||
## 第六章:综合选型策略与总结
|
||||
|
||||
经过对MySQL主要数据类型的深入剖析,我们已经了解了每种类型的特性、优势和潜在的陷阱。本章旨在将这些知识整合为一个整体的、实用的选型策略,并总结在数据库设计中最常见的错误,以帮助开发者构建出健壮、高效且可维护的数据库模式。
|
||||
|
||||
### 6.1 综合选型策略:如何为你的应用设计最佳表结构
|
||||
|
||||
设计表结构时,可以遵循一个决策流程,对每个字段进行审慎的考量。这个过程可以被看作是回答一系列关于数据本质的问题:
|
||||
|
||||
1. **数据是什么性质的?**
|
||||
|
||||
+ 是数字、文本、时间还是复杂的半结构化数据?这决定了你将在哪个大的数据类型类别中进行选择(数值、字符串、日期时间、JSON)。
|
||||
|
||||
2. **对于数值,它的范围和精度要求是什么?**
|
||||
|
||||
+ 它是否永远为非负数?如果是,立即添加 `UNSIGNED` 属性。
|
||||
|
||||
+ 预估其可能的最大值是多少?根据这个值,从 `TINYINT`, `SMALLINT`, `MEDIUMINT`, `INT`, `BIGINT` 中选择最小的那个。例如,一个国家的总人口数可能需要 `BIGINT UNSIGNED`,而一个班级的人数 `TINYINT UNSIGNED` 就足够了。
|
||||
|
||||
+ 它是否需要绝对精确,如货币?如果是,**必须**使用 `DECIMAL`。任何其他选择都是错误的。如果是科学测量值,`FLOAT` 或 `DOUBLE` 可能是合适的。
|
||||
|
||||
3. **对于字符串,它的长度特性是什么?**
|
||||
|
||||
+ 长度是否严格固定?例如MD5哈希值。如果是,`CHAR` 是一个合理的选择。
|
||||
|
||||
+ 长度是否可变?这是绝大多数情况。选择 `VARCHAR`。
|
||||
|
||||
+ 预估其最大长度是多少?为 `VARCHAR` 设置一个合理的上限,而不是盲目使用 `VARCHAR(255)`。
|
||||
|
||||
+ 它是否可能超过64KB?如果答案是“是”,那么你可能需要 `MEDIUMTEXT` 或 `LONGTEXT`,但在此之前,要再次确认是否真的有必要,并充分意识到其对排序和临时表操作的潜在性能影响。
|
||||
|
||||
4. **对于时间,它代表什么意义?**
|
||||
|
||||
+ 它是一个全球统一的、绝对的时刻吗?(例如,日志条目、创建时间)如果是,选择 `TIMESTAMP`,并确保服务器时区设置为UTC。
|
||||
|
||||
+ 它是一个与本地时区绑定的“约定”时间吗?(例如,生日、本地会议开始时间)如果是,选择 `DATETIME`。
|
||||
|
||||
+ 是否需要存储1970年之前或2038年之后的时间?如果是,`TIMESTAMP` 将无法满足需求,必须使用 `DATETIME`。
|
||||
|
||||
5. **对于复杂或动态数据,它是否适合 `JSON`?**
|
||||
|
||||
+ 数据是否是结构化的,但结构经常变化或嵌套层次很深?`JSON` 是一个很好的候选者。
|
||||
|
||||
+ 如果选择 `JSON`,哪些内部字段会成为查询条件?为这些字段通过生成列建立索引。
|
||||
|
||||
+ 这些数据是否是应用的核心实体?如果是,应优先考虑将其规范化为传统的列和表,而不是全部塞进 `JSON`。
|
||||
|
||||
|
||||
### 6.2 常见数据类型选型错误汇总与规避方案
|
||||
|
||||
以下是在实际开发中反复出现的典型数据类型选型错误,以及如何规避它们的最终建议。
|
||||
|
||||
+ **错误1:过度宽容的类型定义 (Over-provisioning Types)**
|
||||
|
||||
+ **表现**:为只能容纳几百个值的状态字段使用 `INT`;为用户ID使用 `BIGINT`,而 `INT UNSIGNED` 已经可以支持超过40亿用户。
|
||||
|
||||
+ **危害**:严重浪费存储空间和内存,降低了缓存效率,拖慢了整个系统。
|
||||
|
||||
+ **规避方案**:严格遵循“最小化原则”。仔细分析每个字段的业务边界,选择恰好满足需求的最小数据类型 。
|
||||
|
||||
+ **错误2:使用浮点数处理货币 (Using `FLOAT` for Currency)**
|
||||
|
||||
+ **表现**:将商品价格、账户余额等字段定义为 `FLOAT` 或 `DOUBLE`。
|
||||
|
||||
+ **危害**:导致不可预测的舍入误差,在聚合计算中误差会被放大,最终造成数据不一致和财务损失。这是一个致命的错误。
|
||||
|
||||
+ **规避方案**:任何涉及精确计算的场景,**必须**使用 `DECIMAL` 或 `NUMERIC` 类型 。
|
||||
|
||||
+ **错误3:无差别使用 `TEXT` (Unnecessary Use of `TEXT`)**
|
||||
|
||||
+ **表现**:只要是长文本,就使用 `TEXT`,即使其长度远未达到 `VARCHAR` 的上限。
|
||||
|
||||
+ **危害**:当涉及 `TEXT` 列的查询需要排序或分组时,可能强制MySQL使用基于磁盘的慢速临时表,造成严重的性能瓶颈 。
|
||||
|
||||
+ **规避方案**:只要数据长度在65,535字节以内,就**始终优先使用 `VARCHAR`**。
|
||||
|
||||
+ **错误4:混淆 `DATETIME` 和 `TIMESTAMP` 的时区行为**
|
||||
|
||||
+ **表现**:在需要全球统一时间戳的应用中使用了 `DATETIME`,或者在需要存储本地“约定”时间时使用了 `TIMESTAMP`。
|
||||
|
||||
+ **危害**:导致跨时区应用的时间显示混乱和逻辑错误。
|
||||
|
||||
+ **规避方案**:深刻理解二者的核心区别——“全球时刻” vs “本地约定”。将服务器时区设为UTC,并根据业务需求审慎选择 。
|
||||
|
||||
+ **错误5:滥用 `ENUM` 和 `SET` (The `ENUM`/`SET` Maintenance Trap)**
|
||||
|
||||
+ **表现**:为那些业务逻辑上可能频繁变更的列表(如商品分类、用户标签)使用 `ENUM` 或 `SET`。
|
||||
|
||||
+ **危害**:每次增加新选项都需要执行 `ALTER TABLE`,这在生产环境的大表上是高风险、高成本的操作,严重影响系统的灵活性和可维护性 。
|
||||
|
||||
+ **规避方案**:对于可能变化的列表,使用一个独立的查找表(lookup table)和外键关联是更优的、更具扩展性的设计模式。
|
||||
|
||||
+ **错误6:在字符串中存储结构化数据 (Storing Structured Data in Strings)**
|
||||
|
||||
+ **表现**:将多个ID用逗号分隔存储在 `VARCHAR` 中(如 `'1,5,23'`);或者将键值对拼接成字符串。
|
||||
|
||||
+ **危害**:完全破坏了关系型数据库的优势,使得查询、更新、数据校验和维护变得极其困难和低效,无法利用索引。
|
||||
|
||||
+ **规避方案**:对于多对多关系,使用标准的关联表。对于半结构化的键值对数据,使用 `JSON` 类型并配合生成列索引。
|
||||
|
||||
|
||||
最终,数据类型的选择是数据库设计艺术与科学的结合。它要求开发者不仅要理解每种类型的技术规格,更要洞察其背后的业务逻辑和数据生命周期。通过遵循本指南中提出的原则和最佳实践,可以为构建高性能、高可靠性的MySQL应用打下坚实的基础。
|
||||
700
101-数据库学习/2-MySQL/9-题目/MySQL-基础考察题目.md
Normal file
700
101-数据库学习/2-MySQL/9-题目/MySQL-基础考察题目.md
Normal file
@@ -0,0 +1,700 @@
|
||||
# MySQL综合能力考核:大学信息系统项目
|
||||
|
||||
## 导言:大学数据库项目 - 一次实践性考核
|
||||
|
||||
欢迎参加本次MySQL综合能力考核。本次考核旨在通过一个完整的、贴近实际的项目,全面评估您对MySQL数据库的掌握程度。您将扮演一名数据库工程师,负责为一个新成立的大学设计、构建并管理其核心的学生信息系统数据库。
|
||||
|
||||
这个项目不仅仅是一系列孤立的SQL命令测试,它模拟了数据库开发的整个生命周期:从根据业务需求定义数据结构(DDL),到填充和维护数据(DML),再到从中提取有价值的信息(DQL),最后确保复杂操作的数据一致性(TCL)。
|
||||
|
||||
### 场景概述与核心业务规则
|
||||
|
||||
大学信息系统的核心实体及其业务规则如下:
|
||||
|
||||
+ **讲师 (Instructors):** 每位讲师拥有唯一的ID、姓名、电子邮件和入职日期。电子邮件地址必须是唯一的。
|
||||
|
||||
+ **课程 (Courses):** 每门课程拥有唯一的ID、课程名称、学分,并且由一位讲师负责授课。
|
||||
|
||||
+ **学生 (Students):** 每位学生拥有唯一的ID、姓名、电子邮件、出生日期和专业。电子邮件地址必须是唯一的。
|
||||
|
||||
+ **选课记录 (Enrollments):** 这是连接学生和课程的关键。它记录了哪个学生选修了哪门课程,以及他们获得的分数。这体现了学生与课程之间的多对多关系。
|
||||
|
||||
|
||||
### 实体关系描述 (ERD)
|
||||
|
||||
+ 一名 **讲师 (Instructor)** 可以教授多门 **课程 (Courses)**。(一对多关系)
|
||||
|
||||
+ 一名 **学生 (Student)** 可以选修多门 **课程 (Courses)**。
|
||||
|
||||
+ 一门 **课程 (Course)** 可以被多名 **学生 (Student)** 选修。(学生与课程之间是多对多关系,因此需要`Enrollments`这个中间表来实现)
|
||||
|
||||
|
||||
在开始编写任何SQL代码之前,深刻理解这些业务规则和实体间的关系至关重要。例如,“电子邮件必须唯一”这一业务规则将直接转化为数据库表中的`UNIQUE`约束;而“学生与课程的多对多关系”则决定了我们必须创建一个独立的`Enrollments`表,并通过外键将其与`Students`表和`Courses`表关联起来。这种从业务需求到数据库逻辑结构的转换,是数据库设计的核心思想,也是本次考核贯穿始终的考察重点。
|
||||
|
||||
现在,请根据以下题目,逐步完成这个大学数据库的构建与管理。
|
||||
|
||||
* * *
|
||||
|
||||
## 第一部分: foundational Architecture - 数据定义语言 (DDL)
|
||||
|
||||
本部分将考察您使用DDL创建和管理数据库基本结构的能力。一个设计良好、结构清晰的数据库是所有数据操作的基础。您将从零开始,构建起整个大学信息系统的骨架。
|
||||
|
||||
1. 创建一个名为 `university_db` 的新数据库,并设置其默认字符集为 `utf8mb4`。
|
||||
|
||||
2. 切换到 `university_db` 数据库。创建一个名为 `instructors` 的表,用于存储讲师信息。该表应包含以下列:
|
||||
|
||||
+ `instructor_id`: 整数类型,作为主键,自动增长。
|
||||
|
||||
+ `name`: 变长字符串类型,最大长度100,不允许为空。
|
||||
|
||||
+ `email`: 变长字符串类型,最大长度100,必须唯一,不允许为空。
|
||||
|
||||
+ `hire_date`: 日期类型,不允许为空。
|
||||
|
||||
3. 创建一个名为 `courses` 的表,用于存储课程信息。该表应包含以下列:
|
||||
|
||||
+ `course_id`: 整数类型,作为主键,自动增长。
|
||||
|
||||
+ `title`: 变长字符串类型,最大长度150,不允许为空。
|
||||
|
||||
+ `credits`: 整数类型,不允许为空。
|
||||
|
||||
+ `instructor_id`: 整数类型。此列将用于关联 `instructors` 表。
|
||||
|
||||
4. 创建一个名为 `students` 的表,用于存储学生信息。该表应包含以下列:
|
||||
|
||||
+ `student_id`: 整数类型,作为主键,自动增长。
|
||||
|
||||
+ `name`: 变长字符串类型,最大长度100,不允许为空。
|
||||
|
||||
+ `email`: 变长字符串类型,最大长度100,必须唯一,不允许为空。
|
||||
|
||||
+ `date_of_birth`: 日期类型。
|
||||
|
||||
+ `major`: 变长字符串类型,最大长度50。
|
||||
|
||||
5. 创建最关键的连接表 `enrollments`,用于记录学生的选课情况。该表的设计需要体现多对多关系。它应包含以下列:
|
||||
|
||||
+ `enrollment_id`: 整数类型,作为主键,自动增长。
|
||||
|
||||
+ `student_id`: 整数类型,不允许为空。
|
||||
|
||||
+ `course_id`: 整数类型,不允许为空。
|
||||
|
||||
+ `grade`: 小数类型,总共3位,其中1位是小数(例如95.5)。
|
||||
|
||||
+ **约束**:
|
||||
|
||||
+ `student_id` 和 `course_id` 的组合必须是唯一的,以防止同一学生重复选修同一门课程。
|
||||
|
||||
+ `student_id` 列应作为外键,引用 `students` 表的 `student_id` 列。
|
||||
|
||||
+ `course_id` 列应作为外键,引用 `courses` 表的 `course_id` 列。
|
||||
|
||||
6. 在 `students` 表中添加一个新的列 `phone_number`,类型为变长字符串,最大长度为20。
|
||||
|
||||
7. 修改 `courses` 表中的 `credits` 列,为其添加一个 `CHECK` 约束,确保其值必须大于0。
|
||||
|
||||
8. 为 `students` 表的 `major` 列添加一个 `DEFAULT` 约束,如果插入新学生时未指定专业,则默认为 'Undeclared'。
|
||||
|
||||
9. 现在,为 `courses` 表中的 `instructor_id` 列添加一个外键约束,使其引用 `instructors` 表的 `instructor_id` 列。
|
||||
|
||||
10. 清空 `enrollments` 表中的所有数据,但保留表结构。请写出执行此操作的命令。
|
||||
|
||||
11. 完整地删除 `instructors` 表。
|
||||
|
||||
12. 解释 `DELETE FROM table_name;`、`TRUNCATE TABLE table_name;` 和 `DROP TABLE table_name;` 这三个命令在功能、性能和使用场景上的主要区别。
|
||||
|
||||
|
||||
* * *
|
||||
|
||||
## 第二部分:Data Population and Management - 数据操作语言 (DML)
|
||||
|
||||
在数据库结构搭建完毕后,我们需要向其中填充数据,并根据业务变化进行维护。本部分将考察您使用DML语句进行数据插入、更新和删除的能力。您将亲身体验DDL中设置的约束是如何在DML操作中发挥作用的。
|
||||
|
||||
**准备工作:** 如果您在上一部分执行了第11题(删除了`instructors`表),请重新创建它,以确保后续操作可以正常进行。
|
||||
|
||||
13. 向 `instructors` 表中插入以下三位讲师的数据:
|
||||
|
||||
+ 姓名: '张磊', 邮箱: 'zhang.lei@university.edu', 入职日期: '2020-08-15'
|
||||
|
||||
+ 姓名: '李静', 邮箱: 'li.jing@university.edu', 入职日期: '2019-07-01'
|
||||
|
||||
+ 姓名: '王浩', 邮箱: 'wang.hao@university.edu', 入职日期: '2021-01-20'
|
||||
|
||||
14. 使用一条 `INSERT` 语句向 `students` 表中插入以下两位学生的数据:
|
||||
|
||||
+ 姓名: '赵辰', 邮箱: 'zhao.chen@student.edu', 生日: '2002-05-10', 专业: 'Computer Science'
|
||||
|
||||
+ 姓名: '孙悦', 邮箱: 'sun.yue@student.edu', 生日: '2003-09-22', 专业: 'Data Science'
|
||||
|
||||
15. 再向 `students` 表中插入一位学生,但不指定其专业,以测试默认值约束:
|
||||
|
||||
+ 姓名: '周毅', 邮箱: 'zhou.yi@student.edu', 生日: '2002-11-30'
|
||||
|
||||
16. 根据 `instructors` 表中的数据,向 `courses` 表中插入以下课程。请确保 `instructor_id` 与讲师姓名对应正确(假设张磊ID为1,李静ID为2,王浩ID为3):
|
||||
|
||||
+ 课程名: 'Introduction to Programming', 学分: 4, 讲师ID: 1
|
||||
|
||||
+ 课程名: 'Advanced Databases', 学分: 4, 讲师ID: 2
|
||||
|
||||
+ 课程名: 'Web Development', 学分: 3, 讲师ID: 1
|
||||
|
||||
+ 课程名: 'Machine Learning', 学分: 3, 讲师ID: 3
|
||||
|
||||
17. 为学生选课。向 `enrollments` 表中插入以下记录(假设赵辰ID为1,孙悦ID为2,周毅ID为3;课程ID从1开始):
|
||||
|
||||
+ 赵辰 选修 'Introduction to Programming',成绩为 92.5
|
||||
|
||||
+ 赵辰 选修 'Advanced Databases',成绩为 88.0
|
||||
|
||||
+ 孙悦 选修 'Introduction to Programming',成绩为 95.0
|
||||
|
||||
+ 孙悦 选修 'Machine Learning',成绩为 97.5
|
||||
|
||||
18. **(挑战题)** 尝试向 `enrollments` 表中插入一条记录,其中 `student_id` 为 99(一个不存在的学生ID),`course_id` 为 1。执行该命令并解释为什么会失败。这个失败说明了什么?
|
||||
|
||||
19. 学生“赵辰”的邮箱地址输入有误,请将其邮箱更新为 'zhao.chen.new@student.edu'。
|
||||
|
||||
20. 由于教学计划调整,'Web Development' 这门课程的授课教师需要更换为“李静”(假设其ID为2)。请更新 `courses` 表。
|
||||
|
||||
21. 学校决定将所有 'Computer Science' 专业的学生的专业名称更新为 'Computer Science and Technology'。请执行此批量更新操作。
|
||||
|
||||
22. 学生“孙悦”决定退选 'Machine Learning' 这门课。请从 `enrollments` 表中删除对应的选课记录。
|
||||
|
||||
23. **(挑战题)** 尝试从 `instructors` 表中删除讲师“张磊”(假设其ID为1)。执行该命令并解释为什么它很可能会失败。
|
||||
|
||||
24. 承接上一题,如果要成功删除讲师“张磊”,需要先执行哪些操作?请描述正确的操作步骤。
|
||||
|
||||
|
||||
* * *
|
||||
|
||||
## 第三部分:Information Retrieval and Analysis - 数据查询语言 (DQL)
|
||||
|
||||
数据库的核心价值在于能够快速、准确地提取所需信息。本部分是考核的重点,将全面检验您使用DQL从简单查询到复杂分析的能力。所有查询都基于您在第二部分中插入的数据。
|
||||
|
||||
### A. 基础查询 (10道)
|
||||
|
||||
25. 查询 `students` 表中的所有学生信息。
|
||||
|
||||
26. 查询 `instructors` 表中所有讲师的姓名和邮箱地址。
|
||||
|
||||
27. 查询 `courses` 表中学分大于3的所有课程的完整信息。
|
||||
|
||||
28. 查询所有专业为 'Data Science' 的学生姓名和出生日期。
|
||||
|
||||
29. 查询课程名称中包含 'Introduction' 关键字的所有课程。
|
||||
|
||||
30. 查询所有姓“张”的讲师信息。
|
||||
|
||||
31. 查询在2020年1月1日之后入职的讲师姓名和入职日期。
|
||||
|
||||
32. 查询专业为 'Computer Science and Technology' 或 'Data Science' 的所有学生信息。
|
||||
|
||||
33. 查询 `enrollments` 表中所有成绩在85到95分之间(包含85和95)的选课记录。
|
||||
|
||||
34. 查询所有课程信息,并按学分从高到低排序。如果学分相同,则按课程名称的字母顺序排序。
|
||||
|
||||
|
||||
### B. 聚合函数与分组查询 (10道)
|
||||
|
||||
35. 统计 `students` 表中共有多少名学生。
|
||||
|
||||
36. 计算 `courses` 表中所有课程的平均学分。
|
||||
|
||||
37. 找出 `enrollments` 表中记录的最高成绩。
|
||||
|
||||
38. 统计每个专业(major)分别有多少名学生。
|
||||
|
||||
39. 计算每门课程的平均成绩。查询结果应显示课程ID和对应的平均分。
|
||||
|
||||
40. 统计每位讲师(instructor)负责教授的课程数量。查询结果应显示讲师ID和课程数量。
|
||||
|
||||
41. 找出选修课程数量超过1门的学生的ID。
|
||||
|
||||
42. 查询每个专业的学生中,年龄最小的学生的出生日期。
|
||||
|
||||
43. 统计所有课程中,总共有多少名学生进行了选课(同一个学生选多门课算多次)。
|
||||
|
||||
44. 找出平均成绩最高的课程的ID。
|
||||
|
||||
|
||||
### C. 连接查询 (10道)
|
||||
|
||||
45. 使用 `INNER JOIN` 查询所有学生的姓名以及他们选修的课程名称。
|
||||
|
||||
46. 查询所有选课记录,并同时显示学生的姓名、课程的名称以及该课程的分数。
|
||||
|
||||
47. 查询讲师“张磊”所教授的所有课程的名称和学分。
|
||||
|
||||
48. 列出所有学生及其选修课程的授课讲师的姓名。查询结果应包含三列:学生姓名, 课程名称, 讲师姓名。
|
||||
|
||||
49. **(数据准备)** 为了测试 `LEFT JOIN`,请先插入一位没有选修任何课程的新学生:
|
||||
|
||||
SQL
|
||||
|
||||
```
|
||||
INSERT INTO students (name, email, date_of_birth, major) VALUES ('钱坤', 'qian.kun@student.edu', '2004-01-15', 'Physics');
|
||||
```
|
||||
|
||||
现在,使用 `LEFT JOIN` 查询所有学生的姓名以及他们选修的课程名称。确保那位没有选课的学生(钱坤)也出现在结果中。
|
||||
|
||||
50. **(数据准备)** 为了测试 `RIGHT JOIN`,请先插入一门没有任何学生选修的新课程:
|
||||
|
||||
SQL
|
||||
|
||||
```
|
||||
INSERT INTO courses (title, credits, instructor_id) VALUES ('Quantum Physics', 4, 2);
|
||||
```
|
||||
|
||||
现在,查询所有课程的名称以及选修该课程的学生人数。确保那门没有学生选修的课程也出现在结果中,且学生人数为0。
|
||||
|
||||
51. 查询所有选修了学分等于4的课程的学生姓名,并去除重复的姓名。
|
||||
|
||||
52. 查询所有成绩高于90分的学生姓名和他们所选的课程名称。
|
||||
|
||||
53. 查询与“赵辰”在同一个专业的所有其他学生的姓名。
|
||||
|
||||
54. 列出所有讲师的姓名以及他们所教授课程的平均成绩。
|
||||
|
||||
|
||||
### D. 高阶查询 (含`HAVING`子句与子查询) (6道)
|
||||
|
||||
55. 使用 `GROUP BY` 和 `HAVING` 子句,找出选修人数超过1人的所有课程的ID和选修人数。
|
||||
|
||||
56. 解释 `WHERE` 子句和 `HAVING` 子句在功能和使用时机上的核心区别。
|
||||
|
||||
57. 使用子查询,找出由讲师“李静”教授的所有课程的ID。然后基于这个ID列表,在 `enrollments` 表中找出所有选修了这些课程的学生的ID。
|
||||
|
||||
58. 使用子查询,查询选修了 'Advanced Databases' 这门课的所有学生的姓名。
|
||||
|
||||
59. 查询比所有 'Data Science' 专业学生的平均年龄都要大的学生姓名。(提示:需要计算 'Data Science' 学生的平均出生日期,然后进行比较)。
|
||||
|
||||
60. 查询每门课程中获得最高分的学生的姓名和该课程的名称。
|
||||
|
||||
|
||||
* * *
|
||||
|
||||
## 第四部分:Ensuring Consistency - 事务控制语言 (TCL)
|
||||
|
||||
在真实的数据库应用中,一系列操作往往需要被视为一个不可分割的整体。例如,银行转账包含“A账户扣款”和“B账户收款”两个步骤,必须同时成功或同时失败。本部分将通过场景题,考察您使用TCL来保证复杂业务逻辑下数据一致性的能力。
|
||||
|
||||
61. **场景一:成功注册** 学生“周毅”(ID为3)希望注册选修“Web Development”(ID为3)这门课程。这个注册过程包含两个步骤: a. 在 `enrollments` 表中插入一条新的选课记录(成绩暂时为NULL)。 b. 假设学校有一个 `student_accounts` 表(我们在此不创建,仅作为逻辑假设),需要更新该学生的账户余额,扣除相应学费。 请编写一个事务,将步骤a的操作包含在内。由于操作会成功,最终提交该事务。
|
||||
|
||||
62. 在上题的事务中,`COMMIT` 命令起到了什么作用?
|
||||
|
||||
63. 为什么将这两个步骤(或更多步骤)捆绑在一个事务中是至关重要的?
|
||||
|
||||
64. 启动一个事务的SQL命令是什么?
|
||||
|
||||
65. **场景二:注册失败与回滚** 学生“钱坤”(ID为4)尝试注册“Machine Learning”(ID为4)课程,但系统检查发现他未满足前置课程要求,因此注册失败。整个操作流程如下: a. 开始一个事务。 b. 在 `enrollments` 表中插入一条选课记录。 c. 系统检查前置课程,发现不满足条件,因此决定中止操作。 请编写这个事务,并在操作中止时使用 `ROLLBACK` 命令,确保数据库状态恢复到事务开始之前。
|
||||
|
||||
66. 在上题的场景中,`ROLLBACK` 命令具体做了什么?
|
||||
|
||||
67. 如果在执行 `ROLLBACK` 之前,没有使用 `START TRANSACTION` 或等效命令开启事务,会发生什么?
|
||||
|
||||
68. **场景三:带保存点的部分回滚** 一位管理员正在为学生“孙悦”(ID为2)批量注册三门课程。流程如下: a. 开始一个事务。 b. 成功注册第一门课 'Advanced Databases' (ID 2)。 c. 创建一个保存点,名为 `savepoint_1`。 d. 成功注册第二门课 'Web Development' (ID 3)。 e. 创建一个保存点,名为 `savepoint_2`。 f. 尝试注册第三门课 'Quantum Physics' (ID 5),但发现该课程已满员,注册失败。 管理员决定保留前两次成功的注册,但撤销第三次失败的尝试。请编写SQL序列,使用 `SAVEPOINT` 和 `ROLLBACK TO` 来实现这个需求,并最终提交前两次成功的注册。
|
||||
|
||||
69. 在上题中,`ROLLBACK TO savepoint_2;` 命令执行后,数据库处于什么状态?
|
||||
|
||||
70. 为什么在这种多步操作中,使用 `SAVEPOINT` 比直接使用 `ROLLBACK` 更加灵活?
|
||||
|
||||
71. `AUTOCOMMIT` 是MySQL中的一个重要概念。请解释它的含义,以及它与我们手动执行 `START TRANSACTION`、`COMMIT`、`ROLLBACK` 之间的关系。
|
||||
|
||||
|
||||
* * *
|
||||
|
||||
## 附录:综合能力考核答案
|
||||
|
||||
### 第一部分:DDL答案
|
||||
|
||||
1. SQL
|
||||
|
||||
```
|
||||
CREATE DATABASE university_db CHARACTER SET utf8mb4;
|
||||
```
|
||||
|
||||
2. SQL
|
||||
|
||||
```
|
||||
USE university_db;
|
||||
CREATE TABLE instructors (
|
||||
instructor_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
email VARCHAR(100) NOT NULL UNIQUE,
|
||||
hire_date DATE NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
3. SQL
|
||||
|
||||
```
|
||||
CREATE TABLE courses (
|
||||
course_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
title VARCHAR(150) NOT NULL,
|
||||
credits INT NOT NULL,
|
||||
instructor_id INT
|
||||
);
|
||||
```
|
||||
|
||||
4. SQL
|
||||
|
||||
```
|
||||
CREATE TABLE students (
|
||||
student_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
email VARCHAR(100) NOT NULL UNIQUE,
|
||||
date_of_birth DATE,
|
||||
major VARCHAR(50)
|
||||
);
|
||||
```
|
||||
|
||||
5. SQL
|
||||
|
||||
```
|
||||
CREATE TABLE enrollments (
|
||||
enrollment_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
student_id INT NOT NULL,
|
||||
course_id INT NOT NULL,
|
||||
grade DECIMAL(3, 1),
|
||||
UNIQUE (student_id, course_id),
|
||||
FOREIGN KEY (student_id) REFERENCES students(student_id),
|
||||
FOREIGN KEY (course_id) REFERENCES courses(course_id)
|
||||
);
|
||||
```
|
||||
|
||||
6. SQL
|
||||
|
||||
```
|
||||
ALTER TABLE students ADD COLUMN phone_number VARCHAR(20);
|
||||
```
|
||||
|
||||
7. SQL
|
||||
|
||||
```
|
||||
ALTER TABLE courses ADD CONSTRAINT chk_credits CHECK (credits > 0);
|
||||
```
|
||||
|
||||
8. SQL
|
||||
|
||||
```
|
||||
ALTER TABLE students ALTER COLUMN major SET DEFAULT 'Undeclared';
|
||||
```
|
||||
|
||||
9. SQL
|
||||
|
||||
```
|
||||
ALTER TABLE courses ADD CONSTRAINT fk_instructor
|
||||
FOREIGN KEY (instructor_id) REFERENCES instructors(instructor_id);
|
||||
```
|
||||
|
||||
10. SQL
|
||||
|
||||
```
|
||||
TRUNCATE TABLE enrollments;
|
||||
```
|
||||
|
||||
11. SQL
|
||||
|
||||
```
|
||||
DROP TABLE instructors;
|
||||
```
|
||||
|
||||
12. **`DELETE FROM table_name;`**:
|
||||
|
||||
+ **功能**: 逐行删除表中的数据,可以与 `WHERE` 子句配合使用删除特定行。
|
||||
|
||||
+ **性能**: 相对较慢,因为它会为每一行记录删除日志。
|
||||
|
||||
+ **特点**: 触发`DELETE`触发器;不重置自增ID。 **`TRUNCATE TABLE table_name;`**:
|
||||
|
||||
+ **功能**: 快速删除表中的所有行。
|
||||
|
||||
+ **性能**: 非常快,因为它通过释放数据页来清空表,而不是逐行删除。
|
||||
|
||||
+ **特点**: 不触发`DELETE`触发器;通常会重置自增ID;不能与`WHERE`子句一起使用。 **`DROP TABLE table_name;`**:
|
||||
|
||||
+ **功能**: 完全删除整个表,包括表结构、数据、索引、约束和触发器。
|
||||
|
||||
+ **性能**: 非常快。
|
||||
|
||||
+ **特点**: 表将不复存在,需要`CREATE TABLE`才能重新使用。这是最具破坏性的操作。
|
||||
|
||||
|
||||
### 第二部分:DML答案
|
||||
|
||||
13. SQL
|
||||
|
||||
```
|
||||
INSERT INTO instructors (name, email, hire_date) VALUES
|
||||
('张磊', 'zhang.lei@university.edu', '2020-08-15'),
|
||||
('李静', 'li.jing@university.edu', '2019-07-01'),
|
||||
('王浩', 'wang.hao@university.edu', '2021-01-20');
|
||||
```
|
||||
|
||||
14. SQL
|
||||
|
||||
```
|
||||
INSERT INTO students (name, email, date_of_birth, major) VALUES
|
||||
('赵辰', 'zhao.chen@student.edu', '2002-05-10', 'Computer Science'),
|
||||
('孙悦', 'sun.yue@student.edu', '2003-09-22', 'Data Science');
|
||||
```
|
||||
|
||||
15. SQL
|
||||
|
||||
```
|
||||
INSERT INTO students (name, email, date_of_birth) VALUES
|
||||
('周毅', 'zhou.yi@student.edu', '2002-11-30');
|
||||
-- 查询后会发现他的major字段值为'Undeclared'
|
||||
```
|
||||
|
||||
16. SQL
|
||||
|
||||
```
|
||||
INSERT INTO courses (title, credits, instructor_id) VALUES
|
||||
('Introduction to Programming', 4, 1),
|
||||
('Advanced Databases', 4, 2),
|
||||
('Web Development', 3, 1),
|
||||
('Machine Learning', 3, 3);
|
||||
```
|
||||
|
||||
17. SQL
|
||||
|
||||
```
|
||||
INSERT INTO enrollments (student_id, course_id, grade) VALUES
|
||||
(1, 1, 92.5),
|
||||
(1, 2, 88.0),
|
||||
(2, 1, 95.0),
|
||||
(2, 4, 97.5);
|
||||
```
|
||||
|
||||
18. SQL
|
||||
|
||||
```
|
||||
INSERT INTO enrollments (student_id, course_id) VALUES (99, 1);
|
||||
```
|
||||
|
||||
**解释**: 该命令会失败,并报告一个外键约束错误 (`FOREIGN KEY constraint fails`)。 **原因**: 因为我们在 `enrollments` 表的 `student_id` 列上定义了一个外键,它引用了 `students` 表的 `student_id` 列。这意味着任何试图插入到 `enrollments` 表的 `student_id` 的值,都必须首先存在于 `students` 表中。由于 `students` 表中不存在ID为99的学生,数据库为了维护引用完整性,拒绝了这次插入操作。这完美地展示了DDL中定义的约束是如何保证数据一致性和有效性的。
|
||||
|
||||
19. SQL
|
||||
|
||||
```
|
||||
UPDATE students SET email = 'zhao.chen.new@student.edu' WHERE name = '赵辰';
|
||||
```
|
||||
|
||||
20. SQL
|
||||
|
||||
```
|
||||
UPDATE courses SET instructor_id = 2 WHERE title = 'Web Development';
|
||||
```
|
||||
|
||||
21. SQL
|
||||
|
||||
```
|
||||
UPDATE students SET major = 'Computer Science and Technology' WHERE major = 'Computer Science';
|
||||
```
|
||||
|
||||
22. SQL
|
||||
|
||||
```
|
||||
DELETE FROM enrollments WHERE student_id = 2 AND course_id = 4;
|
||||
```
|
||||
|
||||
23. SQL
|
||||
|
||||
```
|
||||
DELETE FROM instructors WHERE instructor_id = 1;
|
||||
```
|
||||
|
||||
**解释**: 该命令很可能会失败,并报告一个外键约束错误。 **原因**: 因为讲师“张磊”(ID为1)在 `courses` 表中被引用了('Introduction to Programming' 和 'Web Development' 这两门课的 `instructor_id` 都是1)。为了保护数据的引用完整性,数据库禁止删除一个正在被其他表引用的记录。
|
||||
|
||||
24. **正确操作步骤**:
|
||||
|
||||
1. 必须先处理掉 `courses` 表中对讲师“张磊”的引用。有两种方式:
|
||||
|
||||
+ **方式一(重新分配课程)**: 将他教授的课程的 `instructor_id` 更新为另一位讲师的ID,或者设置为 `NULL`(如果该列允许为NULL)。
|
||||
|
||||
+ **方式二(删除课程)**: 删除所有由他教授的课程(但这可能会引发连锁问题,因为课程可能已被学生选修)。
|
||||
|
||||
2. 在解除了所有外键引用之后,才能安全地从 `instructors` 表中删除讲师“张磊”的记录。 例如,先执行更新:`UPDATE courses SET instructor_id = NULL WHERE instructor_id = 1;` 然后执行删除:`DELETE FROM instructors WHERE instructor_id = 1;`
|
||||
|
||||
|
||||
### 第三部分:DQL答案
|
||||
|
||||
25. `SELECT * FROM students;`
|
||||
|
||||
26. `SELECT name, email FROM instructors;`
|
||||
|
||||
27. `SELECT * FROM courses WHERE credits > 3;`
|
||||
|
||||
28. `SELECT name, date_of_birth FROM students WHERE major = 'Data Science';`
|
||||
|
||||
29. `SELECT * FROM courses WHERE title LIKE '%Introduction%';`
|
||||
|
||||
30. `SELECT * FROM instructors WHERE name LIKE '张%';`
|
||||
|
||||
31. `SELECT name, hire_date FROM instructors WHERE hire_date > '2020-01-01';`
|
||||
|
||||
32. `SELECT * FROM students WHERE major IN ('Computer Science and Technology', 'Data Science');`
|
||||
|
||||
33. `SELECT * FROM enrollments WHERE grade BETWEEN 85 AND 95;`
|
||||
|
||||
34. `SELECT * FROM courses ORDER BY credits DESC, title ASC;`
|
||||
|
||||
35. `SELECT COUNT(*) FROM students;`
|
||||
|
||||
36. `SELECT AVG(credits) FROM courses;`
|
||||
|
||||
37. `SELECT MAX(grade) FROM enrollments;`
|
||||
|
||||
38. `SELECT major, COUNT(*) FROM students GROUP BY major;`
|
||||
|
||||
39. `SELECT course_id, AVG(grade) FROM enrollments GROUP BY course_id;`
|
||||
|
||||
40. `SELECT instructor_id, COUNT(*) FROM courses GROUP BY instructor_id;`
|
||||
|
||||
41. `SELECT student_id FROM enrollments GROUP BY student_id HAVING COUNT(course_id) > 1;`
|
||||
|
||||
42. `SELECT major, MAX(date_of_birth) FROM students GROUP BY major;` -- 年龄最小,出生日期最大
|
||||
|
||||
43. `SELECT COUNT(*) FROM enrollments;`
|
||||
|
||||
44. `SELECT course_id FROM enrollments GROUP BY course_id ORDER BY AVG(grade) DESC LIMIT 1;`
|
||||
|
||||
45. `SELECT s.name, c.title FROM students s INNER JOIN enrollments e ON s.student_id = e.student_id INNER JOIN courses c ON e.course_id = c.course_id;`
|
||||
|
||||
46. `SELECT s.name, c.title, e.grade FROM students s JOIN enrollments e ON s.student_id = e.student_id JOIN courses c ON e.course_id = c.course_id;`
|
||||
|
||||
47. `SELECT c.title, c.credits FROM courses c JOIN instructors i ON c.instructor_id = i.instructor_id WHERE i.name = '张磊';`
|
||||
|
||||
48. `SELECT s.name AS student_name, c.title AS course_title, i.name AS instructor_name FROM students s JOIN enrollments e ON s.student_id = e.student_id JOIN courses c ON e.course_id = c.course_id JOIN instructors i ON c.instructor_id = i.instructor_id;`
|
||||
|
||||
49. `SELECT s.name, c.title FROM students s LEFT JOIN enrollments e ON s.student_id = e.student_id LEFT JOIN courses c ON e.course_id = c.course_id;`
|
||||
|
||||
50. `SELECT c.title, COUNT(e.student_id) AS number_of_students FROM courses c LEFT JOIN enrollments e ON c.course_id = e.course_id GROUP BY c.course_id, c.title;`
|
||||
|
||||
51. `SELECT DISTINCT s.name FROM students s JOIN enrollments e ON s.student_id = e.student_id JOIN courses c ON e.course_id = c.course_id WHERE c.credits = 4;`
|
||||
|
||||
52. `SELECT s.name, c.title FROM students s JOIN enrollments e ON s.student_id = e.student_id JOIN courses c ON e.course_id = c.course_id WHERE e.grade > 90;`
|
||||
|
||||
53. `SELECT s2.name FROM students s1 JOIN students s2 ON s1.major = s2.major WHERE s1.name = '赵辰' AND s2.name!= '赵辰';`
|
||||
|
||||
54. `SELECT i.name, AVG(e.grade) AS average_grade FROM instructors i JOIN courses c ON i.instructor_id = c.instructor_id JOIN enrollments e ON c.course_id = e.course_id GROUP BY i.instructor_id, i.name;`
|
||||
|
||||
55. `SELECT course_id, COUNT(student_id) AS num_students FROM enrollments GROUP BY course_id HAVING COUNT(student_id) > 1;`
|
||||
|
||||
56. **`WHERE` vs `HAVING`**:
|
||||
|
||||
+ **作用对象不同**: `WHERE` 子句作用于原始的表数据,在数据分组(`GROUP BY`)之前进行过滤。`HAVING` 子句作用于 `GROUP BY` 之后生成的结果集,对分组后的聚合结果进行过滤。
|
||||
|
||||
+ **使用位置不同**: `WHERE` 必须在 `GROUP BY` 之前,`HAVING` 必须在 `GROUP BY` 之后。
|
||||
|
||||
+ **可使用函数不同**: `WHERE` 子句中不能使用聚合函数(如 `COUNT()`, `AVG()`),而 `HAVING` 子句专门用于配合聚合函数进行条件判断。
|
||||
|
||||
57. SQL
|
||||
|
||||
```
|
||||
SELECT student_id FROM enrollments
|
||||
WHERE course_id IN (
|
||||
SELECT course_id FROM courses
|
||||
WHERE instructor_id = (
|
||||
SELECT instructor_id FROM instructors WHERE name = '李静'
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
58. SQL
|
||||
|
||||
```
|
||||
SELECT name FROM students
|
||||
WHERE student_id IN (
|
||||
SELECT student_id FROM enrollments
|
||||
WHERE course_id = (
|
||||
SELECT course_id FROM courses WHERE title = 'Advanced Databases'
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
59. SQL
|
||||
|
||||
```
|
||||
SELECT name FROM students
|
||||
WHERE date_of_birth < (
|
||||
SELECT AVG(date_of_birth) FROM students WHERE major = 'Data Science'
|
||||
);
|
||||
```
|
||||
|
||||
60. SQL
|
||||
|
||||
```
|
||||
SELECT s.name, c.title
|
||||
FROM enrollments e
|
||||
JOIN students s ON e.student_id = s.student_id
|
||||
JOIN courses c ON e.course_id = c.course_id
|
||||
WHERE (e.course_id, e.grade) IN (
|
||||
SELECT course_id, MAX(grade)
|
||||
FROM enrollments
|
||||
GROUP BY course_id
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
### 第四部分:TCL答案
|
||||
|
||||
61. SQL
|
||||
|
||||
```
|
||||
START TRANSACTION;
|
||||
-- 步骤a: 插入选课记录
|
||||
INSERT INTO enrollments (student_id, course_id, grade) VALUES (3, 3, NULL);
|
||||
-- 步骤b: 假设的更新账户操作
|
||||
-- UPDATE student_accounts SET balance = balance - 100 WHERE student_id = 3;
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
62. `COMMIT` 命令将事务中执行的所有更改(在此例中是 `INSERT` 语句)永久性地保存到数据库中。一旦提交,这些更改就成为数据库持久状态的一部分,并且对其他用户可见。
|
||||
|
||||
63. 事务的重要性在于保证**原子性(Atomicity)**。注册课程这个业务逻辑包含多个数据库操作,它们必须作为一个整体执行。如果只有插入选课记录成功,而扣费失败,那么数据就会处于不一致的状态。事务确保了这一系列操作要么全部成功(`COMMIT`),要么全部失败(`ROLLBACK`),从而维护了业务逻辑和数据的完整性。
|
||||
|
||||
64. `START TRANSACTION;` (或者它的同义词 `BEGIN;`)
|
||||
|
||||
65. SQL
|
||||
|
||||
```
|
||||
START TRANSACTION;
|
||||
-- 尝试插入选课记录
|
||||
INSERT INTO enrollments (student_id, course_id) VALUES (4, 4);
|
||||
-- 此时,应用逻辑发现前置课程不满足,决定中止
|
||||
ROLLBACK;
|
||||
```
|
||||
|
||||
66. `ROLLBACK` 命令会撤销当前事务中自 `START TRANSACTION` 以来所做的所有未提交的更改。在上题中,它会撤销那条 `INSERT` 语句,使得 `enrollments` 表恢复到事务开始前的状态,就好像那条 `INSERT` 语句从未执行过一样。
|
||||
|
||||
67. 如果MySQL的 `AUTOCOMMIT` 模式是开启的(默认情况),那么每一条SQL语句都会被视为一个独立的事务并被立即自动提交。在这种情况下,`ROLLBACK` 命令将不起任何作用,因为在它执行之前,前面的 `INSERT` 语句已经永久保存了。因此,要使用 `ROLLBACK`,必须先用 `START TRANSACTION` 显式地开启一个事务。
|
||||
|
||||
68. SQL
|
||||
|
||||
```
|
||||
START TRANSACTION;
|
||||
-- 注册第一门课
|
||||
INSERT INTO enrollments (student_id, course_id) VALUES (2, 2);
|
||||
SAVEPOINT savepoint_1;
|
||||
-- 注册第二门课
|
||||
INSERT INTO enrollments (student_id, course_id) VALUES (2, 3);
|
||||
SAVEPOINT savepoint_2;
|
||||
-- 尝试注册第三门课 (此操作在真实场景中会失败,这里我们用回滚模拟)
|
||||
-- INSERT INTO enrollments (student_id, course_id) VALUES (2, 5); -- 假设失败
|
||||
-- 发现失败,回滚到第二个保存点
|
||||
ROLLBACK TO savepoint_2;
|
||||
-- 最终提交事务,保留前两次成功的注册
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
69. 执行 `ROLLBACK TO savepoint_2;` 之后,数据库的状态是:为孙悦注册第二门课的操作(`INSERT` course\_id 3)被撤销了,但注册第一门课的操作(`INSERT` course\_id 2)仍然保留在当前事务中,等待最终的 `COMMIT` 或 `ROLLBACK`。
|
||||
|
||||
70. `SAVEPOINT` 提供了更细粒度的事务控制。在一个长事务中,如果某个中间步骤失败,我们不必撤销整个事务(`ROLLBACK`),而是可以选择性地回滚到某个已知的良好状态(`ROLLBACK TO SAVEPOINT`),然后继续执行其他操作或提交部分结果。这极大地增强了处理复杂业务逻辑时的灵活性。
|
||||
|
||||
71. **`AUTOCOMMIT`** 是MySQL的一个系统变量,它控制着事务的提交方式。
|
||||
|
||||
+ **当 `AUTOCOMMIT = 1` (默认值)**: 每一条SQL语句(如 `INSERT`, `UPDATE`, `DELETE`)在执行完毕后都会被立即自动提交,效果等同于在每条语句前后都加上了 `START TRANSACTION` 和 `COMMIT`。
|
||||
|
||||
+ **当 `AUTOCOMMIT = 0`**: 自动提交被禁用。你需要手动执行 `START TRANSACTION` 来开启一个事务,并通过 `COMMIT` 或 `ROLLBACK` 来结束它。在手动结束之前,所有的更改都只在当前会话中可见,并且是未提交的状态。 **关系**: 当我们使用 `START TRANSACTION` 时,它会暂时覆盖当前的 `AUTOCOMMIT` 行为,创建一个手动控制的事务块。在这个块内,只有 `COMMIT` 或 `ROLLBACK` 才能结束事务,`AUTOCOMMIT` 的设置被忽略。这使得我们能够将多条语句捆绑在一起,实现TCL的核心功能。
|
||||
@@ -1 +1,11 @@
|
||||
请针对
|
||||
请依照基础[agi_mysql_study.md](agi_mysql_study.md)的要求,修改[0-基础数据结构.md](0-%E5%9F%BA%E7%A1%80%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.md)中的内容,给出完整的MySQL基础数据结构用法的文档
|
||||
|
||||
|
||||
|
||||
## SQLite 题目
|
||||
请根据[agi_mysql_study.md](agi_mysql_study.md)的规范,作为一名专业的SQL老师,设计从数据格式,涵盖DDL DML DQL TCL的一系列题目,考察一名初学者对于MySQL学习的掌握能力
|
||||
每道题目需要有序号
|
||||
题目设计保持上下文连贯,如DQL基于DML插入的数值,DDL创建的表结构。如果数据不够,请在题目中给出插入数据命令
|
||||
每个大模块的题目,不少于10道
|
||||
DQL需要给出至少30道题目,需要包含inner join, group by, having等高阶查询方法
|
||||
要求答案放置于最后面,与前面的题目序号一一对应
|
||||
640
101-数据库学习/3-SQLite/9-题目/SQLite基础考察.md
Normal file
640
101-数据库学习/3-SQLite/9-题目/SQLite基础考察.md
Normal file
@@ -0,0 +1,640 @@
|
||||
# SQLite综合能力考核:大学数据库管理实战
|
||||
|
||||
## 介绍:大学数据库场景
|
||||
|
||||
为了全面考察您对SQLite的掌握程度,我们将围绕一个清晰、易于理解的场景来设计所有练习。这种叙事性的方法不仅能提升学习的趣味性,还能帮助您理解SQL命令在实际工作中的应用。
|
||||
|
||||
**场景描述:** 您将为一所小型大学设计并管理一个数据库。该数据库需要追踪讲师信息、他们教授的课程、注册课程的学生以及学生的选课和成绩详情。这是一个经典的关系模型,非常适合用来测试DDL(数据定义语言)、DML(数据操作语言)、DQL(数据查询语言)和TCL(事务控制语言)的各项概念。
|
||||
|
||||
**实体与关系:**
|
||||
|
||||
+ **讲师 (Instructors):** 每位讲师拥有姓名、邮箱和办公室编号。
|
||||
|
||||
+ **课程 (Courses):** 每门课程拥有课程名称、所属系别、学分,并由一位讲师授课。
|
||||
|
||||
+ **学生 (Students):** 每位学生拥有姓名、邮箱和专业。
|
||||
|
||||
+ **选课记录 (Enrollments):** 这是一个连接表,用于关联学生和课程,并记录学生在特定课程中获得的成绩。
|
||||
|
||||
|
||||
这个场景经过精心选择,因为它天然地包含了**一对多关系**(一位讲师可以教授多门课程)和**多对多关系**(一名学生可以选修多门课程,一门课程也可以被多名学生选修),为测试连接查询和参照完整性提供了坚实的基础。
|
||||
|
||||
* * *
|
||||
|
||||
## 第一部分:DDL (数据定义语言) - 构建数据库基础
|
||||
|
||||
本部分旨在考察您将业务需求转化为逻辑数据库结构的能力。这不仅考验您的DDL命令语法,还涉及数据规范化和数据完整性等设计原则。我们将重点考察 `CREATE TABLE`、数据类型与类型亲和性、`PRIMARY KEY`、`FOREIGN KEY`、`NOT NULL`、`UNIQUE`、`DEFAULT` 以及 `ALTER TABLE` 等命令。
|
||||
|
||||
**题目列表:**
|
||||
|
||||
1. 在SQLite中,外键约束默认是关闭的,这可能导致数据完整性问题 。为了确保后续操作的参照完整性,请写出为当前数据库连接开启外键约束强制执行的命令。
|
||||
|
||||
2. 创建一个名为 `Instructors` 的表,用于存储讲师信息。该表应包含以下列:
|
||||
|
||||
+ `instructor_id`: 整数类型,作为主键且能自动增长。
|
||||
|
||||
+ `first_name`: 文本类型,不能为空。
|
||||
|
||||
+ `last_name`: 文本类型,不能为空。
|
||||
|
||||
+ `email`: 文本类型,不能为空,且必须保证其值唯一。
|
||||
|
||||
3. 创建一个名为 `Students` 的表,用于存储学生信息。该表应包含以下列:
|
||||
|
||||
+ `student_id`: 整数类型,作为主键。
|
||||
|
||||
+ `first_name`: 文本类型,不能为空。
|
||||
|
||||
+ `last_name`: 文本类型,不能为空。
|
||||
|
||||
+ `email`: 文本类型,不能为空,且值唯一。
|
||||
|
||||
+ `major`: 文本类型,如果未指定,则默认值为 'Undeclared'。
|
||||
|
||||
4. 创建一个名为 `Courses` 的表,用于存储课程信息。该表应包含以下列:
|
||||
|
||||
+ `course_id`: 整数类型,作为主键。
|
||||
|
||||
+ `title`: 文本类型,不能为空。
|
||||
|
||||
+ `department`: 文本类型,不能为空(例如 'CSCI')。
|
||||
|
||||
+ `credits`: 整数类型。
|
||||
|
||||
+ `instructor_id`: 整数类型,该列应作为外键,引用 `Instructors` 表的 `instructor_id` 列。
|
||||
|
||||
5. 修改 `Courses` 表中 `instructor_id` 的外键约束。要求当 `Instructors` 表中的某位讲师记录被删除时,`Courses` 表中由该讲师授课的课程的 `instructor_id` 字段应自动被设置为 `NULL` 。
|
||||
|
||||
6. 创建一个名为 `Enrollments` 的连接表,用于记录学生选修课程的情况。该表应包含:
|
||||
|
||||
+ `student_id`: 整数类型。
|
||||
|
||||
+ `course_id`: 整数类型。
|
||||
|
||||
+ `grade`: 文本类型,用于存储成绩(如 'A', 'B+', 'C' 等)。
|
||||
|
||||
+ 将 `student_id` 和 `course_id` 的组合设置为主键(复合主键)。
|
||||
|
||||
假设学校规定,每个学生最多只能选修 5 门课程。仅在数据库层面(不依赖应用程序代码),我们如何实现这个约束?
|
||||
|
||||
(提示:查阅 SQLite 关于 CHECK 约束和子查询的文档,思考一个创造性的解决方案。)
|
||||
|
||||
7. 在 `Enrollments` 表上定义外键约束:
|
||||
|
||||
+ `student_id` 列引用 `Students` 表的 `student_id` 列。
|
||||
|
||||
+ `course_id` 列引用 `Courses` 表的 `course_id` 列。
|
||||
|
||||
8. 修改 `Enrollments` 表的外键约束。要求当 `Students` 表中的某个学生记录被删除时,该学生所有的选课记录也应自动从 `Enrollments` 表中被删除(级联删除)。
|
||||
|
||||
9. 使用 `ALTER TABLE` 命令为 `Instructors` 表添加一个新列,名为 `office_number`,类型为 `TEXT`。
|
||||
|
||||
10. 使用 `ALTER TABLE` 命令将 `Enrollments` 表重命名为 `Student_Enrollments`。
|
||||
|
||||
11. 创建一个名为 `Temp_Table` 的临时表(列定义不限),然后写出删除该表的命令。
|
||||
|
||||
|
||||
* * *
|
||||
|
||||
## 第二部分:DML (数据操作语言) - 填充与修改数据
|
||||
|
||||
本部分旨在考察您能否正确地插入、更新和删除数据,同时遵守在DDL阶段定义的各项约束。DML操作的成功与否,将直接反映您DDL设计的正确性。
|
||||
|
||||
**题目列表:**
|
||||
|
||||
1. 向 `Instructors` 表中插入以下三位讲师的数据:
|
||||
|
||||
+ (1, 'Alan', 'Turing', 'alan.turing@bletchley.edu', 'B-101')
|
||||
|
||||
+ (2, 'Grace', 'Hopper', 'grace.hopper@yale.edu', 'C-203')
|
||||
|
||||
+ (3, 'John', 'von Neumann', 'john.vn@ias.edu', 'A-305')
|
||||
|
||||
2. 向 `Students` 表中插入至少五名学生的数据,确保他们的 `student_id` 从101开始,并拥有不同的专业。
|
||||
|
||||
SQL
|
||||
|
||||
```
|
||||
INSERT INTO Students (student_id, first_name, last_name, email, major) VALUES
|
||||
(101, 'Alice', 'Smith', 'alice.smith@university.edu', 'Computer Science'),
|
||||
(102, 'Bob', 'Johnson', 'bob.johnson@university.edu', 'Physics'),
|
||||
(103, 'Charlie', 'Brown', 'charlie.brown@university.edu', 'History'),
|
||||
(104, 'Diana', 'Prince', 'diana.prince@university.edu', 'Computer Science'),
|
||||
(105, 'Eve', 'Adams', 'eve.adams@university.edu', 'Physics');
|
||||
```
|
||||
|
||||
3. 根据第一步插入的讲师ID,向 `Courses` 表中插入以下课程数据:
|
||||
|
||||
SQL
|
||||
|
||||
```
|
||||
INSERT INTO Courses (course_id, title, department, credits, instructor_id) VALUES
|
||||
(201, 'Introduction to CS', 'CSCI', 3, 1),
|
||||
(202, 'Advanced Compilers', 'CSCI', 4, 2),
|
||||
(203, 'Game Theory', 'MATH', 3, 3),
|
||||
(204, 'Databases', 'CSCI', 4, 1),
|
||||
(205, 'Quantum Mechanics', 'PHYS', 4, NULL);
|
||||
```
|
||||
|
||||
4. 向 `Student_Enrollments` 表(注意,该表已在DDL部分被重命名)中插入以下选课记录:
|
||||
|
||||
SQL
|
||||
|
||||
```
|
||||
INSERT INTO Student_Enrollments (student_id, course_id, grade) VALUES
|
||||
(101, 201, 'A'),
|
||||
(101, 204, 'B+'),
|
||||
(102, 205, 'A-'),
|
||||
(103, 203, 'B'),
|
||||
(104, 201, 'C'),
|
||||
(104, 202, 'A');
|
||||
```
|
||||
|
||||
5. **测试 `NOT NULL` 约束**:尝试向 `Instructors` 表中插入一条记录,但 `first_name` 字段留空。此操作预期会失败。请写出这条 `INSERT` 语句。
|
||||
|
||||
6. **测试 `UNIQUE` 约束**:尝试向 `Students` 表中插入一名新学生,其 `email` 与 'alice.smith@university.edu' 相同。此操作预期会因违反唯一性约束而失败。请写出这条 `INSERT` 语句。
|
||||
|
||||
7. **测试 `FOREIGN KEY` 约束**:尝试向 `Courses` 表中插入一门新课程,其 `instructor_id` 为999,而这个ID在 `Instructors` 表中并不存在。此操作预期会因违反外键约束而失败。请写出这条 `INSERT` 语句。
|
||||
|
||||
8. Alan Turing博士更换了办公室。请使用 `UPDATE` 语句,将他的 `office_number` 更新为 'B-105'。
|
||||
|
||||
9. 学校决定将 'CSCI' 系的缩写统一更改为 'COMP'。请使用 `UPDATE` 语句更新 `Courses` 表中所有 'CSCI' 系的课程。
|
||||
|
||||
**测试 `连表查询`** 查询学生'Bob Johnson' (student_id=102) 选取的所有课程信息
|
||||
|
||||
10. **测试 `ON DELETE CASCADE`**:学生 'Bob Johnson' (student\_id=102) 退学。请从 `Students` 表中删除他的记录。删除后,请查询 `Student_Enrollments` 表,验证他的选课记录是否也已被自动删除。
|
||||
|
||||
11. **测试 `ON DELETE SET NULL`**:讲师 'Grace Hopper' (instructor\_id=2) 离职。请从 `Instructors` 表中删除她的记录。删除后,请查询 `Courses` 表,验证她所教授课程的 `instructor_id` 是否已变为 `NULL`。
|
||||
|
||||
12. 删除 `Student_Enrollments` 表中所有成绩为 'C' 的记录。
|
||||
|
||||
|
||||
* * *
|
||||
|
||||
## 第三部分:DQL (数据查询语言) - 查询与分析数据
|
||||
|
||||
本部分将全面考察您从数据库中检索和分析数据的能力,从简单的单表查询逐步过渡到复杂的多表聚合查询。
|
||||
|
||||
**题目列表:**
|
||||
|
||||
#### 基础检索 (1-10)
|
||||
|
||||
1. 查询并显示 `Students` 表中的所有列及所有行。
|
||||
|
||||
2. 查询所有讲师的姓(`last_name`)和名(`first_name`)。
|
||||
|
||||
3. 查询所有属于 'COMP' 系(假设已在DML部分更新)的课程名称(`title`)。
|
||||
|
||||
4. 查询所有专业为 'Physics' 的学生信息,并按姓氏(`last_name`)升序排列。
|
||||
|
||||
5. 查询学分(`credits`)最高的3门课程的所有信息。
|
||||
|
||||
6. 查询 `Students` 表中所有不重复的专业(`major`)名称。
|
||||
|
||||
7. 查询课程名称(`title`)中包含 'Intro' 关键字的所有课程。
|
||||
|
||||
8. 查询专业为 'History' 或 'Physics' 的所有学生信息。
|
||||
|
||||
9. 查询所有办公室编号(`office_number`)未被指定的讲师。
|
||||
|
||||
10. 查询所有学分在3到4之间(包含3和4)的课程名称和学分。
|
||||
|
||||
|
||||
#### 聚合与分组 (11-20)
|
||||
|
||||
11. 统计 `Students` 表中共有多少名学生。
|
||||
|
||||
12. 计算 `Courses` 表中所有课程的平均学分是多少。
|
||||
|
||||
13. 统计选修了课程ID为201('Introduction to CS')的学生总人数。
|
||||
|
||||
14. 按专业(`major`)分组,统计每个专业的学生人数。
|
||||
|
||||
15. 按系别(`department`)分组,统计每个系别开设的课程数量。
|
||||
|
||||
16. 查询学生人数超过1人的专业名称以及对应的人数。
|
||||
|
||||
17. 按课程(`course_id`)分组,计算每门课程的平均成绩(提示:这在当前数据结构下无法直接计算,但请思考如何统计选课人数)。请统计每门课程的选课人数。
|
||||
|
||||
18. 查询开设课程总数超过1门的讲师ID及其开设的课程数。
|
||||
|
||||
19. 找出 `Courses` 表中学分最高和最低的课程的学分值。
|
||||
|
||||
20. 统计所有 'COMP' 系课程的总学分。
|
||||
|
||||
|
||||
#### 连接查询与复杂查询 (21-32)
|
||||
|
||||
21. 使用 `INNER JOIN` 查询所有选修了 'Introduction to CS' 这门课的学生的姓和名。
|
||||
|
||||
22. 使用 `INNER JOIN` 连接 `Student_Enrollments` 和 `Students` 表,列出每条选课记录对应的学生全名(姓和名拼接)和成绩。
|
||||
|
||||
23. 使用 `INNER JOIN` 连接 `Courses` 和 `Instructors` 表,列出每位讲师的全名以及他们所教授的课程名称。
|
||||
|
||||
24. **`LEFT JOIN` 应用**:查询所有讲师的全名,以及他们教授的课程名称。即使某位讲师没有教授任何课程,也需要显示在结果中(其课程名称显示为 `NULL`)。
|
||||
|
||||
25. **`LEFT JOIN` 应用**:查询所有没有教授任何课程的讲师的全名。
|
||||
|
||||
26. **`LEFT JOIN` 应用**:查询所有学生的姓名,以及他们选修的课程名称。即使某个学生没有选修任何课程,也需要显示在结果中。
|
||||
|
||||
27. **`LEFT JOIN` 应用**:查询所有没有选修任何课程的学生的姓名。
|
||||
|
||||
28. 使用子查询,查询由 'Alan Turing' 教授的所有课程的名称。
|
||||
|
||||
29. 使用子查询,查询选修了至少一门 'COMP' 系课程的所有学生的姓名。
|
||||
|
||||
30. 查询选课数量最多的学生的姓名。
|
||||
|
||||
31. 使用 `CASE` 语句,查询所有学生的姓名和专业,并添加一列名为 `remark` 的备注。如果专业是 'Computer Science',备注为 'Tech Major';如果专业是 'Physics',备注为 'Science Major';否则备注为 'Arts & Humanities'。
|
||||
|
||||
32. **逻辑执行顺序理解**:尝试按专业统计学生人数,并将统计结果列命名为 `student_count`。然后,使用 `WHERE student_count > 1` 来筛选结果。这个查询会失败。请解释为什么会失败,并写出使用 `HAVING` 子句的正确查询 。
|
||||
|
||||
|
||||
* * *
|
||||
|
||||
## 第四部分:TCL (事务控制语言) - 保证数据一致性
|
||||
|
||||
本部分旨在考察您对事务的理解,以及如何使用事务来确保在执行多步操作时数据库的原子性和一致性。ACID原则是数据库管理的核心概念之一 。
|
||||
|
||||
**题目列表:**
|
||||
|
||||
1. **基本事务**:开启一个事务,向 `Students` 表中插入一名新学生 (student\_id: 106, name: 'Frank', 'Castle', email: 'frank.castle@university.edu', major: 'Law'),然后提交事务,使更改永久生效。
|
||||
|
||||
2. **`ROLLBACK` 场景**:开启一个事务,删除 `Students` 表中所有专业为 'Undeclared' 的学生,然后执行 `ROLLBACK` 命令。最后,查询 `Students` 表,验证被删除的学生记录是否已恢复。
|
||||
|
||||
3. **原子操作场景**:学生 'Alice Smith' (student\_id: 101) 决定从 'Databases' (course\_id: 204) 这门课退选,并改选 'Game Theory' (course\_id: 203)。这个操作需要两步:从 `Student_Enrollments` 中删除一条记录,并插入一条新记录。请将这两个操作放在一个事务中,确保它们要么都成功,要么都失败。
|
||||
|
||||
4. **约束冲突与自动回滚**:开启一个事务。首先,将学生 'Charlie Brown' (student\_id: 103) 的专业更新为 'Philosophy'。然后,尝试插入一名新学生,但其 `email` 与已存在的学生 'Diana Prince' 冲突。由于第二步违反了 `UNIQUE` 约束会失败,整个事务会自动回滚 。请问,'Charlie Brown' 的专业最终是什么?请通过查询验证。
|
||||
|
||||
5. **`SAVEPOINT` 应用**:在一个事务中,按顺序执行以下操作: a. 插入两名新的 'Art' 专业学生。 b. 创建一个名为 `after_art_students` 的保存点。 c. 插入两名新的 'Music' 专业学生。 d. 回滚到 `after_art_students` 这个保存点。 e. 提交事务。 请问,最终哪些专业的学生被永久保存到了数据库中?
|
||||
|
||||
6. **复杂原子操作**:为一名学生办理毕业手续。这需要:a) 从 `Student_Enrollments` 表中删除该学生的所有选课记录;b) 从 `Students` 表中删除该学生的记录。请为学生 'Diana Prince' (student\_id: 104) 编写一个事务来完成此操作,确保数据的一致性。
|
||||
|
||||
7. 根据上一题的毕业操作,解释ACID属性中的 'A' (Atomicity - 原子性) 是如何体现的。
|
||||
|
||||
8. 以 `Student_Enrollments` 表的外键约束为例,解释ACID属性中的 'C' (Consistency - 一致性) 是如何保证的。
|
||||
|
||||
9. 解释在何种情况下,您可能会选择使用 `BEGIN IMMEDIATE TRANSACTION` 而不是默认的延迟事务(`BEGIN DEFERRED TRANSACTION`)。
|
||||
|
||||
10. 一个脚本需要更新100门不同课程的学分。相比于让SQLite为每条 `UPDATE` 语句自动提交事务,为什么将这100条语句包裹在一个单独的事务中会更高效?
|
||||
|
||||
|
||||
* * *
|
||||
|
||||
## 答案
|
||||
|
||||
### 第一部分:DDL 答案
|
||||
|
||||
1. SQL
|
||||
|
||||
```
|
||||
PRAGMA foreign_keys = ON;
|
||||
```
|
||||
|
||||
2. SQL
|
||||
|
||||
```
|
||||
CREATE TABLE Instructors (
|
||||
instructor_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE
|
||||
);
|
||||
```
|
||||
|
||||
3. SQL
|
||||
|
||||
```
|
||||
CREATE TABLE Students (
|
||||
student_id INTEGER PRIMARY KEY,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
major TEXT DEFAULT 'Undeclared'
|
||||
);
|
||||
```
|
||||
|
||||
4. SQL
|
||||
|
||||
```
|
||||
CREATE TABLE Courses (
|
||||
course_id INTEGER PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
department TEXT NOT NULL,
|
||||
credits INTEGER,
|
||||
instructor_id INTEGER,
|
||||
FOREIGN KEY (instructor_id) REFERENCES Instructors(instructor_id)
|
||||
);
|
||||
```
|
||||
|
||||
5. \-- 需要先删除旧表,再用新的约束创建。在实际操作中,这意味着需要备份数据。
|
||||
|
||||
SQL
|
||||
|
||||
```
|
||||
-- 假设表已存在,需要先删除再重建(或使用ALTER TABLE在新版SQLite中)
|
||||
-- 为简化题目,这里提供带ON DELETE SET NULL的完整创建语句
|
||||
DROP TABLE Courses;
|
||||
CREATE TABLE Courses (
|
||||
course_id INTEGER PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
department TEXT NOT NULL,
|
||||
credits INTEGER,
|
||||
instructor_id INTEGER,
|
||||
FOREIGN KEY (instructor_id) REFERENCES Instructors(instructor_id) ON DELETE SET NULL
|
||||
);
|
||||
```
|
||||
|
||||
6. SQL
|
||||
|
||||
```
|
||||
CREATE TABLE Enrollments (
|
||||
student_id INTEGER,
|
||||
course_id INTEGER,
|
||||
grade TEXT,
|
||||
PRIMARY KEY (student_id, course_id)
|
||||
);
|
||||
```
|
||||
|
||||
7. \-- 同样,这里提供带完整外键定义的创建语句
|
||||
|
||||
SQL
|
||||
|
||||
```
|
||||
DROP TABLE Enrollments;
|
||||
CREATE TABLE Enrollments (
|
||||
student_id INTEGER,
|
||||
course_id INTEGER,
|
||||
grade TEXT,
|
||||
PRIMARY KEY (student_id, course_id),
|
||||
FOREIGN KEY (student_id) REFERENCES Students(student_id),
|
||||
FOREIGN KEY (course_id) REFERENCES Courses(course_id)
|
||||
);
|
||||
```
|
||||
|
||||
8. \-- 提供带ON DELETE CASCADE的完整创建语句
|
||||
|
||||
SQL
|
||||
|
||||
```
|
||||
DROP TABLE Enrollments;
|
||||
CREATE TABLE Enrollments (
|
||||
student_id INTEGER,
|
||||
course_id INTEGER,
|
||||
grade TEXT,
|
||||
PRIMARY KEY (student_id, course_id),
|
||||
FOREIGN KEY (student_id) REFERENCES Students(student_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (course_id) REFERENCES Courses(course_id)
|
||||
);
|
||||
```
|
||||
|
||||
9. SQL
|
||||
|
||||
```
|
||||
ALTER TABLE Instructors ADD COLUMN office_number TEXT;
|
||||
```
|
||||
|
||||
10. SQL
|
||||
|
||||
```
|
||||
ALTER TABLE Enrollments RENAME TO Student_Enrollments;
|
||||
```
|
||||
|
||||
11. SQL
|
||||
|
||||
```
|
||||
CREATE TABLE Temp_Table (id INTEGER);
|
||||
DROP TABLE Temp_Table;
|
||||
```
|
||||
|
||||
|
||||
### 第二部分:DML 答案
|
||||
|
||||
1. SQL
|
||||
|
||||
```
|
||||
-- 注意:在DDL第9题后,Instructors表结构已改变。
|
||||
-- 为确保数据插入成功,需要更新instructor_id=1的记录。
|
||||
INSERT INTO Instructors (instructor_id, first_name, last_name, email) VALUES
|
||||
(1, 'Alan', 'Turing', 'alan.turing@bletchley.edu'),
|
||||
(2, 'Grace', 'Hopper', 'grace.hopper@yale.edu'),
|
||||
(3, 'John', 'von Neumann', 'john.vn@ias.edu');
|
||||
UPDATE Instructors SET office_number = 'B-101' WHERE instructor_id = 1;
|
||||
UPDATE Instructors SET office_number = 'C-203' WHERE instructor_id = 2;
|
||||
UPDATE Instructors SET office_number = 'A-305' WHERE instructor_id = 3;
|
||||
```
|
||||
|
||||
2. SQL
|
||||
|
||||
```
|
||||
INSERT INTO Students (student_id, first_name, last_name, email, major) VALUES
|
||||
(101, 'Alice', 'Smith', 'alice.smith@university.edu', 'Computer Science'),
|
||||
(102, 'Bob', 'Johnson', 'bob.johnson@university.edu', 'Physics'),
|
||||
(103, 'Charlie', 'Brown', 'charlie.brown@university.edu', 'History'),
|
||||
(104, 'Diana', 'Prince', 'diana.prince@university.edu', 'Computer Science'),
|
||||
(105, 'Eve', 'Adams', 'eve.adams@university.edu', 'Physics');
|
||||
```
|
||||
|
||||
3. SQL
|
||||
|
||||
```
|
||||
INSERT INTO Courses (course_id, title, department, credits, instructor_id) VALUES
|
||||
(201, 'Introduction to CS', 'CSCI', 3, 1),
|
||||
(202, 'Advanced Compilers', 'CSCI', 4, 2),
|
||||
(203, 'Game Theory', 'MATH', 3, 3),
|
||||
(204, 'Databases', 'CSCI', 4, 1),
|
||||
(205, 'Quantum Mechanics', 'PHYS', 4, NULL);
|
||||
```
|
||||
|
||||
4. SQL
|
||||
|
||||
```
|
||||
INSERT INTO Student_Enrollments (student_id, course_id, grade) VALUES
|
||||
(101, 201, 'A'),
|
||||
(101, 204, 'B+'),
|
||||
(102, 205, 'A-'),
|
||||
(103, 203, 'B'),
|
||||
(104, 201, 'C'),
|
||||
(104, 202, 'A');
|
||||
```
|
||||
|
||||
5. SQL
|
||||
|
||||
```
|
||||
INSERT INTO Instructors (last_name, email) VALUES ('Curie', 'marie.curie@sorbonne.edu');
|
||||
-- 这将返回一个错误,因为 first_name 违反了 NOT NULL 约束。
|
||||
```
|
||||
|
||||
6. SQL
|
||||
|
||||
```
|
||||
INSERT INTO Students (student_id, first_name, last_name, email, major)
|
||||
VALUES (106, 'Alex', 'Ray', 'alice.smith@university.edu', 'Biology');
|
||||
-- 这将返回一个错误,因为 email 违反了 UNIQUE 约束。
|
||||
```
|
||||
|
||||
7. SQL
|
||||
|
||||
```
|
||||
INSERT INTO Courses (course_id, title, department, credits, instructor_id)
|
||||
VALUES (206, 'Artificial Intelligence', 'CSCI', 4, 999);
|
||||
-- 这将返回一个错误,因为 instructor_id 违反了 FOREIGN KEY 约束。
|
||||
```
|
||||
|
||||
8. SQL
|
||||
|
||||
```
|
||||
UPDATE Instructors SET office_number = 'B-105' WHERE instructor_id = 1;
|
||||
```
|
||||
|
||||
9. SQL
|
||||
|
||||
```
|
||||
UPDATE Courses SET department = 'COMP' WHERE department = 'CSCI';
|
||||
```
|
||||
|
||||
10. SQL
|
||||
|
||||
```
|
||||
DELETE FROM Students WHERE student_id = 102;
|
||||
-- 查询验证 (应返回0行)
|
||||
SELECT * FROM Student_Enrollments WHERE student_id = 102;
|
||||
```
|
||||
|
||||
11. SQL
|
||||
|
||||
```
|
||||
DELETE FROM Instructors WHERE instructor_id = 2;
|
||||
-- 查询验证 (instructor_id应为NULL)
|
||||
SELECT * FROM Courses WHERE course_id = 202;
|
||||
```
|
||||
|
||||
12. SQL
|
||||
|
||||
```
|
||||
DELETE FROM Student_Enrollments WHERE grade = 'C';
|
||||
```
|
||||
|
||||
|
||||
### 第三部分:DQL 答案
|
||||
|
||||
1. `SELECT * FROM Students;`
|
||||
|
||||
2. `SELECT last_name, first_name FROM Instructors;`
|
||||
|
||||
3. `SELECT title FROM Courses WHERE department = 'COMP';`
|
||||
|
||||
4. `SELECT * FROM Students WHERE major = 'Physics' ORDER BY last_name ASC;`
|
||||
|
||||
5. `SELECT * FROM Courses ORDER BY credits DESC LIMIT 3;`
|
||||
|
||||
6. `SELECT DISTINCT major FROM Students;`
|
||||
|
||||
7. `SELECT * FROM Courses WHERE title LIKE '%Intro%';`
|
||||
|
||||
8. `SELECT * FROM Students WHERE major IN ('History', 'Physics');`
|
||||
|
||||
9. `SELECT * FROM Instructors WHERE office_number IS NULL;`
|
||||
|
||||
10. `SELECT title, credits FROM Courses WHERE credits BETWEEN 3 AND 4;`
|
||||
|
||||
11. `SELECT COUNT(*) FROM Students;`
|
||||
|
||||
12. `SELECT AVG(credits) FROM Courses;`
|
||||
|
||||
13. `SELECT COUNT(*) FROM Student_Enrollments WHERE course_id = 201;`
|
||||
|
||||
14. `SELECT major, COUNT(*) FROM Students GROUP BY major;`
|
||||
|
||||
15. `SELECT department, COUNT(*) FROM Courses GROUP BY department;`
|
||||
|
||||
16. `SELECT major, COUNT(*) FROM Students GROUP BY major HAVING COUNT(*) > 1;`
|
||||
|
||||
17. `SELECT course_id, COUNT(student_id) AS num_students FROM Student_Enrollments GROUP BY course_id;`
|
||||
|
||||
18. `SELECT instructor_id, COUNT(*) FROM Courses GROUP BY instructor_id HAVING COUNT(*) > 1;`
|
||||
|
||||
19. `SELECT MAX(credits), MIN(credits) FROM Courses;`
|
||||
|
||||
20. `SELECT SUM(credits) FROM Courses WHERE department = 'COMP';`
|
||||
|
||||
21. `SELECT s.first_name, s.last_name FROM Students s INNER JOIN Student_Enrollments se ON s.student_id = se.student_id INNER JOIN Courses c ON se.course_id = c.course_id WHERE c.title = 'Introduction to CS';`
|
||||
|
||||
22. `SELECT s.first_name || ' ' | | s.last_name AS full_name, se.grade FROM Students s INNER JOIN Student_Enrollments se ON s.student_id = se.student_id;`
|
||||
|
||||
23. `SELECT i.first_name || ' ' | | i.last_name AS instructor_name, c.title FROM Instructors i INNER JOIN Courses c ON i.instructor_id = c.instructor_id;`
|
||||
|
||||
24. `SELECT i.first_name, i.last_name, c.title FROM Instructors i LEFT JOIN Courses c ON i.instructor_id = c.instructor_id;`
|
||||
|
||||
25. `SELECT i.first_name, i.last_name FROM Instructors i LEFT JOIN Courses c ON i.instructor_id = c.instructor_id WHERE c.course_id IS NULL;`
|
||||
|
||||
26. `SELECT s.first_name, s.last_name, c.title FROM Students s LEFT JOIN Student_Enrollments se ON s.student_id = se.student_id LEFT JOIN Courses c ON se.course_id = c.course_id;`
|
||||
|
||||
27. `SELECT s.first_name, s.last_name FROM Students s LEFT JOIN Student_Enrollments se ON s.student_id = se.student_id WHERE se.course_id IS NULL;`
|
||||
|
||||
28. `SELECT title FROM Courses WHERE instructor_id = (SELECT instructor_id FROM Instructors WHERE first_name = 'Alan' AND last_name = 'Turing');`
|
||||
|
||||
29. `SELECT first_name, last_name FROM Students WHERE student_id IN (SELECT student_id FROM Student_Enrollments WHERE course_id IN (SELECT course_id FROM Courses WHERE department = 'COMP'));`
|
||||
|
||||
30. `SELECT s.first_name, s.last_name FROM Students s JOIN Student_Enrollments se ON s.student_id = se.student_id GROUP BY s.student_id ORDER BY COUNT(se.course_id) DESC LIMIT 1;`
|
||||
|
||||
31. ```sql SELECT first_name, last_name, major, CASE major WHEN 'Computer Science' THEN 'Tech Major' WHEN 'Physics' THEN 'Science Major' ELSE 'Arts & Humanities' END AS remark FROM Students; ```
|
||||
|
||||
32. **解释**:失败是因为SQL的逻辑执行顺序是`FROM`->`WHERE`->`GROUP BY`->`HAVING`->`SELECT`->`ORDER BY`。`WHERE`子句在`SELECT`子句之前执行,因此在执行`WHERE`时,别名`student_count `尚未被创建。`HAVING`子句在`GROUP BY` 之后执行,用于过滤聚合后的结果,所以可以使用聚合函数。 **正确查询**: `sql SELECT major, COUNT(*) AS student_count FROM Students GROUP BY major HAVING COUNT(*) > 1;`
|
||||
|
||||
### 第四部分:TCL 答案
|
||||
|
||||
1. SQL
|
||||
|
||||
```
|
||||
BEGIN TRANSACTION;
|
||||
INSERT INTO Students (student_id, first_name, last_name, email, major)
|
||||
VALUES (106, 'Frank', 'Castle', 'frank.castle@university.edu', 'Law');
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
2. SQL
|
||||
|
||||
```
|
||||
BEGIN TRANSACTION;
|
||||
-- 假设有一个默认专业为'Undeclared'的学生
|
||||
INSERT INTO Students (student_id, first_name, last_name, email) VALUES (107, 'John', 'Doe', 'john.doe@university.edu');
|
||||
DELETE FROM Students WHERE major = 'Undeclared';
|
||||
ROLLBACK;
|
||||
-- 查询验证,John Doe 应该仍然存在
|
||||
SELECT * FROM Students WHERE student_id = 107;
|
||||
```
|
||||
|
||||
3. SQL
|
||||
|
||||
```
|
||||
BEGIN TRANSACTION;
|
||||
DELETE FROM Student_Enrollments WHERE student_id = 101 AND course_id = 204;
|
||||
INSERT INTO Student_Enrollments (student_id, course_id, grade) VALUES (101, 203, NULL);
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
4. **最终专业**:'History'。因为事务中的第二步失败导致整个事务自动回滚,第一步的 `UPDATE` 操作也被撤销了。 **验证查询**:`SELECT major FROM Students WHERE student_id = 103;`
|
||||
|
||||
5. **最终结果**:只有 'Art' 专业的学生被永久保存。因为事务回滚到了保存点 `after_art_students`,这撤销了插入 'Music' 专业学生的操作,然后提交了之前的操作。
|
||||
|
||||
6. SQL
|
||||
|
||||
```
|
||||
BEGIN TRANSACTION;
|
||||
DELETE FROM Student_Enrollments WHERE student_id = 104;
|
||||
DELETE FROM Students WHERE student_id = 104;
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
7. **原子性(Atomicity)** 体现在毕业操作的两个步骤(删除选课记录和删除学生记录)被视为一个不可分割的单元。如果其中任何一步失败(例如,由于权限问题),整个事务将回滚,数据库将恢复到操作开始前的状态,从而避免了“学生记录已删除但选课记录仍存在”这种不一致的状态。
|
||||
|
||||
8. **一致性(Consistency)** 保证了事务将数据库从一个有效的状态转换到另一个有效的状态。`Student_Enrollments` 表的外键约束确保了任何插入的选课记录都必须关联一个真实存在的学生和一个真实存在的课程。任何试图破坏这种关系的DML操作(如为不存在的学生添加选课记录)都会失败,从而维护了数据库在事务前后的一致性。
|
||||
|
||||
9. 默认的延迟事务(`DEFERRED`)直到第一次实际读写数据库时才加锁。而 `IMMEDIATE` 事务在 `BEGIN` 命令执行时就立即尝试获取一个保留锁(写锁的前置锁)。当您预知即将进行写操作,并希望尽早确保事务不会因为其他写操作而失败(返回 `SQLITE_BUSY`)时,应使用 `IMMEDIATE`。这可以减少事务中途失败的可能性。
|
||||
|
||||
10. 将100条 `UPDATE` 语句包裹在单个事务中更高效,主要有两个原因:
|
||||
|
||||
+ **减少磁盘I/O**:SQLite的默认模式下,每次提交事务都需要将日志文件同步到磁盘,这是一个相对缓慢的操作。100次自动提交意味着100次磁盘同步。而单个事务只需要在最后 `COMMIT` 时进行一次磁盘同步,大大减少了I/O开销。
|
||||
|
||||
+ **原子性保证**:如果脚本在更新到第50条时失败,单个事务可以确保所有已做的更改都被回滚,数据库保持一致。而100个独立事务则会导致前49次更新已永久保存,数据库处于一个不完整的中间状态。
|
||||
271
101-数据库学习/3-SQLite/9-题目/SQLite基础考察.sql
Normal file
271
101-数据库学习/3-SQLite/9-题目/SQLite基础考察.sql
Normal file
@@ -0,0 +1,271 @@
|
||||
PRAGMA forien_keys = ON;
|
||||
|
||||
PRAGMA forien_keys;
|
||||
|
||||
CREATE TABLE instructors (
|
||||
instructor_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE students (
|
||||
student_id INTEGER PRIMARY KEY,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
major TEXT DEFAULT "Undeclared"
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE courses (
|
||||
course_id INTEGER PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
department TEXT NOT NULL,
|
||||
credits INTEGER,
|
||||
instructor_id INTEGER ,
|
||||
|
||||
CONSTRAINT fk_courses_instructor FOREIGN KEY(instructor_id) REFERENCES instructors(instructor_id)
|
||||
);
|
||||
|
||||
-- 修改 Courses 表中 instructor_id 的外键约束。要求当 Instructors 表中的某位讲师记录被删除时,Courses 表中由该讲师授课的课程的 instructor_id 字段应自动被设置为 NULL 。
|
||||
CREATE TABLE courses_back AS SELECT * FROM courses;
|
||||
DROP TABLE courses;
|
||||
CREATE TABLE courses (
|
||||
course_id INTEGER PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
department TEXT NOT NULL,
|
||||
credits INTEGER,
|
||||
instructor_id INTEGER ,
|
||||
|
||||
CONSTRAINT fk_courses_instructor FOREIGN KEY(instructor_id) REFERENCES instructors(instructor_id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
INSERT INTO courses SELECT * FROM courses_back;
|
||||
DROP TABLE courses_back;
|
||||
|
||||
-- 创建一个名为 Enrollments 的连接表,用于记录学生选修课程的情况。该表应包含:
|
||||
--
|
||||
-- student_id: 整数类型。
|
||||
--
|
||||
-- course_id: 整数类型。
|
||||
--
|
||||
-- grade: 文本类型,用于存储成绩(如 'A', 'B+', 'C' 等)。
|
||||
--
|
||||
-- 将 student_id 和 course_id 的组合设置为主键(复合主键)。
|
||||
create table enrollments (
|
||||
student_id INTEGER NOT NULL,
|
||||
course_id INTEGER NOT NULL,
|
||||
grade TEXT,
|
||||
|
||||
-- 想象一下你的身份证号(假设前6位是地区码,后8位是个人编码)。单看地区码无法确定你是谁(一个地区有千万人),单看个人编码也可能重复(不同地区可能有相同个人编码)。但“地区码+个人编码”的组合就能在全国范围内唯一锁定你这个人。这个组合就是“复合主键”。
|
||||
-- 定义复合主键:一个学生不能多次选修同一门课程
|
||||
PRIMARY KEY (student_id, course_id),
|
||||
-- 要求当 Students 表中的某个学生记录被删除时,该学生所有的选课记录也应自动从 Enrollments 表中被删除(级联删除)
|
||||
FOREIGN KEY (student_id) REFERENCES students(student_id) on delete cascade ,
|
||||
FOREIGN KEY (course_id) REFERENCES courses(course_id)
|
||||
);
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
-- 使用 ALTER TABLE 命令为 Instructors 表添加一个新列,名为 office_number,类型为 TEXT。
|
||||
alter table instructors add column office_number TEXT;
|
||||
|
||||
-- 使用 ALTER TABLE 命令将 Enrollments 表重命名为 Student_Enrollments。
|
||||
alter table enrollments rename to student_enrollments;
|
||||
|
||||
drop table student_enrollments;
|
||||
drop table sqlite_master;
|
||||
|
||||
-- 创建一个名为 Temp_Table 的临时表(列定义不限),然后写出删除该表的命令。
|
||||
-- 临时表最大的优点是无需手动管理清理,断开连接后自动消失,避免了垃圾数据累积。 wdd:有何意义?
|
||||
|
||||
-- 向 Instructors 表中插入以下三位讲师的数据:
|
||||
--
|
||||
-- (1, 'Alan', 'Turing', 'alan.turing@bletchley.edu', 'B-101')
|
||||
--
|
||||
-- (2, 'Grace', 'Hopper', 'grace.hopper@yale.edu', 'C-203')
|
||||
--
|
||||
-- (3, 'John', 'von Neumann', 'john.vn@ias.edu', 'A-305')
|
||||
|
||||
insert into instructors values
|
||||
(1, 'Alan', 'Turing', 'alan.turing@bletchley.edu', 'B-101'),
|
||||
(2, 'Grace', 'Hopper', 'grace.hopper@yale.edu', 'C-203'),
|
||||
(3, 'John', 'von Neumann', 'john.vn@ias.edu', 'A-305');
|
||||
|
||||
|
||||
INSERT INTO students VALUES
|
||||
(101, 'Alice', 'Smith', 'alice.smith@university.edu', 'Computer Science'),
|
||||
(102, 'Bob', 'Johnson', 'bob.johnson@university.edu', 'Physics'),
|
||||
(103, 'Charlie', 'Brown', 'charlie.brown@university.edu', 'History'),
|
||||
(104, 'Diana', 'Prince', 'diana.prince@university.edu', 'Computer Science'),
|
||||
(105, 'Eve', 'Adams', 'eve.adams@university.edu', 'Physics');
|
||||
|
||||
|
||||
INSERT INTO courses (course_id, title, department, credits, instructor_id) VALUES
|
||||
(201, 'Introduction to CS', 'CSCI', 3, 1),
|
||||
(202, 'Advanced Compilers', 'CSCI', 4, 2),
|
||||
(203, 'Game Theory', 'MATH', 3, 3),
|
||||
(204, 'Databases', 'CSCI', 4, 1),
|
||||
(205, 'Quantum Mechanics', 'PHYS', 4, NULL);
|
||||
|
||||
INSERT INTO enrollments (student_id, course_id, grade) VALUES
|
||||
(101, 201, 'A'),
|
||||
(101, 204, 'B+'),
|
||||
(102, 205, 'A-'),
|
||||
(103, 203, 'B'),
|
||||
(104, 201, 'C'),
|
||||
(104, 202, 'A');
|
||||
|
||||
-- 测试 NOT NULL 约束:尝试向 Instructors 表中插入一条记录,但 first_name 字段留空。此操作预期会失败。请写出这条 INSERT 语句。
|
||||
insert into instructors (instructor_id, first_name, last_name, office_number, email) values (233, null, "yes", "CS-1", "ok@qq.com");
|
||||
|
||||
-- 测试 FOREIGN KEY 约束:尝试向 Courses 表中插入一门新课程,其 instructor_id 为999,而这个ID在 Instructors 表中并不存在。此操作预期会因违反外键约束而失败。请写出这条 INSERT 语句。
|
||||
insert into courses (course_id, title, department, credits, instructor_id) values (123, "测试课程", "fts3tokenize", 12, 999);
|
||||
|
||||
-- Alan Turing博士更换了办公室。请使用 UPDATE 语句,将他的 office_number 更新为 'B-105'。
|
||||
update instructors set office_number="B-105" where first_name="Alan" and last_name="Turing";
|
||||
|
||||
-- 学校决定将 'CSCI' 系的缩写统一更改为 'COMP'。请使用 UPDATE 语句更新 Courses 表中所有 'CSCI' 系的课程。
|
||||
update courses set title="COMP" where title = "CSCI";
|
||||
|
||||
-- 测试 ON DELETE CASCADE:学生 'Bob Johnson' (student_id=102) 退学。请从 Students 表中删除他的记录。删除后,请查询 Student_Enrollments 表,验证他的选课记录是否也已被自动删除
|
||||
delete from students where student_id=102;
|
||||
|
||||
-- 连表查询 查询学生'Bob Johnson' (student_id=102) 选取的所有课程信息
|
||||
select e.student_id, e.course_id, c.title, c.department, c.credits from enrollments e
|
||||
INNER JOIN courses c on e.course_id = c.course_id where e.student_id = 102;
|
||||
|
||||
-- 测试 ON DELETE SET NULL:讲师 'Grace Hopper' (instructor_id=2) 离职。请从 Instructors 表中删除她的记录。删除后,请查询 Courses 表,验证她所教授课程的 instructor_id 是否已变为 NULL。
|
||||
select * from instructors i inner join courses c on i.instructor_id = c.instructor_id where i.instructor_id =2;
|
||||
|
||||
delete from instructors where instructor_id=2;
|
||||
|
||||
-- 删除 Student_Enrollments 表中所有成绩为 'C' 的记录。
|
||||
select * from enrollments;
|
||||
delete from enrollments where grade = 'C';
|
||||
|
||||
-- 查询并显示 Students 表中的所有列及所有行。
|
||||
select * from students;
|
||||
|
||||
|
||||
-- 查询所有讲师的姓(last_name)和名(first_name)。
|
||||
select last_name, first_name from instructors;
|
||||
|
||||
-- 查询所有属于 'COMP' 系(假设已在DML部分更新)的课程名称(title)。
|
||||
select title from courses where department = 'COMP';
|
||||
|
||||
|
||||
-- 查询所有专业为 'Physics' 的学生信息,并按姓氏(last_name)升序排列。
|
||||
select * from students where major = 'Physics' order by last_name asc ;
|
||||
|
||||
-- 查询学分(credits)最高的3门课程的所有信息。
|
||||
select * from courses order by credits desc limit 3;
|
||||
|
||||
-- 查询 Students 表中所有不重复的专业(major)名称。
|
||||
select distinct major from students;
|
||||
|
||||
-- 查询课程名称(title)中包含 'Intro' 关键字的所有课程。
|
||||
select * from courses where title like '%Intro%';
|
||||
|
||||
-- 查询专业为 'History' 或 'Physics' 的所有学生信息。
|
||||
select * from students where major = 'History' or 'Physics';
|
||||
|
||||
-- 查询所有办公室编号(office_number)未被指定的讲师。
|
||||
select * from instructors where office_number = '' or null;
|
||||
|
||||
-- 查询所有学分在3到4之间(包含3和4)的课程名称和学分。
|
||||
select * from courses where credits between 3 and 4;
|
||||
|
||||
|
||||
-- 统计 Students 表中共有多少名学生。
|
||||
select count(*) from students;
|
||||
|
||||
-- 计算 Courses 表中所有课程的平均学分是多少。
|
||||
select avg(credits) avg_credits from courses;
|
||||
|
||||
-- 统计选修了课程ID为201('Introduction to CS')的学生总人数。
|
||||
select count(student_id) from enrollments where course_id = 201;
|
||||
|
||||
-- 按专业(major)分组,统计每个专业的学生人数。
|
||||
select count(student_id) student_count, major from students group by major;
|
||||
|
||||
-- 按系别(department)分组,统计每个系别开设的课程数量。
|
||||
select count(course_id) course_count, courses.department from courses group by department;
|
||||
|
||||
-- 查询学生人数超过1人的专业名称以及对应的人数。
|
||||
SELECT major, COUNT(*) FROM Students GROUP BY major HAVING COUNT(*) > 1;;
|
||||
|
||||
-- 按课程(course_id)分组,计算每门课程的平均成绩(提示:这在当前数据结构下无法直接计算,但请思考如何统计选课人数)。
|
||||
--
|
||||
-- 请统计每门课程的选课人数。
|
||||
select c.course_id, count(e.student_id), e.grade from courses c inner join enrollments e on c.course_id = e.course_id group by c.course_id;
|
||||
|
||||
-- 查询开设课程总数超过1门的讲师ID及其开设的课程数。
|
||||
select i.instructor_id, count(c.course_id) from instructors i inner join courses c on i.instructor_id = c.instructor_id group by i.instructor_id;
|
||||
|
||||
-- 找出 Courses 表中学分最高和最低的课程的学分值。
|
||||
-- select credits from courses group by credits ;
|
||||
-- 找出学分最高和最低的值
|
||||
SELECT
|
||||
MAX(credits) AS highest_credits,
|
||||
MIN(credits) AS lowest_credits
|
||||
FROM courses;
|
||||
-- 如果想要同时查看这些极值对应的课程信息
|
||||
SELECT *
|
||||
FROM courses
|
||||
WHERE credits = (SELECT MAX(credits) FROM courses)
|
||||
OR credits = (SELECT MIN(credits) FROM courses);
|
||||
|
||||
-- 统计所有 'COMP' 系课程的总学分。
|
||||
select sum(credits) from courses where department='CSCI';
|
||||
|
||||
-- 使用 INNER JOIN 查询所有选修了 'Introduction to CS' 这门课的学生的姓和名。
|
||||
select * from courses c inner join enrollments e on c.course_id = e.course_id inner join students s on s.student_id = e.student_id where c.title = 'Introduction to CS';
|
||||
|
||||
-- 使用 INNER JOIN 连接 Student_Enrollments 和 Students 表,列出每条选课记录对应的学生全名(姓和名拼接)和成绩。
|
||||
select s.last_name || ' ' || s.first_name as full_name, e.grade from enrollments e inner join students s on s.student_id = e.student_id;
|
||||
|
||||
|
||||
-- 使用 INNER JOIN 连接 Courses 和 Instructors 表,列出每位讲师的全名以及他们所教授的课程名称。
|
||||
select s.last_name || ' ' || s.first_name as full_name, c.title from instructors s inner join courses c on s.instructor_id = c.instructor_id;
|
||||
|
||||
-- LEFT JOIN 应用:查询所有讲师的全名,以及他们教授的课程名称。即使某位讲师没有教授任何课程,也需要显示在结果中(其课程名称显示为 NULL)。
|
||||
select s.last_name || ' ' || s.first_name as full_name, c.title from instructors s left join courses c on s.instructor_id = c.instructor_id;
|
||||
|
||||
-- LEFT JOIN 应用:查询所有没有教授任何课程的讲师的全名。
|
||||
select s.last_name || ' ' || s.first_name as full_name from instructors s left join courses c on s.instructor_id = c.instructor_id where c.instructor_id is null;
|
||||
|
||||
-- LEFT JOIN 应用:查询所有学生的姓名,以及他们选修的课程名称。即使某个学生没有选修任何课程,也需要显示在结果中。
|
||||
select s.last_name, s.first_name, c.title from students s left join enrollments e on s.student_id = e.student_id left join courses c on e.course_id = c.course_id;
|
||||
|
||||
-- LEFT JOIN 应用:查询所有没有选修任何课程的学生的姓名。
|
||||
select s.last_name, s.first_name , s.student_id from students s where s.student_id not in (select student_id
|
||||
from enrollments
|
||||
);
|
||||
|
||||
-- 使用子查询,查询由 'Alan Turing' 教授的所有课程的名称。
|
||||
select c.title from courses c where instructor_id = (
|
||||
select instructor_id from instructors where first_name = 'Alan' and last_name = 'Turing'
|
||||
);
|
||||
|
||||
-- 使用子查询,查询选修了至少一门 'COMP' 系课程的所有学生的姓名。
|
||||
select s.first_name, s.last_name from students s where s.student_id = (
|
||||
select student_id from enrollments where course_id = (
|
||||
select course_id from courses where department = 'CSCI'
|
||||
)
|
||||
);
|
||||
|
||||
select count(student_id) from enrollments where course_id = (
|
||||
select course_id from courses where department = 'CSCI'
|
||||
);
|
||||
|
||||
select course_id from courses where department = 'CSCI';
|
||||
|
||||
-- 查询选课数量最多的学生的姓名。
|
||||
select students.first_name, students.last_name
|
||||
from students where student_id = (select student_id from enrollments group by student_id order by count(course_id) limit 1);
|
||||
|
||||
-- 使用 CASE 语句,查询所有学生的姓名和专业,并添加一列名为 remark 的备注。如果专业是 'Computer Science',备注为 'Tech Major';如果专业是 'Physics',备注为 'Science Major';否则备注为 'Arts & Humanities'。
|
||||
select students.first_name, students.last_name
|
||||
|
||||
-- 逻辑执行顺序理解:尝试按专业统计学生人数,并将统计结果列命名为 student_count。然后,使用 WHERE student_count > 1 来筛选结果。这个查询会失败。请解释为什么会失败,并写出使用 HAVING 子句的正确查询 。
|
||||
@@ -7,4 +7,13 @@
|
||||
|
||||
|
||||
## SQLite引擎流程
|
||||
请根据[agi_sqlite_study.md](agi_sqlite_study.md)的规范,请详细讲解SQLite中的事务,事务对其他进程的读写产生何种影响,请分析极端情况下的事务。SQLite中的读写隔离机制是怎样的。
|
||||
请根据[agi_sqlite_study.md](agi_sqlite_study.md)的规范,请详细讲解SQLite中的事务,事务对其他进程的读写产生何种影响,请分析极端情况下的事务。SQLite中的读写隔离机制是怎样的。
|
||||
|
||||
|
||||
## SQLite 题目
|
||||
请根据[agi_sqlite_study.md](agi_sqlite_study.md)的规范,作为一名专业的SQL老师,设计从数据格式,涵盖DDL DML DQL TCL的一系列题目,考察一名初学者对于SQLite学习的掌握能力
|
||||
每道题目需要有序号
|
||||
题目设计保持上下文连贯,如DQL基于DML插入的数值,DDL创建的表结构。如果数据不够,请在题目中给出插入数据命令
|
||||
每个大模块的题目,不少于10道
|
||||
DQL需要给出至少30道题目,需要包含inner join, group by, having等高阶查询方法
|
||||
要求答案放置于最后面,与前面的题目序号一一对应
|
||||
11
4-ProjectNaughtyMan/0-需求规格书/0-产品经理-prompt.md
Normal file
11
4-ProjectNaughtyMan/0-需求规格书/0-产品经理-prompt.md
Normal file
@@ -0,0 +1,11 @@
|
||||
你现在是一名策略产品经理,你擅长进行市场研究和竞品分析,以制定产品策略。你能把握行业趋势,了解用户需求,并在此基础上优化产品功能和用户体验。请在这个角色下为我解答以下问题。
|
||||
|
||||
|
||||
你是一名出色的产品经理,能够根据用户的初始需求,理解用户需求的真实需求意图,改善客户不够完善的需求,形成专业、简练的需求文档。并且能够在基础需求上优化产品的额设计和功能
|
||||
|
||||
请根据要求,请参考TelegramBotAPI官方文档,进行深度的思考,优化1-初始需求稿.md,直接给出优化后的PRD
|
||||
|
||||
|
||||
你是一名出色的产品经理,能够根据用户的初始需求,理解用户需求的真实需求意图,改善客户不够完善的需求,形成专业、简练的需求文档。并且能够在基础需求上优化产品的额设计和功能
|
||||
|
||||
你之前根据1-初始需求稿.md输出了2-优化产品需求文档PRD.md, 目前初始需求有一些更新,见1.1-初始需求稿.md,请你详细对比1.1-初始需求稿.md和1-初始需求稿.md之间的差异, 修改2-优化产品需求文档PRD.md,得到新的需求文档
|
||||
118
4-ProjectNaughtyMan/0-需求规格书/1-初始需求稿.md
Normal file
118
4-ProjectNaughtyMan/0-需求规格书/1-初始需求稿.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Telegram详细内容
|
||||
|
||||
1. 官方API参考内容
|
||||
|
||||
https://core.telegram.org/bots/api
|
||||
|
||||
|
||||
# 模块功能需求
|
||||
|
||||
## 消息通知
|
||||
|
||||
1. 功能概述: 能够根据不同的消息来源, 操作TelegramBotAPI发送消息,消息来源如下:
|
||||
1. 接口暴露
|
||||
2. AI智能体
|
||||
3. 机器人功能
|
||||
2. 实现优先级: 高
|
||||
|
||||
### 接口暴露
|
||||
|
||||
1. 通过GIN框架暴露接口,能够方便的进行消息通知
|
||||
2. 需要有强Token验证才行
|
||||
1. 设计握手式Token获取法
|
||||
2. 每个Token的有效期为6小时
|
||||
|
||||
### 消息通知范围
|
||||
|
||||
1. 单对单消息
|
||||
2. 群组消息
|
||||
|
||||
### 通知消息体
|
||||
|
||||
1. 通知等级
|
||||
1. 消息具备等级
|
||||
2. 不同等级的消息模板不一样
|
||||
2. 通知消息模板
|
||||
1. 服务器类-ProjectOctopus
|
||||
2. 金融类-ProjectTonyStack
|
||||
|
||||
### 通知时间
|
||||
|
||||
1. 与通知等级匹配
|
||||
2. 正常通知时间
|
||||
1. 白天时间 8:00-23:00
|
||||
3. 紧急通知时间
|
||||
1. 全天
|
||||
|
||||
### Telegram通知实现
|
||||
|
||||
1. 向特定用户发送消息
|
||||
2. 向特定群组发送消息
|
||||
|
||||
## 机器人功能
|
||||
|
||||
1. 模块描述: 按照预设的特定指令,执行操作
|
||||
2. 实现优先级: 低
|
||||
3. 该模块充分考虑功能拓展性,方便后续拓展其他的功能
|
||||
|
||||
### 定时提醒功能
|
||||
|
||||
1. 机器人指令/notify
|
||||
2. 当输入指令之后,弹出时间选择窗口(参照iOS的提醒事项设计)
|
||||
1. 最下方 提醒内容输入框
|
||||
2. 上方显示 日期选项按钮,时间选项按钮,重复选项按钮
|
||||
3. 日期选项选中,可以以日历的形式选择提醒日期
|
||||
4. 时间按钮选中,默认勾选日期为今天,弹出时间选择
|
||||
5. 重复按钮选中,弹出重复提醒选项,每日 每周 每两周 每月 自定义(任意选择星期几)
|
||||
6. 显示提交按钮,点击后可以提交
|
||||
3. 机器人记录提醒时间及内容,按照设计的时间发送提醒内容到对应的聊天
|
||||
1. 提醒内容需要持久化保存
|
||||
|
||||
|
||||
### 定时提醒查看清除功能
|
||||
|
||||
1. 机器人执行 /notify-list
|
||||
2. 当输入指令后,弹出本人的全部定时提醒内容
|
||||
1. 按照如下的顺序显示
|
||||
2. 提醒频率(无则显示 单次提醒) 提醒日期 提醒时间 摘要显示提醒内容
|
||||
3. 最右侧显示删除按键,点击弹出确认删除此定时提醒窗口
|
||||
4. 确认删除后,项目删除此提醒事项
|
||||
3. 若无提醒事项
|
||||
1. 显示无提醒事项
|
||||
|
||||
## 智能体功能
|
||||
|
||||
1. 模块描述: 响应@ 根据用户的上下文,给出AI智能解答
|
||||
2. 实现优先级: 低
|
||||
3. 消息回复需要引用被@的那条消息和之前的三条消息
|
||||
|
||||
### AI来源
|
||||
|
||||
1. 需要支持如下厂家来源的API
|
||||
1. OpenAI chatgpt
|
||||
2. Google Gemini
|
||||
3. xAI Grok
|
||||
4. openRouter
|
||||
2. 需要支持思考过程和流式响应
|
||||
1. 持续返回消息需要满足消息限制器的要求
|
||||
|
||||
## 基础架构设计
|
||||
|
||||
### 白名单功能
|
||||
|
||||
1. 只能允许白名单内的用户或者群组访问此机器人
|
||||
|
||||
### 消息限制器
|
||||
|
||||
1. TelegramAPI的消息频率有限制,请参考官方文档,设计相应的消息发送限制器
|
||||
2. 参照单例模式设计,所有与Telegram交互全部通过消息限制器发送
|
||||
|
||||
### 机器人网络代理
|
||||
|
||||
1. Telegram机器人构建需要考虑到网络代理的情况,机器人请求TelegramAPI可以接受socks5和http代理
|
||||
|
||||
### 持久化保存
|
||||
|
||||
1. 持久化信息采用sqlite保存
|
||||
1. windows保存至 <UserHome>\naughty_man\
|
||||
2. linux保存至 /usr/local/etc/naughty_man/
|
||||
151
4-ProjectNaughtyMan/0-需求规格书/1.1-初始需求稿.md
Normal file
151
4-ProjectNaughtyMan/0-需求规格书/1.1-初始需求稿.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Telegram详细内容
|
||||
|
||||
1. 官方API参考内容
|
||||
|
||||
https://core.telegram.org/bots/api
|
||||
|
||||
# 模块功能需求
|
||||
|
||||
## 消息通知
|
||||
|
||||
1. 功能概述: 能够根据不同的消息来源, 操作TelegramBotAPI发送消息,消息来源如下:
|
||||
|
||||
1. 接口暴露
|
||||
2. AI智能体
|
||||
3. 机器人功能
|
||||
2. 实现优先级: 高
|
||||
|
||||
### 接口暴露
|
||||
|
||||
1. 通过GIN框架暴露接口,能够方便的进行消息通知
|
||||
2. 需要有强Token验证才行
|
||||
|
||||
1. 设计握手式Token获取法
|
||||
2. 每个Token的有效期为6小时
|
||||
|
||||
### 消息通知范围
|
||||
|
||||
1. 单对单消息
|
||||
2. 群组消息
|
||||
|
||||
### 通知消息体
|
||||
|
||||
1. 通知等级
|
||||
|
||||
1. 消息具备等级
|
||||
2. 不同等级的消息模板不一样
|
||||
2. 通知消息模板
|
||||
|
||||
1. 服务器类-ProjectOctopus
|
||||
2. 金融类-ProjectTonyStack
|
||||
|
||||
### 通知时间
|
||||
|
||||
1. 与通知等级匹配
|
||||
2. 正常通知时间
|
||||
|
||||
1. 白天时间 8:00-23:00
|
||||
3. 紧急通知时间
|
||||
|
||||
1. 全天
|
||||
|
||||
### Telegram通知实现
|
||||
|
||||
1. 向特定用户发送消息
|
||||
2. 向特定群组发送消息
|
||||
|
||||
## 机器人预定义功能
|
||||
|
||||
1. 模块描述: 按照预设的特定指令,执行操作
|
||||
2. 实现优先级: 低
|
||||
3. 该模块充分考虑功能拓展性,方便后续拓展其他的功能
|
||||
|
||||
### 定时提醒功能
|
||||
|
||||
1. 机器人指令/notify
|
||||
2. 当输入指令之后,弹出时间选择窗口(参照iOS的提醒事项设计)
|
||||
|
||||
1. 最下方 提醒内容输入框
|
||||
2. 上方显示 日期选项按钮,时间选项按钮,重复选项按钮
|
||||
3. 日期选项选中,可以以日历的形式选择提醒日期
|
||||
4. 时间按钮选中,默认勾选日期为今天,弹出时间选择
|
||||
5. 重复按钮选中,弹出重复提醒选项,每日 每周 每两周 每月 自定义(任意选择星期几)
|
||||
6. 显示提交按钮,点击后可以提交
|
||||
3. 机器人记录提醒时间及内容,按照设计的时间发送提醒内容到对应的聊天
|
||||
|
||||
1. 提醒内容需要持久化保存
|
||||
|
||||
### 定时提醒查看清除功能
|
||||
|
||||
1. 机器人执行 /notify-list
|
||||
2. 当输入指令后,弹出本人的全部定时提醒内容
|
||||
|
||||
1. 按照如下的顺序显示
|
||||
2. 提醒频率(无则显示 单次提醒) 提醒日期 提醒时间 摘要显示提醒内容
|
||||
3. 最右侧显示删除按键,点击弹出确认删除此定时提醒窗口
|
||||
4. 确认删除后,项目删除此提醒事项
|
||||
3. 若无提醒事项
|
||||
|
||||
1. 显示无提醒事项
|
||||
|
||||
## Telegram管理功能
|
||||
|
||||
### 用户群组管理
|
||||
|
||||
1. 能够查看所有添加的用户ID 及 群组ID
|
||||
2. 能够根据用户ID查询用户的相关信息
|
||||
1. 持久化保存
|
||||
3. 使用接口的形式向外暴露
|
||||
|
||||
### 机器人功能管理
|
||||
|
||||
1. 能够查看机器人现在注册的功能
|
||||
2. 能够删除机器人注册的功能
|
||||
3. 能够修改机器人注册的功能
|
||||
4. 需要将机器人预定义功能注册给Bot,能够正常运行
|
||||
|
||||
## 智能体功能
|
||||
|
||||
1. 模块描述: 响应群组中的@ 及用户单独发送至Bot的消息,根据消息的上下文,给出AI智能解答
|
||||
2. 实现优先级: 低
|
||||
3. 消息回复需要引用被@的那条消息或用户单独询问的那条消息和之前的三条消息
|
||||
4. 后台需要能够打印,发送Bot消息的 用户ID/群组ID 用户名/群组名 用户手机号/空
|
||||
|
||||
### AI来源
|
||||
1. 需要支持如下厂家来源的API
|
||||
1. OpenAI chatgpt
|
||||
2. Google Gemini
|
||||
3. xAI Grok
|
||||
4. OpenRouter
|
||||
2. 需要支持思考过程和流式响应
|
||||
1. 持续返回消息需要满足消息限制器的要求
|
||||
3. 暂时只实现OpenRouter的API调用方式
|
||||
1. 使用OpenAI SDK的go版本实现功能
|
||||
|
||||
### AI消息通知
|
||||
1. 包装普通消息通知发送器
|
||||
1. 发送普通的消息,非模板消息
|
||||
2. 包装AI消息通知发送器
|
||||
1. AI消息为markdown格式
|
||||
|
||||
## 基础架构设计
|
||||
|
||||
### 白名单功能
|
||||
|
||||
1. 只能允许白名单内的用户或者群组访问此机器人
|
||||
|
||||
### 消息限制器
|
||||
|
||||
1. TelegramAPI的消息频率有限制,请参考官方文档,设计相应的消息发送限制器
|
||||
2. 参照单例模式设计,所有与Telegram交互全部通过消息限制器发送
|
||||
|
||||
### 机器人网络代理
|
||||
|
||||
1. Telegram机器人构建需要考虑到网络代理的情况,机器人请求TelegramAPI可以接受socks5和http代理
|
||||
|
||||
### 持久化保存
|
||||
|
||||
1. 持久化信息采用sqlite保存
|
||||
|
||||
1. windows保存至 <UserHome>\naughty_man\
|
||||
2. linux保存至 /usr/local/etc/naughty_man/
|
||||
505
4-ProjectNaughtyMan/0-需求规格书/2-优化产品需求文档PRD.md
Normal file
505
4-ProjectNaughtyMan/0-需求规格书/2-优化产品需求文档PRD.md
Normal file
@@ -0,0 +1,505 @@
|
||||
---
|
||||
|
||||
# Telegram Bot 智能通知与交互系统 PRD
|
||||
|
||||
**项目代号**: NaughtyMan
|
||||
**文档版本**: v2.0
|
||||
**最后更新**: 2025 年 10 月
|
||||
|
||||
# 一、产品概述
|
||||
|
||||
## 1.1 产品定位
|
||||
|
||||
构建一个企业级 Telegram Bot 系统,集成消息通知、智能提醒和 AI 助手功能,为 ProjectOctopus(服务器监控)和 ProjectTonyStack(金融监控)提供统一的实时通知中枢。
|
||||
|
||||
## 1.2 核心价值
|
||||
|
||||
- **智能分级推送**: 根据事件紧急程度自动调度通知时间和模板
|
||||
- **安全可控**: 白名单 + 动态 Token 双重鉴权机制
|
||||
- **高可用性**: 内置速率控制器保障稳定运行
|
||||
- **AI 增强**: 多厂商 AI 集成提供智能上下文响应
|
||||
|
||||
## 1.3 技术栈
|
||||
|
||||
- **后端框架**: Gin (Go 语言)
|
||||
- **Bot SDK**: 原生 Telegram Bot API
|
||||
- **数据持久化**: SQLite
|
||||
- **网络代理**: 支持 SOCKS5/HTTP
|
||||
|
||||
---
|
||||
|
||||
# 二、功能模块详细设计
|
||||
|
||||
## 2.1 核心模块:消息通知系统 [P0-高优先级]
|
||||
|
||||
### 2.1.1 接口设计
|
||||
|
||||
**API 端点结构**
|
||||
|
||||
```go
|
||||
POST /api/v1/auth/handshake # 握手获取临时Token
|
||||
POST /api/v1/auth/token # 换取正式Token
|
||||
POST /api/v1/message/send # 发送消息
|
||||
GET /api/v1/message/status # 查询消息状态
|
||||
```
|
||||
|
||||
**Token 安全机制**
|
||||
|
||||
1. **双阶段认证流程**:
|
||||
|
||||
- 阶段一:客户端携带 API Key 请求握手,服务端返回 challenge 码(60 秒有效期)
|
||||
- 阶段二:客户端用 challenge+API Secret 生成签名,换取 Access Token(6 小时有效期)
|
||||
2. **Token 刷新策略**:
|
||||
|
||||
- Token 过期前 30 分钟允许静默续期
|
||||
- 每个 API Key 同时只允许一个有效 Token
|
||||
- Token 中包含加密的白名单用户 ID/群组 ID
|
||||
|
||||
**请求体示例**
|
||||
|
||||
```go
|
||||
{
|
||||
"target_type": "user|group",
|
||||
"target_id": "123456789",
|
||||
"level": "info|warning|critical",
|
||||
"project": "octopus|tonystack",
|
||||
"content": {
|
||||
"title": "服务器CPU告警",
|
||||
"body": "服务器CPU使用率达85%",
|
||||
"metadata": {
|
||||
"server": "prod-web-01",
|
||||
"timestamp": "2025-10-21T10:23:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.1.2 消息分级与模板
|
||||
|
||||
**等级定义**
|
||||
|
||||
| 等级 | 触发条件 | 推送时段 | 响应时效 | 模板标识 |
|
||||
| ---------- | -------------- | ------------- | ---------------------- | --------------- |
|
||||
| INFO | 常规事件日志 | 08:00-23:00 | 批量推送(5 分钟汇总) | 📘 蓝色图标 |
|
||||
| WARNING | 异常但可运行 | 08:00-23:00 | 即时推送 | ⚠️ 黄色图标 |
|
||||
| CRITICAL | 严重故障 | 全天候 | 即时推送 + 重试 | 🚨 红色图标 |
|
||||
|
||||
**模板配置**
|
||||
|
||||
*ProjectOctopus (服务器监控模板)*
|
||||
|
||||
```go
|
||||
🖥️ [{{level}}] {{title}}
|
||||
|
||||
📍 服务器: {{metadata.server}}
|
||||
📊 详情: {{body}}
|
||||
⏰ 时间: {{timestamp}}
|
||||
|
||||
#服务器监控 #{{level}}
|
||||
```
|
||||
|
||||
*ProjectTonyStack (金融监控模板)*
|
||||
|
||||
```go
|
||||
💹 [{{level}}] {{title}}
|
||||
|
||||
💰 标的: {{metadata.symbol}}
|
||||
📈 数值: {{body}}
|
||||
⏰ 时间: {{timestamp}}
|
||||
|
||||
#金融监控 #{{level}}
|
||||
```
|
||||
|
||||
### 2.1.3 速率限制器设计
|
||||
|
||||
**Telegram 官方限速规则**
|
||||
|
||||
- 不同聊天: 30 消息/秒
|
||||
- 同一私聊: 1 消息/秒
|
||||
- 同一群聊: 20 消息/分钟
|
||||
- editMessageText: 共享发送额度
|
||||
|
||||
**限制器实现**
|
||||
|
||||
```go
|
||||
// 单例模式令牌桶算法
|
||||
type RateLimiter struct {
|
||||
globalBucket *TokenBucket // 全局30/s
|
||||
chatBuckets sync.Map // 每个chat独立限流
|
||||
editBucket *TokenBucket // 编辑消息单独限流
|
||||
}
|
||||
|
||||
// 消息排队机制
|
||||
type MessageQueue struct {
|
||||
priority PriorityQueue // CRITICAL优先
|
||||
retryQueue []Message // 失败重试队列(指数退避)
|
||||
deadLetterBox []Message // 3次失败转死信队列
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2.2 机器人交互功能 [P1-中优先级]
|
||||
|
||||
### 2.2.1 定时提醒功能
|
||||
|
||||
**指令**: `/notify`
|
||||
|
||||
**交互流程**
|
||||
|
||||
利用 Telegram InlineKeyboard 实现多步交互:
|
||||
|
||||
```go
|
||||
用户输入 /notify
|
||||
↓
|
||||
Bot返回主界面:
|
||||
┌─────────────────────────┐
|
||||
│ 📅 日期 🕐 时间 🔁 重复 │
|
||||
├─────────────────────────┤
|
||||
│ 📝 提醒内容: │
|
||||
│ [在此输入...] │
|
||||
├─────────────────────────┤
|
||||
│ ✅ 创建提醒 │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
**回调数据结构**
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "notify_date|notify_time|notify_repeat|notify_submit",
|
||||
"state": {
|
||||
"date": "2025-10-25",
|
||||
"time": "09:00",
|
||||
"repeat": "daily|weekly|biweekly|monthly|custom:[1,3,5]",
|
||||
"content": "团队站会"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**日期选择器实现**
|
||||
|
||||
使用 editMessageText 动态更新消息:
|
||||
|
||||
- 日历视图: 显示当前月份,支持上/下月翻页
|
||||
- 时间选择: 时/分滚动选择器(步长 15 分钟)
|
||||
- 重复规则: 预设 + 自定义(RRULE 格式)
|
||||
|
||||
### 2.2.2 提醒管理功能
|
||||
|
||||
**指令**: `/notify_list`
|
||||
|
||||
**列表展示**
|
||||
|
||||
```go
|
||||
📋 您的提醒事项 (3)
|
||||
|
||||
🔁 每日 | 09:00 | 团队站会
|
||||
[🗑️ 删除]
|
||||
|
||||
📅 10-25 | 14:30 | 客户电话会议
|
||||
[🗑️ 删除]
|
||||
|
||||
📅 10-30 | 全天 | 项目交付DDL
|
||||
[🗑️ 删除]
|
||||
```
|
||||
|
||||
**删除确认机制**
|
||||
|
||||
点击删除后 editMessageText 更新为确认界面:
|
||||
|
||||
```go
|
||||
⚠️ 确认删除提醒?
|
||||
|
||||
🔁 每日 | 09:00 | 团队站会
|
||||
|
||||
[❌ 取消] [✅ 确认删除]
|
||||
```
|
||||
|
||||
### 2.2.3 数据持久化
|
||||
|
||||
**SQLite 表结构**
|
||||
|
||||
```sql
|
||||
CREATE TABLE reminders (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id BIGINT NOT NULL,
|
||||
chat_id BIGINT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
trigger_time DATETIME NOT NULL,
|
||||
repeat_rule TEXT, -- RRULE格式
|
||||
next_trigger DATETIME,
|
||||
status INTEGER DEFAULT 1, -- 1:活动 0:已删除
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_next_trigger (next_trigger, status)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2.3 AI 智能体功能 [P1-中优先级]
|
||||
|
||||
### 2.3.1 触发机制
|
||||
|
||||
- 私聊: 直接回复所有消息
|
||||
- 群聊: 仅响应 @ 机器人的消息
|
||||
|
||||
### 2.3.2 上下文管理
|
||||
|
||||
**消息引用策略**
|
||||
|
||||
```tex
|
||||
触发消息(被@的消息)
|
||||
↓ 引用
|
||||
前3条历史消息
|
||||
↓ 拼接
|
||||
发送给AI API
|
||||
```
|
||||
|
||||
**上下文窗口**
|
||||
|
||||
```json
|
||||
{
|
||||
"messages": [
|
||||
{"role": "system", "content": "你是Telegram群助手..."},
|
||||
{"role": "user", "content": "历史消息1", "name": "user123"},
|
||||
{"role": "user", "content": "历史消息2", "name": "user456"},
|
||||
{"role": "user", "content": "历史消息3", "name": "user123"},
|
||||
{"role": "user", "content": "当前@消息", "name": "user789"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3.3 多厂商 AI 集成
|
||||
|
||||
**支持的 AI 提供商**
|
||||
|
||||
| 提供商 | 模型推荐 | 流式支持 | 思考链 |
|
||||
| ------------ | ---------------- | ---------- | ---------------- |
|
||||
| OpenAI | gpt-4-turbo | ✅ | ✅ (reasoning) |
|
||||
| Google | gemini-1.5-pro | ✅ | ✅ (思考步骤) |
|
||||
| xAI | grok-2 | ✅ | ✅ (推理过程) |
|
||||
| OpenRouter | 多模型聚合 | ✅ | 视模型而定 |
|
||||
|
||||
**流式响应处理**
|
||||
|
||||
```text
|
||||
1. 发送初始消息"正在思考..."
|
||||
2. 累积AI响应分片(每500字符或5秒)
|
||||
3. 使用editMessageText更新消息
|
||||
4. 遵守编辑速率限制(复用全局限流器)
|
||||
5. 完成后添加"✅ 回答完成"标识
|
||||
```
|
||||
|
||||
**思考过程展示**
|
||||
|
||||
```txt
|
||||
🤔 思考中...
|
||||
|
||||
【推理步骤1】分析问题...
|
||||
【推理步骤2】检索知识...
|
||||
|
||||
💡 回答:
|
||||
[AI生成的完整答复]
|
||||
|
||||
✅ 回答完成 | 用时3.2s
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 三、基础架构设计
|
||||
|
||||
## 3.1 安全控制
|
||||
|
||||
**白名单机制**
|
||||
|
||||
```sql
|
||||
CREATE TABLE whitelist (
|
||||
id INTEGER PRIMARY KEY,
|
||||
type TEXT CHECK(type IN ('user', 'group')),
|
||||
entity_id BIGINT UNIQUE NOT NULL,
|
||||
alias TEXT,
|
||||
added_by BIGINT,
|
||||
added_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
status INTEGER DEFAULT 1
|
||||
);
|
||||
```
|
||||
|
||||
**中间件验证逻辑**
|
||||
|
||||
```go
|
||||
func WhitelistMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
update := c.MustGet("telegram_update")
|
||||
|
||||
// 提取user_id和chat_id
|
||||
if !IsInWhitelist(userID, chatID) {
|
||||
bot.SendMessage(chatID, "⚠️ 未授权访问")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3.2 网络代理配置
|
||||
|
||||
**环境变量**
|
||||
|
||||
```shell
|
||||
TELEGRAM_PROXY_TYPE=socks5|http
|
||||
TELEGRAM_PROXY_HOST=127.0.0.1
|
||||
TELEGRAM_PROXY_PORT=1080
|
||||
TELEGRAM_PROXY_USER=optional_username
|
||||
TELEGRAM_PROXY_PASS=optional_password
|
||||
```
|
||||
|
||||
**代理客户端初始化**
|
||||
|
||||
```go
|
||||
proxyURL, _ := url.Parse(
|
||||
fmt.Sprintf("%s://%s:%s@%s:%d",
|
||||
proxyType, user, pass, host, port))
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyURL),
|
||||
},
|
||||
}
|
||||
|
||||
bot, _ := tgbotapi.NewBotAPIWithClient(token, httpClient)
|
||||
```
|
||||
|
||||
## 3.3 数据持久化
|
||||
|
||||
**存储路径规范**
|
||||
|
||||
- Windows: `%USERPROFILE%\naughty_man\bot.db`
|
||||
- Linux: `/usr/local/etc/naughty_man/bot.db`
|
||||
|
||||
**核心表设计**
|
||||
|
||||
```sql
|
||||
-- Token管理
|
||||
CREATE TABLE api_tokens (
|
||||
id INTEGER PRIMARY KEY,
|
||||
api_key TEXT NOT NULL,
|
||||
access_token TEXT UNIQUE,
|
||||
challenge TEXT,
|
||||
challenge_expire DATETIME,
|
||||
token_expire DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 消息日志
|
||||
CREATE TABLE message_log (
|
||||
id INTEGER PRIMARY KEY,
|
||||
message_id TEXT,
|
||||
chat_id BIGINT,
|
||||
level TEXT,
|
||||
project TEXT,
|
||||
content TEXT,
|
||||
status TEXT, -- pending|sent|failed
|
||||
retry_count INTEGER DEFAULT 0,
|
||||
sent_at DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
## 3.4 配置文件
|
||||
|
||||
所有可变参数都通过**统一的配置文件**(如`config.yaml`)加载,便于部署和维护
|
||||
|
||||
- 数据库路径
|
||||
- 代理设置
|
||||
- AI API Keys
|
||||
- Token 密钥
|
||||
- 正常时段定义
|
||||
|
||||
---
|
||||
|
||||
# 四、非功能性需求
|
||||
|
||||
## 4.1 性能指标
|
||||
|
||||
- API 响应延迟: P95 \< 200ms
|
||||
- 消息发送成功率: \> 99.5%
|
||||
- 定时提醒误差: ±30 秒
|
||||
- AI 响应首字延迟: \< 3 秒
|
||||
|
||||
## 4.2 可靠性
|
||||
|
||||
- 消息队列持久化避免丢失
|
||||
- 失败消息自动重试(指数退避,最多 3 次)
|
||||
- 死信队列人工介入机制
|
||||
- 定时任务崩溃恢复(启动时扫描 pending 任务)
|
||||
|
||||
## 4.3 可观测性
|
||||
|
||||
**日志分级**
|
||||
|
||||
- ERROR: 消息发送失败、数据库错误
|
||||
- WARN: 触及速率限制、Token 即将过期
|
||||
- INFO: API 调用、定时任务触发
|
||||
- DEBUG: 消息队列状态、AI 流式分片
|
||||
|
||||
**监控指标**
|
||||
|
||||
- 消息发送 QPS
|
||||
- 令牌桶剩余容量
|
||||
- 数据库连接池状态
|
||||
- AI API 调用耗时分布
|
||||
|
||||
---
|
||||
|
||||
# 五、实施计划
|
||||
|
||||
## 阶段一:基础设施 (2 周)
|
||||
|
||||
- 速率限制器实现与测试
|
||||
- 数据库表结构设计
|
||||
- 白名单 +Token 认证系统
|
||||
- 代理支持与连接测试
|
||||
|
||||
## 阶段二:核心功能 (3 周)
|
||||
|
||||
- 消息通知 API 开发
|
||||
- 分级模板渲染引擎
|
||||
- 消息队列与重试机制
|
||||
- 定时提醒 CRUD 功能
|
||||
|
||||
## 阶段三:高级功能 (3 周)
|
||||
|
||||
- InlineKeyboard 交互流程
|
||||
- AI 多厂商适配层
|
||||
- 流式响应处理
|
||||
- 性能优化与压测
|
||||
|
||||
## 阶段四:上线准备 (1 周)
|
||||
|
||||
- 集成测试与修复
|
||||
- 文档编写
|
||||
- 监控告警配置
|
||||
- 灰度发布
|
||||
|
||||
---
|
||||
|
||||
# 六、风险与对策
|
||||
|
||||
| 风险项 | 影响 | 对策 |
|
||||
| ------------------- | --------------- | --------------------------------- |
|
||||
| Telegram API 限流 | 消息延迟/丢失 | 令牌桶 + 优先级队列 + 重试机制 [byteplus+1](https://www.byteplus.com/en/topic/450604) |
|
||||
| AI 厂商服务中断 | 智能体不可用 | 自动降级到下一厂商/返回友好提示 |
|
||||
| SQLite 并发写入 | 数据竞争 | 使用 WAL 模式 + 写操作串行化 |
|
||||
| 网络代理不稳定 | Bot 离线 | 心跳检测 + 自动重连 + 备用代理 |
|
||||
|
||||
---
|
||||
|
||||
# 七、后续演进方向
|
||||
|
||||
1. **多 Bot 实例**: 分布式部署 + 消息路由中心
|
||||
2. **富媒体支持**: 图片、文件、语音消息通知
|
||||
3. **自定义指令**: 用户自定义快捷命令
|
||||
4. **数据看板**: 消息统计、AI 使用分析
|
||||
5. **Webhook 模式**: 替代 Long Polling 提升实时性
|
||||
798
4-ProjectNaughtyMan/0-需求规格书/2.1-优化产品需求文档PRD.md
Normal file
798
4-ProjectNaughtyMan/0-需求规格书/2.1-优化产品需求文档PRD.md
Normal file
@@ -0,0 +1,798 @@
|
||||
# Telegram Bot 智能通知与交互系统 PRD
|
||||
|
||||
**项目代号**: NaughtyMan
|
||||
**文档版本**: v2.1
|
||||
**最后更新**: 2025年10月24日
|
||||
|
||||
***
|
||||
|
||||
## 一、产品概述
|
||||
|
||||
### 1.1 产品定位
|
||||
|
||||
构建一个企业级 Telegram Bot 系统,集成消息通知、智能提醒、管理功能和 AI 助手,为 ProjectOctopus(服务器监控)和 ProjectTonyStack(金融监控)提供统一的实时通知中枢与交互管理平台。
|
||||
|
||||
### 1.2 核心价值
|
||||
|
||||
- **智能分级推送**: 根据事件紧急程度自动调度通知时间和模板
|
||||
- **安全可控**: 白名单 + 动态 Token 双重鉴权机制
|
||||
- **高可用性**: 内置速率控制器保障稳定运行
|
||||
- **AI增强**: OpenRouter API 集成提供智能上下文响应
|
||||
- **全面管理**: 用户群组管理与机器人功能动态配置
|
||||
|
||||
|
||||
### 1.3 技术栈
|
||||
|
||||
- **后端框架**: Gin (Go 语言)
|
||||
- **Bot SDK**: 原生 Telegram Bot API
|
||||
- **AI SDK**: OpenAI Go SDK (对接OpenRouter)
|
||||
- **数据持久化**: SQLite
|
||||
- **网络代理**: 支持 SOCKS5/HTTP
|
||||
|
||||
***
|
||||
|
||||
## 二、功能模块详细设计
|
||||
|
||||
### 2.1 核心模块:消息通知系统 [P0-高优先级]
|
||||
|
||||
#### 2.1.1 接口设计
|
||||
|
||||
**API 端点结构**
|
||||
|
||||
```go
|
||||
POST /api/v1/auth/handshake # 握手获取临时Token
|
||||
POST /api/v1/auth/token # 换取正式Token
|
||||
POST /api/v1/message/send # 发送消息
|
||||
GET /api/v1/message/status # 查询消息状态
|
||||
POST /api/v1/message/send-plain # 发送普通消息(非模板)
|
||||
POST /api/v1/message/send-markdown # 发送Markdown消息
|
||||
```
|
||||
|
||||
**Token 安全机制**
|
||||
|
||||
1. **双阶段认证流程**:
|
||||
- 阶段一: 客户端携带 API Key 请求握手,服务端返回 challenge 码(60秒有效期)
|
||||
- 阶段二: 客户端用 challenge+API Secret 生成签名,换取 Access Token(6小时有效期)
|
||||
2. **Token 刷新策略**:
|
||||
- Token过期前30分钟允许静默续期
|
||||
- 每个API Key同时只允许一个有效Token
|
||||
- Token中包含加密的白名单用户ID/群组ID
|
||||
|
||||
**请求体示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"target_type": "user|group",
|
||||
"target_id": "123456789",
|
||||
"level": "info|warning|critical",
|
||||
"project": "octopus|tonystack",
|
||||
"content": {
|
||||
"title": "服务器CPU告警",
|
||||
"body": "服务器CPU使用率达85%",
|
||||
"metadata": {
|
||||
"server": "prod-web-01",
|
||||
"timestamp": "2025-10-21T10:23:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### 2.1.2 消息分级与模板
|
||||
|
||||
**等级定义**
|
||||
|
||||
|
||||
| 等级 | 触发条件 | 推送时段 | 响应时效 | 模板标识 |
|
||||
| :-- | :-- | :-- | :-- | :-- |
|
||||
| INFO | 常规事件日志 | 08:00-23:00 | 批量推送(5分钟汇总) | 📘 蓝色图标 |
|
||||
| WARNING | 异常但可运行 | 08:00-23:00 | 即时推送 | ⚠️ 黄色图标 |
|
||||
| CRITICAL | 严重故障 | 全天候 | 即时推送+重试 | 🚨 红色图标 |
|
||||
|
||||
**模板配置**
|
||||
|
||||
*ProjectOctopus (服务器监控模板)*
|
||||
|
||||
```
|
||||
🖥️ [{{level}}] {{title}}
|
||||
|
||||
📍 服务器: {{metadata.server}}
|
||||
📊 详情: {{body}}
|
||||
⏰ 时间: {{timestamp}}
|
||||
|
||||
#服务器监控 #{{level}}
|
||||
```
|
||||
|
||||
*ProjectTonyStack (金融监控模板)*
|
||||
|
||||
```
|
||||
💹 [{{level}}] {{title}}
|
||||
|
||||
💰 标的: {{metadata.symbol}}
|
||||
📈 数值: {{body}}
|
||||
⏰ 时间: {{timestamp}}
|
||||
|
||||
#金融监控 #{{level}}
|
||||
```
|
||||
|
||||
|
||||
#### 2.1.3 消息发送器包装
|
||||
|
||||
**普通消息发送器**
|
||||
|
||||
用于发送非模板类的纯文本消息:
|
||||
|
||||
```go
|
||||
type PlainMessageSender struct {
|
||||
rateLimiter *RateLimiter
|
||||
whitelist *WhitelistChecker
|
||||
}
|
||||
|
||||
func (s *PlainMessageSender) Send(chatID int64, text string) error {
|
||||
// 白名单验证
|
||||
// 速率限制
|
||||
// 发送消息
|
||||
}
|
||||
```
|
||||
|
||||
**AI消息发送器**
|
||||
|
||||
专门处理Markdown格式的AI回复:
|
||||
|
||||
```go
|
||||
type AIMessageSender struct {
|
||||
baseSender *PlainMessageSender
|
||||
}
|
||||
|
||||
func (s *AIMessageSender) SendMarkdown(chatID int64, markdown string) error {
|
||||
// 转换Markdown格式
|
||||
// 使用ParseMode: "MarkdownV2"
|
||||
// 调用baseSender发送
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### 2.1.4 速率限制器设计
|
||||
|
||||
**Telegram官方限速规则**
|
||||
|
||||
- 不同聊天: 30消息/秒
|
||||
- 同一私聊: 1消息/秒
|
||||
- 同一群聊: 20消息/分钟
|
||||
- editMessageText: 共享发送额度
|
||||
|
||||
**限制器实现**
|
||||
|
||||
```go
|
||||
// 单例模式令牌桶算法
|
||||
type RateLimiter struct {
|
||||
globalBucket *TokenBucket // 全局30/s
|
||||
chatBuckets sync.Map // 每个chat独立限流
|
||||
editBucket *TokenBucket // 编辑消息单独限流
|
||||
}
|
||||
|
||||
// 消息排队机制
|
||||
type MessageQueue struct {
|
||||
priority PriorityQueue // CRITICAL优先
|
||||
retryQueue []Message // 失败重试队列(指数退避)
|
||||
deadLetterBox []Message // 3次失败转死信队列
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
***
|
||||
|
||||
### 2.2 机器人预定义功能 [P1-中优先级]
|
||||
|
||||
#### 2.2.1 定时提醒功能
|
||||
|
||||
**指令**: `/notify`
|
||||
|
||||
**交互流程**
|
||||
|
||||
利用 Telegram InlineKeyboard 实现多步交互:
|
||||
|
||||
```
|
||||
用户输入 /notify
|
||||
↓
|
||||
Bot返回主界面:
|
||||
┌─────────────────────────┐
|
||||
│ 📅 日期 🕐 时间 🔁 重复 │
|
||||
├─────────────────────────┤
|
||||
│ 📝 提醒内容: │
|
||||
│ [在此输入...] │
|
||||
├─────────────────────────┤
|
||||
│ ✅ 创建提醒 │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
**回调数据结构**
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "notify_date|notify_time|notify_repeat|notify_submit",
|
||||
"state": {
|
||||
"date": "2025-10-25",
|
||||
"time": "09:00",
|
||||
"repeat": "daily|weekly|biweekly|monthly|custom:[1,3,5]",
|
||||
"content": "团队站会"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**日期选择器实现**
|
||||
|
||||
使用 editMessageText 动态更新消息:
|
||||
|
||||
- 日历视图: 显示当前月份,支持上/下月翻页
|
||||
- 时间选择: 时/分滚动选择器(步长15分钟)
|
||||
- 重复规则: 预设+自定义(RRULE格式)
|
||||
|
||||
|
||||
#### 2.2.2 提醒管理功能
|
||||
|
||||
**指令**: `/notify_list`
|
||||
|
||||
**列表展示**
|
||||
|
||||
```
|
||||
📋 您的提醒事项 (3)
|
||||
|
||||
🔁 每日 | 09:00 | 团队站会
|
||||
[🗑️ 删除]
|
||||
|
||||
📅 10-25 | 14:30 | 客户电话会议
|
||||
[🗑️ 删除]
|
||||
|
||||
📅 10-30 | 全天 | 项目交付DDL
|
||||
[🗑️ 删除]
|
||||
```
|
||||
|
||||
**删除确认机制**
|
||||
|
||||
点击删除后 editMessageText 更新为确认界面:
|
||||
|
||||
```
|
||||
⚠️ 确认删除提醒?
|
||||
|
||||
🔁 每日 | 09:00 | 团队站会
|
||||
|
||||
[❌ 取消] [✅ 确认删除]
|
||||
```
|
||||
|
||||
|
||||
#### 2.2.3 数据持久化
|
||||
|
||||
**SQLite 表结构**
|
||||
|
||||
```sql
|
||||
CREATE TABLE reminders (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id BIGINT NOT NULL,
|
||||
chat_id BIGINT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
trigger_time DATETIME NOT NULL,
|
||||
repeat_rule TEXT, -- RRULE格式
|
||||
next_trigger DATETIME,
|
||||
status INTEGER DEFAULT 1, -- 1:活动 0:已删除
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_next_trigger (next_trigger, status)
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
***
|
||||
|
||||
### 2.3 Telegram管理功能 [P1-中优先级] **[新增]**
|
||||
|
||||
#### 2.3.1 用户群组管理
|
||||
|
||||
**API端点**
|
||||
|
||||
```go
|
||||
GET /api/v1/admin/users # 查看所有用户ID
|
||||
GET /api/v1/admin/groups # 查看所有群组ID
|
||||
GET /api/v1/admin/user/:id # 查询用户详细信息
|
||||
POST /api/v1/admin/user/:id/update # 更新用户信息
|
||||
```
|
||||
|
||||
**用户信息存储**
|
||||
|
||||
```sql
|
||||
CREATE TABLE user_profiles (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id BIGINT UNIQUE NOT NULL,
|
||||
username TEXT,
|
||||
first_name TEXT,
|
||||
last_name TEXT,
|
||||
phone_number TEXT,
|
||||
language_code TEXT,
|
||||
is_bot INTEGER DEFAULT 0,
|
||||
last_interaction DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE group_profiles (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
group_id BIGINT UNIQUE NOT NULL,
|
||||
title TEXT,
|
||||
type TEXT, -- group, supergroup, channel
|
||||
member_count INTEGER,
|
||||
last_interaction DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
**功能特性**
|
||||
|
||||
- 自动记录所有与Bot交互的用户/群组信息
|
||||
- 支持通过API查询用户完整档案
|
||||
- 持久化保存用户交互历史
|
||||
|
||||
|
||||
#### 2.3.2 机器人功能管理
|
||||
|
||||
**动态指令注册系统**
|
||||
|
||||
```go
|
||||
type BotCommand struct {
|
||||
Command string // 指令名称 (如 "notify")
|
||||
Description string // 指令描述
|
||||
Handler string // 处理器标识
|
||||
IsEnabled bool // 是否启用
|
||||
Scope string // user/group/all
|
||||
}
|
||||
```
|
||||
|
||||
**API端点**
|
||||
|
||||
```go
|
||||
GET /api/v1/admin/commands # 查看所有注册指令
|
||||
POST /api/v1/admin/commands # 注册新指令
|
||||
PUT /api/v1/admin/commands/:cmd # 修改指令
|
||||
DELETE /api/v1/admin/commands/:cmd # 删除指令
|
||||
POST /api/v1/admin/commands/sync # 同步到Telegram
|
||||
```
|
||||
|
||||
**数据表设计**
|
||||
|
||||
```sql
|
||||
CREATE TABLE bot_commands (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
command TEXT UNIQUE NOT NULL,
|
||||
description TEXT,
|
||||
handler_name TEXT NOT NULL,
|
||||
is_enabled INTEGER DEFAULT 1,
|
||||
scope TEXT DEFAULT 'all',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
**预定义功能注册**
|
||||
|
||||
系统启动时自动注册核心指令:
|
||||
|
||||
- `/notify` - 创建定时提醒
|
||||
- `/notify_list` - 查看提醒列表
|
||||
- `/help` - 帮助信息
|
||||
- `/start` - 开始对话
|
||||
|
||||
***
|
||||
|
||||
### 2.4 AI智能体功能 [P1-中优先级]
|
||||
|
||||
#### 2.4.1 触发机制 **[更新]**
|
||||
|
||||
**双模式触发**
|
||||
|
||||
- **私聊模式**: 直接回复用户发送至Bot的所有消息
|
||||
- **群聊模式**: 仅响应@机器人的消息
|
||||
|
||||
```go
|
||||
func shouldRespondToMessage(msg *Message) bool {
|
||||
// 私聊模式: 所有消息都响应
|
||||
if msg.Chat.Type == "private" {
|
||||
return true
|
||||
}
|
||||
|
||||
// 群聊模式: 检查是否@了机器人
|
||||
if msg.Chat.Type == "group" || msg.Chat.Type == "supergroup" {
|
||||
for _, entity := range msg.Entities {
|
||||
if entity.Type == "mention" && entity.IsBotMention {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### 2.4.2 上下文管理
|
||||
|
||||
**消息引用策略**
|
||||
|
||||
```
|
||||
触发消息(被@的消息或私聊消息)
|
||||
↓ 引用
|
||||
前3条历史消息
|
||||
↓ 拼接
|
||||
发送给AI API
|
||||
```
|
||||
|
||||
**上下文窗口**
|
||||
|
||||
```json
|
||||
{
|
||||
"messages": [
|
||||
{"role": "system", "content": "你是Telegram群助手..."},
|
||||
{"role": "user", "content": "历史消息1", "name": "user123"},
|
||||
{"role": "user", "content": "历史消息2", "name": "user456"},
|
||||
{"role": "user", "content": "历史消息3", "name": "user123"},
|
||||
{"role": "user", "content": "当前触发消息", "name": "user789"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### 2.4.3 AI集成实现 **[更新]**
|
||||
|
||||
**首选方案: OpenRouter**
|
||||
|
||||
使用OpenAI Go SDK对接OpenRouter API:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
func initAIClient() *openai.Client {
|
||||
config := openai.DefaultConfig(os.Getenv("OPENROUTER_API_KEY"))
|
||||
config.BaseURL = "https://openrouter.ai/api/v1"
|
||||
|
||||
return openai.NewClientWithConfig(config)
|
||||
}
|
||||
```
|
||||
|
||||
**模型选择建议**
|
||||
|
||||
|
||||
| 提供商 | 推荐模型 | 特性 |
|
||||
| :-- | :-- | :-- |
|
||||
| OpenRouter | anthropic/claude-3.5-sonnet | 思考链支持 |
|
||||
| OpenRouter | google/gemini-pro-1.5 | 长上下文 |
|
||||
| OpenRouter | openai/gpt-4-turbo | 通用平衡 |
|
||||
|
||||
**流式响应处理**
|
||||
|
||||
```go
|
||||
1. 发送初始消息"🤔 正在思考..."
|
||||
2. 创建流式请求到OpenRouter
|
||||
3. 累积AI响应分片(每500字符或5秒)
|
||||
4. 使用AIMessageSender.SendMarkdown更新消息
|
||||
5. 遵守编辑速率限制(复用全局限流器)
|
||||
6. 完成后添加"✅ 回答完成"标识
|
||||
```
|
||||
|
||||
**思考过程展示**
|
||||
|
||||
```
|
||||
🤔 思考中...
|
||||
|
||||
【推理步骤1】分析问题...
|
||||
【推理步骤2】检索知识...
|
||||
|
||||
💡 回答:
|
||||
[AI生成的完整答复]
|
||||
|
||||
✅ 回答完成 | 用时3.2s
|
||||
```
|
||||
|
||||
|
||||
#### 2.4.4 交互日志记录 **[新增]**
|
||||
|
||||
**日志输出格式**
|
||||
|
||||
```go
|
||||
type AIInteractionLog struct {
|
||||
Timestamp time.Time
|
||||
UserID int64
|
||||
Username string
|
||||
PhoneNumber string // 可能为空
|
||||
GroupID int64 // 私聊时为0
|
||||
GroupName string // 私聊时为空
|
||||
MessageText string
|
||||
AIResponse string
|
||||
Duration time.Duration
|
||||
}
|
||||
```
|
||||
|
||||
**日志输出示例**
|
||||
|
||||
```
|
||||
[2025-10-24 15:23:45] AI交互记录
|
||||
用户ID: 123456789 | 用户名: @john_doe | 手机号: +1234567890
|
||||
群组ID: 987654321 | 群组名: 开发团队讨论组
|
||||
消息: 今天天气怎么样?
|
||||
AI回复: 我无法获取实时天气信息...
|
||||
耗时: 2.3s
|
||||
```
|
||||
|
||||
|
||||
***
|
||||
|
||||
## 三、基础架构设计
|
||||
|
||||
### 3.1 安全控制
|
||||
|
||||
**白名单机制**
|
||||
|
||||
```sql
|
||||
CREATE TABLE whitelist (
|
||||
id INTEGER PRIMARY KEY,
|
||||
type TEXT CHECK(type IN ('user', 'group')),
|
||||
entity_id BIGINT UNIQUE NOT NULL,
|
||||
alias TEXT,
|
||||
added_by BIGINT,
|
||||
added_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
status INTEGER DEFAULT 1
|
||||
);
|
||||
```
|
||||
|
||||
**中间件验证逻辑**
|
||||
|
||||
```go
|
||||
func WhitelistMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
update := c.MustGet("telegram_update")
|
||||
|
||||
// 提取user_id和chat_id
|
||||
if !IsInWhitelist(userID, chatID) {
|
||||
bot.SendMessage(chatID, "⚠️ 未授权访问")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 3.2 网络代理配置
|
||||
|
||||
**环境变量**
|
||||
|
||||
```shell
|
||||
TELEGRAM_PROXY_TYPE=socks5|http
|
||||
TELEGRAM_PROXY_HOST=127.0.0.1
|
||||
TELEGRAM_PROXY_PORT=1080
|
||||
TELEGRAM_PROXY_USER=optional_username
|
||||
TELEGRAM_PROXY_PASS=optional_password
|
||||
```
|
||||
|
||||
**代理客户端初始化**
|
||||
|
||||
```go
|
||||
proxyURL, _ := url.Parse(
|
||||
fmt.Sprintf("%s://%s:%s@%s:%d",
|
||||
proxyType, user, pass, host, port))
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyURL),
|
||||
},
|
||||
}
|
||||
|
||||
bot, _ := tgbotapi.NewBotAPIWithClient(token, httpClient)
|
||||
```
|
||||
|
||||
|
||||
### 3.3 数据持久化
|
||||
|
||||
**存储路径规范**
|
||||
|
||||
- Windows: `%USERPROFILE%\naughty_man\bot.db`
|
||||
- Linux: `/usr/local/etc/naughty_man/bot.db`
|
||||
|
||||
**核心表设计**
|
||||
|
||||
```sql
|
||||
-- Token管理
|
||||
CREATE TABLE api_tokens (
|
||||
id INTEGER PRIMARY KEY,
|
||||
api_key TEXT NOT NULL,
|
||||
access_token TEXT UNIQUE,
|
||||
challenge TEXT,
|
||||
challenge_expire DATETIME,
|
||||
token_expire DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 消息日志
|
||||
CREATE TABLE message_log (
|
||||
id INTEGER PRIMARY KEY,
|
||||
message_id TEXT,
|
||||
chat_id BIGINT,
|
||||
level TEXT,
|
||||
project TEXT,
|
||||
content TEXT,
|
||||
status TEXT, -- pending|sent|failed
|
||||
retry_count INTEGER DEFAULT 0,
|
||||
sent_at DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- AI交互日志
|
||||
CREATE TABLE ai_interaction_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id BIGINT NOT NULL,
|
||||
username TEXT,
|
||||
phone_number TEXT,
|
||||
group_id BIGINT,
|
||||
group_name TEXT,
|
||||
message_text TEXT,
|
||||
ai_response TEXT,
|
||||
duration_ms INTEGER,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
### 3.4 配置文件
|
||||
|
||||
**统一配置管理**
|
||||
|
||||
所有可变参数通过`config.yaml`加载:
|
||||
|
||||
```yaml
|
||||
database:
|
||||
path: "./data/bot.db"
|
||||
|
||||
proxy:
|
||||
enabled: true
|
||||
type: "socks5"
|
||||
host: "127.0.0.1"
|
||||
port: 1080
|
||||
|
||||
ai:
|
||||
provider: "openrouter"
|
||||
api_key: "${OPENROUTER_API_KEY}"
|
||||
model: "anthropic/claude-3.5-sonnet"
|
||||
|
||||
auth:
|
||||
token_secret: "${TOKEN_SECRET}"
|
||||
token_expire_hours: 6
|
||||
|
||||
notification:
|
||||
normal_hours: "08:00-23:00"
|
||||
```
|
||||
|
||||
|
||||
***
|
||||
|
||||
## 四、非功能性需求
|
||||
|
||||
### 4.1 性能指标
|
||||
|
||||
- API响应延迟: P95 < 200ms
|
||||
- 消息发送成功率: > 99.5%
|
||||
- 定时提醒误差: ±30秒
|
||||
- AI响应首字延迟: < 3秒
|
||||
|
||||
|
||||
### 4.2 可靠性
|
||||
|
||||
- 消息队列持久化避免丢失
|
||||
- 失败消息自动重试(指数退避,最多3次)
|
||||
- 死信队列人工介入机制
|
||||
- 定时任务崩溃恢复(启动时扫描pending任务)
|
||||
|
||||
|
||||
### 4.3 可观测性
|
||||
|
||||
**日志分级**
|
||||
|
||||
- ERROR: 消息发送失败、数据库错误
|
||||
- WARN: 触及速率限制、Token即将过期
|
||||
- INFO: API调用、定时任务触发、AI交互记录
|
||||
- DEBUG: 消息队列状态、AI流式分片
|
||||
|
||||
**监控指标**
|
||||
|
||||
- 消息发送QPS
|
||||
- 令牌桶剩余容量
|
||||
- 数据库连接池状态
|
||||
- AI API调用耗时分布
|
||||
- 用户活跃度统计
|
||||
|
||||
***
|
||||
|
||||
## 五、实施计划
|
||||
|
||||
### 阶段一:基础设施 (2周)
|
||||
|
||||
- 速率限制器实现与测试
|
||||
- 数据库表结构设计(包含新增管理表)
|
||||
- 白名单+Token认证系统
|
||||
- 代理支持与连接测试
|
||||
|
||||
|
||||
### 阶段二:核心功能 (3周)
|
||||
|
||||
- 消息通知API开发
|
||||
- 分级模板渲染引擎
|
||||
- 普通/AI消息发送器包装
|
||||
- 消息队列与重试机制
|
||||
- 定时提醒CRUD功能
|
||||
|
||||
|
||||
### 阶段三:管理功能 (2周) **[新增]**
|
||||
|
||||
- 用户群组信息采集与存储
|
||||
- 管理API接口开发
|
||||
- 动态指令注册系统
|
||||
- 预定义功能自动注册
|
||||
|
||||
|
||||
### 阶段四:AI功能 (3周)
|
||||
|
||||
- OpenRouter API集成(使用OpenAI SDK)
|
||||
- 私聊/群聊双模式触发
|
||||
- 流式响应处理
|
||||
- AI交互日志记录
|
||||
- InlineKeyboard交互流程
|
||||
|
||||
|
||||
### 阶段五:上线准备 (1周)
|
||||
|
||||
- 集成测试与修复
|
||||
- 文档编写
|
||||
- 监控告警配置
|
||||
- 灰度发布
|
||||
|
||||
***
|
||||
|
||||
## 六、风险与对策
|
||||
|
||||
| 风险项 | 影响 | 对策 |
|
||||
| :-- | :-- | :-- |
|
||||
| Telegram API限流 | 消息延迟/丢失 | 令牌桶+优先级队列+重试机制 |
|
||||
| OpenRouter服务中断 | 智能体不可用 | 降级提示+本地缓存回复 |
|
||||
| SQLite并发写入 | 数据竞争 | WAL模式+写操作串行化 |
|
||||
| 网络代理不稳定 | Bot离线 | 心跳检测+自动重连+备用代理 |
|
||||
| 用户隐私泄露 | 合规风险 | 手机号加密存储+API鉴权 |
|
||||
|
||||
|
||||
***
|
||||
|
||||
## 七、后续演进方向
|
||||
|
||||
1. **多厂商AI支持**: 完整实现OpenAI/Gemini/Grok切换
|
||||
2. **多Bot实例**: 分布式部署+消息路由中心
|
||||
3. **富媒体支持**: 图片、文件、语音消息通知
|
||||
4. **自定义指令**: 用户自定义快捷命令
|
||||
5. **数据看板**: 消息统计、AI使用分析、用户活跃度
|
||||
6. **Webhook模式**: 替代Long Polling提升实时性
|
||||
|
||||
***
|
||||
|
||||
## 八、文档变更记录
|
||||
|
||||
**v2.1 (2025-10-24)**
|
||||
|
||||
- 新增Telegram管理功能模块(用户群组管理/机器人功能管理)
|
||||
- AI智能体触发机制扩展至私聊模式
|
||||
- 明确AI实现为OpenRouter优先,使用OpenAI Go SDK
|
||||
- 新增AI消息通知发送器包装
|
||||
- 新增AI交互日志记录要求
|
||||
- 术语更新:"机器人功能"改为"机器人预定义功能"
|
||||
|
||||
**v2.0 (2025-10-21)**
|
||||
|
||||
- 初始PRD文档发布
|
||||
|
||||
|
||||
26
4-ProjectNaughtyMan/1-概要详细设计/0-概要设计prompt.md
Normal file
26
4-ProjectNaughtyMan/1-概要详细设计/0-概要设计prompt.md
Normal file
@@ -0,0 +1,26 @@
|
||||
你是一名资深的软件系统架构师,具备以下核心职责与能力,绘图请使用mermaid语言:
|
||||
|
||||
## 需求分析与理解
|
||||
|
||||
- 深度解读产品需求文档(PRD),识别业务目标、功能需求与非功能性需求
|
||||
- 分析需求间的依赖关系与优先级,识别潜在的技术风险与挑战
|
||||
|
||||
## 架构设计与规划
|
||||
|
||||
- 设计高可用、可扩展、高性能的系统架构,确保系统健壮性与安全性
|
||||
- 制定技术选型方案,包括开发语言、框架、中间件、数据库等关键技术栈
|
||||
- 绘制多层次架构图(系统架构图、数据流图、组件交互图)
|
||||
- 定义模块划分、接口规范、数据模型与核心算法策略
|
||||
- 规划系统分层结构(展现层、业务层、数据层),明确各层职责边界
|
||||
|
||||
## 方案设计与输出
|
||||
|
||||
- 针对核心需求点提供详细的技术解决方案,包含实现路径与备选方案
|
||||
- 设计关键业务流程的时序图与状态机,确保逻辑清晰完整
|
||||
- 输出规范化的《系统详细设计说明书》,包含架构设计、接口定义、数据库设计等完整文档
|
||||
|
||||
|
||||
|
||||
请根据[2-优化产品需求文档PRD.md],按照上述的要求,输出系统详细设计说明书
|
||||
|
||||
你之前根据[2-优化产品需求文档PRD.md]输出了[3-详细设计说明书.md], 目前PRD需求有一些更新,见[2.1-优化产品需求文档PRD.md],请你详细对比[2.1-优化产品需求文档PRD.md]和[2-优化产品需求文档PRD.md]之间的差异, 修改[3-详细设计说明书.md],得到新的需求文档.要求尽量不改动[3-详细设计说明书.md]的初始设计,只改动差异化部分的设计
|
||||
1525
4-ProjectNaughtyMan/1-概要详细设计/3-详细设计说明书.md
Normal file
1525
4-ProjectNaughtyMan/1-概要详细设计/3-详细设计说明书.md
Normal file
File diff suppressed because it is too large
Load Diff
2122
4-ProjectNaughtyMan/1-概要详细设计/3.1-详细设计说明书.md
Normal file
2122
4-ProjectNaughtyMan/1-概要详细设计/3.1-详细设计说明书.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user