From b91056e587fb8948a9c5c229dd3ea01d83d7f599 Mon Sep 17 00:00:00 2001 From: SYC1123 <41404977+SYC1123@users.noreply.github.com> Date: Tue, 2 Nov 2021 12:17:38 +0800 Subject: [PATCH 01/16] Add files via upload --- .../2.2新闻推荐系统实战/docs/MongoDB基础.md | 1239 +++++++++++++++++ 1 file changed, 1239 insertions(+) create mode 100644 docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MongoDB基础.md diff --git a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MongoDB基础.md b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MongoDB基础.md new file mode 100644 index 00000000..186ee062 --- /dev/null +++ b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MongoDB基础.md @@ -0,0 +1,1239 @@ +# MongoDB简介 + +MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。 + +在高负载的情况下,添加更多的节点,可以保证服务器性能。 + +MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。 + +MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。 + +![img](https://i.loli.net/2021/11/02/sgy5CQIfnR9cmpO.png) + +## 主要特点 + +- MongoDB 是一个面向文档存储的数据库,操作起来比较简单和容易。 +- 你可以在MongoDB记录中设置任何属性的索引 (如:FirstName="Sameer",Address="8 Gandhi Road")来实现更快的排序。 +- 你可以通过本地或者网络创建数据镜像,这使得MongoDB有更强的扩展性。 +- 如果负载的增加(需要更多的存储空间和更强的处理能力) ,它可以分布在计算机网络中的其他节点上这就是所谓的分片。 +- Mongo支持丰富的查询表达式。查询指令使用JSON形式的标记,可轻易查询文档中内嵌的对象及数组。 +- MongoDb 使用update()命令可以实现替换完成的文档(数据)或者一些指定的数据字段 。 +- Mongodb中的Map/reduce主要是用来对数据进行批量处理和聚合操作。 +- Map和Reduce。Map函数调用emit(key,value)遍历集合中所有的记录,将key与value传给Reduce函数进行处理。 +- Map函数和Reduce函数是使用Javascript编写的,并可以通过db.runCommand或mapreduce命令来执行MapReduce操作。 +- GridFS是MongoDB中的一个内置功能,可以用于存放大量小文件。 +- MongoDB允许在服务端执行脚本,可以用Javascript编写某个函数,直接在服务端执行,也可以把函数的定义存储在服务端,下次直接调用即可。 +- MongoDB支持各种编程语言:RUBY,PYTHON,JAVA,C++,PHP,C#等多种语言。 +- MongoDB安装简单 + + + +# Linux平台安装MongoDB + +MongoDB 提供了 linux 各个发行版本 64 位的安装包,你可以在官网下载安装包。 + +MongoDB 源码下载地址:https://www.mongodb.com/download-center#community + +安装前我们需要安装各个 Linux 平台依赖包。 + +**Red Hat/CentOS:** + +``` +sudo yum install libcurl openssl +``` + +**Ubuntu 18.04 LTS ("Bionic")/Debian 10 "Buster":** + +``` +sudo apt-get install libcurl4 openssl +``` + +**Ubuntu 16.04 LTS ("Xenial")/Debian 9 "Stretch":** + +``` +sudo apt-get install libcurl3 openssl +``` + +查看ubuntu的版本 + +``` +lsb_release -a +``` + +![image-20211026193919108](https://i.loli.net/2021/11/02/4Ml1tYIbLimWS2X.png) + + + +![image-20211026201305053](https://i.loli.net/2021/11/02/cHV1hAf4s52ECUw.png) + +![image-20211026201645786](https://i.loli.net/2021/11/02/Imq9ZYdxrRXiGkl.png) + + + +这里我们选择 tgz 下载,下载完安装包,并解压 **tgz**(以下演示的是 64 位 Linux上的安装) 。 + +``` +wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.4.10.tgz #下载 +wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.4.10.tgz #解压 +``` + +MongoDB 的可执行文件位于 bin 目录下,所以可以将其添加到 **PATH** 路径中 + +``` +export PATH=/bin:$PATH +``` + +****为你 MongoDB 的安装路径。 + +## 创建数据库目录 + +默认情况下 MongoDB 启动后会初始化以下两个目录: + +- 数据存储目录:/var/lib/mongodb +- 日志文件目录:/var/log/mongodb + +我们在启动前可以先创建这两个目录: + +``` +sudo mkdir -p /var/lib/mongo +sudo mkdir -p /var/log/mongodb +``` + +接下来启动 Mongodb 服务: + +``` +mongod --dbpath /var/lib/mongo --logpath /var/log/mongodb/mongod.log --fork +``` + +--------------------------------------------------------- + +## MongoDB 后台管理 Shell + +如果你需要进入 mongodb 后台管理,由于已经将MongoDB可执行文件添加到PATH路径,所以可以直接执行 mongo 命令文件。 + +MongoDB Shell 是 MongoDB 自带的交互式 Javascript shell,用来对 MongoDB 进行操作和管理的交互式环境。 + +当你进入 mongoDB 后台后,它默认会链接到 test 文档(数据库): + +![image-20211027223343278](https://i.loli.net/2021/11/02/dDlZE71WqtsS2i8.png) + +# MongoDB 概念解析 + +在mongodb中基本的概念是文档、集合、数据库。下表将帮助您更容易理解Mongo中的一些概念: + +| SQL术语/概念 | MongoDB术语/概念 | 解释/说明 | +| :----------- | :--------------- | :---------------------------------- | +| database | database | 数据库 | +| table | collection | 数据库表/集合 | +| row | document | 数据记录行/文档 | +| column | field | 数据字段/域 | +| index | index | 索引 | +| table joins | | 表连接,MongoDB不支持 | +| primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 | + +## MongoDB 创建数据库 + +### 数据库 + +一个mongodb中可以建立多个数据库。 + +MongoDB的默认数据库为"db",该数据库存储在data目录中。 + +MongoDB的单个实例可以容纳多个独立的数据库,每一个都有自己的集合和权限,不同的数据库也放置在不同的文件中。 + +**"show dbs"** 命令可以显示所有数据的列表。 + +``` +toby@recsys:~$ mongo +MongoDB shell version: 2.6.10 +connecting to: test +> show dbs +admin (empty) +local 0.078GB +``` + +执行 **"db"** 命令可以显示当前数据库对象或集合。 + +``` +toby@recsys:~$ mongo +MongoDB shell version: 2.6.10 +connecting to: test +> db +test +``` + +运行"use"命令,可以连接到一个指定的数据库。 + +``` +toby@recsys:~$ mongo +MongoDB shell version: 2.6.10 +connecting to: test +> use admin +switched to db admin +> db +admin +> +``` + +### 语法 + +MongoDB 创建数据库的语法格式如下: + +``` +use DATABASE_NAME +``` + +如果数据库不存在,则创建数据库,否则切换到指定数据库。 + +### 实例 + +以下实例我们创建了数据库 tobytest: + +``` +toby@recsys:~$ mongo +MongoDB shell version: 2.6.10 +connecting to: test +> use tobytest +switched to db tobytest +> db +tobytest +> +``` + +如果你想查看所有数据库,可以使用 **show dbs** 命令: + +``` +> show dbs +admin (empty) +local 0.078GB +> +``` + +可以看到,我们刚创建的数据库 tobytest并不在数据库的列表中, 要显示它,我们需要向 tobytest数据库插入一些数据。 + +``` +> db.tobytest.insert({"name":"Toby"}) +WriteResult({ "nInserted" : 1 }) +> show dbs +admin (empty) +local 0.078GB +tobytest 0.078GB +> +``` + +MongoDB 中默认的数据库为 test,如果你没有创建新的数据库,集合将存放在 test 数据库中。 + +> **注意:** 在 MongoDB 中,集合只有在内容插入后才会创建! 就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建。 + +## MongoDB 创建集合 + +MongoDB 中使用 **createCollection()** 方法来创建集合。 + +语法格式: + +``` +db.createCollection(name, options) +``` + +参数说明: + +- name: 要创建的集合名称 +- options: 可选参数, 指定有关内存大小及索引的选项 + +options 可以是如下参数: + +| 字段 | 类型 | 描述 | +| :---------- | :--- | :----------------------------------------------------------- | +| capped | 布尔 | (可选)如果为 true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。 **当该值为 true 时,必须指定 size 参数。** | +| autoIndexId | 布尔 | 3.2 之后不再支持该参数。(可选)如为 true,自动在 _id 字段创建索引。默认为 false。 | +| size | 数值 | (可选)为固定集合指定一个最大值,即字节数。 **如果 capped 为 true,也需要指定该字段。** | +| max | 数值 | (可选)指定固定集合中包含文档的最大数量。 | + +在插入文档时,MongoDB 首先检查固定集合的 size 字段,然后检查 max 字段。 + +### 实例 + +在 tobytest 数据库中创建 runoob 集合: + +``` +> use tobytest +switched to db tobytest +> db.createCollection("tobycollection") +{ "ok" : 1 } +> +``` + +如果要查看已有集合,可以使用 **show collections** 或 **show tables** 命令: + +``` +> show tables +system.indexes +tobycollection +tobytest +> +``` + +## MongoDB 删除集合 + +MongoDB 中使用 drop() 方法来删除集合。 + +**语法格式:** + +``` +db.collection.drop() +``` + +参数说明: + +- 无 + +**返回值** + +如果成功删除选定集合,则 drop() 方法返回 true,否则返回 false。 + +### 实例 + +在数据库 tobytest中,我们可以先通过 **show collections** 命令查看已存在的集合: + +``` +> use tobytest +switched to db tobytest +> show collections +system.indexes +tobycollection +tobytest +> +``` + +接着删除集合 tobycollection: + +``` +> db.tobycollection.drop() +true +> +``` + +通过 show collections 再次查看数据库 tobytest中的集合: + +``` +> show collections +system.indexes +tobytest +> +``` + +从结果中可以看出 tobycollection集合已被删除。 + +## MongoDB 插入文档 + +文档的数据结构和 JSON 基本一样。 + +所有存储在集合中的数据都是 BSON 格式。 + +BSON 是一种类似 JSON 的二进制形式的存储格式,是 Binary JSON 的简称。 + +### 插入文档 + +MongoDB 使用 insert() 或 save() 方法向集合中插入文档,语法如下: + +``` +db.COLLECTION_NAME.insert(document) +或 +db.COLLECTION_NAME.save(document) +``` + +- save():如果 _id 主键存在则更新数据,如果不存在就插入数据。该方法新版本中已废弃,可以使用 **db.collection.insertOne()** 或 **db.collection.replaceOne()** 来代替。 +- insert(): 若插入的数据主键已经存在,则会抛 **org.springframework.dao.DuplicateKeyException** 异常,提示主键重复,不保存当前数据。 + +### 实例 + +以下文档可以存储在 MongoDB 的 tobytest 数据库 的 col 集合中: + +``` +> db.col.insert({title:'Toby MongoDB', +... description:'this is MongoDB', +... tags:['mongodb','database','NoSQL'], +... likes:1 +... }) +WriteResult({ "nInserted" : 1 }) +> +``` + +以上实例中 col 是我们的集合名,如果该集合不在该数据库中, MongoDB 会自动创建该集合并插入文档。 + +查看已插入文档: + +``` +> db.col.find() +{ "_id" : ObjectId("617970fc286e9ff2b1250d70"), "title" : "Toby MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } +> +``` + +我们也可以将数据定义为一个变量,如下所示: + +``` +> document=({title:'Toby another MongoDB', +... description:'this is another MongoDB', +... tags:['mongodb','database','NoSQL'], +... likes:2 +... }) +``` + +执行后显示结果如下: + +``` +{ + "title" : "Toby another MongoDB", + "description" : "this is another MongoDB", + "tags" : [ + "mongodb", + "database", + "NoSQL" + ], + "likes" : 2 +} +``` + +执行插入操作: + +``` +> db.col.insert(document) +WriteResult({ "nInserted" : 1 }) +> db.col.find() +{ "_id" : ObjectId("617970fc286e9ff2b1250d70"), "title" : "Toby MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } +{ "_id" : ObjectId("61797229286e9ff2b1250d71"), "title" : "Toby another MongoDB", "description" : "this is another MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 2 } +> +``` + +## MongoDB 更新文档 + +MongoDB 使用 **update()** 和 **save()** 方法来更新集合中的文档。接下来让我们详细来看下两个函数的应用及其区别。 + +------ + +### update() 方法 + +update() 方法用于更新已存在的文档。语法格式如下: + +``` +db.collection.update( + , + , + { + upsert: , + multi: , + writeConcern: + } +) +``` + +**参数说明:** + +- **query** : update的查询条件,类似sql update查询内where后面的。 +- **update** : update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的 +- **upsert** : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。 +- **multi** : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。 +- **writeConcern** :可选,抛出异常的级别。 + +### 实例 + +我们在集合 col 中插入如下数据: + +``` +> db.col.insert({title:'Toby MongoDB', +... description:'this is MongoDB', +... tags:['mongodb','database','NoSQL'], +... likes:1 +... }) +WriteResult({ "nInserted" : 1 }) +> +``` + +接着我们通过 update() 方法来更新标题(title): + +``` +> db.col.update({'title':'Toby MongoDB'},{$set:{'title':'MongoDB'}}) +WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) +> db.col.find().pretty() +{ + "_id" : ObjectId("617970fc286e9ff2b1250d70"), + "title" : "MongoDB", + "description" : "this is MongoDB", + "tags" : [ + "mongodb", + "database", + "NoSQL" + ], + "likes" : 1 +} +{ + "_id" : ObjectId("61797229286e9ff2b1250d71"), + "title" : "Toby another MongoDB", + "description" : "this is another MongoDB", + "tags" : [ + "mongodb", + "database", + "NoSQL" + ], + "likes" : 2 +} +> +``` + +可以看到标题(title)由原来的 "Toby MongoDB" 更新为了 "MongoDB"。 + +## MongoDB 删除文档 + +MongoDB remove() 函数是用来移除集合中的数据。 + +MongoDB 数据更新可以使用 update() 函数。在执行 remove() 函数前先执行 find() 命令来判断执行的条件是否正确,这是一个比较好的习惯。 + +### 语法 + +remove() 方法的基本语法格式如下所示: + +``` +db.collection.remove( + , + +) +``` + +如果你的 MongoDB 是 2.6 版本以后的,语法格式如下: + +``` +db.collection.remove( + , + { + justOne: , + writeConcern: + } +) +``` + +**参数说明:** + +- **query** :(可选)删除的文档的条件。 +- **justOne** : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。 +- **writeConcern** :(可选)抛出异常的级别。 + +### 实例 + +以下文档我们执行两次插入操作: + +``` +> db.col.insert({title:'Toby MongoDB', description:'this is MongoDB', tags:['mongodb','database','NoSQL'], likes:1 }) +WriteResult({ "nInserted" : 1 }) +> db.col.insert({title:'Toby MongoDB', description:'this is MongoDB', tags:['mongodb','database','NoSQL'], likes:1 }) +WriteResult({ "nInserted" : 1 }) +> +``` + +使用 find() 函数查询数据: + +``` +> db.col.find() +{ "_id" : ObjectId("617970fc286e9ff2b1250d70"), "title" : "MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } +{ "_id" : ObjectId("61797229286e9ff2b1250d71"), "title" : "Toby another MongoDB", "description" : "this is another MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 2 } +{ "_id" : ObjectId("6179747d286e9ff2b1250d72"), "title" : "Toby MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } +{ "_id" : ObjectId("61797481286e9ff2b1250d73"), "title" : "Toby MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } +> +``` + +接下来我们移除 title 为 'Toby MongoDB' 的文档: + +``` +> db.col.remove({'title':'Toby MongoDB'}) +WriteResult({ "nRemoved" : 2 }) # 删除了两个 +> db.col.find() +{ "_id" : ObjectId("617970fc286e9ff2b1250d70"), "title" : "MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } +{ "_id" : ObjectId("61797229286e9ff2b1250d71"), "title" : "Toby another MongoDB", "description" : "this is another MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 2 } +> +``` + +如果你只想删除第一条找到的记录可以设置 justOne 为 1,如下所示: + +``` +>db.COLLECTION_NAME.remove(DELETION_CRITERIA,1) +``` + +如果你想删除所有数据,可以使用以下方式(类似常规 SQL 的 truncate 命令): + +``` +> db.col.remove({}) +WriteResult({ "nRemoved" : 2 }) +> db.col.find() +> +``` + +## MongoDB 查询文档 + +MongoDB 查询文档使用 find() 方法。 + +find() 方法以非结构化的方式来显示所有文档。 + +### 语法 + +MongoDB 查询数据的语法格式如下: + +``` +db.collection.find(query, projection) +``` + +- **query** :可选,使用查询操作符指定查询条件 +- **projection** :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。 + +如果你需要以易读的方式来读取数据,可以使用 pretty() 方法,语法格式如下: + +``` +>db.col.find().pretty() +``` + +pretty() 方法以格式化的方式来显示所有文档。 + +### 实例 + +以下实例我们查询了集合 col 中的数据: + +``` +> db.col.insert({title:'Toby MongoDB', description:'this is MongoDB',by:'Toby', tags:['mongodb','database','NoSQL'], likes:100 }) +WriteResult({ "nInserted" : 1 }) +> db.col.find().pretty() +{ + "_id" : ObjectId("6179772f286e9ff2b1250d75"), + "title" : "Toby MongoDB", + "description" : "this is MongoDB", + "by" : "Toby", + "tags" : [ + "mongodb", + "database", + "NoSQL" + ], + "likes" : 100 +} +> +``` + +除了 find() 方法之外,还有一个 findOne() 方法,它只返回一个文档。 + +### MongoDB AND 条件 + +MongoDB 的 find() 方法可以传入多个键(key),每个键(key)以逗号隔开,即常规 SQL 的 AND 条件。 + +语法格式如下: + +``` +>db.col.find({key1:value1, key2:value2}).pretty() +``` + +#### 实例 + +以下实例通过 **by** 和 **title** 键来查询 **Toby** 中 **Toby MongoDB** 的数据 + +``` +> db.col.find({'by':'Toby','title':'Toby MongoDB'}).prettydb.col.find({'by':'Toby','title':'Toby MongoDB'}).pretty() +{ + "_id" : ObjectId("6179772f286e9ff2b1250d75"), + "title" : "Toby MongoDB", + "description" : "this is MongoDB", + "by" : "Toby", + "tags" : [ + "mongodb", + "database", + "NoSQL" + ], + "likes" : 100 +} +> +``` + +以上实例中类似于 WHERE 语句:**WHERE by='Toby' AND title='Toby MongoDB'** + +------ + +### MongoDB OR 条件 + +MongoDB OR 条件语句使用了关键字 **$or**,语法格式如下: + +``` +>db.col.find( + { + $or: [ + {key1: value1}, {key2:value2} + ] + } +).pretty() +``` + +#### 实例 + +以下实例中,我们演示了查询键 **by** 值为 **Toby**或键 **title** 值为 **Toby MongoDB** 的文档。 + +``` +> db.col.find({$or:[{"by":"Toby"},{"title":"Toby MongoDB"}]}).pretty() +{ + "_id" : ObjectId("6179772f286e9ff2b1250d75"), + "title" : "Toby MongoDB", + "description" : "this is MongoDB", + "by" : "Toby", + "tags" : [ + "mongodb", + "database", + "NoSQL" + ], + "likes" : 100 +} +> +``` + +------ + +### AND 和 OR 联合使用 + +以下实例演示了 AND 和 OR 联合使用,类似常规 SQL 语句为: **'where likes>50 AND (by = 'Toby' OR title = 'Toby MongoDB')'** + +``` +> db.col.find({"likes":{$gt:50},$or:[{"by":"Toby"},{"title":"Toby MongoDB"}]}).pretty() +{ + "_id" : ObjectId("6179772f286e9ff2b1250d75"), + "title" : "Toby MongoDB", + "description" : "this is MongoDB", + "by" : "Toby", + "tags" : [ + "mongodb", + "database", + "NoSQL" + ], + "likes" : 100 +} +> +``` + +## MongoDB 排序 + +------ + +### MongoDB sort() 方法 + +在 MongoDB 中使用 sort() 方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。 + +#### 语法 + +sort()方法基本语法如下所示: + +``` +>db.COLLECTION_NAME.find().sort({KEY:1}) +``` + +#### 实例 + +col 集合中的数据如下: + +``` +> db.col.find().pretty() +{ + "_id" : ObjectId("61797a56286e9ff2b1250d78"), + "title" : "Toby PHP", + "description" : "this is PHP", + "by" : "Toby", + "tags" : [ + "PHP", + "Language" + ], + "likes" : 100 +} +{ + "_id" : ObjectId("61797a62286e9ff2b1250d79"), + "title" : "Toby JAVA", + "description" : "this is JAVA", + "by" : "Toby", + "tags" : [ + "JAVA", + "Language" + ], + "likes" : 50 +} +{ + "_id" : ObjectId("61797a83286e9ff2b1250d7a"), + "title" : "Toby Python", + "description" : "this is Python", + "by" : "Toby", + "tags" : [ + "Python", + "Language" + ], + "likes" : 20 +} +> +``` + +以下实例演示了 col 集合中的数据按字段 likes 的降序排列: + +``` +> db.col.find({},{'title':1,_id:0}).sort({"likes":-1}) +{ "title" : "Toby PHP" } +{ "title" : "Toby JAVA" } +{ "title" : "Toby Python" } +> +``` + +# Python MongoDB + +------ + +## PyMongo + +Python 要连接 MongoDB 需要 MongoDB 驱动,这里我们使用 PyMongo 驱动来连接。 + +### pip 安装 + +pip 是一个通用的 Python 包管理工具,提供了对 Python 包的查找、下载、安装、卸载的功能。 + +安装 pymongo: + +``` +$ python3 -m pip install pymongo +``` + +### 测试 PyMongo + +接下来我们可以创建一个测试文件 demo_test_mongodb.py,代码如下: + +``` +import pymongo +``` + +执行以上代码文件,如果没有出现错误,表示安装成功。 + +## 创建数据库 + +### 创建一个数据库 + +创建数据库需要使用 MongoClient 对象,并且指定连接的 URL 地址和要创建的数据库名。 + +如下实例中,我们创建的数据库 pydb: + +#### 实例 + +```python +import pymongo +myclient=pymongo.MongoClient("mongodb://localhost:27017/") +mydb=myclient["pydb"] +``` + +> **注意:** 在 MongoDB 中,数据库只有在内容插入后才会创建! 就是说,数据库创建后要创建集合(数据表)并插入一个文档(记录),数据库才会真正创建。 + +### 判断数据库是否已存在 + +我们可以读取 MongoDB 中的所有数据库,并判断指定的数据库是否存在: + +#### 实例 + +```python +import pymongo +myclient=pymongo.MongoClient("mongodb://localhost:27017/") +mydb=myclient["pydb"] + +dblist = myclient.list_database_names() +# dblist = myclient.database_names() +if "pydb" in dblist: + print("数据库已存在!") +else: + print('数据库不存在') +``` + +> **注意:**database_names 在最新版本的 Python 中已废弃,Python3.7+ 之后的版本改为了 list_database_names()。 + + + +![image-20211030141217841](https://i.loli.net/2021/11/02/K4oZ3xvmiGXUWsQ.png) + +## 创建集合 + +MongoDB 中的集合类似 SQL 的表。 + +### 创建一个集合 + +MongoDB 使用数据库对象来创建集合,实例如下: + +#### 实例 + +```python +import pymongo +myclient=pymongo.MongoClient("mongodb://localhost:27017/") +mydb=myclient["pydb"] + +mycol=myclient["col_set"] +``` + + + +> **注意:** 在 MongoDB 中,集合只有在内容插入后才会创建! 就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建。 + +### 判断集合是否已存在 + +我们可以读取 MongoDB 数据库中的所有集合,并判断指定的集合是否存在: + +#### 实例 + +```python +import pymongo +myclient=pymongo.MongoClient("mongodb://localhost:27017/") +mydb=myclient["pydb"] + +mycol=myclient["col_set"] + +collist = mydb. list_collection_names() +if "col_set" in collist: # 判断 sites 集合是否存在 + print("集合已存在!") +else: + print('集合不存在') +``` + +![image-20211030141526295](https://i.loli.net/2021/11/02/K7mJARPe1dM2Yos.png) + +## Python Mongodb 插入文档 + +MongoDB 中的一个文档类似 SQL 表中的一条记录。 + +### 插入集合 + +集合中插入文档使用 **insert_one()** 方法,该方法的第一参数是字典 **name => value** 对。 + +以下实例向 **col_set** 集合中插入文档: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +mydict = { "name": "Toby", "age": "23", "url": "https://juejin.cn/user/3403743731649863" } + +x = mycol.insert_one(mydict) +print(x) +``` + +![image-20211030142137931](https://i.loli.net/2021/11/02/yY6EmCx4PfLolFQ.png) + +在命令行看一下是否插入成功 + +``` +> use pydb +switched to db pydb +> db.col_set.find() +{ "_id" : ObjectId("617ce42cbc6011eaf1529012"), "name" : "Toby", "url" : "https://juejin.cn/user/3403743731649863", "age" : "23" } +> +``` + +### 插入多个文档 + +集合中插入多个文档使用 **insert_many()** 方法,该方法的第一参数是字典列表。 + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +mylist = [ + { "name": "Tom", "age": "100", "url": "https://juejin.cn/user/3403743731649863" }, + { "name": "Mary", "age": "101", "url": "https://juejin.cn/user/3403743731649863" }, + { "name": "Timi", "age": "10", "url": "https://juejin.cn/user/3403743731649863" }, +] + +x = mycol.insert_many(mylist) + +# 输出插入的所有文档对应的 _id 值 +print(x.inserted_ids) +``` + +![image-20211030142656115](https://i.loli.net/2021/11/02/7sS9XRKqUCFnrh6.png) + +在命令行看一下是否插入成功 + +``` +> use pydb +switched to db pydb +> db.col_set.find() +{ "_id" : ObjectId("617ce42cbc6011eaf1529012"), "name" : "Toby", "url" : "https://juejin.cn/user/3403743731649863", "age" : "23" } +{ "_id" : ObjectId("617ce591826d13d898f97890"), "name" : "Tom", "url" : "https://juejin.cn/user/3403743731649863", "age" : "100" } +{ "_id" : ObjectId("617ce591826d13d898f97891"), "name" : "Mary", "url" : "https://juejin.cn/user/3403743731649863", "age" : "101" } +{ "_id" : ObjectId("617ce591826d13d898f97892"), "name" : "Timi", "url" : "https://juejin.cn/user/3403743731649863", "age" : "10" } +> +``` + +## Python Mongodb 查询文档 + +MongoDB 中使用了 find 和 find_one 方法来查询集合中的数据,它类似于 SQL 中的 SELECT 语句。 + +### 查询一条数据 + +我们可以使用 **find_one()** 方法来查询集合中的一条数据。 + +查询 **col_set** 文档中的第一条数据: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +x = mycol.find_one() + +print(x) +``` + +![image-20211030142943707](https://i.loli.net/2021/11/02/F8GOH7PIiVUyA4J.png) + +### 查询集合中所有数据 + +**find()** 方法可以查询集合中的所有数据,类似 SQL 中的 **SELECT \*** 操作。 + +以下实例查找 **col_set** 集合中的所有数据: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +for x in mycol.find(): + print(x) +``` + +![image-20211030143207556](https://i.loli.net/2021/11/02/7kQH6zy5EjChqx1.png) + +### 查询指定字段的数据 + +我们可以使用 find() 方法来查询指定字段的数据,将要返回的字段对应值设置为 1。 + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +for x in mycol.find({},{ "_id": 0, "name": 1, "age": 1 }): + print(x) +``` + +![image-20211030144042132](https://i.loli.net/2021/11/02/DbfneXgkLsFoIQJ.png) + +### 根据指定条件查询 + +我们可以在 **find()** 中设置参数来过滤数据。 + +以下实例查找 name 字段为 "Toby" 的数据: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +myquery = { "name": "Toby" } + +mydoc = mycol.find(myquery) + +for x in mydoc: + print(x) +``` + +![image-20211030144414902](https://i.loli.net/2021/11/02/nYx3mH5oZfNdLAu.png) + +### 返回指定条数记录 + +如果我们要对查询结果设置指定条数的记录可以使用 **limit()** 方法,该方法只接受一个数字参数。 + +以下实例返回 3 条文档记录: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +myresult = mycol.find().limit(3) + +# 输出结果 +for x in myresult: + print(x) +``` + +![image-20211030144609160](https://i.loli.net/2021/11/02/hpztCWj49APuIZr.png) + +## Python Mongodb 修改文档 + +我们可以在 MongoDB 中使用 **update_one()** 方法修改文档中的记录。该方法第一个参数为查询的条件,第二个参数为要修改的字段。 + +如果查找到的匹配数据多于一条,则只会修改第一条。 + +以下实例将 age字段的值 23改为 12345: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +myquery = { "age": "23" } +newvalues = { "$set": { "age": "12345" } } + +mycol.update_one(myquery, newvalues) + +# 输出修改后的 "sites" 集合 +for x in mycol.find(): + print(x) +``` + +![image-20211030144819907](https://i.loli.net/2021/11/02/Lun1miz7sFH6SJZ.png) + +## 排序 + +**sort()** 方法可以指定升序或降序排序。 + +**sort()** 方法第一个参数为要排序的字段,第二个字段指定排序规则,**1** 为升序,**-1** 为降序,默认为升序。 + +对字段 age 按升序排序: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +mydoc = mycol.find().sort("age") +for x in mydoc: + print(x) +``` + +![image-20211030145059219](https://i.loli.net/2021/11/02/QGZ6B4AsMqSei3W.png) + +对字段 age按降序排序: + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +mydoc = mycol.find().sort("alexa", -1) + +for x in mydoc: + print(x) +``` + +![image-20211030145239034](https://i.loli.net/2021/11/02/B3v5Dkh6fYoQnTj.png) + +## Python Mongodb 删除数据 + +我们可以使用 **delete_one()** 方法来删除一个文档,该方法第一个参数为查询对象,指定要删除哪些数据。 + +以下实例删除 name 字段值为 "Timi" 的文档: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +myquery = { "name": "Timi" } + +mycol.delete_one(myquery) + +# 删除后输出 +for x in mycol.find(): + print(x) +``` + +![image-20211030145408484](https://i.loli.net/2021/11/02/crw3HJN2vQzyBW6.png) + +### 删除集合中的所有文档 + +**delete_many()** 方法如果传入的是一个空的查询对象,则会删除集合中的所有文档: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +x = mycol.delete_many({}) + +print(x.deleted_count, "个文档已删除") +``` + +![image-20211030145528857](https://i.loli.net/2021/11/02/a7l5NsKAJhVBcPk.png) + +## 删除集合 + +我们可以使用 **drop()** 方法来删除一个集合。 + +以下实例删除了 col_set集合: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +mycol.drop() + +``` + +我们在终端查看一下 + +``` +> use pydb +switched to db pydb +> show tables +system.indexes +> +``` + + + +参考链接: + +* https://www.runoob.com/python3/python-mongodb.html + +* https://www.runoob.com/mongodb/mongodb-tutorial.html + From 424150c09aa10dd08ff6058eb797b91568457675 Mon Sep 17 00:00:00 2001 From: RuyiLuo Date: Tue, 2 Nov 2021 16:17:44 +0800 Subject: [PATCH 02/16] Update readme.md --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 19f9e06b..ed158648 100644 --- a/readme.md +++ b/readme.md @@ -55,8 +55,8 @@ - **2.2.1 构建物料池** - 2.2.1.1 Mysql基础 - - 2.2.1.2 MongoDB基础 - - 2.2.1.3 Redis基础 + - [2.2.1.2 MongoDB基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/MongoDB%E5%9F%BA%E7%A1%80.md) + - [2.2.1.3 Redis基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/Redis%E5%9F%BA%E7%A1%80.md) - 2.2.1.4 scrapy框架基础 - 2.2.1.5 scrapy新闻爬取 - 2.2.1.6 新闻画像的构建 From eff8943224845cbe95bedc22827b45c56cf5e235 Mon Sep 17 00:00:00 2001 From: Lyons-T <75569795+Lyons-T@users.noreply.github.com> Date: Wed, 3 Nov 2021 19:01:37 +0800 Subject: [PATCH 03/16] Add files via upload --- .../2.2新闻推荐系统实战/docs/2.2.1.1 MySQL基础.md | 2310 +++++++++++++++++ 1 file changed, 2310 insertions(+) create mode 100644 docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.1 MySQL基础.md diff --git a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.1 MySQL基础.md b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.1 MySQL基础.md new file mode 100644 index 00000000..feccaa7b --- /dev/null +++ b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.1 MySQL基础.md @@ -0,0 +1,2310 @@ +# 前言 MySQL简介 + +​ MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。 + +​ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行的开源数据库,因此被广泛地应用在Internet上的中小型网站中。随着MySQL的不断成熟,它也逐渐用于更多大规模网站和应用,比如维基百科、Google和Facebook等网站。非常流行的开源软件组合LAMP中的“M”指的就是MySQL。 + +[百度百科]: https://baike.baidu.com/item/mySQL/471251 +[维基百科]: https://zh.wikipedia.org/wiki/MySQL + + + +# 一、 Ubuntu下安装MySQL + +安装教程是在`Ubuntu20.04`下进行的,安装的MySQL版本为`8.0.27`。 + +## 1.1 安装 + +```bash +sudo apt install mysql-server mysql-client +``` + +在输入密码后,再输入`yes`即可开始安装。 + +安装完成后,通过运行命令`mysql -V`查看版本号: + +```bash +lyons@ubuntu:~$ mysql -V +mysql Ver 8.0.27-0ubuntu0.20.04.1 for Linux on x86_64 ((Ubuntu)) +``` + +验证MySQL服务正在运行,命令行下输入: + +```bash +sudo service mysql status +``` + +如果正在运行,则会显示: + +```bash +● mysql.service - MySQL Community Server + Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled) + Active: active (running) since Wed 2021-10-27 10:27:59 CST; 9h ago + Main PID: 6179 (mysqld) + Status: "Server is operational" + Tasks: 39 (limit: 4599) + Memory: 348.9M + CGroup: /system.slice/mysql.service + └─6179 /usr/sbin/mysqld + +10月 27 10:27:59 ubuntu systemd[1]: Starting MySQL Community Server... +10月 27 10:27:59 ubuntu systemd[1]: Started MySQL Community Server. +``` + + + +## 1.2 配置MySQL的安全性 + +1. 首先,运行命令`mysql_secure_installation`: + + ```bash + sudo mysql_secure_installation + ``` + +2. `VALIDATE PASSWORD COMPONENT` + + 设置验证密码插件。它被用来测试`MySQL`用户的密码强度,并且提高安全性。如果想设置验证密码插件,请输入`y`: + + ```bash + Connecting to MySQL using a blank password. + + VALIDATE PASSWORD COMPONENT can be used to test passwords + and improve security. It checks the strength of password + and allows the users to set only those passwords which are + secure enough. Would you like to setup VALIDATE PASSWORD component? + + Press y|Y for Yes, any other key for No: y + ``` + + 接下来,将进行密码验证等级设置,根据数字设置对应等级,这里设置为0: + + ```bash + There are three levels of password validation policy: + + LOW Length >= 8 + MEDIUM Length >= 8, numeric, mixed case, and special characters + STRONG Length >= 8, numeric, mixed case, special characters and dictionary file + + Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 0 + ``` + +3. 设置密码 + + 为MySQL root用户设置密码,设置过程中密码不会显示。如果设置了验证密码插件,将会显示密码的强度。 + + ``` + Please set the password for root here. + New password: + + Re-enter new password: + + Estimated strength of the password: 25 + Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y + ``` + +4. 移除匿名用户 + + 默认情况下,MySQL安装有一个匿名用户,允许任何人登录MySQL,而不必为他们创建用户帐户。输入`y`进行删除: + + ``` + By default, a MySQL installation has an anonymous user, + allowing anyone to log into MySQL without having to have + a user account created for them. This is intended only for + testing, and to make the installation go a bit smoother. + You should remove them before moving into a production + environment. + + Remove anonymous users? (Press y|Y for Yes, any other key for No) : y + Success. + ``` + +5. 禁止远程root用户登录 + + 输入`y`后按`enter`,将会禁止`root`用户登录。 + + ``` + Normally, root should only be allowed to connect from + 'localhost'. This ensures that someone cannot guess at + the root password from the network. + + Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y + Success. + ``` + +6. 删除测试库 + + 输入`y`后按`enter`,将会删除测试库。 + + ``` + By default, MySQL comes with a database named 'test' that + anyone can access. This is also intended only for testing, + and should be removed before moving into a production + environment. + + + Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y + - Dropping test database... + Success. + ``` + +7. 重新加载特权表 + + 输入`y`后按`enter`,将会重新加载特权表。 + + ``` + - Removing privileges on test database... + Success. + + Reloading the privilege tables will ensure that all changes + made so far will take effect immediately. + + Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y + Success. + + All done! + ``` + + 至此,配置完成。 + + + +## 1.3 以root用户登录 + +在MySQL 8.0上,root 用户默认通过`auth_socket`插件授权。`auth_socket`插件通过 Unix socket 文件来验证所有连接到`localhost`的用户。 + +这意味着你不能通过提供密码,验证为 root。此时,输入`mysql -uroot -p`可能会被拒绝访问: + +```bash +lyons@ubuntu:~$ mysql -uroot -p +mysql: [Warning] Using a password on the command line interface can be insecure. +ERROR 1698 (28000): Access denied for user 'root'@'localhost' +``` + +若要以 root 用户身份登录 MySQL服务器,输入`sudo mysql`,如下: + +```bash +# 登录密码为linux系统用户的root密码 +lyons@ubuntu:~$ sudo mysql +[sudo] lyons 的密码: +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 55 +Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu) + +Copyright (c) 2000, 2021, Oracle and/or its affiliates. + +Oracle is a registered trademark of Oracle Corporation and/or its +affiliates. Other names may be trademarks of their respective +owners. + +Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. + +mysql> +``` + +退出MySQL,请输入`exit`命令: + +```mysql +mysql> exit +Bye +lyons@ubuntu:~$ +``` + +如果你想以 root 身份登录 MySQL 服务器,便于使用其他的程序。可以将验证方法从`auth_socket`修改成`mysql_native_password`。 + ++ **方式1** + +你可以通过运行下面的命令实现: + +```bash +-- 语法中的'你的密码’指的是你自己设置的登录密码,可设置为字母数字组合。 +ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; +FLUSH PRIVILEGES; +``` + +示例: + +```mysql +-- 在mysql下,将密码设置为'mysql123' +mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'mysql123'; +Query OK, 0 rows affected (0.00 sec) + +-- 刷新系统权限 +mysql> FLUSH PRIVILEGES; +Query OK, 0 rows affected (0.01 sec) + +mysql> exit +Bye + +-- 现在便可以通过mysql -uroot -p登录 +-- 登录密码为前面设置的'mysql123' +lyons@ubuntu:~$ mysql -uroot -p +Enter password: +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 57 +Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu) + +Copyright (c) 2000, 2021, Oracle and/or its affiliates. + +Oracle is a registered trademark of Oracle Corporation and/or its +affiliates. Other names may be trademarks of their respective +owners. + +Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. + +mysql> +mysql> exit +Bye + +-- 同时,命令sudo mysql会被拒绝访问 +lyons@ubuntu:~$ sudo mysql +ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO) +``` + +当然,若要再次修改回`sudo mysql`的方式来登录`root`用户,方法类似: + +```mysql +ALTER USER 'root'@'localhost' IDENTIFIED WITH auth_socket BY '你的密码'; + +FLUSH PRIVILEGES; +``` + ++ **方式2** + + 推荐的选项,就是创建一个新的独立管理用户,拥有所有数据库的访问权限: + +```mysql +# 创建用户 +CREATE USER '用户名'@'localhost' identified by '你的密码' + +# 赋予admin用户全部的权限,你也可以只授予部分权限 +GRANT ALL PRIVILEGES ON *.* TO '用户名'@'localhost'; +``` + +​ 示例: + +```mysql +# 创建名为admin的用户,密码为mysql123 +mysql> create user 'admin'@'localhost' identified by 'mysql123'; +Query OK, 0 rows affected (0.01 sec) + +# 将访问所有database以及表的权利授权用户admin +#with gran option表示该用户可给其它用户赋予权限,但不可能超过该用户已有的权限 +mysql> grant all privileges on *.* to 'admin'@'localhost' with grant option; +Query OK, 0 rows affected (0.00 sec) + +mysql> FLUSH PRIVILEGES; +Query OK, 0 rows affected (0.00 sec) + +# 查看已有的用户 +mysql> select user, host from mysql.user; ++------------------+-----------+ +| user | host | ++------------------+-----------+ +| admin | localhost | +| debian-sys-maint | localhost | +| mysql.infoschema | localhost | +| mysql.session | localhost | +| mysql.sys | localhost | +| root | localhost | ++------------------+-----------+ +6 rows in set (0.00 sec) + +# 退出root用户登录 +mysql> exit +Bye + +# 登录admin用户,输入密码mysql123即可登录成功 +lyons@ubuntu:~$ mysql -uadmin -p +Enter password: +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 16 +Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu) + +Copyright (c) 2000, 2021, Oracle and/or its affiliates. + +Oracle is a registered trademark of Oracle Corporation and/or its +affiliates. Other names may be trademarks of their respective +owners. + +Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. + +mysql> +``` + +说明:`'admin'@'localhost'`中,`localhost`指本地才可连接,可以将其换成`%`指任意`ip`都能连接,也可以指定`ip`连接。 + + + +## 1.4 修改密码 + +将用户`admin`的登录密码修改为`mysql321`: + +```mysql +ALTER USER 'admin'@'localhost' IDENTIFIED WITH mysql_native_password BY 'mysql321'; +``` + + + +## 1.5 撤销用户授权 + +```mysql +# 查看用户的权限 +show grants for 'admin'@'localhost'; + +# 撤销用户的权限 +# 用户有什么权限就撤销什么 +revoke all privileges on *.* from 'admin'@'localhost'; +``` + + + +## 1.6 删除用户 + +```MYSQL +drop user 'admin'@'localhost'; +``` + + + +> 注:MySQL 8.0版本和5.0部分命令有所改掉,上述语法都是在8.0版本下运行通过的;请务必检查自己的MySQL版本号。 + + + +# 二、MySQL预备知识 + +在正式学习MySQL之前,我们先来了解一下SQL语句的书写规范以及命名规则等。 + +## 2.1 SQL书写规范 + +在写SQL语句时,要求按照如下规范进行: + ++ SQL 语句要以分号(;)结尾 + ++ SQL 不区分关键字的大小写 ,这对于表名和列名同样适用。 + ++ 插入到表中的数据是区分大小写的。例如,数据Computer、COMPUTER 或computer,三者是不一样的。 + ++ 常数的书写方式是固定的,在SQL 语句中直接书写的字符串、日期或者数字等称为常数。常数的书写方式如下所示。 + + + SQL 语句中含有字符串的时候,需要像'abc'这样,使用单引号(')将字符串括起来,用来标识这是一个字符串。 + + SQL 语句中含有日期的时候,同样需要使用单引号将其括起来。日期的格式有很多种('26 Jan 2010' 或者'10/01/26' 等)。 + + 在SQL 语句中书写数字的时候,不需要使用任何符号标识,直接写成1000 这样的数字即可。 + ++ 单词之间需要用半角空格或者换行来分隔。 + ++ SQL中的注释主要采用`--`和`/* ... */`的方式,第二种方式可以换行。在MySQL下,还可以通过`#`来进行注释。 + + + +## 2.2 命名规则 + ++ 在数据库中,只能使用半角英文字母、数字、下划线(_)作为数据库、表和列的名称 。 ++ 名称必须以半角英文字母作为开头。 ++ 名称不能重复,同一个数据库下不能有2张相同的表。 + + + +## 2.3. 数据类型 + +MySQL 支持所有标准 SQL 数值数据类型,包括: + +### (1)数值类型 + +数值包含的类型如下: + ++ 整型数据:`TINYINT`、`INTEGER`、`SMALLINT`、`MEDIUMINT`、`DECIMAL` 、`NUMERIC` 和`BIGINT`。 + ++ 浮点型数据:`DECIMAL`、`FLOAT`、`REAL` 和 `DOUBLE PRECISION`)。 + +其中,关键字`INT`是`INTEGER`的同义词,关键字DEC是的同义词。 + +不同关键字的主要区别就是表示的范围或精度不一样。具体如下表: + +| 类型 | 大小 | 范围(有符号) | 范围(无符号) | 用途 | +| :----------: | :--------------------------------------: | :----------------------------------------------------------- | :----------------------------------------------------------- | :-------------- | +| TINYINT | 1 Bytes | (-128,127) | (0,255) | 小整数值 | +| SMALLINT | 2 Bytes | (-32 768,32 767) | (0,65 535) | 大整数值 | +| MEDIUMINT | 3 Bytes | (-8 388 608,8 388 607) | (0,16 777 215) | 大整数值 | +| INT或INTEGER | 4 Bytes | (-2 147 483 648,2 147 483 647) | (0,4 294 967 295) | 大整数值 | +| BIGINT | 8 Bytes | (-9,223,372,036,854,775,808,9 223 372 036 854 775 807) | (0,18 446 744 073 709 551 615) | 极大整数值 | +| FLOAT | 4 Bytes | (-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) | 0,(1.175 494 351 E-38,3.402 823 466 E+38) | 单精度 浮点数值 | +| DOUBLE | 8 Bytes | (-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 双精度 浮点数值 | +| DECIMAL | 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 | 依赖于M和D的值 | 依赖于M和D的值 | 小数值 | + +### (2)日期和时间类型 + +表示时间值的日期和时间类型为`DATETIME`、`DATE`、`TIMESTAMP`、`TIME`和`YEAR`。具体如下表: + +| 类型 | 大小 ( bytes) | 范围 | 格式 | 用途 | +| :-------- | :------------ | :----------------------------------------------------------- | :------------------ | :----------------------- | +| DATE | 3 | 1000-01-01/9999-12-31 | YYYY-MM-DD | 日期值 | +| TIME | 3 | '-838:59:59'/'838:59:59' | HH:MM:SS | 时间值或持续时间 | +| YEAR | 1 | 1901/2155 | YYYY | 年份值 | +| DATETIME | 8 | 1000-01-01 00:00:00/9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS | 混合日期和时间值 | +| TIMESTAMP | 4 | 1970-01-01 00:00:00/2038结束时间是第 2147483647 秒,北京时间 2038-1-19 11:14:07,格林尼治时间 2038年1月19日 凌晨 03:14:07 | YYYYMMDD HHMMSS | 混合日期和时间值,时间戳 | + +### (3)字符串类型 + +字符串类型指`CHAR`、`VARCHAR`、`BINARY`、`VARBINARY`、`BLOB`、`TEXT`、`ENUM`和`SET`。具体如下表: + +| 类型 | 大小 | 用途 | +| :--------- | :-------------------- | :------------------------------ | +| CHAR | 0-255 bytes | 定长字符串 | +| VARCHAR | 0-65535 bytes | 变长字符串 | +| TINYBLOB | 0-255 bytes | 不超过 255 个字符的二进制字符串 | +| TINYTEXT | 0-255 bytes | 短文本字符串 | +| BLOB | 0-65 535 bytes | 二进制形式的长文本数据 | +| TEXT | 0-65 535 bytes | 长文本数据 | +| MEDIUMBLOB | 0-16 777 215 bytes | 二进制形式的中等长度文本数据 | +| MEDIUMTEXT | 0-16 777 215 bytes | 中等长度文本数据 | +| LONGBLOB | 0-4 294 967 295 bytes | 二进制形式的极大文本数据 | +| LONGTEXT | 0-4 294 967 295 bytes | 极大文本数据 | + ++ `char`声明的是定长字符串。若实际中字符串长度不足,则会在末尾使用空格进行填充至声明的长度。 + ++ `varchar`声明的是可变长字符串。存储过程中,只会按照字符串的实际长度来存储,但会多占用一位来存放实际字节的长度。 + + + +# 三、 数据库的基本操作 + +首先,我们来学习在MySQL下如何操作数据库。 + +## 3.1 数据库的创建 + +通过`CREATE`命令,可以创建指定名称的数据库,语法结构如下: + +```mysql +CREATE DATABASE [IF NOT EXISTS] <数据库名称>; +``` + +MySQL 的数据存储区将以目录方式表示 MySQL 数据库,因此数据库名称必须符合操作系统的文件夹命名规则,不能以数字开头,尽量要有实际意义。 + +MySQL下不运行存在两个相同名字的数据库,否则会报错。如果使用`IF NOT EXISTS`(可选项),可以避免此类错误。 + +示例: + +```mysql +-- 创建名为shop的数据库。 +CREATE DATABASE shop; +``` + + + +## 3.2 数据库的查看 + +1. 查看所有存在的数据库 + +```MYSQL +SHOW DATABASES [LIKE '数据库名'];; +``` + +`LIKE`从句是可选项,用于匹配指定的数据库名称。`LIKE` 从句可以部分匹配,也可以完全匹配。 + +示例: + +```mysql +SHOW DATABASES; + +-- 结果如下: ++--------------------+ +| Database | ++--------------------+ +| information_schema | +| mysql | +| performance_schema | +| shop | +| sys | ++--------------------+ +5 rows in set (0.01 sec) +``` + +```mysql +-- %表示任意0个或多个字符,可匹配任意类型和长度的字符。 +SHOW DATABASES LIKE 'S%'; + +-- 结果如下 ++---------------+ +| Database (S%) | ++---------------+ +| shop | +| sys | ++---------------+ +2 rows in set (0.00 sec) +``` + +2. 查看创建的数据库 + +```mysql +SHOW CREATE DATABASE <数据库名>; +``` + +示例: + +```mysql +SHOW CREATE DATABASE shop; + +-- 或者 +SHOW CREATE DATABASE shop \G + +-- 结果如下 +*************************** 1. row *************************** + Database: shop +Create Database: CREATE DATABASE `shop` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */ +1 row in set (0.00 sec) +``` + +`CHARACTER SET utf8mb4`表示编码字符集为`utf8mb4`。 + + + +## 3.3 选择数据库 + +在操作数据库前,必须指定所要操作的数据库。通过`USE`命令,可以切换到对应的数据库下。 + +```mysql +USE <数据库名> +``` + +示例: + +```mysql +-- 切换到数据库shop下。 +USE shop; + +-- 结果如下 +Database changed +``` + + + +## 3.4 删除数据库 + +通过`DROP`命令,可以将相应数据库进行删除。 + +```mysql +DROP DATABASE [IF EXISTS] <数据库名> +``` + +其中,`IF EXISTS`为可选性,用于防止数据库不存在时报错。 + +示例: + +```mysql +DROP DATABASE shop; + +SHOW DATABASES; +``` + +考虑到后面表的操作都是shop数据库下,在实验完`DROP`删除数据库命令后,请从新创建数据库shop并通过`USE`命令切换到该数据库下。 + + + +# 四、表的基本操作 + +表相当于文件,表中的一条记录就相当于文件的一行内容,不同的是,表中的一条记录有对应的标题,称为表的字段。 + +## 4.1 表的创建 + +创建表的语法结构如下: + +```mysql +CREATE TABLE <表名> (<字段1> <数据类型> <该列所需约束>, + <字段2> <数据类型> <该列所需约束>, + <字段3> <数据类型> <该列所需约束>, + <字段4> <数据类型> <该列所需约束>, + . + . + . + <该表的约束1>, <该表的约束2>,……); +``` + +示例: + +```mysql +-- 创建一个名为Product的表 +CREATE TABLE Product( + product_id CHAR(4) NOT NULL, + product_name VARCHAR(100) NOT NULL, + product_type VARCHAR(32) NOT NULL, + sale_price INT, + purchase_price INT, + regist_date DATE, + PRIMARY KEY (product_id) +); +``` + +在第二章中,我们介绍过不同的数据类型: + ++ `CHAR`为定长字符,这里`CHAR`旁边括号里的数字表示该字段最长为多少字符,少于该数字将会使用空格进行填充。 + ++ `VARCHAR`表示变长字符,括号里的数字表示该字段最长为多少字符,存储时只会按照字符的实际长度来存储,但会使用额外的1-2字节来存储值长度。 + + + +简单介绍一下该语句中出现的约束条件,约束条件在后面会详细介绍: + ++ `PRIMARY KEY`:主键,表示该字段对应的内容唯一且不能为空。 ++ `NOT NULL`:在 `NULL` 之前加上了表示否定的` NOT`,表示该字段不能输入空白。 + +通过`SHOW TABLES`命令来查看当前数据库下的所有的表名: + +```mysql +SHOW TABLES; + +-- 结果如下 ++----------------+ +| Tables_in_shop | ++----------------+ +| Product | ++----------------+ +1 rows in set (0.00 sec) +``` + +通过`DESC <表名>`来查看表的结构: + +```mysql +DESC Product; + +-- 结果如下 ++----------------+--------------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++----------------+--------------+------+-----+---------+-------+ +| product_id | char(4) | NO | PRI | NULL | | +| product_name | varchar(100) | NO | | NULL | | +| product_type | varchar(32) | NO | | NULL | | +| sale_price | int | YES | | NULL | | +| purchase_price | int | YES | | NULL | | +| regist_date | date | YES | | NULL | | ++----------------+--------------+------+-----+---------+-------+ +6 rows in set (0.00 sec) +``` + + + +## 4.2 表的删除 + +删除表的语法结构如下: + +```mysql +DROP TABLE <表名>; + +-- 例如:DROP TABLE Product; +``` + +说明:通过`DROP`删除的表示无法恢复的,在删除表的时候请谨慎。 + + + +## 4.3 表的更新 + +通过`ALTER TABLE`语句,我们可以对表字段进行不同的操作,下面通过示例来具体学习用法。 + +示例: + +1. 创建一张名为Student的表 + +```mysql +CREATE TABLE Student( + id INT PRIMARY KEY, + name CHAR(15) +); +``` + + + +```mysql +DESC student; + +-- 结果如下 ++-------+----------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++-------+----------+------+-----+---------+-------+ +| id | int | NO | PRI | NULL | | +| name | char(15) | YES | | NULL | | ++-------+----------+------+-----+---------+-------+ +2 rows in set (0.00 sec) +``` + +2. 更改表名 + + 通过`RENAME`命令,将表名从Student => Students。 + +```mysql +ALTER TABLE Student RENAME Students; +``` + +3. 插入新的字段 + + 通过`ADD`命令,新增字段sex和age。 + +```mysql +-- 不同的字段通过逗号分开 +ALTER TABLE Students ADD sex CHAR(1), ADD age INT; +``` + +​ 其它插入技巧: + +```mysql +-- 通过FIRST在表首插入字段stu_num +ALTER TABLE Students ADD stu_num INT FIRST; + +-- 指定在字段sex后插入字段height +ALTER TABLE Students ADD height INT AFTER sex; +``` + +```mysql +DESC Students; + +-- 结果如下 ++---------+----------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++---------+----------+------+-----+---------+-------+ +| stu_num | int | YES | | NULL | | +| id | int | NO | PRI | NULL | | +| name | char(15) | YES | | NULL | | +| sex | char(1) | YES | | NULL | | +| height | int | YES | | NULL | | +| age | int | YES | | NULL | | ++---------+----------+------+-----+---------+-------+ +6 rows in set (0.00 sec) +``` + +4. 字段的删除 + + 通过`DROP`命令,可以对不在需要的字段进行删除。 + +```mysql +-- 删除字段stu_num +ALTER TABLE Students DROP stu_num; +``` + +5. 字段的修改 + + 通过`MODIFY`修改字段的数据类型。 + +```mysql +-- 修改字段age的数据类型 +ALTER TABLE Students MODIFY age CHAR(3); +``` + +​ 通过`CHANGE`命令,修改字段名或类型 + +```mysql +-- 修改字段name为stu_name,不修改数据类型 +ALTER TABLE Students CHANGE name stu_name CHAR(15); + +-- 修改字段sex为stu_sex,数据类型修改为int +ALTER TABLE Students CHANGE sex stu_sex INT; +``` + +```mysql +DESC Students; + +-- 结果如下 ++----------+----------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++----------+----------+------+-----+---------+-------+ +| id | int | NO | PRI | NULL | | +| stu_name | char(20) | YES | | NULL | | +| stu_sex | int | YES | | NULL | | +| height | int | YES | | NULL | | +| age | char(3) | YES | | NULL | | ++----------+----------+------+-----+---------+-------+ +5 rows in set (0.00 sec) +``` + + + +## 4.4 表的查询 + +通过`SELECT`语句,可以从表中取出所要查看的字段的内容: + +```mysql +SELECT <字段名>, …… + FROM <表名>; +``` + +如要直接查询表的全部字段: + +```mysql +SELECT * + FROM <表名>; +``` + +其中,**星号(*)**代表全部字段的意思。 + +示例: + +1. 建表并插入数据 + + 在MySQL中,我们通过`INSERT`语句往表中插入数据,该语句在后面会详细介绍,该小节的重点是学会使用`SELECT`。 + +```mysql +-- 向Product表中插入数据 +INSERT INTO Product VALUES + ('0001', 'T恤衫', '衣服', 1000, 500, '2009-09-20'), + ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11'), + ('0003', '运动T恤', '衣服', 4000, 2800, NULL), + ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'), + ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'), + ('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20'), + ('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28'), + ('0008', '圆珠笔', '办公用品', 100, NULL,'2009-11-11') + ; +``` + +2. 查看表的内容 + +```mysql +-- 查看表的全部内容 +SELECT * + FROM Product; + +-- 结果如下 ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | +| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | +| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | +| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | +| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | +| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | +| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | +| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | ++------------+--------------+--------------+------------+----------------+-------------+ +8 rows in set (0.00 sec) +``` + +```mysql +-- 查看部分字段包含的内容 +SELECT + product_id, + product_name, + sale_price + FROM Product; + +-- 结果如下 ++------------+--------------+------------+ +| product_id | product_name | sale_price | ++------------+--------------+------------+ +| 0001 | T恤衫 | 1000 | +| 0002 | 打孔器 | 500 | +| 0003 | 运动T恤 | 4000 | +| 0004 | 菜刀 | 3000 | +| 0005 | 高压锅 | 6800 | +| 0006 | 叉子 | 500 | +| 0007 | 擦菜板 | 880 | +| 0008 | 圆珠笔 | 100 | ++------------+--------------+------------+ +8 rows in set (0.00 sec) +``` + +3. 对查看的字段从新命名 + + 通过`AS`语句对展示的字段另起别名,这不会修改表内字段的名字。 + +```mysql +SELECT + product_id AS ID, + product_type AS TYPE + FROM Product; + +-- 结果如下 ++------+--------------+ +| ID | TYPE | ++------+--------------+ +| 0001 | 衣服 | +| 0002 | 办公用品 | +| 0003 | 衣服 | +| 0004 | 厨房用具 | +| 0005 | 厨房用具 | +| 0006 | 厨房用具 | +| 0007 | 厨房用具 | +| 0008 | 办公用品 | ++------+--------------+ +8 rows in set (0.00 sec) +``` + +​ 设定汉语别名时需要使用双引号(")括起来,英文字符则不需要。 + +```Mysql +SELECT + product_id AS "产品编号", + product_type AS "产品类型" + FROM Product; +``` + +4. 常数的查询 + + `SELECT`子句中,除了可以写字段外,还可以写常数。 + +```mysql +SELECT + '商品' AS string, + '2009-05-24' AS date, + product_id, + product_name + FROM Product; + +-- 结果如下 ++--------+------------+------------+--------------+ +| string | date | product_id | product_name | ++--------+------------+------------+--------------+ +| 商品 | 2009-05-24 | 0001 | T恤衫 | +| 商品 | 2009-05-24 | 0002 | 打孔器 | +| 商品 | 2009-05-24 | 0003 | 运动T恤 | +| 商品 | 2009-05-24 | 0004 | 菜刀 | +| 商品 | 2009-05-24 | 0005 | 高压锅 | +| 商品 | 2009-05-24 | 0006 | 叉子 | +| 商品 | 2009-05-24 | 0007 | 擦菜板 | +| 商品 | 2009-05-24 | 0008 | 圆珠笔 | ++--------+------------+------------+--------------+ +8 rows in set (0.00 sec) +``` + +5. 删除重复行 + + 在`SELECT`语句中使用`DISTINCT`可以去除重复行。 + +```mysql +SELECT + DISTINCT regist_date + FROM Product; + +-- 结果如下 ++-------------+ +| regist_date | ++-------------+ +| 2009-09-20 | +| 2009-09-11 | +| NULL | +| 2009-01-15 | +| 2008-04-28 | +| 2009-11-11 | ++-------------+ +6 rows in set (0.01 sec) +``` + +​ 在使用`DISTINCT` 时,`NULL `也被视为一类数据。`NULL `存在于多行中时,会被合并为一条`NULL `数据。 + +​ 还可以通过组合使用,来去除列组合重复的数据。`DISTINCT `关键字只能用在第一个列名之前。 + +```mysql +SELECT + DISTINCT product_type, regist_date + FROM Product; + +-- 结果如下,列出了所有的组合 ++--------------+-------------+ +| product_type | regist_date | ++--------------+-------------+ +| 衣服 | 2009-09-20 | +| 办公用品 | 2009-09-11 | +| 衣服 | NULL | +| 厨房用具 | 2009-09-20 | +| 厨房用具 | 2009-01-15 | +| 厨房用具 | 2008-04-28 | +| 办公用品 | 2009-11-11 | ++--------------+-------------+ +7 rows in set (0.00 sec) +``` + +6. 指定查询条件 + + 首先通过`WHERE` 子句查询出符合指定条件的记录,然后再选取出` SELECT `语句指定的列,语法结构如下: + +```mysql +SELECT <字段名>, …… + FROM <表名> + WHERE <条件表达式>; +``` + +​ 示例: + +```mysql +SELECT product_name + FROM Product + WHERE product_type = '衣服'; + +-- 结果如下 ++--------------+ +| product_name | ++--------------+ +| T恤衫 | +| 运动T恤 | ++--------------+ +2 rows in set (0.01 sec) +``` + +注意,`WHERE`子句要紧跟在`FROM`子句之后。 + + + +## 4.5 表的复制 + +表的复制可以将表结构与表中的数据全部复制,或者只复制表的结构。 + +```mysql +-- 将整个表复制过来 +CREATE TABLE Product_COPY1 + SELECT * FROM Product; + +SELECT * FROM Product_COPY1; + +-- 结果如下 ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | +| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | +| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | +| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | +| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | +| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | +| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | +| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | ++------------+--------------+--------------+------------+----------------+-------------+ +8 rows in set (0.00 sec) +``` + +```mysql +-- 通过LIKE复制表结构 +CREATE TABLE Product_COPY2 + LIKe Product; + +SELECT * FROM Product_COPY2; + +-- 结果如下 +Empty set (0.00 sec) -- 表为空的 + +DESC Product_COPY2; + +-- 结果如下 +-- 表结构已复制过来 ++----------------+--------------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++----------------+--------------+------+-----+---------+-------+ +| product_id | char(4) | NO | PRI | NULL | | +| product_name | varchar(100) | NO | | NULL | | +| product_type | varchar(32) | NO | | NULL | | +| sale_price | int | YES | | 0 | | +| purchase_price | int | YES | | NULL | | +| regist_date | date | YES | | NULL | | ++----------------+--------------+------+-----+---------+-------+ +6 rows in set (0.01 sec) +``` + + + +# 五、运算符 + +## 5.1 算术运算符 + +我们可以在`SELECT`语句中使用计算表达式: + +```mysql +SELECT + product_name, + sale_price, + sale_price * 2 AS "sale_price_x2" + FROM Product; + +-- 结果如下 ++--------------+------------+---------------+ +| product_name | sale_price | sale_price_x2 | ++--------------+------------+---------------+ +| T恤衫 | 1000 | 2000 | +| 打孔器 | 500 | 1000 | +| 运动T恤 | 4000 | 8000 | +| 菜刀 | 3000 | 6000 | +| 高压锅 | 6800 | 13600 | +| 叉子 | 500 | 1000 | +| 擦菜板 | 880 | 1760 | +| 圆珠笔 | 100 | 200 | ++--------------+------------+---------------+ +8 rows in set (0.00 sec) +``` + ++ 四则运算所使用的运算符**(+、-、*、/)**称为算术运算符。 + ++ 在运算表达式中,也可以使用**()**,括号中的运算表达式优先级会得到提升。 + ++ **NULL**的计算结果,仍然还是**NULL**。 + + + +## 5.2 比较运算符 + +在 `WHERE` 子句中通过使用比较运算符可以组合出各种各样的条件表达式。 + +```mysql +SELECT product_name, product_type + FROM Product + WHERE sale_price = 500; +``` + +常见比较运算符如下表: + +| 运算符 | 含义 | +| ------ | -------- | +| = | 相等 | +| <> | 不相等 | +| \>= | 大于等于 | +| \> | 大于 | +| <= | 小于等于 | +| < | 小于 | + ++ 不能对**NULL**使用任何比较运算符,只能通过`IS NULL`语句来判断: + +```mysql +SELECT + product_name, + purchase_price + FROM Product + WHERE purchase_price IS NULL; +``` + +​ 希望选取不是 NULL 的记录时,需要使用`IS NOT NULL`运算符。 + ++ 对字符串使用比较符 + +​ MySQL中字符串的排序与数字不同,典型的规则就是按照字典顺序进行比较,也就是像姓名那样,按照条目在字典中出现的顺序来进行排序。例如: + +```mysql +'1' < '10' < '11' < '2' < '222' < '3' +``` + + + +## 5.3 逻辑运算符 + +1. 使用`NOT`否认某一条件: + +```mysql +SELECT + product_name, + product_type, + sale_price + FROM Product + WHERE NOT sale_price >= 1000; +``` + +2. `AND`运算符合`OR`运算符 + +```mysql +SELECT product_type, sale_price + FROM Product + WHERE product_type = '厨房用具' + AND sale_price >= 3000; + +-- 结果如下 ++--------------+------------+ +| product_type | sale_price | ++--------------+------------+ +| 厨房用具 | 3000 | +| 厨房用具 | 6800 | ++--------------+------------+ +2 rows in set (0.00 sec) +``` + +```mysql +SELECT product_type, sale_price + FROM Product + WHERE product_type = '厨房用具' + OR sale_price >= 3000; + +-- 结果如下 ++--------------+------------+ +| product_type | sale_price | ++--------------+------------+ +| 衣服 | 4000 | +| 厨房用具 | 3000 | +| 厨房用具 | 6800 | +| 厨房用具 | 500 | +| 厨房用具 | 880 | ++--------------+------------+ +5 rows in set (0.00 sec) +``` + +3. 逻辑运算符和真值 + ++ 符**NOT**、**AND** 和 **OR** 称为逻辑运算符; ++ 真值就是值为**真(TRUE)**或**假 (FALSE)**; + ++ 在查询**NULL**时,SQL中存在第三种真值,**不确定(UNKNOWN)**,**NULL**和任何值做逻辑运算结果都是不确定; ++ 考虑 **NULL** 时的条件判断也会变得异常复杂,因此尽量给字段加上**NOT NULL**的约束。 + + + +# 六、分组查询 + +## 6.1 聚合函数 + +通过 SQL 对数据进行某种操作或计算时需要使用函数。 + ++ `COUNT`:计算表中的记录数(行数) + ++ `SUM`: 计算表中数值列中数据的合计值 + ++ `AVG`: 计算表中数值列中数据的平均值 + ++ `MAX`: 求出表中任意列中数据的最大值 + ++ `MIN`: 求出表中任意列中数据的最小值 + +示例: + +```mysql +-- 计算全部数据的行数 +SELECT COUNT(*) FROM Product; + +-- 结果如下 ++----------+ +| COUNT(*) | ++----------+ +| 8 | ++----------+ +1 row in set (0.00 sec) +``` + +**注意点1**:除了`COUNT`可以将`*`作为参数,其它的函数均不可以。 + +```mysql +-- 计算最高的销售价格 +SELECT MAX(sale_price) FROM Product; + +-- 结果如下 ++-----------------+ +| MAX(sale_price) | ++-----------------+ +| 680000 | ++-----------------+ +1 row in set (0.00 sec) +``` + +**注意点2:**当将字段名作为参数传递给函数时,只会计算不包含`NULL`的行。 + +示例: + +```mysql +-- purchase_price字段是包含NULL值的 +SELECT purchase_price FROM Product; + +-- 结果如下 ++----------------+ +| purchase_price | ++----------------+ +| 500 | +| 320 | +| 2800 | +| 700 | +| 1250 | +| NULL | +| 198 | +| NULL | ++----------------+ +8 rows in set (0.00 sec) +``` + +以*为参数传递给`COUNT`函数 + +```mysql +SELECT COUNT(*) FROM Product; + +-- 结果如下 ++----------+ +| COUNT(*) | ++----------+ +| 8 | ++----------+ +1 row in set (0.00 sec) +``` + +以purchase_price为参数传递给`COUNT`函数 + +```mysql +SELECT COUNT(purchase_price) FROM Product; + +-- 结果如下 ++-----------------------+ +| COUNT(purchase_price) | ++-----------------------+ +| 6 | ++-----------------------+ +1 row in set (0.00 sec) +``` + +可以看到结果并不一样,函数忽略了值为**NULL**的行。 + +`SUM`,`AVG`函数时也一样,计算时会直接忽略,**并不会当做0来处理!**特别注意`AVG`函数,计算时分母也不会算上`NULL`行。 + +**注意点3**:`MAX/MIN`函数几乎适用于所有数据类型的列,包括字符和日期。`SUM/AVG`函数只适用于数值类型的列。 + +**注意点4**:在聚合函数删除重复值 + +```mysql +SELECT COUNT(DISTINCT product_type) + FROM Product; + +-- 结果如下 ++------------------------------+ +| COUNT(DISTINCT product_type) | ++------------------------------+ +| 3 | ++------------------------------+ +1 row in set (0.01 sec) +``` + +`DISTINCT`必须写在括号中。这是因为必须要在计算行数之前删除 product_type 字段中的重复数据。 + + + +## 6.2 对表分组 + +如果对Python的Pandas熟悉,那么大家应该很了解`groupby`函数,可以根据指定的列名,对表进行分组。在MySQL中,也存在同样作用的函数,即`GROUP BY`。 + +语法结构如下: + +```mysql +SELECT <列名1>, <列名2>, <列名3>, …… + FROM <表名> + GROUP BY <列名1>, <列名2>, <列名3>, ……; +``` + +示例: + +```mysql +SELECT product_type, COUNT(*) + FROM Product + GROUP BY product_type; + +-- 结果如下 ++--------------+----------+ +| product_type | COUNT(*) | ++--------------+----------+ +| 衣服 | 2 | +| 办公用品 | 2 | +| 厨房用具 | 4 | ++--------------+----------+ +3 rows in set (0.01 sec) +``` + +1. 在该语句中,我们首先通过`GROUP BY`函数对指定的字段product_type进行分组。分组时,product_type字段中具有相同值的行会汇聚到同一组。 + +2. 最后通过`COUNT`函数,统计不同分组的包含的行数。 + +简单来理解: + ++ 例如做操时,老师将不同身高的同学进行分组,相同身高的同学会被分到同一组,分组后我们又统计了每个小组的学生数。 + ++ 将这里的同学可以理解为表中的一行数据,身高理解为表的某一字段。 ++ 分组操作就是`GROUP BY`,`GROUP BY`后面接的字段等价于按照身高分组,统计学生数就等价于在`SELECT`后用了`COUNT(*)`函数。 + +注意:`GROUP BY `子句的位置一定要写在`FROM` 语句之后(如果有 `WHERE` 子句的话需要写在 `WHERE` 子句之后) + +``` +1. SELECT → 2. FROM → 3. WHERE → 4. GROUP BY +``` + +当被聚合的键中,包含`NULL`时,在结果中会以“不确定”行(空行)的形式表现出来,也就是字段中为`NULL`的数据会被聚合为一组。 + +## 6.3 使用WHERE语句 + +在对表进行分组之前,也可以是先使用`WHERE`对表进行条件过滤,然后再进行分组处理。语法结构如下: + +```mysql +SELECT <列名1>, <列名2>, <列名3>, …… + FROM <表名> + WHERE + GROUP BY <列名1>, <列名2>, <列名3>, ……; +``` + +示例: + +```mysql +-- WHERE语句先将表中类型为衣服的行筛选出来 +-- 然后再按照purchase_price来进行分组 +SELECT purchase_price, COUNT(*) + FROM Product + WHERE product_type = '衣服' + GROUP BY purchase_price; + +-- 结果如下 ++----------------+----------+ +| purchase_price | COUNT(*) | ++----------------+----------+ +| 500 | 1 | +| 2800 | 1 | ++----------------+----------+ +2 rows in set (0.01 sec) +``` + +该语法实际的执行顺序为: + +``` +FROM → WHERE → GROUP BY → SELECT +``` + ++ 使用`GROUP BY`子句时,`SELECT`子句中不能出现聚合键之外的字段名。即,若`GROUP BY`选中purchase_price字段进行分组,则在`SELECT`语句中只能选中purchase_price字段,其它字段如product_id等均不行。 ++ `WHERE`语句中,不可以使用聚合函数。`WHERE`子句只能指定记录(行)的条件,而不能用来指定组的条件。即`WHERE MAX(purchase_price) > 1000`这样的语句是非法的。 + + + +## 6.4 为聚合结果指定条件 + +前面提到了`WHERE`语句中不能使用聚合函数,但是实际操作时需要通过聚合函数来进行过滤怎么办呢?这就要用到`HAVING`语句了。语法结构如下: + +```mysql +SELECT <列名1>, <列名2>, <列名3>, …… + FROM <表名> + GROUP BY <列名1>, <列名2>, <列名3>, …… +HAVING <分组结果对应的条件> +``` + +在`HAVING`的子句中能够使用的 3 种要素如下所示: + +● 常数 + +● 聚合函数 + +● `GROUP BY`子句中指定的字段名(即聚合键) + +示例: + +```mysql +-- 不使用HAVING语句 +SELECT product_type, AVG(sale_price) + FROM Product + GROUP BY product_type; + +-- 结果如下 ++--------------+-----------------+ +| product_type | AVG(sale_price) | ++--------------+-----------------+ +| 衣服 | 2500.0000 | +| 办公用品 | 300.0000 | +| 厨房用具 | 279500.0000 | ++--------------+-----------------+ +3 rows in set (0.00 sec) +``` + +```mysql +-- 使用HAVING语句 +-- 通过HAVING语句将销售平均价格大于等于2500的组给保留了 +SELECT product_type, AVG(sale_price) + FROM Product + GROUP BY product_type +HAVING AVG(sale_price) >= 2500; + +-- 结果如下 ++--------------+-----------------+ +| product_type | AVG(sale_price) | ++--------------+-----------------+ +| 衣服 | 2500.0000 | +| 厨房用具 | 279500.0000 | ++--------------+-----------------+ +2 rows in set (0.00 sec) +``` + +可以看到使用`HAVING`语句后,输出的结果有所变化。大致流程如下: + ++ 首先,`FROM`语句会选中表Product; ++ 然后,`GROUP BY`语句会选中字段product_type进行分组; ++ 之后,通过`HAVING`语句将销售平均价格大于等于2500的组保留下来; ++ 最后,通过`SELECT`语句将保留下的组的产品类型和平均价格显示出来; + + + +如果是对**表的行**进行条件指定,`WHERE`和`HAVING`都可以生效。 + +```mysql +-- 下面两条语句执行结果一致 +SELECT product_type, COUNT(*) + FROM Product + GROUP BY product_type + HAVING product_type = '衣服'; + +SELECT product_type, COUNT(*) + FROM Product + WHERE product_type = '衣服' + GROUP BY product_type; + +-- 结果如下 ++--------------+----------+ +| product_type | COUNT(*) | ++--------------+----------+ +| 衣服 | 2 | ++--------------+----------+ +1 row in set (0.01 sec) +``` + +但是,一般而言如果是对表的行进行条件指定,最好还是使用`WHERE`语句,因为`WHERE`的执行速度更快。 + + + +## 6.5 对表的查询结果进行排序 + +如果希望对表的查询结果根据某指定的字段进行排序,可以使用`ORDER BY`语句。语法结构如下: + +```mysql +SELECT <列名1>, <列名2>, <列名3>, …… + FROM <表名> + ORDER BY <排序基准列1>, <排序基准列2>, …… +``` + +示例: + +```mysql +SELECT product_id, product_name, sale_price, purchase_price + FROM Product; + +-- 结果如下 ++------------+--------------+------------+----------------+ +| product_id | product_name | sale_price | purchase_price | ++------------+--------------+------------+----------------+ +| 0001 | T恤衫 | 1000 | 500 | +| 0002 | 打孔器 | 500 | 320 | +| 0003 | 运动T恤 | 4000 | 2800 | +| 0004 | 菜刀 | 300000 | 700 | +| 0005 | 高压锅 | 680000 | 1250 | +| 0006 | 叉子 | 50000 | NULL | +| 0007 | 擦菜板 | 88000 | 198 | +| 0008 | 圆珠笔 | 100 | NULL | ++------------+--------------+------------+----------------+ +8 rows in set (0.01 sec) +``` + +```mysql +-- 根据字段sale_price的值进行排序 +SELECT product_id, product_name, sale_price, purchase_price + FROM Product +ORDER BY sale_price; + +-- 结果如下 ++------------+--------------+------------+----------------+ +| product_id | product_name | sale_price | purchase_price | ++------------+--------------+------------+----------------+ +| 0008 | 圆珠笔 | 100 | NULL | +| 0002 | 打孔器 | 500 | 320 | +| 0001 | T恤衫 | 1000 | 500 | +| 0003 | 运动T恤 | 4000 | 2800 | +| 0006 | 叉子 | 50000 | NULL | +| 0007 | 擦菜板 | 88000 | 198 | +| 0004 | 菜刀 | 300000 | 700 | +| 0005 | 高压锅 | 680000 | 1250 | ++------------+--------------+------------+----------------+ +8 rows in set (0.00 sec) +``` + +可以看到`ORDER BY`默认是按照升序的方式进行排序的,正式的书写方式应该是在字段后加上关键字`ASC`,即`ORDER BY sale_price ASC`。 + +如果我们希望按照降序的方式,可以通过`DESC`关键词进行指定。 + +```mysql +SELECT product_id, product_name, sale_price, purchase_price + FROM Product +ORDER BY sale_price DESC; + +-- 结果如下 ++------------+--------------+------------+----------------+ +| product_id | product_name | sale_price | purchase_price | ++------------+--------------+------------+----------------+ +| 0005 | 高压锅 | 680000 | 1250 | +| 0004 | 菜刀 | 300000 | 700 | +| 0007 | 擦菜板 | 88000 | 198 | +| 0006 | 叉子 | 50000 | NULL | +| 0003 | 运动T恤 | 4000 | 2800 | +| 0001 | T恤衫 | 1000 | 500 | +| 0002 | 打孔器 | 500 | 320 | +| 0008 | 圆珠笔 | 100 | NULL | ++------------+--------------+------------+----------------+ +8 rows in set (0.00 sec) +``` + +前面展示了指定一个字段来对表进行排序,实际上我们可以指定多个字段来进行排序。 + +示例: + +```mysql +SELECT regist_date, product_id, sale_price, purchase_price + FROM Product +ORDER BY regist_date, product_id; + +-- 结果如下 ++-------------+------------+------------+----------------+ +| regist_date | product_id | sale_price | purchase_price | ++-------------+------------+------------+----------------+ +| 2009-10-10 | 0002 | 500 | 320 | +| 2009-10-10 | 0003 | 4000 | 2800 | +| 2009-10-10 | 0004 | 300000 | 700 | +| 2009-10-10 | 0005 | 680000 | 1250 | +| 2009-10-10 | 0006 | 50000 | NULL | +| 2009-10-10 | 0007 | 88000 | 198 | +| 2009-10-10 | 0008 | 100 | NULL | +| 2021-10-30 | 0001 | 1000 | 500 | ++-------------+------------+------------+----------------+ +``` + +可以看到先按照`regist_date`的大小进行排序,在字段`regist_date`中具有相同的值的行,接着会按照`product_id`进行排序。 + +使用含有 NULL 的列作为排序键时,NULL 会在结果的开头或末尾汇总显示。 + +在`ORDER BY`子句中可以使用`SELECT`子句中定义的别名。 + +```mysql +-- 将product_id命名为ID,然后按照ID进行排序 +SELECT product_id as ID, product_name, sale_price, purchase_price + FROM Product +ORDER BY ID; + +-- 结果如下 ++------+--------------+------------+----------------+ +| ID | product_name | sale_price | purchase_price | ++------+--------------+------------+----------------+ +| 0001 | T恤衫 | 1000 | 500 | +| 0002 | 打孔器 | 500 | 320 | +| 0003 | 运动T恤 | 4000 | 2800 | +| 0004 | 菜刀 | 300000 | 700 | +| 0005 | 高压锅 | 680000 | 1250 | +| 0006 | 叉子 | 50000 | NULL | +| 0007 | 擦菜板 | 88000 | 198 | +| 0008 | 圆珠笔 | 100 | NULL | ++------+--------------+------------+----------------+ +8 rows in set (0.00 sec) +``` + +为什么`ORDER BY`中可以使用`SELECT`定义的别名呢? + +这是因为在MySQL中,`ORDER BY `的执行次序在`SELECT`之后。 + + + +# 七、数据的插入及更新 + +## 7.1 数据的插入 + +通过命令`INSERT`,可以向表中插入数据: + +```mysql +-- 往表中插入一行数据 +INSERT INTO <表名> (字段1, 字段2, 字段3, ……) VALUES (值1, 值2, 值3, ……); + +-- 往表中插入多行数据 +INSERT INTO <表名> (字段1, 字段2, 字段3, ……) VALUES + (值1, 值2, 值3, ……), + (值1, 值2, 值3, ……), + ... + ; +``` + +示例: + +1. 创建表并插入数据 + +```mysql +-- 创建表 +CREATE TABLE ProductIns +(product_id CHAR(4) NOT NULL, + product_name VARCHAR(100) NOT NULL, + product_type VARCHAR(32) NOT NULL, + sale_price INTEGER DEFAULT 0, -- DEFAULT 0:表示将字段sale_price的默认值设为0 + purchase_price INT , + regist_date DATE , + PRIMARY KEY (product_id)); + +-- 通过单行方式插入 +INSERT INTO + ProductIns(product_id, product_name, product_type, sale_price, purchase_price, regist_date) + VALUES ('0001', '打孔器', '办公用品', 500, 320, '2009-09-11'); + +-- 当对表插入全字段时,可以省略表后的字段清单 +INSERT INTO ProductIns VALUES('0002', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'); + +-- 通过多行方式插入 +INSERT INTO ProductIns VALUES + ('0003', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'), + ('0004', '订书机', '办公用品', 100, 50, '2009-09-11'), + ('0005', '裙子', '衣服', 4100, 3200, '2009-01-23'), + ('0006', '运动T恤', '衣服', 4000, 2800, NULL), + ('0007', '牙刷', '日用品', 20, 10, '2010-03-22'); +``` + +```mysql +SELECT * FROM ProductIns; + +-- 结果如下 ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | +| 0002 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | +| 0003 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | +| 0004 | 订书机 | 办公用品 | 100 | 50 | 2009-09-11 | +| 0005 | 裙子 | 衣服 | 4100 | 3200 | 2009-01-23 | +| 0006 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | +| 0007 | 牙刷 | 日用品 | 20 | 10 | 2010-03-22 | ++------------+--------------+--------------+------------+----------------+-------------+ +7 rows in set (0.00 sec) +``` + +2. 插入NULL + + `INSERT `语句中想给某一列赋予**NULL**值时,可以直接在` VALUES`子句的值清单中写入**NULL**。 + +```mysql +INSERT INTO ProductIns VALUES ('0008', '叉子', '厨房用具', 500, NULL, '2009-09-20'); +``` + +3. 插入默认值 + + 在前面我们创建表时,字段sale_price包含了一条约束条件,默认为0。我们在插入数据时,可以直接用`DEFAULT`对该字段赋值。前提是,该字段被指定了默认值。 + +```mysql +-- 通过显式方法设定默认值 +INSERT INTO + ProductIns (product_id, product_name, product_type, sale_price, purchase_price, regist_date) + VALUES ('0009', '擦菜板', '厨房用具', DEFAULT, 790, '2009-04-28'); + +-- 通过隐式方法插入默认值 +INSERT INTO + ProductIns (product_id, product_name, product_type, purchase_price, regist_date) + VALUES ('0010', '擦菜板', '厨房用具', 790, '2009-04-28'); +``` + + + +## 7.2 数据的删除 + +通过`DROP TABLE`或者`DELETE`语句,可以对表进行删除,但二者存在一定的区别。 + ++ `DROP TABLE` 语句可以将表完全删除。 ++ `DELETE` 语句会留下表结构,而删除表中的全部数据。 + +无论通过哪种方式删除,数据都是难以恢复的。 + +1. 通过`DROP`进行删除 + + 语法结构为: + +```mysql +DROP <表名>; +``` + +2. 通过`DELETE`进行删除 + + 语法结构如下,记得要加`FROM`: + +```mysql +DELETE FROM <表名>; +``` + +​ 同时,也可以通过`WHERE`语句来指定删除的条件: + +```mysql +DELETE FROM <表名> + WHERE <条件>; +``` + +​ 需要注意的是,`DELETE`语句的删除对象并不是表或者列,而是记录(行)。 + +示例: + +```mysql +SELECT * FROM Product; + +-- 结果如下 +mysql> SELECT * FROM Product; ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | +| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | +| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | +| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | +| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | +| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | +| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | +| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | ++------------+--------------+--------------+------------+----------------+-------------+ +8 rows in set (0.00 sec) + +-- 删除销售价格大于等于4000的行 +DELETE FROM Product + WHERE sale_price >= 4000; + +-- 结果如下 +mysql> SELECT * FROM Product; ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | +| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | +| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | +| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | +| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | +| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | ++------------+--------------+--------------+------------+----------------+-------------+ +6 rows in set (0.00 sec) +``` + +3. 通过`TRUNCATE`进行删除 + + 在MySQL中,还存在一种删除表的方式,就是利用`TRUNCATE`语句。它的功能和`DROP`类似,但是不能通过`WHERE`指定条件,优点是速度比`DROP`快得多。 + +```mysql +TRUNCATE Product; + +-- 结果如下 +mysql> SELECT * FROM Product; +Empty set (0.00 sec) + +mysql> DESC Product; ++----------------+--------------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++----------------+--------------+------+-----+---------+-------+ +| product_id | char(4) | NO | PRI | NULL | | +| product_name | varchar(100) | NO | | NULL | | +| product_type | varchar(32) | NO | | NULL | | +| sale_price | int | YES | | NULL | | +| purchase_price | int | YES | | NULL | | +| regist_date | date | YES | | NULL | | ++----------------+--------------+------+-----+---------+-------+ +6 rows in set (0.00 sec) +``` + + + +## 7.3 数据的更新 + +当我们使用`INSERT`语句插入错误的数据后,若我们不想删除后从新插入,那就要使用到`UPDATE`语句。 + +1. 基本用法 + + `UPDATE`的语法结构如下: + +```mysql +UPDATE <表名> + SET <字段名> = <表达式>; +``` + +​ 示例: + +```mysql +-- 由于前面演示删除语句时,表Product的内容已清空 +-- 所以,这里从新进行数据插入 +INSERT INTO Product VALUES + ('0001', 'T恤衫', '衣服', 1000, 500, '2009-09-20'), + ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11'), + ('0003', '运动T恤', '衣服', 4000, 2800, NULL), + ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'), + ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'), + ('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20'), + ('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28'), + ('0008', '圆珠笔', '办公用品', 100, NULL,'2009-11-11') + ; + +-- 修改表中所有行regist_date的值 +UPDATE Product + SET regist_date = '2009-10-10'; + +-- 结果如下 +mysql> SELECT * FROM Product; ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-10-10 | +| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-10-10 | +| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | 2009-10-10 | +| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-10-10 | +| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-10-10 | +| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-10-10 | +| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2009-10-10 | +| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-10-10 | ++------------+--------------+--------------+------------+----------------+-------------+ +8 rows in set (0.00 sec) +``` + +2. 指定条件 + +```mysql +UPDATE <表名> + SET <列名> = <表达式> + WHERE <条件>; +``` + +​ 示例: + +```mysql +UPDATE Product + SET regist_date = '2021-10-30' + WHERE product_id = '0001'; +``` + +​ 注意,你也可是使用**NULL**对表进行更新,不过更新的字段必须满足没有**主键**和**NOT NULL**的约束条件。 + +3. 多列更新 + + 多列更新只需要用逗号(,)连接更改的字段即可。 + +```mysql +UPDATE Product + SET + sale_price = sale_price * 10, + purchase_price = purchase_price / 2 + WHERE product_type = '厨房用具'; +``` + + + +# 八、Pymysql的使用 + +在正式介绍`pymysql`的用法之前,我们先思考一件事,我们希望借助`pymysql`完成什么事情? + +之前,我们在命令行下,通过输入SQL语句来完成对数据库和表的增删改查。那么,我们也希望能够在Python下能够完成同样的操作,并且能够返回相应的反馈。具体任务包括: + +1. 登陆并连接到MySQL下的用户; +2. 切换到相应的数据库下; +3. 完成对表的增删改查; + +接下来的内容将围绕这3部分来介绍。 + + + +## 8.1 安装pymysql + +通过`pip`,我们可以完成对`pymysql`的安装: + +```bash +python3 -m pip install PyMySQL +``` + + + +## 8.2 连接数据库 + +如果希望在Python中操作MySQL数据库,那么首先就要登陆到MySQL下的用户。 + +我们通过创建库pymysql下的类`connect`的一个实例来登陆到数据库。 + +示例: + +```python +import pymysql + +# 这里登陆到我之前创建的admin账户 +db = pymysql.connect( + host='localhost', + user='admin', + password='mysql123', + database='shop', + charset='utf8mb4', + cursorclass=pymysql.cursors.DictCursor +) +``` + +参数解释: + ++ `host`:数据库服务器地址,默认`localhost`; ++ `user`:所要登陆的用户名; ++ `password`:用户的登录密码; ++ `database`:所要连接的数据库库名; ++ `charset`:使用的字符类型; ++ `cursorclass`:定义游标使用的类型,通过指定游标使用的类型,在返回输出的结果时将按照指定的类型进行返回。例如,这里设置为字典游标。 + + + +## 8.3 创建游标 + +关于游标,可以理解为在命令行中的光标。在命令行中,我们是在光标处键入语句的。这里游标的起到类似作用。 + +```python +# 创建游标 +cursor = db.cursor() +``` + +实际上,除了在初始化`connect`的实例时指定游标类型,我们在初始化游标时也可以指定游标类型,默认为元组类型。 + +```python +cursor = db.cursor(cursor=pymysql.cursors.DictCursor) +``` + +`cursors`共支持四类游标: + ++ `Cursor`: 默认,元组类型 + ++ `DictCursor`: 字典类型 + ++ `SSCursor`: 无缓冲元组类型 + ++ `SSDictCursor`: 无缓冲字典类型 + + + +## 8.4 类方法 + +初始化完类`connect`和`cursor`的实例后,我们先来了解一下这两个类下包含的方法。了解这些方法有利于我们后面在python下操作mysql: + ++ `connect`下的类方法: + + `close()`:在完成操作后,需要关闭与数据库之间的连接; + + `commit()`:如果执行语句中发生了数据更改,需要提交更改到稳定的存储器; + + `cursor(cursor=None)`:创建一个游标,前面我们在初始化`connect`类是指定了游标类型,通过`cursor`初始化游标时,也可以进行游标类型指定; + + `rollback()`:事务回滚; + ++ `pymysql.cursors`下的类方法: + + `close()`:结束时,关闭游标; + + `execute()`:通过游标执行语句; + + `executemany()`:通过游标执行多条语句; + + `fetchone()`:获取单条数据; + + `fetchmany(size=None)`:获取size条数据; + + `fetchall()`:获取单条数据; + + `scroll(value, mode)`:数据的查询操作都是基于游标,可以通过`scroll`控制游标的位置。 + + `mode=absolute`:绝对位置移动,控制游标位置到上一次查询的第`value`条数据,最小值为`0`; + + `mode=relative`:相对位置移动,基于当前位置,跳过`value`条数据; + +更详细的资料,可参考官方的API或者Github: + +[pymysql github]: https://github.com/PyMySQL/PyMySQL + +[pymysql document]: https://pymysql.readthedocs.io/en/latest/modules/index.html# + + + +## 8.5 实战 + ++ 示例1: + +  在这个示例中,我们将做两件事情:创建表和插入数据。 + +```python +import pymysql + +# 以admin身份连接到数据库shop +connection = pymysql.connect( + host='localhost', + user='admin', + password='mysql123', + database='shop', + charset='utf8mb4', +) + +# 创建游标 +cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) + +# 1. 创建了一个表 +sql = """ +CREATE TABLE Employee( + id INT PRIMARY KEY, + name CHAR(15) NOT NULL + ) + """ + +# 提交执行 +cursor.execute(sql) + +# 2. 往表中插入数据 +sql = "INSERT INTO Employee (id, name) VALUES (%s, %s)" +values = [(1, 'XiaoBai'), + (2, 'XiaoHei'), + (3, 'XiaoHong'), + (4, 'XiaoMei'), + (5, 'XiaoLi')] + +try: + # 通过executemany可以插入多条数据 + cursor.executemany(sql, values) + # 提交事务 + connection.commit() +except: + connection.rollback() + + +# 3. 关闭光标及连接 +cursor.close() +connection.close() +``` + ++ 示例2 + + 在示例1的基础上,我们继续执行查询工作。 + +```python +import pymysql + +# 以admin身份连接到数据库shop +connection = pymysql.connect( + host='localhost', + user='admin', + password='mysql123', + database='shop', + charset='utf8mb4', +) + +with connection: + # 创建游标 + cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) + + # 1. 通过fetchone只查询一条 + cursor.execute("SHOW CREATE TABLE Employee") + result = cursor.fetchone() + print(f'查询结果1: \n{result}') + + # 2. 通过fetchmany查询size条 + cursor.execute("DESC Employee") + result = cursor.fetchmany(size=2) + print(f'查询结果2: \n{result}') + + # 3. 通过fetchall查询所有 + cursor.execute("SELECT * FROM Employee") + result = cursor.fetchall() + print(f'查询结果3: \n{result}') + + # 4. 通过scroll回滚到第0条进行查询 + cursor.scroll(0, mode='absolute') + result = cursor.fetchone() + print(f'查询结果4: \n{result}') + + # 5. 通过scroll跳过2条进行查询 + cursor.scroll(2, mode='relative') + result = cursor.fetchone() + print(f'查询结果5: \n{result}') + + cursor.close() +``` + +​ 控制台打印结果如下: + +```bash +查询结果1: +{'Table': 'Employee', 'Create Table': 'CREATE TABLE `Employee` (\n `id` int NOT NULL,\n `name` char(15) NOT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci'} +查询结果2: +[{'Field': 'id', 'Type': 'int', 'Null': 'NO', 'Key': 'PRI', 'Default': None, 'Extra': ''}, {'Field': 'name', 'Type': 'char(15)', 'Null': 'NO', 'Key': '', 'Default': None, 'Extra': ''}] +查询结果3: +[{'id': 1, 'name': 'XiaoBai'}, {'id': 2, 'name': 'XiaoHei'}, {'id': 3, 'name': 'XiaoHong'}, {'id': 4, 'name': 'XiaoMei'}, {'id': 5, 'name': 'XiaoLi'}] +查询结果4: +{'id': 1, 'name': 'XiaoBai'} +查询结果5: +{'id': 4, 'name': 'XiaoMei'} +``` + ++ 示例3: + + ​ 该示例将演示SQL注入的问题。先建立一个表并插入数据: + +```python +import pymysql + +# 以admin身份连接到数据库shop +connection = pymysql.connect( + host='localhost', + user='admin', + password='mysql123', + database='shop', + charset='utf8mb4', +) + +# 创建游标 +cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) + +sql = """ + CREATE TABLE UserInfo( + id INT PRIMARY KEY, + name VARCHAR(15), + password CHAR(15) NOT NULL + ) + """ + +cursor.execute(sql) + +sql = "INSERT INTO UserInfo (id, name, password) VALUES (%s, %s, %s)" +values = [(1, 'XiaoBai', '123'), + (2, 'XiaoHei', '234'), + (3, 'XiaoHong', '567'), + (4, 'XiaoMei', '321'), + (5, 'XiaoLi', '789')] + +cursor.executemany(sql, values) +connection.commit() +``` + +​ 再写一个程序,根据输入判定登陆是否成功: + +```python +import pymysql + +# 以admin身份连接到数据库shop +connection = pymysql.connect( + host='localhost', + user='admin', + password='mysql123', + database='shop', + charset='utf8mb4', +) + +# 创建游标 +cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) + +while True: + user = input("输入用户:").strip() + password = input("输入密码:").strip() + sql = "select name, password from UserInfo where name='%s' and password='%s' " % (user, password) + + cursor.execute(sql) + # 打印用户和密码 + result=cursor.fetchone() + print(result) + + if result: + print("成功登陆\n") + else: + print("登陆失败\n") +``` + +​ 在控制台下,我们进行了三组用户和密码的验证: + +```python +输入用户:XiaoBai +输入密码:123 +{'name': 'XiaoBai', 'password': '123'} +成功登陆 + +输入用户:XiaoBai +输入密码:321 +None +登陆失败 + +输入用户:XiaoBai' -- dsd +输入密码:321 +{'name': 'XiaoBai', 'password': '123'} +成功登陆 +``` + +​ 可以看出,第1组和第2组验证正常,但是第三组出现了异常,输入错误的密码却可以正确登陆。 + +​ 这是因为在MySQL中`--`的含义是注释,如果通过字符串进行拼接: + +```mysql +select name, password from UserInfo where name='XiaoBai' -- dsd' and password='321' +``` + +​ 实际等价于: + +```mysql +sselect name, password from UserInfo where name='XiaoBai' +``` + +​ 解决办法:通过`execute`或者`executemany`来进行拼接。将语句: + +```python +sql = "select name, password from UserInfo where name='%s' and password='%s' " % (user, password) +cursor.execute(sql) +``` + +​ 改为: + +```python +sql = "select name, password from UserInfo where name=%s and password=%s" +cursor.execute(sql, (user, password)) +``` + From 0761c0ac3e3de57987eaa0ca26f61f7d0bc955de Mon Sep 17 00:00:00 2001 From: Lyons-T <75569795+Lyons-T@users.noreply.github.com> Date: Wed, 3 Nov 2021 19:04:33 +0800 Subject: [PATCH 04/16] =?UTF-8?q?Rename=202.2.1.1=20MySQL=E5=9F=BA?= =?UTF-8?q?=E7=A1=80.md=20to=20MySQL=E5=9F=BA=E7=A1=80.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2.2新闻推荐系统实战/docs/{2.2.1.1 MySQL基础.md => MySQL基础.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/{2.2.1.1 MySQL基础.md => MySQL基础.md} (100%) diff --git a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.1 MySQL基础.md b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MySQL基础.md similarity index 100% rename from docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.1 MySQL基础.md rename to docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MySQL基础.md From 6119f742a19a462818c6a2b57a0ffcdba2700d82 Mon Sep 17 00:00:00 2001 From: Lyons-T <75569795+Lyons-T@users.noreply.github.com> Date: Wed, 3 Nov 2021 19:07:58 +0800 Subject: [PATCH 05/16] =?UTF-8?q?Delete=20MySQL=E5=9F=BA=E7=A1=80.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2.2新闻推荐系统实战/docs/MySQL基础.md | 2310 ----------------- 1 file changed, 2310 deletions(-) delete mode 100644 docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MySQL基础.md diff --git a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MySQL基础.md b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MySQL基础.md deleted file mode 100644 index feccaa7b..00000000 --- a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MySQL基础.md +++ /dev/null @@ -1,2310 +0,0 @@ -# 前言 MySQL简介 - -​ MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。 - -​ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行的开源数据库,因此被广泛地应用在Internet上的中小型网站中。随着MySQL的不断成熟,它也逐渐用于更多大规模网站和应用,比如维基百科、Google和Facebook等网站。非常流行的开源软件组合LAMP中的“M”指的就是MySQL。 - -[百度百科]: https://baike.baidu.com/item/mySQL/471251 -[维基百科]: https://zh.wikipedia.org/wiki/MySQL - - - -# 一、 Ubuntu下安装MySQL - -安装教程是在`Ubuntu20.04`下进行的,安装的MySQL版本为`8.0.27`。 - -## 1.1 安装 - -```bash -sudo apt install mysql-server mysql-client -``` - -在输入密码后,再输入`yes`即可开始安装。 - -安装完成后,通过运行命令`mysql -V`查看版本号: - -```bash -lyons@ubuntu:~$ mysql -V -mysql Ver 8.0.27-0ubuntu0.20.04.1 for Linux on x86_64 ((Ubuntu)) -``` - -验证MySQL服务正在运行,命令行下输入: - -```bash -sudo service mysql status -``` - -如果正在运行,则会显示: - -```bash -● mysql.service - MySQL Community Server - Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled) - Active: active (running) since Wed 2021-10-27 10:27:59 CST; 9h ago - Main PID: 6179 (mysqld) - Status: "Server is operational" - Tasks: 39 (limit: 4599) - Memory: 348.9M - CGroup: /system.slice/mysql.service - └─6179 /usr/sbin/mysqld - -10月 27 10:27:59 ubuntu systemd[1]: Starting MySQL Community Server... -10月 27 10:27:59 ubuntu systemd[1]: Started MySQL Community Server. -``` - - - -## 1.2 配置MySQL的安全性 - -1. 首先,运行命令`mysql_secure_installation`: - - ```bash - sudo mysql_secure_installation - ``` - -2. `VALIDATE PASSWORD COMPONENT` - - 设置验证密码插件。它被用来测试`MySQL`用户的密码强度,并且提高安全性。如果想设置验证密码插件,请输入`y`: - - ```bash - Connecting to MySQL using a blank password. - - VALIDATE PASSWORD COMPONENT can be used to test passwords - and improve security. It checks the strength of password - and allows the users to set only those passwords which are - secure enough. Would you like to setup VALIDATE PASSWORD component? - - Press y|Y for Yes, any other key for No: y - ``` - - 接下来,将进行密码验证等级设置,根据数字设置对应等级,这里设置为0: - - ```bash - There are three levels of password validation policy: - - LOW Length >= 8 - MEDIUM Length >= 8, numeric, mixed case, and special characters - STRONG Length >= 8, numeric, mixed case, special characters and dictionary file - - Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 0 - ``` - -3. 设置密码 - - 为MySQL root用户设置密码,设置过程中密码不会显示。如果设置了验证密码插件,将会显示密码的强度。 - - ``` - Please set the password for root here. - New password: - - Re-enter new password: - - Estimated strength of the password: 25 - Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y - ``` - -4. 移除匿名用户 - - 默认情况下,MySQL安装有一个匿名用户,允许任何人登录MySQL,而不必为他们创建用户帐户。输入`y`进行删除: - - ``` - By default, a MySQL installation has an anonymous user, - allowing anyone to log into MySQL without having to have - a user account created for them. This is intended only for - testing, and to make the installation go a bit smoother. - You should remove them before moving into a production - environment. - - Remove anonymous users? (Press y|Y for Yes, any other key for No) : y - Success. - ``` - -5. 禁止远程root用户登录 - - 输入`y`后按`enter`,将会禁止`root`用户登录。 - - ``` - Normally, root should only be allowed to connect from - 'localhost'. This ensures that someone cannot guess at - the root password from the network. - - Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y - Success. - ``` - -6. 删除测试库 - - 输入`y`后按`enter`,将会删除测试库。 - - ``` - By default, MySQL comes with a database named 'test' that - anyone can access. This is also intended only for testing, - and should be removed before moving into a production - environment. - - - Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y - - Dropping test database... - Success. - ``` - -7. 重新加载特权表 - - 输入`y`后按`enter`,将会重新加载特权表。 - - ``` - - Removing privileges on test database... - Success. - - Reloading the privilege tables will ensure that all changes - made so far will take effect immediately. - - Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y - Success. - - All done! - ``` - - 至此,配置完成。 - - - -## 1.3 以root用户登录 - -在MySQL 8.0上,root 用户默认通过`auth_socket`插件授权。`auth_socket`插件通过 Unix socket 文件来验证所有连接到`localhost`的用户。 - -这意味着你不能通过提供密码,验证为 root。此时,输入`mysql -uroot -p`可能会被拒绝访问: - -```bash -lyons@ubuntu:~$ mysql -uroot -p -mysql: [Warning] Using a password on the command line interface can be insecure. -ERROR 1698 (28000): Access denied for user 'root'@'localhost' -``` - -若要以 root 用户身份登录 MySQL服务器,输入`sudo mysql`,如下: - -```bash -# 登录密码为linux系统用户的root密码 -lyons@ubuntu:~$ sudo mysql -[sudo] lyons 的密码: -Welcome to the MySQL monitor. Commands end with ; or \g. -Your MySQL connection id is 55 -Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu) - -Copyright (c) 2000, 2021, Oracle and/or its affiliates. - -Oracle is a registered trademark of Oracle Corporation and/or its -affiliates. Other names may be trademarks of their respective -owners. - -Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. - -mysql> -``` - -退出MySQL,请输入`exit`命令: - -```mysql -mysql> exit -Bye -lyons@ubuntu:~$ -``` - -如果你想以 root 身份登录 MySQL 服务器,便于使用其他的程序。可以将验证方法从`auth_socket`修改成`mysql_native_password`。 - -+ **方式1** - -你可以通过运行下面的命令实现: - -```bash --- 语法中的'你的密码’指的是你自己设置的登录密码,可设置为字母数字组合。 -ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; -FLUSH PRIVILEGES; -``` - -示例: - -```mysql --- 在mysql下,将密码设置为'mysql123' -mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'mysql123'; -Query OK, 0 rows affected (0.00 sec) - --- 刷新系统权限 -mysql> FLUSH PRIVILEGES; -Query OK, 0 rows affected (0.01 sec) - -mysql> exit -Bye - --- 现在便可以通过mysql -uroot -p登录 --- 登录密码为前面设置的'mysql123' -lyons@ubuntu:~$ mysql -uroot -p -Enter password: -Welcome to the MySQL monitor. Commands end with ; or \g. -Your MySQL connection id is 57 -Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu) - -Copyright (c) 2000, 2021, Oracle and/or its affiliates. - -Oracle is a registered trademark of Oracle Corporation and/or its -affiliates. Other names may be trademarks of their respective -owners. - -Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. - -mysql> -mysql> exit -Bye - --- 同时,命令sudo mysql会被拒绝访问 -lyons@ubuntu:~$ sudo mysql -ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO) -``` - -当然,若要再次修改回`sudo mysql`的方式来登录`root`用户,方法类似: - -```mysql -ALTER USER 'root'@'localhost' IDENTIFIED WITH auth_socket BY '你的密码'; - -FLUSH PRIVILEGES; -``` - -+ **方式2** - - 推荐的选项,就是创建一个新的独立管理用户,拥有所有数据库的访问权限: - -```mysql -# 创建用户 -CREATE USER '用户名'@'localhost' identified by '你的密码' - -# 赋予admin用户全部的权限,你也可以只授予部分权限 -GRANT ALL PRIVILEGES ON *.* TO '用户名'@'localhost'; -``` - -​ 示例: - -```mysql -# 创建名为admin的用户,密码为mysql123 -mysql> create user 'admin'@'localhost' identified by 'mysql123'; -Query OK, 0 rows affected (0.01 sec) - -# 将访问所有database以及表的权利授权用户admin -#with gran option表示该用户可给其它用户赋予权限,但不可能超过该用户已有的权限 -mysql> grant all privileges on *.* to 'admin'@'localhost' with grant option; -Query OK, 0 rows affected (0.00 sec) - -mysql> FLUSH PRIVILEGES; -Query OK, 0 rows affected (0.00 sec) - -# 查看已有的用户 -mysql> select user, host from mysql.user; -+------------------+-----------+ -| user | host | -+------------------+-----------+ -| admin | localhost | -| debian-sys-maint | localhost | -| mysql.infoschema | localhost | -| mysql.session | localhost | -| mysql.sys | localhost | -| root | localhost | -+------------------+-----------+ -6 rows in set (0.00 sec) - -# 退出root用户登录 -mysql> exit -Bye - -# 登录admin用户,输入密码mysql123即可登录成功 -lyons@ubuntu:~$ mysql -uadmin -p -Enter password: -Welcome to the MySQL monitor. Commands end with ; or \g. -Your MySQL connection id is 16 -Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu) - -Copyright (c) 2000, 2021, Oracle and/or its affiliates. - -Oracle is a registered trademark of Oracle Corporation and/or its -affiliates. Other names may be trademarks of their respective -owners. - -Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. - -mysql> -``` - -说明:`'admin'@'localhost'`中,`localhost`指本地才可连接,可以将其换成`%`指任意`ip`都能连接,也可以指定`ip`连接。 - - - -## 1.4 修改密码 - -将用户`admin`的登录密码修改为`mysql321`: - -```mysql -ALTER USER 'admin'@'localhost' IDENTIFIED WITH mysql_native_password BY 'mysql321'; -``` - - - -## 1.5 撤销用户授权 - -```mysql -# 查看用户的权限 -show grants for 'admin'@'localhost'; - -# 撤销用户的权限 -# 用户有什么权限就撤销什么 -revoke all privileges on *.* from 'admin'@'localhost'; -``` - - - -## 1.6 删除用户 - -```MYSQL -drop user 'admin'@'localhost'; -``` - - - -> 注:MySQL 8.0版本和5.0部分命令有所改掉,上述语法都是在8.0版本下运行通过的;请务必检查自己的MySQL版本号。 - - - -# 二、MySQL预备知识 - -在正式学习MySQL之前,我们先来了解一下SQL语句的书写规范以及命名规则等。 - -## 2.1 SQL书写规范 - -在写SQL语句时,要求按照如下规范进行: - -+ SQL 语句要以分号(;)结尾 - -+ SQL 不区分关键字的大小写 ,这对于表名和列名同样适用。 - -+ 插入到表中的数据是区分大小写的。例如,数据Computer、COMPUTER 或computer,三者是不一样的。 - -+ 常数的书写方式是固定的,在SQL 语句中直接书写的字符串、日期或者数字等称为常数。常数的书写方式如下所示。 - - + SQL 语句中含有字符串的时候,需要像'abc'这样,使用单引号(')将字符串括起来,用来标识这是一个字符串。 - + SQL 语句中含有日期的时候,同样需要使用单引号将其括起来。日期的格式有很多种('26 Jan 2010' 或者'10/01/26' 等)。 - + 在SQL 语句中书写数字的时候,不需要使用任何符号标识,直接写成1000 这样的数字即可。 - -+ 单词之间需要用半角空格或者换行来分隔。 - -+ SQL中的注释主要采用`--`和`/* ... */`的方式,第二种方式可以换行。在MySQL下,还可以通过`#`来进行注释。 - - - -## 2.2 命名规则 - -+ 在数据库中,只能使用半角英文字母、数字、下划线(_)作为数据库、表和列的名称 。 -+ 名称必须以半角英文字母作为开头。 -+ 名称不能重复,同一个数据库下不能有2张相同的表。 - - - -## 2.3. 数据类型 - -MySQL 支持所有标准 SQL 数值数据类型,包括: - -### (1)数值类型 - -数值包含的类型如下: - -+ 整型数据:`TINYINT`、`INTEGER`、`SMALLINT`、`MEDIUMINT`、`DECIMAL` 、`NUMERIC` 和`BIGINT`。 - -+ 浮点型数据:`DECIMAL`、`FLOAT`、`REAL` 和 `DOUBLE PRECISION`)。 - -其中,关键字`INT`是`INTEGER`的同义词,关键字DEC是的同义词。 - -不同关键字的主要区别就是表示的范围或精度不一样。具体如下表: - -| 类型 | 大小 | 范围(有符号) | 范围(无符号) | 用途 | -| :----------: | :--------------------------------------: | :----------------------------------------------------------- | :----------------------------------------------------------- | :-------------- | -| TINYINT | 1 Bytes | (-128,127) | (0,255) | 小整数值 | -| SMALLINT | 2 Bytes | (-32 768,32 767) | (0,65 535) | 大整数值 | -| MEDIUMINT | 3 Bytes | (-8 388 608,8 388 607) | (0,16 777 215) | 大整数值 | -| INT或INTEGER | 4 Bytes | (-2 147 483 648,2 147 483 647) | (0,4 294 967 295) | 大整数值 | -| BIGINT | 8 Bytes | (-9,223,372,036,854,775,808,9 223 372 036 854 775 807) | (0,18 446 744 073 709 551 615) | 极大整数值 | -| FLOAT | 4 Bytes | (-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) | 0,(1.175 494 351 E-38,3.402 823 466 E+38) | 单精度 浮点数值 | -| DOUBLE | 8 Bytes | (-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 双精度 浮点数值 | -| DECIMAL | 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 | 依赖于M和D的值 | 依赖于M和D的值 | 小数值 | - -### (2)日期和时间类型 - -表示时间值的日期和时间类型为`DATETIME`、`DATE`、`TIMESTAMP`、`TIME`和`YEAR`。具体如下表: - -| 类型 | 大小 ( bytes) | 范围 | 格式 | 用途 | -| :-------- | :------------ | :----------------------------------------------------------- | :------------------ | :----------------------- | -| DATE | 3 | 1000-01-01/9999-12-31 | YYYY-MM-DD | 日期值 | -| TIME | 3 | '-838:59:59'/'838:59:59' | HH:MM:SS | 时间值或持续时间 | -| YEAR | 1 | 1901/2155 | YYYY | 年份值 | -| DATETIME | 8 | 1000-01-01 00:00:00/9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS | 混合日期和时间值 | -| TIMESTAMP | 4 | 1970-01-01 00:00:00/2038结束时间是第 2147483647 秒,北京时间 2038-1-19 11:14:07,格林尼治时间 2038年1月19日 凌晨 03:14:07 | YYYYMMDD HHMMSS | 混合日期和时间值,时间戳 | - -### (3)字符串类型 - -字符串类型指`CHAR`、`VARCHAR`、`BINARY`、`VARBINARY`、`BLOB`、`TEXT`、`ENUM`和`SET`。具体如下表: - -| 类型 | 大小 | 用途 | -| :--------- | :-------------------- | :------------------------------ | -| CHAR | 0-255 bytes | 定长字符串 | -| VARCHAR | 0-65535 bytes | 变长字符串 | -| TINYBLOB | 0-255 bytes | 不超过 255 个字符的二进制字符串 | -| TINYTEXT | 0-255 bytes | 短文本字符串 | -| BLOB | 0-65 535 bytes | 二进制形式的长文本数据 | -| TEXT | 0-65 535 bytes | 长文本数据 | -| MEDIUMBLOB | 0-16 777 215 bytes | 二进制形式的中等长度文本数据 | -| MEDIUMTEXT | 0-16 777 215 bytes | 中等长度文本数据 | -| LONGBLOB | 0-4 294 967 295 bytes | 二进制形式的极大文本数据 | -| LONGTEXT | 0-4 294 967 295 bytes | 极大文本数据 | - -+ `char`声明的是定长字符串。若实际中字符串长度不足,则会在末尾使用空格进行填充至声明的长度。 - -+ `varchar`声明的是可变长字符串。存储过程中,只会按照字符串的实际长度来存储,但会多占用一位来存放实际字节的长度。 - - - -# 三、 数据库的基本操作 - -首先,我们来学习在MySQL下如何操作数据库。 - -## 3.1 数据库的创建 - -通过`CREATE`命令,可以创建指定名称的数据库,语法结构如下: - -```mysql -CREATE DATABASE [IF NOT EXISTS] <数据库名称>; -``` - -MySQL 的数据存储区将以目录方式表示 MySQL 数据库,因此数据库名称必须符合操作系统的文件夹命名规则,不能以数字开头,尽量要有实际意义。 - -MySQL下不运行存在两个相同名字的数据库,否则会报错。如果使用`IF NOT EXISTS`(可选项),可以避免此类错误。 - -示例: - -```mysql --- 创建名为shop的数据库。 -CREATE DATABASE shop; -``` - - - -## 3.2 数据库的查看 - -1. 查看所有存在的数据库 - -```MYSQL -SHOW DATABASES [LIKE '数据库名'];; -``` - -`LIKE`从句是可选项,用于匹配指定的数据库名称。`LIKE` 从句可以部分匹配,也可以完全匹配。 - -示例: - -```mysql -SHOW DATABASES; - --- 结果如下: -+--------------------+ -| Database | -+--------------------+ -| information_schema | -| mysql | -| performance_schema | -| shop | -| sys | -+--------------------+ -5 rows in set (0.01 sec) -``` - -```mysql --- %表示任意0个或多个字符,可匹配任意类型和长度的字符。 -SHOW DATABASES LIKE 'S%'; - --- 结果如下 -+---------------+ -| Database (S%) | -+---------------+ -| shop | -| sys | -+---------------+ -2 rows in set (0.00 sec) -``` - -2. 查看创建的数据库 - -```mysql -SHOW CREATE DATABASE <数据库名>; -``` - -示例: - -```mysql -SHOW CREATE DATABASE shop; - --- 或者 -SHOW CREATE DATABASE shop \G - --- 结果如下 -*************************** 1. row *************************** - Database: shop -Create Database: CREATE DATABASE `shop` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */ -1 row in set (0.00 sec) -``` - -`CHARACTER SET utf8mb4`表示编码字符集为`utf8mb4`。 - - - -## 3.3 选择数据库 - -在操作数据库前,必须指定所要操作的数据库。通过`USE`命令,可以切换到对应的数据库下。 - -```mysql -USE <数据库名> -``` - -示例: - -```mysql --- 切换到数据库shop下。 -USE shop; - --- 结果如下 -Database changed -``` - - - -## 3.4 删除数据库 - -通过`DROP`命令,可以将相应数据库进行删除。 - -```mysql -DROP DATABASE [IF EXISTS] <数据库名> -``` - -其中,`IF EXISTS`为可选性,用于防止数据库不存在时报错。 - -示例: - -```mysql -DROP DATABASE shop; - -SHOW DATABASES; -``` - -考虑到后面表的操作都是shop数据库下,在实验完`DROP`删除数据库命令后,请从新创建数据库shop并通过`USE`命令切换到该数据库下。 - - - -# 四、表的基本操作 - -表相当于文件,表中的一条记录就相当于文件的一行内容,不同的是,表中的一条记录有对应的标题,称为表的字段。 - -## 4.1 表的创建 - -创建表的语法结构如下: - -```mysql -CREATE TABLE <表名> (<字段1> <数据类型> <该列所需约束>, - <字段2> <数据类型> <该列所需约束>, - <字段3> <数据类型> <该列所需约束>, - <字段4> <数据类型> <该列所需约束>, - . - . - . - <该表的约束1>, <该表的约束2>,……); -``` - -示例: - -```mysql --- 创建一个名为Product的表 -CREATE TABLE Product( - product_id CHAR(4) NOT NULL, - product_name VARCHAR(100) NOT NULL, - product_type VARCHAR(32) NOT NULL, - sale_price INT, - purchase_price INT, - regist_date DATE, - PRIMARY KEY (product_id) -); -``` - -在第二章中,我们介绍过不同的数据类型: - -+ `CHAR`为定长字符,这里`CHAR`旁边括号里的数字表示该字段最长为多少字符,少于该数字将会使用空格进行填充。 - -+ `VARCHAR`表示变长字符,括号里的数字表示该字段最长为多少字符,存储时只会按照字符的实际长度来存储,但会使用额外的1-2字节来存储值长度。 - - - -简单介绍一下该语句中出现的约束条件,约束条件在后面会详细介绍: - -+ `PRIMARY KEY`:主键,表示该字段对应的内容唯一且不能为空。 -+ `NOT NULL`:在 `NULL` 之前加上了表示否定的` NOT`,表示该字段不能输入空白。 - -通过`SHOW TABLES`命令来查看当前数据库下的所有的表名: - -```mysql -SHOW TABLES; - --- 结果如下 -+----------------+ -| Tables_in_shop | -+----------------+ -| Product | -+----------------+ -1 rows in set (0.00 sec) -``` - -通过`DESC <表名>`来查看表的结构: - -```mysql -DESC Product; - --- 结果如下 -+----------------+--------------+------+-----+---------+-------+ -| Field | Type | Null | Key | Default | Extra | -+----------------+--------------+------+-----+---------+-------+ -| product_id | char(4) | NO | PRI | NULL | | -| product_name | varchar(100) | NO | | NULL | | -| product_type | varchar(32) | NO | | NULL | | -| sale_price | int | YES | | NULL | | -| purchase_price | int | YES | | NULL | | -| regist_date | date | YES | | NULL | | -+----------------+--------------+------+-----+---------+-------+ -6 rows in set (0.00 sec) -``` - - - -## 4.2 表的删除 - -删除表的语法结构如下: - -```mysql -DROP TABLE <表名>; - --- 例如:DROP TABLE Product; -``` - -说明:通过`DROP`删除的表示无法恢复的,在删除表的时候请谨慎。 - - - -## 4.3 表的更新 - -通过`ALTER TABLE`语句,我们可以对表字段进行不同的操作,下面通过示例来具体学习用法。 - -示例: - -1. 创建一张名为Student的表 - -```mysql -CREATE TABLE Student( - id INT PRIMARY KEY, - name CHAR(15) -); -``` - - - -```mysql -DESC student; - --- 结果如下 -+-------+----------+------+-----+---------+-------+ -| Field | Type | Null | Key | Default | Extra | -+-------+----------+------+-----+---------+-------+ -| id | int | NO | PRI | NULL | | -| name | char(15) | YES | | NULL | | -+-------+----------+------+-----+---------+-------+ -2 rows in set (0.00 sec) -``` - -2. 更改表名 - - 通过`RENAME`命令,将表名从Student => Students。 - -```mysql -ALTER TABLE Student RENAME Students; -``` - -3. 插入新的字段 - - 通过`ADD`命令,新增字段sex和age。 - -```mysql --- 不同的字段通过逗号分开 -ALTER TABLE Students ADD sex CHAR(1), ADD age INT; -``` - -​ 其它插入技巧: - -```mysql --- 通过FIRST在表首插入字段stu_num -ALTER TABLE Students ADD stu_num INT FIRST; - --- 指定在字段sex后插入字段height -ALTER TABLE Students ADD height INT AFTER sex; -``` - -```mysql -DESC Students; - --- 结果如下 -+---------+----------+------+-----+---------+-------+ -| Field | Type | Null | Key | Default | Extra | -+---------+----------+------+-----+---------+-------+ -| stu_num | int | YES | | NULL | | -| id | int | NO | PRI | NULL | | -| name | char(15) | YES | | NULL | | -| sex | char(1) | YES | | NULL | | -| height | int | YES | | NULL | | -| age | int | YES | | NULL | | -+---------+----------+------+-----+---------+-------+ -6 rows in set (0.00 sec) -``` - -4. 字段的删除 - - 通过`DROP`命令,可以对不在需要的字段进行删除。 - -```mysql --- 删除字段stu_num -ALTER TABLE Students DROP stu_num; -``` - -5. 字段的修改 - - 通过`MODIFY`修改字段的数据类型。 - -```mysql --- 修改字段age的数据类型 -ALTER TABLE Students MODIFY age CHAR(3); -``` - -​ 通过`CHANGE`命令,修改字段名或类型 - -```mysql --- 修改字段name为stu_name,不修改数据类型 -ALTER TABLE Students CHANGE name stu_name CHAR(15); - --- 修改字段sex为stu_sex,数据类型修改为int -ALTER TABLE Students CHANGE sex stu_sex INT; -``` - -```mysql -DESC Students; - --- 结果如下 -+----------+----------+------+-----+---------+-------+ -| Field | Type | Null | Key | Default | Extra | -+----------+----------+------+-----+---------+-------+ -| id | int | NO | PRI | NULL | | -| stu_name | char(20) | YES | | NULL | | -| stu_sex | int | YES | | NULL | | -| height | int | YES | | NULL | | -| age | char(3) | YES | | NULL | | -+----------+----------+------+-----+---------+-------+ -5 rows in set (0.00 sec) -``` - - - -## 4.4 表的查询 - -通过`SELECT`语句,可以从表中取出所要查看的字段的内容: - -```mysql -SELECT <字段名>, …… - FROM <表名>; -``` - -如要直接查询表的全部字段: - -```mysql -SELECT * - FROM <表名>; -``` - -其中,**星号(*)**代表全部字段的意思。 - -示例: - -1. 建表并插入数据 - - 在MySQL中,我们通过`INSERT`语句往表中插入数据,该语句在后面会详细介绍,该小节的重点是学会使用`SELECT`。 - -```mysql --- 向Product表中插入数据 -INSERT INTO Product VALUES - ('0001', 'T恤衫', '衣服', 1000, 500, '2009-09-20'), - ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11'), - ('0003', '运动T恤', '衣服', 4000, 2800, NULL), - ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'), - ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'), - ('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20'), - ('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28'), - ('0008', '圆珠笔', '办公用品', 100, NULL,'2009-11-11') - ; -``` - -2. 查看表的内容 - -```mysql --- 查看表的全部内容 -SELECT * - FROM Product; - --- 结果如下 -+------------+--------------+--------------+------------+----------------+-------------+ -| product_id | product_name | product_type | sale_price | purchase_price | regist_date | -+------------+--------------+--------------+------------+----------------+-------------+ -| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | -| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | -| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | -| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | -| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | -| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | -| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | -| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | -+------------+--------------+--------------+------------+----------------+-------------+ -8 rows in set (0.00 sec) -``` - -```mysql --- 查看部分字段包含的内容 -SELECT - product_id, - product_name, - sale_price - FROM Product; - --- 结果如下 -+------------+--------------+------------+ -| product_id | product_name | sale_price | -+------------+--------------+------------+ -| 0001 | T恤衫 | 1000 | -| 0002 | 打孔器 | 500 | -| 0003 | 运动T恤 | 4000 | -| 0004 | 菜刀 | 3000 | -| 0005 | 高压锅 | 6800 | -| 0006 | 叉子 | 500 | -| 0007 | 擦菜板 | 880 | -| 0008 | 圆珠笔 | 100 | -+------------+--------------+------------+ -8 rows in set (0.00 sec) -``` - -3. 对查看的字段从新命名 - - 通过`AS`语句对展示的字段另起别名,这不会修改表内字段的名字。 - -```mysql -SELECT - product_id AS ID, - product_type AS TYPE - FROM Product; - --- 结果如下 -+------+--------------+ -| ID | TYPE | -+------+--------------+ -| 0001 | 衣服 | -| 0002 | 办公用品 | -| 0003 | 衣服 | -| 0004 | 厨房用具 | -| 0005 | 厨房用具 | -| 0006 | 厨房用具 | -| 0007 | 厨房用具 | -| 0008 | 办公用品 | -+------+--------------+ -8 rows in set (0.00 sec) -``` - -​ 设定汉语别名时需要使用双引号(")括起来,英文字符则不需要。 - -```Mysql -SELECT - product_id AS "产品编号", - product_type AS "产品类型" - FROM Product; -``` - -4. 常数的查询 - - `SELECT`子句中,除了可以写字段外,还可以写常数。 - -```mysql -SELECT - '商品' AS string, - '2009-05-24' AS date, - product_id, - product_name - FROM Product; - --- 结果如下 -+--------+------------+------------+--------------+ -| string | date | product_id | product_name | -+--------+------------+------------+--------------+ -| 商品 | 2009-05-24 | 0001 | T恤衫 | -| 商品 | 2009-05-24 | 0002 | 打孔器 | -| 商品 | 2009-05-24 | 0003 | 运动T恤 | -| 商品 | 2009-05-24 | 0004 | 菜刀 | -| 商品 | 2009-05-24 | 0005 | 高压锅 | -| 商品 | 2009-05-24 | 0006 | 叉子 | -| 商品 | 2009-05-24 | 0007 | 擦菜板 | -| 商品 | 2009-05-24 | 0008 | 圆珠笔 | -+--------+------------+------------+--------------+ -8 rows in set (0.00 sec) -``` - -5. 删除重复行 - - 在`SELECT`语句中使用`DISTINCT`可以去除重复行。 - -```mysql -SELECT - DISTINCT regist_date - FROM Product; - --- 结果如下 -+-------------+ -| regist_date | -+-------------+ -| 2009-09-20 | -| 2009-09-11 | -| NULL | -| 2009-01-15 | -| 2008-04-28 | -| 2009-11-11 | -+-------------+ -6 rows in set (0.01 sec) -``` - -​ 在使用`DISTINCT` 时,`NULL `也被视为一类数据。`NULL `存在于多行中时,会被合并为一条`NULL `数据。 - -​ 还可以通过组合使用,来去除列组合重复的数据。`DISTINCT `关键字只能用在第一个列名之前。 - -```mysql -SELECT - DISTINCT product_type, regist_date - FROM Product; - --- 结果如下,列出了所有的组合 -+--------------+-------------+ -| product_type | regist_date | -+--------------+-------------+ -| 衣服 | 2009-09-20 | -| 办公用品 | 2009-09-11 | -| 衣服 | NULL | -| 厨房用具 | 2009-09-20 | -| 厨房用具 | 2009-01-15 | -| 厨房用具 | 2008-04-28 | -| 办公用品 | 2009-11-11 | -+--------------+-------------+ -7 rows in set (0.00 sec) -``` - -6. 指定查询条件 - - 首先通过`WHERE` 子句查询出符合指定条件的记录,然后再选取出` SELECT `语句指定的列,语法结构如下: - -```mysql -SELECT <字段名>, …… - FROM <表名> - WHERE <条件表达式>; -``` - -​ 示例: - -```mysql -SELECT product_name - FROM Product - WHERE product_type = '衣服'; - --- 结果如下 -+--------------+ -| product_name | -+--------------+ -| T恤衫 | -| 运动T恤 | -+--------------+ -2 rows in set (0.01 sec) -``` - -注意,`WHERE`子句要紧跟在`FROM`子句之后。 - - - -## 4.5 表的复制 - -表的复制可以将表结构与表中的数据全部复制,或者只复制表的结构。 - -```mysql --- 将整个表复制过来 -CREATE TABLE Product_COPY1 - SELECT * FROM Product; - -SELECT * FROM Product_COPY1; - --- 结果如下 -+------------+--------------+--------------+------------+----------------+-------------+ -| product_id | product_name | product_type | sale_price | purchase_price | regist_date | -+------------+--------------+--------------+------------+----------------+-------------+ -| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | -| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | -| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | -| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | -| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | -| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | -| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | -| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | -+------------+--------------+--------------+------------+----------------+-------------+ -8 rows in set (0.00 sec) -``` - -```mysql --- 通过LIKE复制表结构 -CREATE TABLE Product_COPY2 - LIKe Product; - -SELECT * FROM Product_COPY2; - --- 结果如下 -Empty set (0.00 sec) -- 表为空的 - -DESC Product_COPY2; - --- 结果如下 --- 表结构已复制过来 -+----------------+--------------+------+-----+---------+-------+ -| Field | Type | Null | Key | Default | Extra | -+----------------+--------------+------+-----+---------+-------+ -| product_id | char(4) | NO | PRI | NULL | | -| product_name | varchar(100) | NO | | NULL | | -| product_type | varchar(32) | NO | | NULL | | -| sale_price | int | YES | | 0 | | -| purchase_price | int | YES | | NULL | | -| regist_date | date | YES | | NULL | | -+----------------+--------------+------+-----+---------+-------+ -6 rows in set (0.01 sec) -``` - - - -# 五、运算符 - -## 5.1 算术运算符 - -我们可以在`SELECT`语句中使用计算表达式: - -```mysql -SELECT - product_name, - sale_price, - sale_price * 2 AS "sale_price_x2" - FROM Product; - --- 结果如下 -+--------------+------------+---------------+ -| product_name | sale_price | sale_price_x2 | -+--------------+------------+---------------+ -| T恤衫 | 1000 | 2000 | -| 打孔器 | 500 | 1000 | -| 运动T恤 | 4000 | 8000 | -| 菜刀 | 3000 | 6000 | -| 高压锅 | 6800 | 13600 | -| 叉子 | 500 | 1000 | -| 擦菜板 | 880 | 1760 | -| 圆珠笔 | 100 | 200 | -+--------------+------------+---------------+ -8 rows in set (0.00 sec) -``` - -+ 四则运算所使用的运算符**(+、-、*、/)**称为算术运算符。 - -+ 在运算表达式中,也可以使用**()**,括号中的运算表达式优先级会得到提升。 - -+ **NULL**的计算结果,仍然还是**NULL**。 - - - -## 5.2 比较运算符 - -在 `WHERE` 子句中通过使用比较运算符可以组合出各种各样的条件表达式。 - -```mysql -SELECT product_name, product_type - FROM Product - WHERE sale_price = 500; -``` - -常见比较运算符如下表: - -| 运算符 | 含义 | -| ------ | -------- | -| = | 相等 | -| <> | 不相等 | -| \>= | 大于等于 | -| \> | 大于 | -| <= | 小于等于 | -| < | 小于 | - -+ 不能对**NULL**使用任何比较运算符,只能通过`IS NULL`语句来判断: - -```mysql -SELECT - product_name, - purchase_price - FROM Product - WHERE purchase_price IS NULL; -``` - -​ 希望选取不是 NULL 的记录时,需要使用`IS NOT NULL`运算符。 - -+ 对字符串使用比较符 - -​ MySQL中字符串的排序与数字不同,典型的规则就是按照字典顺序进行比较,也就是像姓名那样,按照条目在字典中出现的顺序来进行排序。例如: - -```mysql -'1' < '10' < '11' < '2' < '222' < '3' -``` - - - -## 5.3 逻辑运算符 - -1. 使用`NOT`否认某一条件: - -```mysql -SELECT - product_name, - product_type, - sale_price - FROM Product - WHERE NOT sale_price >= 1000; -``` - -2. `AND`运算符合`OR`运算符 - -```mysql -SELECT product_type, sale_price - FROM Product - WHERE product_type = '厨房用具' - AND sale_price >= 3000; - --- 结果如下 -+--------------+------------+ -| product_type | sale_price | -+--------------+------------+ -| 厨房用具 | 3000 | -| 厨房用具 | 6800 | -+--------------+------------+ -2 rows in set (0.00 sec) -``` - -```mysql -SELECT product_type, sale_price - FROM Product - WHERE product_type = '厨房用具' - OR sale_price >= 3000; - --- 结果如下 -+--------------+------------+ -| product_type | sale_price | -+--------------+------------+ -| 衣服 | 4000 | -| 厨房用具 | 3000 | -| 厨房用具 | 6800 | -| 厨房用具 | 500 | -| 厨房用具 | 880 | -+--------------+------------+ -5 rows in set (0.00 sec) -``` - -3. 逻辑运算符和真值 - -+ 符**NOT**、**AND** 和 **OR** 称为逻辑运算符; -+ 真值就是值为**真(TRUE)**或**假 (FALSE)**; - -+ 在查询**NULL**时,SQL中存在第三种真值,**不确定(UNKNOWN)**,**NULL**和任何值做逻辑运算结果都是不确定; -+ 考虑 **NULL** 时的条件判断也会变得异常复杂,因此尽量给字段加上**NOT NULL**的约束。 - - - -# 六、分组查询 - -## 6.1 聚合函数 - -通过 SQL 对数据进行某种操作或计算时需要使用函数。 - -+ `COUNT`:计算表中的记录数(行数) - -+ `SUM`: 计算表中数值列中数据的合计值 - -+ `AVG`: 计算表中数值列中数据的平均值 - -+ `MAX`: 求出表中任意列中数据的最大值 - -+ `MIN`: 求出表中任意列中数据的最小值 - -示例: - -```mysql --- 计算全部数据的行数 -SELECT COUNT(*) FROM Product; - --- 结果如下 -+----------+ -| COUNT(*) | -+----------+ -| 8 | -+----------+ -1 row in set (0.00 sec) -``` - -**注意点1**:除了`COUNT`可以将`*`作为参数,其它的函数均不可以。 - -```mysql --- 计算最高的销售价格 -SELECT MAX(sale_price) FROM Product; - --- 结果如下 -+-----------------+ -| MAX(sale_price) | -+-----------------+ -| 680000 | -+-----------------+ -1 row in set (0.00 sec) -``` - -**注意点2:**当将字段名作为参数传递给函数时,只会计算不包含`NULL`的行。 - -示例: - -```mysql --- purchase_price字段是包含NULL值的 -SELECT purchase_price FROM Product; - --- 结果如下 -+----------------+ -| purchase_price | -+----------------+ -| 500 | -| 320 | -| 2800 | -| 700 | -| 1250 | -| NULL | -| 198 | -| NULL | -+----------------+ -8 rows in set (0.00 sec) -``` - -以*为参数传递给`COUNT`函数 - -```mysql -SELECT COUNT(*) FROM Product; - --- 结果如下 -+----------+ -| COUNT(*) | -+----------+ -| 8 | -+----------+ -1 row in set (0.00 sec) -``` - -以purchase_price为参数传递给`COUNT`函数 - -```mysql -SELECT COUNT(purchase_price) FROM Product; - --- 结果如下 -+-----------------------+ -| COUNT(purchase_price) | -+-----------------------+ -| 6 | -+-----------------------+ -1 row in set (0.00 sec) -``` - -可以看到结果并不一样,函数忽略了值为**NULL**的行。 - -`SUM`,`AVG`函数时也一样,计算时会直接忽略,**并不会当做0来处理!**特别注意`AVG`函数,计算时分母也不会算上`NULL`行。 - -**注意点3**:`MAX/MIN`函数几乎适用于所有数据类型的列,包括字符和日期。`SUM/AVG`函数只适用于数值类型的列。 - -**注意点4**:在聚合函数删除重复值 - -```mysql -SELECT COUNT(DISTINCT product_type) - FROM Product; - --- 结果如下 -+------------------------------+ -| COUNT(DISTINCT product_type) | -+------------------------------+ -| 3 | -+------------------------------+ -1 row in set (0.01 sec) -``` - -`DISTINCT`必须写在括号中。这是因为必须要在计算行数之前删除 product_type 字段中的重复数据。 - - - -## 6.2 对表分组 - -如果对Python的Pandas熟悉,那么大家应该很了解`groupby`函数,可以根据指定的列名,对表进行分组。在MySQL中,也存在同样作用的函数,即`GROUP BY`。 - -语法结构如下: - -```mysql -SELECT <列名1>, <列名2>, <列名3>, …… - FROM <表名> - GROUP BY <列名1>, <列名2>, <列名3>, ……; -``` - -示例: - -```mysql -SELECT product_type, COUNT(*) - FROM Product - GROUP BY product_type; - --- 结果如下 -+--------------+----------+ -| product_type | COUNT(*) | -+--------------+----------+ -| 衣服 | 2 | -| 办公用品 | 2 | -| 厨房用具 | 4 | -+--------------+----------+ -3 rows in set (0.01 sec) -``` - -1. 在该语句中,我们首先通过`GROUP BY`函数对指定的字段product_type进行分组。分组时,product_type字段中具有相同值的行会汇聚到同一组。 - -2. 最后通过`COUNT`函数,统计不同分组的包含的行数。 - -简单来理解: - -+ 例如做操时,老师将不同身高的同学进行分组,相同身高的同学会被分到同一组,分组后我们又统计了每个小组的学生数。 - -+ 将这里的同学可以理解为表中的一行数据,身高理解为表的某一字段。 -+ 分组操作就是`GROUP BY`,`GROUP BY`后面接的字段等价于按照身高分组,统计学生数就等价于在`SELECT`后用了`COUNT(*)`函数。 - -注意:`GROUP BY `子句的位置一定要写在`FROM` 语句之后(如果有 `WHERE` 子句的话需要写在 `WHERE` 子句之后) - -``` -1. SELECT → 2. FROM → 3. WHERE → 4. GROUP BY -``` - -当被聚合的键中,包含`NULL`时,在结果中会以“不确定”行(空行)的形式表现出来,也就是字段中为`NULL`的数据会被聚合为一组。 - -## 6.3 使用WHERE语句 - -在对表进行分组之前,也可以是先使用`WHERE`对表进行条件过滤,然后再进行分组处理。语法结构如下: - -```mysql -SELECT <列名1>, <列名2>, <列名3>, …… - FROM <表名> - WHERE - GROUP BY <列名1>, <列名2>, <列名3>, ……; -``` - -示例: - -```mysql --- WHERE语句先将表中类型为衣服的行筛选出来 --- 然后再按照purchase_price来进行分组 -SELECT purchase_price, COUNT(*) - FROM Product - WHERE product_type = '衣服' - GROUP BY purchase_price; - --- 结果如下 -+----------------+----------+ -| purchase_price | COUNT(*) | -+----------------+----------+ -| 500 | 1 | -| 2800 | 1 | -+----------------+----------+ -2 rows in set (0.01 sec) -``` - -该语法实际的执行顺序为: - -``` -FROM → WHERE → GROUP BY → SELECT -``` - -+ 使用`GROUP BY`子句时,`SELECT`子句中不能出现聚合键之外的字段名。即,若`GROUP BY`选中purchase_price字段进行分组,则在`SELECT`语句中只能选中purchase_price字段,其它字段如product_id等均不行。 -+ `WHERE`语句中,不可以使用聚合函数。`WHERE`子句只能指定记录(行)的条件,而不能用来指定组的条件。即`WHERE MAX(purchase_price) > 1000`这样的语句是非法的。 - - - -## 6.4 为聚合结果指定条件 - -前面提到了`WHERE`语句中不能使用聚合函数,但是实际操作时需要通过聚合函数来进行过滤怎么办呢?这就要用到`HAVING`语句了。语法结构如下: - -```mysql -SELECT <列名1>, <列名2>, <列名3>, …… - FROM <表名> - GROUP BY <列名1>, <列名2>, <列名3>, …… -HAVING <分组结果对应的条件> -``` - -在`HAVING`的子句中能够使用的 3 种要素如下所示: - -● 常数 - -● 聚合函数 - -● `GROUP BY`子句中指定的字段名(即聚合键) - -示例: - -```mysql --- 不使用HAVING语句 -SELECT product_type, AVG(sale_price) - FROM Product - GROUP BY product_type; - --- 结果如下 -+--------------+-----------------+ -| product_type | AVG(sale_price) | -+--------------+-----------------+ -| 衣服 | 2500.0000 | -| 办公用品 | 300.0000 | -| 厨房用具 | 279500.0000 | -+--------------+-----------------+ -3 rows in set (0.00 sec) -``` - -```mysql --- 使用HAVING语句 --- 通过HAVING语句将销售平均价格大于等于2500的组给保留了 -SELECT product_type, AVG(sale_price) - FROM Product - GROUP BY product_type -HAVING AVG(sale_price) >= 2500; - --- 结果如下 -+--------------+-----------------+ -| product_type | AVG(sale_price) | -+--------------+-----------------+ -| 衣服 | 2500.0000 | -| 厨房用具 | 279500.0000 | -+--------------+-----------------+ -2 rows in set (0.00 sec) -``` - -可以看到使用`HAVING`语句后,输出的结果有所变化。大致流程如下: - -+ 首先,`FROM`语句会选中表Product; -+ 然后,`GROUP BY`语句会选中字段product_type进行分组; -+ 之后,通过`HAVING`语句将销售平均价格大于等于2500的组保留下来; -+ 最后,通过`SELECT`语句将保留下的组的产品类型和平均价格显示出来; - - - -如果是对**表的行**进行条件指定,`WHERE`和`HAVING`都可以生效。 - -```mysql --- 下面两条语句执行结果一致 -SELECT product_type, COUNT(*) - FROM Product - GROUP BY product_type - HAVING product_type = '衣服'; - -SELECT product_type, COUNT(*) - FROM Product - WHERE product_type = '衣服' - GROUP BY product_type; - --- 结果如下 -+--------------+----------+ -| product_type | COUNT(*) | -+--------------+----------+ -| 衣服 | 2 | -+--------------+----------+ -1 row in set (0.01 sec) -``` - -但是,一般而言如果是对表的行进行条件指定,最好还是使用`WHERE`语句,因为`WHERE`的执行速度更快。 - - - -## 6.5 对表的查询结果进行排序 - -如果希望对表的查询结果根据某指定的字段进行排序,可以使用`ORDER BY`语句。语法结构如下: - -```mysql -SELECT <列名1>, <列名2>, <列名3>, …… - FROM <表名> - ORDER BY <排序基准列1>, <排序基准列2>, …… -``` - -示例: - -```mysql -SELECT product_id, product_name, sale_price, purchase_price - FROM Product; - --- 结果如下 -+------------+--------------+------------+----------------+ -| product_id | product_name | sale_price | purchase_price | -+------------+--------------+------------+----------------+ -| 0001 | T恤衫 | 1000 | 500 | -| 0002 | 打孔器 | 500 | 320 | -| 0003 | 运动T恤 | 4000 | 2800 | -| 0004 | 菜刀 | 300000 | 700 | -| 0005 | 高压锅 | 680000 | 1250 | -| 0006 | 叉子 | 50000 | NULL | -| 0007 | 擦菜板 | 88000 | 198 | -| 0008 | 圆珠笔 | 100 | NULL | -+------------+--------------+------------+----------------+ -8 rows in set (0.01 sec) -``` - -```mysql --- 根据字段sale_price的值进行排序 -SELECT product_id, product_name, sale_price, purchase_price - FROM Product -ORDER BY sale_price; - --- 结果如下 -+------------+--------------+------------+----------------+ -| product_id | product_name | sale_price | purchase_price | -+------------+--------------+------------+----------------+ -| 0008 | 圆珠笔 | 100 | NULL | -| 0002 | 打孔器 | 500 | 320 | -| 0001 | T恤衫 | 1000 | 500 | -| 0003 | 运动T恤 | 4000 | 2800 | -| 0006 | 叉子 | 50000 | NULL | -| 0007 | 擦菜板 | 88000 | 198 | -| 0004 | 菜刀 | 300000 | 700 | -| 0005 | 高压锅 | 680000 | 1250 | -+------------+--------------+------------+----------------+ -8 rows in set (0.00 sec) -``` - -可以看到`ORDER BY`默认是按照升序的方式进行排序的,正式的书写方式应该是在字段后加上关键字`ASC`,即`ORDER BY sale_price ASC`。 - -如果我们希望按照降序的方式,可以通过`DESC`关键词进行指定。 - -```mysql -SELECT product_id, product_name, sale_price, purchase_price - FROM Product -ORDER BY sale_price DESC; - --- 结果如下 -+------------+--------------+------------+----------------+ -| product_id | product_name | sale_price | purchase_price | -+------------+--------------+------------+----------------+ -| 0005 | 高压锅 | 680000 | 1250 | -| 0004 | 菜刀 | 300000 | 700 | -| 0007 | 擦菜板 | 88000 | 198 | -| 0006 | 叉子 | 50000 | NULL | -| 0003 | 运动T恤 | 4000 | 2800 | -| 0001 | T恤衫 | 1000 | 500 | -| 0002 | 打孔器 | 500 | 320 | -| 0008 | 圆珠笔 | 100 | NULL | -+------------+--------------+------------+----------------+ -8 rows in set (0.00 sec) -``` - -前面展示了指定一个字段来对表进行排序,实际上我们可以指定多个字段来进行排序。 - -示例: - -```mysql -SELECT regist_date, product_id, sale_price, purchase_price - FROM Product -ORDER BY regist_date, product_id; - --- 结果如下 -+-------------+------------+------------+----------------+ -| regist_date | product_id | sale_price | purchase_price | -+-------------+------------+------------+----------------+ -| 2009-10-10 | 0002 | 500 | 320 | -| 2009-10-10 | 0003 | 4000 | 2800 | -| 2009-10-10 | 0004 | 300000 | 700 | -| 2009-10-10 | 0005 | 680000 | 1250 | -| 2009-10-10 | 0006 | 50000 | NULL | -| 2009-10-10 | 0007 | 88000 | 198 | -| 2009-10-10 | 0008 | 100 | NULL | -| 2021-10-30 | 0001 | 1000 | 500 | -+-------------+------------+------------+----------------+ -``` - -可以看到先按照`regist_date`的大小进行排序,在字段`regist_date`中具有相同的值的行,接着会按照`product_id`进行排序。 - -使用含有 NULL 的列作为排序键时,NULL 会在结果的开头或末尾汇总显示。 - -在`ORDER BY`子句中可以使用`SELECT`子句中定义的别名。 - -```mysql --- 将product_id命名为ID,然后按照ID进行排序 -SELECT product_id as ID, product_name, sale_price, purchase_price - FROM Product -ORDER BY ID; - --- 结果如下 -+------+--------------+------------+----------------+ -| ID | product_name | sale_price | purchase_price | -+------+--------------+------------+----------------+ -| 0001 | T恤衫 | 1000 | 500 | -| 0002 | 打孔器 | 500 | 320 | -| 0003 | 运动T恤 | 4000 | 2800 | -| 0004 | 菜刀 | 300000 | 700 | -| 0005 | 高压锅 | 680000 | 1250 | -| 0006 | 叉子 | 50000 | NULL | -| 0007 | 擦菜板 | 88000 | 198 | -| 0008 | 圆珠笔 | 100 | NULL | -+------+--------------+------------+----------------+ -8 rows in set (0.00 sec) -``` - -为什么`ORDER BY`中可以使用`SELECT`定义的别名呢? - -这是因为在MySQL中,`ORDER BY `的执行次序在`SELECT`之后。 - - - -# 七、数据的插入及更新 - -## 7.1 数据的插入 - -通过命令`INSERT`,可以向表中插入数据: - -```mysql --- 往表中插入一行数据 -INSERT INTO <表名> (字段1, 字段2, 字段3, ……) VALUES (值1, 值2, 值3, ……); - --- 往表中插入多行数据 -INSERT INTO <表名> (字段1, 字段2, 字段3, ……) VALUES - (值1, 值2, 值3, ……), - (值1, 值2, 值3, ……), - ... - ; -``` - -示例: - -1. 创建表并插入数据 - -```mysql --- 创建表 -CREATE TABLE ProductIns -(product_id CHAR(4) NOT NULL, - product_name VARCHAR(100) NOT NULL, - product_type VARCHAR(32) NOT NULL, - sale_price INTEGER DEFAULT 0, -- DEFAULT 0:表示将字段sale_price的默认值设为0 - purchase_price INT , - regist_date DATE , - PRIMARY KEY (product_id)); - --- 通过单行方式插入 -INSERT INTO - ProductIns(product_id, product_name, product_type, sale_price, purchase_price, regist_date) - VALUES ('0001', '打孔器', '办公用品', 500, 320, '2009-09-11'); - --- 当对表插入全字段时,可以省略表后的字段清单 -INSERT INTO ProductIns VALUES('0002', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'); - --- 通过多行方式插入 -INSERT INTO ProductIns VALUES - ('0003', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'), - ('0004', '订书机', '办公用品', 100, 50, '2009-09-11'), - ('0005', '裙子', '衣服', 4100, 3200, '2009-01-23'), - ('0006', '运动T恤', '衣服', 4000, 2800, NULL), - ('0007', '牙刷', '日用品', 20, 10, '2010-03-22'); -``` - -```mysql -SELECT * FROM ProductIns; - --- 结果如下 -+------------+--------------+--------------+------------+----------------+-------------+ -| product_id | product_name | product_type | sale_price | purchase_price | regist_date | -+------------+--------------+--------------+------------+----------------+-------------+ -| 0001 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | -| 0002 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | -| 0003 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | -| 0004 | 订书机 | 办公用品 | 100 | 50 | 2009-09-11 | -| 0005 | 裙子 | 衣服 | 4100 | 3200 | 2009-01-23 | -| 0006 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | -| 0007 | 牙刷 | 日用品 | 20 | 10 | 2010-03-22 | -+------------+--------------+--------------+------------+----------------+-------------+ -7 rows in set (0.00 sec) -``` - -2. 插入NULL - - `INSERT `语句中想给某一列赋予**NULL**值时,可以直接在` VALUES`子句的值清单中写入**NULL**。 - -```mysql -INSERT INTO ProductIns VALUES ('0008', '叉子', '厨房用具', 500, NULL, '2009-09-20'); -``` - -3. 插入默认值 - - 在前面我们创建表时,字段sale_price包含了一条约束条件,默认为0。我们在插入数据时,可以直接用`DEFAULT`对该字段赋值。前提是,该字段被指定了默认值。 - -```mysql --- 通过显式方法设定默认值 -INSERT INTO - ProductIns (product_id, product_name, product_type, sale_price, purchase_price, regist_date) - VALUES ('0009', '擦菜板', '厨房用具', DEFAULT, 790, '2009-04-28'); - --- 通过隐式方法插入默认值 -INSERT INTO - ProductIns (product_id, product_name, product_type, purchase_price, regist_date) - VALUES ('0010', '擦菜板', '厨房用具', 790, '2009-04-28'); -``` - - - -## 7.2 数据的删除 - -通过`DROP TABLE`或者`DELETE`语句,可以对表进行删除,但二者存在一定的区别。 - -+ `DROP TABLE` 语句可以将表完全删除。 -+ `DELETE` 语句会留下表结构,而删除表中的全部数据。 - -无论通过哪种方式删除,数据都是难以恢复的。 - -1. 通过`DROP`进行删除 - - 语法结构为: - -```mysql -DROP <表名>; -``` - -2. 通过`DELETE`进行删除 - - 语法结构如下,记得要加`FROM`: - -```mysql -DELETE FROM <表名>; -``` - -​ 同时,也可以通过`WHERE`语句来指定删除的条件: - -```mysql -DELETE FROM <表名> - WHERE <条件>; -``` - -​ 需要注意的是,`DELETE`语句的删除对象并不是表或者列,而是记录(行)。 - -示例: - -```mysql -SELECT * FROM Product; - --- 结果如下 -mysql> SELECT * FROM Product; -+------------+--------------+--------------+------------+----------------+-------------+ -| product_id | product_name | product_type | sale_price | purchase_price | regist_date | -+------------+--------------+--------------+------------+----------------+-------------+ -| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | -| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | -| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | -| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | -| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | -| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | -| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | -| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | -+------------+--------------+--------------+------------+----------------+-------------+ -8 rows in set (0.00 sec) - --- 删除销售价格大于等于4000的行 -DELETE FROM Product - WHERE sale_price >= 4000; - --- 结果如下 -mysql> SELECT * FROM Product; -+------------+--------------+--------------+------------+----------------+-------------+ -| product_id | product_name | product_type | sale_price | purchase_price | regist_date | -+------------+--------------+--------------+------------+----------------+-------------+ -| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | -| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | -| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | -| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | -| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | -| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | -+------------+--------------+--------------+------------+----------------+-------------+ -6 rows in set (0.00 sec) -``` - -3. 通过`TRUNCATE`进行删除 - - 在MySQL中,还存在一种删除表的方式,就是利用`TRUNCATE`语句。它的功能和`DROP`类似,但是不能通过`WHERE`指定条件,优点是速度比`DROP`快得多。 - -```mysql -TRUNCATE Product; - --- 结果如下 -mysql> SELECT * FROM Product; -Empty set (0.00 sec) - -mysql> DESC Product; -+----------------+--------------+------+-----+---------+-------+ -| Field | Type | Null | Key | Default | Extra | -+----------------+--------------+------+-----+---------+-------+ -| product_id | char(4) | NO | PRI | NULL | | -| product_name | varchar(100) | NO | | NULL | | -| product_type | varchar(32) | NO | | NULL | | -| sale_price | int | YES | | NULL | | -| purchase_price | int | YES | | NULL | | -| regist_date | date | YES | | NULL | | -+----------------+--------------+------+-----+---------+-------+ -6 rows in set (0.00 sec) -``` - - - -## 7.3 数据的更新 - -当我们使用`INSERT`语句插入错误的数据后,若我们不想删除后从新插入,那就要使用到`UPDATE`语句。 - -1. 基本用法 - - `UPDATE`的语法结构如下: - -```mysql -UPDATE <表名> - SET <字段名> = <表达式>; -``` - -​ 示例: - -```mysql --- 由于前面演示删除语句时,表Product的内容已清空 --- 所以,这里从新进行数据插入 -INSERT INTO Product VALUES - ('0001', 'T恤衫', '衣服', 1000, 500, '2009-09-20'), - ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11'), - ('0003', '运动T恤', '衣服', 4000, 2800, NULL), - ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'), - ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'), - ('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20'), - ('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28'), - ('0008', '圆珠笔', '办公用品', 100, NULL,'2009-11-11') - ; - --- 修改表中所有行regist_date的值 -UPDATE Product - SET regist_date = '2009-10-10'; - --- 结果如下 -mysql> SELECT * FROM Product; -+------------+--------------+--------------+------------+----------------+-------------+ -| product_id | product_name | product_type | sale_price | purchase_price | regist_date | -+------------+--------------+--------------+------------+----------------+-------------+ -| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-10-10 | -| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-10-10 | -| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | 2009-10-10 | -| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-10-10 | -| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-10-10 | -| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-10-10 | -| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2009-10-10 | -| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-10-10 | -+------------+--------------+--------------+------------+----------------+-------------+ -8 rows in set (0.00 sec) -``` - -2. 指定条件 - -```mysql -UPDATE <表名> - SET <列名> = <表达式> - WHERE <条件>; -``` - -​ 示例: - -```mysql -UPDATE Product - SET regist_date = '2021-10-30' - WHERE product_id = '0001'; -``` - -​ 注意,你也可是使用**NULL**对表进行更新,不过更新的字段必须满足没有**主键**和**NOT NULL**的约束条件。 - -3. 多列更新 - - 多列更新只需要用逗号(,)连接更改的字段即可。 - -```mysql -UPDATE Product - SET - sale_price = sale_price * 10, - purchase_price = purchase_price / 2 - WHERE product_type = '厨房用具'; -``` - - - -# 八、Pymysql的使用 - -在正式介绍`pymysql`的用法之前,我们先思考一件事,我们希望借助`pymysql`完成什么事情? - -之前,我们在命令行下,通过输入SQL语句来完成对数据库和表的增删改查。那么,我们也希望能够在Python下能够完成同样的操作,并且能够返回相应的反馈。具体任务包括: - -1. 登陆并连接到MySQL下的用户; -2. 切换到相应的数据库下; -3. 完成对表的增删改查; - -接下来的内容将围绕这3部分来介绍。 - - - -## 8.1 安装pymysql - -通过`pip`,我们可以完成对`pymysql`的安装: - -```bash -python3 -m pip install PyMySQL -``` - - - -## 8.2 连接数据库 - -如果希望在Python中操作MySQL数据库,那么首先就要登陆到MySQL下的用户。 - -我们通过创建库pymysql下的类`connect`的一个实例来登陆到数据库。 - -示例: - -```python -import pymysql - -# 这里登陆到我之前创建的admin账户 -db = pymysql.connect( - host='localhost', - user='admin', - password='mysql123', - database='shop', - charset='utf8mb4', - cursorclass=pymysql.cursors.DictCursor -) -``` - -参数解释: - -+ `host`:数据库服务器地址,默认`localhost`; -+ `user`:所要登陆的用户名; -+ `password`:用户的登录密码; -+ `database`:所要连接的数据库库名; -+ `charset`:使用的字符类型; -+ `cursorclass`:定义游标使用的类型,通过指定游标使用的类型,在返回输出的结果时将按照指定的类型进行返回。例如,这里设置为字典游标。 - - - -## 8.3 创建游标 - -关于游标,可以理解为在命令行中的光标。在命令行中,我们是在光标处键入语句的。这里游标的起到类似作用。 - -```python -# 创建游标 -cursor = db.cursor() -``` - -实际上,除了在初始化`connect`的实例时指定游标类型,我们在初始化游标时也可以指定游标类型,默认为元组类型。 - -```python -cursor = db.cursor(cursor=pymysql.cursors.DictCursor) -``` - -`cursors`共支持四类游标: - -+ `Cursor`: 默认,元组类型 - -+ `DictCursor`: 字典类型 - -+ `SSCursor`: 无缓冲元组类型 - -+ `SSDictCursor`: 无缓冲字典类型 - - - -## 8.4 类方法 - -初始化完类`connect`和`cursor`的实例后,我们先来了解一下这两个类下包含的方法。了解这些方法有利于我们后面在python下操作mysql: - -+ `connect`下的类方法: - + `close()`:在完成操作后,需要关闭与数据库之间的连接; - + `commit()`:如果执行语句中发生了数据更改,需要提交更改到稳定的存储器; - + `cursor(cursor=None)`:创建一个游标,前面我们在初始化`connect`类是指定了游标类型,通过`cursor`初始化游标时,也可以进行游标类型指定; - + `rollback()`:事务回滚; - -+ `pymysql.cursors`下的类方法: - + `close()`:结束时,关闭游标; - + `execute()`:通过游标执行语句; - + `executemany()`:通过游标执行多条语句; - + `fetchone()`:获取单条数据; - + `fetchmany(size=None)`:获取size条数据; - + `fetchall()`:获取单条数据; - + `scroll(value, mode)`:数据的查询操作都是基于游标,可以通过`scroll`控制游标的位置。 - + `mode=absolute`:绝对位置移动,控制游标位置到上一次查询的第`value`条数据,最小值为`0`; - + `mode=relative`:相对位置移动,基于当前位置,跳过`value`条数据; - -更详细的资料,可参考官方的API或者Github: - -[pymysql github]: https://github.com/PyMySQL/PyMySQL - -[pymysql document]: https://pymysql.readthedocs.io/en/latest/modules/index.html# - - - -## 8.5 实战 - -+ 示例1: - -  在这个示例中,我们将做两件事情:创建表和插入数据。 - -```python -import pymysql - -# 以admin身份连接到数据库shop -connection = pymysql.connect( - host='localhost', - user='admin', - password='mysql123', - database='shop', - charset='utf8mb4', -) - -# 创建游标 -cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) - -# 1. 创建了一个表 -sql = """ -CREATE TABLE Employee( - id INT PRIMARY KEY, - name CHAR(15) NOT NULL - ) - """ - -# 提交执行 -cursor.execute(sql) - -# 2. 往表中插入数据 -sql = "INSERT INTO Employee (id, name) VALUES (%s, %s)" -values = [(1, 'XiaoBai'), - (2, 'XiaoHei'), - (3, 'XiaoHong'), - (4, 'XiaoMei'), - (5, 'XiaoLi')] - -try: - # 通过executemany可以插入多条数据 - cursor.executemany(sql, values) - # 提交事务 - connection.commit() -except: - connection.rollback() - - -# 3. 关闭光标及连接 -cursor.close() -connection.close() -``` - -+ 示例2 - - 在示例1的基础上,我们继续执行查询工作。 - -```python -import pymysql - -# 以admin身份连接到数据库shop -connection = pymysql.connect( - host='localhost', - user='admin', - password='mysql123', - database='shop', - charset='utf8mb4', -) - -with connection: - # 创建游标 - cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) - - # 1. 通过fetchone只查询一条 - cursor.execute("SHOW CREATE TABLE Employee") - result = cursor.fetchone() - print(f'查询结果1: \n{result}') - - # 2. 通过fetchmany查询size条 - cursor.execute("DESC Employee") - result = cursor.fetchmany(size=2) - print(f'查询结果2: \n{result}') - - # 3. 通过fetchall查询所有 - cursor.execute("SELECT * FROM Employee") - result = cursor.fetchall() - print(f'查询结果3: \n{result}') - - # 4. 通过scroll回滚到第0条进行查询 - cursor.scroll(0, mode='absolute') - result = cursor.fetchone() - print(f'查询结果4: \n{result}') - - # 5. 通过scroll跳过2条进行查询 - cursor.scroll(2, mode='relative') - result = cursor.fetchone() - print(f'查询结果5: \n{result}') - - cursor.close() -``` - -​ 控制台打印结果如下: - -```bash -查询结果1: -{'Table': 'Employee', 'Create Table': 'CREATE TABLE `Employee` (\n `id` int NOT NULL,\n `name` char(15) NOT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci'} -查询结果2: -[{'Field': 'id', 'Type': 'int', 'Null': 'NO', 'Key': 'PRI', 'Default': None, 'Extra': ''}, {'Field': 'name', 'Type': 'char(15)', 'Null': 'NO', 'Key': '', 'Default': None, 'Extra': ''}] -查询结果3: -[{'id': 1, 'name': 'XiaoBai'}, {'id': 2, 'name': 'XiaoHei'}, {'id': 3, 'name': 'XiaoHong'}, {'id': 4, 'name': 'XiaoMei'}, {'id': 5, 'name': 'XiaoLi'}] -查询结果4: -{'id': 1, 'name': 'XiaoBai'} -查询结果5: -{'id': 4, 'name': 'XiaoMei'} -``` - -+ 示例3: - - ​ 该示例将演示SQL注入的问题。先建立一个表并插入数据: - -```python -import pymysql - -# 以admin身份连接到数据库shop -connection = pymysql.connect( - host='localhost', - user='admin', - password='mysql123', - database='shop', - charset='utf8mb4', -) - -# 创建游标 -cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) - -sql = """ - CREATE TABLE UserInfo( - id INT PRIMARY KEY, - name VARCHAR(15), - password CHAR(15) NOT NULL - ) - """ - -cursor.execute(sql) - -sql = "INSERT INTO UserInfo (id, name, password) VALUES (%s, %s, %s)" -values = [(1, 'XiaoBai', '123'), - (2, 'XiaoHei', '234'), - (3, 'XiaoHong', '567'), - (4, 'XiaoMei', '321'), - (5, 'XiaoLi', '789')] - -cursor.executemany(sql, values) -connection.commit() -``` - -​ 再写一个程序,根据输入判定登陆是否成功: - -```python -import pymysql - -# 以admin身份连接到数据库shop -connection = pymysql.connect( - host='localhost', - user='admin', - password='mysql123', - database='shop', - charset='utf8mb4', -) - -# 创建游标 -cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) - -while True: - user = input("输入用户:").strip() - password = input("输入密码:").strip() - sql = "select name, password from UserInfo where name='%s' and password='%s' " % (user, password) - - cursor.execute(sql) - # 打印用户和密码 - result=cursor.fetchone() - print(result) - - if result: - print("成功登陆\n") - else: - print("登陆失败\n") -``` - -​ 在控制台下,我们进行了三组用户和密码的验证: - -```python -输入用户:XiaoBai -输入密码:123 -{'name': 'XiaoBai', 'password': '123'} -成功登陆 - -输入用户:XiaoBai -输入密码:321 -None -登陆失败 - -输入用户:XiaoBai' -- dsd -输入密码:321 -{'name': 'XiaoBai', 'password': '123'} -成功登陆 -``` - -​ 可以看出,第1组和第2组验证正常,但是第三组出现了异常,输入错误的密码却可以正确登陆。 - -​ 这是因为在MySQL中`--`的含义是注释,如果通过字符串进行拼接: - -```mysql -select name, password from UserInfo where name='XiaoBai' -- dsd' and password='321' -``` - -​ 实际等价于: - -```mysql -sselect name, password from UserInfo where name='XiaoBai' -``` - -​ 解决办法:通过`execute`或者`executemany`来进行拼接。将语句: - -```python -sql = "select name, password from UserInfo where name='%s' and password='%s' " % (user, password) -cursor.execute(sql) -``` - -​ 改为: - -```python -sql = "select name, password from UserInfo where name=%s and password=%s" -cursor.execute(sql, (user, password)) -``` - From de2bbe6c9d2228633d38f6288cb3a91afa653a47 Mon Sep 17 00:00:00 2001 From: Lyons-T <75569795+Lyons-T@users.noreply.github.com> Date: Wed, 3 Nov 2021 19:09:50 +0800 Subject: [PATCH 06/16] Add files via upload --- .../2.2新闻推荐系统实战/docs/MySQL基础.md | 2310 +++++++++++++++++ 1 file changed, 2310 insertions(+) create mode 100644 docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MySQL基础.md diff --git a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MySQL基础.md b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MySQL基础.md new file mode 100644 index 00000000..feccaa7b --- /dev/null +++ b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MySQL基础.md @@ -0,0 +1,2310 @@ +# 前言 MySQL简介 + +​ MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。 + +​ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行的开源数据库,因此被广泛地应用在Internet上的中小型网站中。随着MySQL的不断成熟,它也逐渐用于更多大规模网站和应用,比如维基百科、Google和Facebook等网站。非常流行的开源软件组合LAMP中的“M”指的就是MySQL。 + +[百度百科]: https://baike.baidu.com/item/mySQL/471251 +[维基百科]: https://zh.wikipedia.org/wiki/MySQL + + + +# 一、 Ubuntu下安装MySQL + +安装教程是在`Ubuntu20.04`下进行的,安装的MySQL版本为`8.0.27`。 + +## 1.1 安装 + +```bash +sudo apt install mysql-server mysql-client +``` + +在输入密码后,再输入`yes`即可开始安装。 + +安装完成后,通过运行命令`mysql -V`查看版本号: + +```bash +lyons@ubuntu:~$ mysql -V +mysql Ver 8.0.27-0ubuntu0.20.04.1 for Linux on x86_64 ((Ubuntu)) +``` + +验证MySQL服务正在运行,命令行下输入: + +```bash +sudo service mysql status +``` + +如果正在运行,则会显示: + +```bash +● mysql.service - MySQL Community Server + Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled) + Active: active (running) since Wed 2021-10-27 10:27:59 CST; 9h ago + Main PID: 6179 (mysqld) + Status: "Server is operational" + Tasks: 39 (limit: 4599) + Memory: 348.9M + CGroup: /system.slice/mysql.service + └─6179 /usr/sbin/mysqld + +10月 27 10:27:59 ubuntu systemd[1]: Starting MySQL Community Server... +10月 27 10:27:59 ubuntu systemd[1]: Started MySQL Community Server. +``` + + + +## 1.2 配置MySQL的安全性 + +1. 首先,运行命令`mysql_secure_installation`: + + ```bash + sudo mysql_secure_installation + ``` + +2. `VALIDATE PASSWORD COMPONENT` + + 设置验证密码插件。它被用来测试`MySQL`用户的密码强度,并且提高安全性。如果想设置验证密码插件,请输入`y`: + + ```bash + Connecting to MySQL using a blank password. + + VALIDATE PASSWORD COMPONENT can be used to test passwords + and improve security. It checks the strength of password + and allows the users to set only those passwords which are + secure enough. Would you like to setup VALIDATE PASSWORD component? + + Press y|Y for Yes, any other key for No: y + ``` + + 接下来,将进行密码验证等级设置,根据数字设置对应等级,这里设置为0: + + ```bash + There are three levels of password validation policy: + + LOW Length >= 8 + MEDIUM Length >= 8, numeric, mixed case, and special characters + STRONG Length >= 8, numeric, mixed case, special characters and dictionary file + + Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 0 + ``` + +3. 设置密码 + + 为MySQL root用户设置密码,设置过程中密码不会显示。如果设置了验证密码插件,将会显示密码的强度。 + + ``` + Please set the password for root here. + New password: + + Re-enter new password: + + Estimated strength of the password: 25 + Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y + ``` + +4. 移除匿名用户 + + 默认情况下,MySQL安装有一个匿名用户,允许任何人登录MySQL,而不必为他们创建用户帐户。输入`y`进行删除: + + ``` + By default, a MySQL installation has an anonymous user, + allowing anyone to log into MySQL without having to have + a user account created for them. This is intended only for + testing, and to make the installation go a bit smoother. + You should remove them before moving into a production + environment. + + Remove anonymous users? (Press y|Y for Yes, any other key for No) : y + Success. + ``` + +5. 禁止远程root用户登录 + + 输入`y`后按`enter`,将会禁止`root`用户登录。 + + ``` + Normally, root should only be allowed to connect from + 'localhost'. This ensures that someone cannot guess at + the root password from the network. + + Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y + Success. + ``` + +6. 删除测试库 + + 输入`y`后按`enter`,将会删除测试库。 + + ``` + By default, MySQL comes with a database named 'test' that + anyone can access. This is also intended only for testing, + and should be removed before moving into a production + environment. + + + Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y + - Dropping test database... + Success. + ``` + +7. 重新加载特权表 + + 输入`y`后按`enter`,将会重新加载特权表。 + + ``` + - Removing privileges on test database... + Success. + + Reloading the privilege tables will ensure that all changes + made so far will take effect immediately. + + Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y + Success. + + All done! + ``` + + 至此,配置完成。 + + + +## 1.3 以root用户登录 + +在MySQL 8.0上,root 用户默认通过`auth_socket`插件授权。`auth_socket`插件通过 Unix socket 文件来验证所有连接到`localhost`的用户。 + +这意味着你不能通过提供密码,验证为 root。此时,输入`mysql -uroot -p`可能会被拒绝访问: + +```bash +lyons@ubuntu:~$ mysql -uroot -p +mysql: [Warning] Using a password on the command line interface can be insecure. +ERROR 1698 (28000): Access denied for user 'root'@'localhost' +``` + +若要以 root 用户身份登录 MySQL服务器,输入`sudo mysql`,如下: + +```bash +# 登录密码为linux系统用户的root密码 +lyons@ubuntu:~$ sudo mysql +[sudo] lyons 的密码: +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 55 +Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu) + +Copyright (c) 2000, 2021, Oracle and/or its affiliates. + +Oracle is a registered trademark of Oracle Corporation and/or its +affiliates. Other names may be trademarks of their respective +owners. + +Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. + +mysql> +``` + +退出MySQL,请输入`exit`命令: + +```mysql +mysql> exit +Bye +lyons@ubuntu:~$ +``` + +如果你想以 root 身份登录 MySQL 服务器,便于使用其他的程序。可以将验证方法从`auth_socket`修改成`mysql_native_password`。 + ++ **方式1** + +你可以通过运行下面的命令实现: + +```bash +-- 语法中的'你的密码’指的是你自己设置的登录密码,可设置为字母数字组合。 +ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; +FLUSH PRIVILEGES; +``` + +示例: + +```mysql +-- 在mysql下,将密码设置为'mysql123' +mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'mysql123'; +Query OK, 0 rows affected (0.00 sec) + +-- 刷新系统权限 +mysql> FLUSH PRIVILEGES; +Query OK, 0 rows affected (0.01 sec) + +mysql> exit +Bye + +-- 现在便可以通过mysql -uroot -p登录 +-- 登录密码为前面设置的'mysql123' +lyons@ubuntu:~$ mysql -uroot -p +Enter password: +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 57 +Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu) + +Copyright (c) 2000, 2021, Oracle and/or its affiliates. + +Oracle is a registered trademark of Oracle Corporation and/or its +affiliates. Other names may be trademarks of their respective +owners. + +Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. + +mysql> +mysql> exit +Bye + +-- 同时,命令sudo mysql会被拒绝访问 +lyons@ubuntu:~$ sudo mysql +ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO) +``` + +当然,若要再次修改回`sudo mysql`的方式来登录`root`用户,方法类似: + +```mysql +ALTER USER 'root'@'localhost' IDENTIFIED WITH auth_socket BY '你的密码'; + +FLUSH PRIVILEGES; +``` + ++ **方式2** + + 推荐的选项,就是创建一个新的独立管理用户,拥有所有数据库的访问权限: + +```mysql +# 创建用户 +CREATE USER '用户名'@'localhost' identified by '你的密码' + +# 赋予admin用户全部的权限,你也可以只授予部分权限 +GRANT ALL PRIVILEGES ON *.* TO '用户名'@'localhost'; +``` + +​ 示例: + +```mysql +# 创建名为admin的用户,密码为mysql123 +mysql> create user 'admin'@'localhost' identified by 'mysql123'; +Query OK, 0 rows affected (0.01 sec) + +# 将访问所有database以及表的权利授权用户admin +#with gran option表示该用户可给其它用户赋予权限,但不可能超过该用户已有的权限 +mysql> grant all privileges on *.* to 'admin'@'localhost' with grant option; +Query OK, 0 rows affected (0.00 sec) + +mysql> FLUSH PRIVILEGES; +Query OK, 0 rows affected (0.00 sec) + +# 查看已有的用户 +mysql> select user, host from mysql.user; ++------------------+-----------+ +| user | host | ++------------------+-----------+ +| admin | localhost | +| debian-sys-maint | localhost | +| mysql.infoschema | localhost | +| mysql.session | localhost | +| mysql.sys | localhost | +| root | localhost | ++------------------+-----------+ +6 rows in set (0.00 sec) + +# 退出root用户登录 +mysql> exit +Bye + +# 登录admin用户,输入密码mysql123即可登录成功 +lyons@ubuntu:~$ mysql -uadmin -p +Enter password: +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 16 +Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu) + +Copyright (c) 2000, 2021, Oracle and/or its affiliates. + +Oracle is a registered trademark of Oracle Corporation and/or its +affiliates. Other names may be trademarks of their respective +owners. + +Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. + +mysql> +``` + +说明:`'admin'@'localhost'`中,`localhost`指本地才可连接,可以将其换成`%`指任意`ip`都能连接,也可以指定`ip`连接。 + + + +## 1.4 修改密码 + +将用户`admin`的登录密码修改为`mysql321`: + +```mysql +ALTER USER 'admin'@'localhost' IDENTIFIED WITH mysql_native_password BY 'mysql321'; +``` + + + +## 1.5 撤销用户授权 + +```mysql +# 查看用户的权限 +show grants for 'admin'@'localhost'; + +# 撤销用户的权限 +# 用户有什么权限就撤销什么 +revoke all privileges on *.* from 'admin'@'localhost'; +``` + + + +## 1.6 删除用户 + +```MYSQL +drop user 'admin'@'localhost'; +``` + + + +> 注:MySQL 8.0版本和5.0部分命令有所改掉,上述语法都是在8.0版本下运行通过的;请务必检查自己的MySQL版本号。 + + + +# 二、MySQL预备知识 + +在正式学习MySQL之前,我们先来了解一下SQL语句的书写规范以及命名规则等。 + +## 2.1 SQL书写规范 + +在写SQL语句时,要求按照如下规范进行: + ++ SQL 语句要以分号(;)结尾 + ++ SQL 不区分关键字的大小写 ,这对于表名和列名同样适用。 + ++ 插入到表中的数据是区分大小写的。例如,数据Computer、COMPUTER 或computer,三者是不一样的。 + ++ 常数的书写方式是固定的,在SQL 语句中直接书写的字符串、日期或者数字等称为常数。常数的书写方式如下所示。 + + + SQL 语句中含有字符串的时候,需要像'abc'这样,使用单引号(')将字符串括起来,用来标识这是一个字符串。 + + SQL 语句中含有日期的时候,同样需要使用单引号将其括起来。日期的格式有很多种('26 Jan 2010' 或者'10/01/26' 等)。 + + 在SQL 语句中书写数字的时候,不需要使用任何符号标识,直接写成1000 这样的数字即可。 + ++ 单词之间需要用半角空格或者换行来分隔。 + ++ SQL中的注释主要采用`--`和`/* ... */`的方式,第二种方式可以换行。在MySQL下,还可以通过`#`来进行注释。 + + + +## 2.2 命名规则 + ++ 在数据库中,只能使用半角英文字母、数字、下划线(_)作为数据库、表和列的名称 。 ++ 名称必须以半角英文字母作为开头。 ++ 名称不能重复,同一个数据库下不能有2张相同的表。 + + + +## 2.3. 数据类型 + +MySQL 支持所有标准 SQL 数值数据类型,包括: + +### (1)数值类型 + +数值包含的类型如下: + ++ 整型数据:`TINYINT`、`INTEGER`、`SMALLINT`、`MEDIUMINT`、`DECIMAL` 、`NUMERIC` 和`BIGINT`。 + ++ 浮点型数据:`DECIMAL`、`FLOAT`、`REAL` 和 `DOUBLE PRECISION`)。 + +其中,关键字`INT`是`INTEGER`的同义词,关键字DEC是的同义词。 + +不同关键字的主要区别就是表示的范围或精度不一样。具体如下表: + +| 类型 | 大小 | 范围(有符号) | 范围(无符号) | 用途 | +| :----------: | :--------------------------------------: | :----------------------------------------------------------- | :----------------------------------------------------------- | :-------------- | +| TINYINT | 1 Bytes | (-128,127) | (0,255) | 小整数值 | +| SMALLINT | 2 Bytes | (-32 768,32 767) | (0,65 535) | 大整数值 | +| MEDIUMINT | 3 Bytes | (-8 388 608,8 388 607) | (0,16 777 215) | 大整数值 | +| INT或INTEGER | 4 Bytes | (-2 147 483 648,2 147 483 647) | (0,4 294 967 295) | 大整数值 | +| BIGINT | 8 Bytes | (-9,223,372,036,854,775,808,9 223 372 036 854 775 807) | (0,18 446 744 073 709 551 615) | 极大整数值 | +| FLOAT | 4 Bytes | (-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) | 0,(1.175 494 351 E-38,3.402 823 466 E+38) | 单精度 浮点数值 | +| DOUBLE | 8 Bytes | (-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 双精度 浮点数值 | +| DECIMAL | 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 | 依赖于M和D的值 | 依赖于M和D的值 | 小数值 | + +### (2)日期和时间类型 + +表示时间值的日期和时间类型为`DATETIME`、`DATE`、`TIMESTAMP`、`TIME`和`YEAR`。具体如下表: + +| 类型 | 大小 ( bytes) | 范围 | 格式 | 用途 | +| :-------- | :------------ | :----------------------------------------------------------- | :------------------ | :----------------------- | +| DATE | 3 | 1000-01-01/9999-12-31 | YYYY-MM-DD | 日期值 | +| TIME | 3 | '-838:59:59'/'838:59:59' | HH:MM:SS | 时间值或持续时间 | +| YEAR | 1 | 1901/2155 | YYYY | 年份值 | +| DATETIME | 8 | 1000-01-01 00:00:00/9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS | 混合日期和时间值 | +| TIMESTAMP | 4 | 1970-01-01 00:00:00/2038结束时间是第 2147483647 秒,北京时间 2038-1-19 11:14:07,格林尼治时间 2038年1月19日 凌晨 03:14:07 | YYYYMMDD HHMMSS | 混合日期和时间值,时间戳 | + +### (3)字符串类型 + +字符串类型指`CHAR`、`VARCHAR`、`BINARY`、`VARBINARY`、`BLOB`、`TEXT`、`ENUM`和`SET`。具体如下表: + +| 类型 | 大小 | 用途 | +| :--------- | :-------------------- | :------------------------------ | +| CHAR | 0-255 bytes | 定长字符串 | +| VARCHAR | 0-65535 bytes | 变长字符串 | +| TINYBLOB | 0-255 bytes | 不超过 255 个字符的二进制字符串 | +| TINYTEXT | 0-255 bytes | 短文本字符串 | +| BLOB | 0-65 535 bytes | 二进制形式的长文本数据 | +| TEXT | 0-65 535 bytes | 长文本数据 | +| MEDIUMBLOB | 0-16 777 215 bytes | 二进制形式的中等长度文本数据 | +| MEDIUMTEXT | 0-16 777 215 bytes | 中等长度文本数据 | +| LONGBLOB | 0-4 294 967 295 bytes | 二进制形式的极大文本数据 | +| LONGTEXT | 0-4 294 967 295 bytes | 极大文本数据 | + ++ `char`声明的是定长字符串。若实际中字符串长度不足,则会在末尾使用空格进行填充至声明的长度。 + ++ `varchar`声明的是可变长字符串。存储过程中,只会按照字符串的实际长度来存储,但会多占用一位来存放实际字节的长度。 + + + +# 三、 数据库的基本操作 + +首先,我们来学习在MySQL下如何操作数据库。 + +## 3.1 数据库的创建 + +通过`CREATE`命令,可以创建指定名称的数据库,语法结构如下: + +```mysql +CREATE DATABASE [IF NOT EXISTS] <数据库名称>; +``` + +MySQL 的数据存储区将以目录方式表示 MySQL 数据库,因此数据库名称必须符合操作系统的文件夹命名规则,不能以数字开头,尽量要有实际意义。 + +MySQL下不运行存在两个相同名字的数据库,否则会报错。如果使用`IF NOT EXISTS`(可选项),可以避免此类错误。 + +示例: + +```mysql +-- 创建名为shop的数据库。 +CREATE DATABASE shop; +``` + + + +## 3.2 数据库的查看 + +1. 查看所有存在的数据库 + +```MYSQL +SHOW DATABASES [LIKE '数据库名'];; +``` + +`LIKE`从句是可选项,用于匹配指定的数据库名称。`LIKE` 从句可以部分匹配,也可以完全匹配。 + +示例: + +```mysql +SHOW DATABASES; + +-- 结果如下: ++--------------------+ +| Database | ++--------------------+ +| information_schema | +| mysql | +| performance_schema | +| shop | +| sys | ++--------------------+ +5 rows in set (0.01 sec) +``` + +```mysql +-- %表示任意0个或多个字符,可匹配任意类型和长度的字符。 +SHOW DATABASES LIKE 'S%'; + +-- 结果如下 ++---------------+ +| Database (S%) | ++---------------+ +| shop | +| sys | ++---------------+ +2 rows in set (0.00 sec) +``` + +2. 查看创建的数据库 + +```mysql +SHOW CREATE DATABASE <数据库名>; +``` + +示例: + +```mysql +SHOW CREATE DATABASE shop; + +-- 或者 +SHOW CREATE DATABASE shop \G + +-- 结果如下 +*************************** 1. row *************************** + Database: shop +Create Database: CREATE DATABASE `shop` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */ +1 row in set (0.00 sec) +``` + +`CHARACTER SET utf8mb4`表示编码字符集为`utf8mb4`。 + + + +## 3.3 选择数据库 + +在操作数据库前,必须指定所要操作的数据库。通过`USE`命令,可以切换到对应的数据库下。 + +```mysql +USE <数据库名> +``` + +示例: + +```mysql +-- 切换到数据库shop下。 +USE shop; + +-- 结果如下 +Database changed +``` + + + +## 3.4 删除数据库 + +通过`DROP`命令,可以将相应数据库进行删除。 + +```mysql +DROP DATABASE [IF EXISTS] <数据库名> +``` + +其中,`IF EXISTS`为可选性,用于防止数据库不存在时报错。 + +示例: + +```mysql +DROP DATABASE shop; + +SHOW DATABASES; +``` + +考虑到后面表的操作都是shop数据库下,在实验完`DROP`删除数据库命令后,请从新创建数据库shop并通过`USE`命令切换到该数据库下。 + + + +# 四、表的基本操作 + +表相当于文件,表中的一条记录就相当于文件的一行内容,不同的是,表中的一条记录有对应的标题,称为表的字段。 + +## 4.1 表的创建 + +创建表的语法结构如下: + +```mysql +CREATE TABLE <表名> (<字段1> <数据类型> <该列所需约束>, + <字段2> <数据类型> <该列所需约束>, + <字段3> <数据类型> <该列所需约束>, + <字段4> <数据类型> <该列所需约束>, + . + . + . + <该表的约束1>, <该表的约束2>,……); +``` + +示例: + +```mysql +-- 创建一个名为Product的表 +CREATE TABLE Product( + product_id CHAR(4) NOT NULL, + product_name VARCHAR(100) NOT NULL, + product_type VARCHAR(32) NOT NULL, + sale_price INT, + purchase_price INT, + regist_date DATE, + PRIMARY KEY (product_id) +); +``` + +在第二章中,我们介绍过不同的数据类型: + ++ `CHAR`为定长字符,这里`CHAR`旁边括号里的数字表示该字段最长为多少字符,少于该数字将会使用空格进行填充。 + ++ `VARCHAR`表示变长字符,括号里的数字表示该字段最长为多少字符,存储时只会按照字符的实际长度来存储,但会使用额外的1-2字节来存储值长度。 + + + +简单介绍一下该语句中出现的约束条件,约束条件在后面会详细介绍: + ++ `PRIMARY KEY`:主键,表示该字段对应的内容唯一且不能为空。 ++ `NOT NULL`:在 `NULL` 之前加上了表示否定的` NOT`,表示该字段不能输入空白。 + +通过`SHOW TABLES`命令来查看当前数据库下的所有的表名: + +```mysql +SHOW TABLES; + +-- 结果如下 ++----------------+ +| Tables_in_shop | ++----------------+ +| Product | ++----------------+ +1 rows in set (0.00 sec) +``` + +通过`DESC <表名>`来查看表的结构: + +```mysql +DESC Product; + +-- 结果如下 ++----------------+--------------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++----------------+--------------+------+-----+---------+-------+ +| product_id | char(4) | NO | PRI | NULL | | +| product_name | varchar(100) | NO | | NULL | | +| product_type | varchar(32) | NO | | NULL | | +| sale_price | int | YES | | NULL | | +| purchase_price | int | YES | | NULL | | +| regist_date | date | YES | | NULL | | ++----------------+--------------+------+-----+---------+-------+ +6 rows in set (0.00 sec) +``` + + + +## 4.2 表的删除 + +删除表的语法结构如下: + +```mysql +DROP TABLE <表名>; + +-- 例如:DROP TABLE Product; +``` + +说明:通过`DROP`删除的表示无法恢复的,在删除表的时候请谨慎。 + + + +## 4.3 表的更新 + +通过`ALTER TABLE`语句,我们可以对表字段进行不同的操作,下面通过示例来具体学习用法。 + +示例: + +1. 创建一张名为Student的表 + +```mysql +CREATE TABLE Student( + id INT PRIMARY KEY, + name CHAR(15) +); +``` + + + +```mysql +DESC student; + +-- 结果如下 ++-------+----------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++-------+----------+------+-----+---------+-------+ +| id | int | NO | PRI | NULL | | +| name | char(15) | YES | | NULL | | ++-------+----------+------+-----+---------+-------+ +2 rows in set (0.00 sec) +``` + +2. 更改表名 + + 通过`RENAME`命令,将表名从Student => Students。 + +```mysql +ALTER TABLE Student RENAME Students; +``` + +3. 插入新的字段 + + 通过`ADD`命令,新增字段sex和age。 + +```mysql +-- 不同的字段通过逗号分开 +ALTER TABLE Students ADD sex CHAR(1), ADD age INT; +``` + +​ 其它插入技巧: + +```mysql +-- 通过FIRST在表首插入字段stu_num +ALTER TABLE Students ADD stu_num INT FIRST; + +-- 指定在字段sex后插入字段height +ALTER TABLE Students ADD height INT AFTER sex; +``` + +```mysql +DESC Students; + +-- 结果如下 ++---------+----------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++---------+----------+------+-----+---------+-------+ +| stu_num | int | YES | | NULL | | +| id | int | NO | PRI | NULL | | +| name | char(15) | YES | | NULL | | +| sex | char(1) | YES | | NULL | | +| height | int | YES | | NULL | | +| age | int | YES | | NULL | | ++---------+----------+------+-----+---------+-------+ +6 rows in set (0.00 sec) +``` + +4. 字段的删除 + + 通过`DROP`命令,可以对不在需要的字段进行删除。 + +```mysql +-- 删除字段stu_num +ALTER TABLE Students DROP stu_num; +``` + +5. 字段的修改 + + 通过`MODIFY`修改字段的数据类型。 + +```mysql +-- 修改字段age的数据类型 +ALTER TABLE Students MODIFY age CHAR(3); +``` + +​ 通过`CHANGE`命令,修改字段名或类型 + +```mysql +-- 修改字段name为stu_name,不修改数据类型 +ALTER TABLE Students CHANGE name stu_name CHAR(15); + +-- 修改字段sex为stu_sex,数据类型修改为int +ALTER TABLE Students CHANGE sex stu_sex INT; +``` + +```mysql +DESC Students; + +-- 结果如下 ++----------+----------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++----------+----------+------+-----+---------+-------+ +| id | int | NO | PRI | NULL | | +| stu_name | char(20) | YES | | NULL | | +| stu_sex | int | YES | | NULL | | +| height | int | YES | | NULL | | +| age | char(3) | YES | | NULL | | ++----------+----------+------+-----+---------+-------+ +5 rows in set (0.00 sec) +``` + + + +## 4.4 表的查询 + +通过`SELECT`语句,可以从表中取出所要查看的字段的内容: + +```mysql +SELECT <字段名>, …… + FROM <表名>; +``` + +如要直接查询表的全部字段: + +```mysql +SELECT * + FROM <表名>; +``` + +其中,**星号(*)**代表全部字段的意思。 + +示例: + +1. 建表并插入数据 + + 在MySQL中,我们通过`INSERT`语句往表中插入数据,该语句在后面会详细介绍,该小节的重点是学会使用`SELECT`。 + +```mysql +-- 向Product表中插入数据 +INSERT INTO Product VALUES + ('0001', 'T恤衫', '衣服', 1000, 500, '2009-09-20'), + ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11'), + ('0003', '运动T恤', '衣服', 4000, 2800, NULL), + ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'), + ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'), + ('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20'), + ('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28'), + ('0008', '圆珠笔', '办公用品', 100, NULL,'2009-11-11') + ; +``` + +2. 查看表的内容 + +```mysql +-- 查看表的全部内容 +SELECT * + FROM Product; + +-- 结果如下 ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | +| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | +| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | +| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | +| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | +| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | +| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | +| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | ++------------+--------------+--------------+------------+----------------+-------------+ +8 rows in set (0.00 sec) +``` + +```mysql +-- 查看部分字段包含的内容 +SELECT + product_id, + product_name, + sale_price + FROM Product; + +-- 结果如下 ++------------+--------------+------------+ +| product_id | product_name | sale_price | ++------------+--------------+------------+ +| 0001 | T恤衫 | 1000 | +| 0002 | 打孔器 | 500 | +| 0003 | 运动T恤 | 4000 | +| 0004 | 菜刀 | 3000 | +| 0005 | 高压锅 | 6800 | +| 0006 | 叉子 | 500 | +| 0007 | 擦菜板 | 880 | +| 0008 | 圆珠笔 | 100 | ++------------+--------------+------------+ +8 rows in set (0.00 sec) +``` + +3. 对查看的字段从新命名 + + 通过`AS`语句对展示的字段另起别名,这不会修改表内字段的名字。 + +```mysql +SELECT + product_id AS ID, + product_type AS TYPE + FROM Product; + +-- 结果如下 ++------+--------------+ +| ID | TYPE | ++------+--------------+ +| 0001 | 衣服 | +| 0002 | 办公用品 | +| 0003 | 衣服 | +| 0004 | 厨房用具 | +| 0005 | 厨房用具 | +| 0006 | 厨房用具 | +| 0007 | 厨房用具 | +| 0008 | 办公用品 | ++------+--------------+ +8 rows in set (0.00 sec) +``` + +​ 设定汉语别名时需要使用双引号(")括起来,英文字符则不需要。 + +```Mysql +SELECT + product_id AS "产品编号", + product_type AS "产品类型" + FROM Product; +``` + +4. 常数的查询 + + `SELECT`子句中,除了可以写字段外,还可以写常数。 + +```mysql +SELECT + '商品' AS string, + '2009-05-24' AS date, + product_id, + product_name + FROM Product; + +-- 结果如下 ++--------+------------+------------+--------------+ +| string | date | product_id | product_name | ++--------+------------+------------+--------------+ +| 商品 | 2009-05-24 | 0001 | T恤衫 | +| 商品 | 2009-05-24 | 0002 | 打孔器 | +| 商品 | 2009-05-24 | 0003 | 运动T恤 | +| 商品 | 2009-05-24 | 0004 | 菜刀 | +| 商品 | 2009-05-24 | 0005 | 高压锅 | +| 商品 | 2009-05-24 | 0006 | 叉子 | +| 商品 | 2009-05-24 | 0007 | 擦菜板 | +| 商品 | 2009-05-24 | 0008 | 圆珠笔 | ++--------+------------+------------+--------------+ +8 rows in set (0.00 sec) +``` + +5. 删除重复行 + + 在`SELECT`语句中使用`DISTINCT`可以去除重复行。 + +```mysql +SELECT + DISTINCT regist_date + FROM Product; + +-- 结果如下 ++-------------+ +| regist_date | ++-------------+ +| 2009-09-20 | +| 2009-09-11 | +| NULL | +| 2009-01-15 | +| 2008-04-28 | +| 2009-11-11 | ++-------------+ +6 rows in set (0.01 sec) +``` + +​ 在使用`DISTINCT` 时,`NULL `也被视为一类数据。`NULL `存在于多行中时,会被合并为一条`NULL `数据。 + +​ 还可以通过组合使用,来去除列组合重复的数据。`DISTINCT `关键字只能用在第一个列名之前。 + +```mysql +SELECT + DISTINCT product_type, regist_date + FROM Product; + +-- 结果如下,列出了所有的组合 ++--------------+-------------+ +| product_type | regist_date | ++--------------+-------------+ +| 衣服 | 2009-09-20 | +| 办公用品 | 2009-09-11 | +| 衣服 | NULL | +| 厨房用具 | 2009-09-20 | +| 厨房用具 | 2009-01-15 | +| 厨房用具 | 2008-04-28 | +| 办公用品 | 2009-11-11 | ++--------------+-------------+ +7 rows in set (0.00 sec) +``` + +6. 指定查询条件 + + 首先通过`WHERE` 子句查询出符合指定条件的记录,然后再选取出` SELECT `语句指定的列,语法结构如下: + +```mysql +SELECT <字段名>, …… + FROM <表名> + WHERE <条件表达式>; +``` + +​ 示例: + +```mysql +SELECT product_name + FROM Product + WHERE product_type = '衣服'; + +-- 结果如下 ++--------------+ +| product_name | ++--------------+ +| T恤衫 | +| 运动T恤 | ++--------------+ +2 rows in set (0.01 sec) +``` + +注意,`WHERE`子句要紧跟在`FROM`子句之后。 + + + +## 4.5 表的复制 + +表的复制可以将表结构与表中的数据全部复制,或者只复制表的结构。 + +```mysql +-- 将整个表复制过来 +CREATE TABLE Product_COPY1 + SELECT * FROM Product; + +SELECT * FROM Product_COPY1; + +-- 结果如下 ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | +| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | +| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | +| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | +| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | +| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | +| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | +| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | ++------------+--------------+--------------+------------+----------------+-------------+ +8 rows in set (0.00 sec) +``` + +```mysql +-- 通过LIKE复制表结构 +CREATE TABLE Product_COPY2 + LIKe Product; + +SELECT * FROM Product_COPY2; + +-- 结果如下 +Empty set (0.00 sec) -- 表为空的 + +DESC Product_COPY2; + +-- 结果如下 +-- 表结构已复制过来 ++----------------+--------------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++----------------+--------------+------+-----+---------+-------+ +| product_id | char(4) | NO | PRI | NULL | | +| product_name | varchar(100) | NO | | NULL | | +| product_type | varchar(32) | NO | | NULL | | +| sale_price | int | YES | | 0 | | +| purchase_price | int | YES | | NULL | | +| regist_date | date | YES | | NULL | | ++----------------+--------------+------+-----+---------+-------+ +6 rows in set (0.01 sec) +``` + + + +# 五、运算符 + +## 5.1 算术运算符 + +我们可以在`SELECT`语句中使用计算表达式: + +```mysql +SELECT + product_name, + sale_price, + sale_price * 2 AS "sale_price_x2" + FROM Product; + +-- 结果如下 ++--------------+------------+---------------+ +| product_name | sale_price | sale_price_x2 | ++--------------+------------+---------------+ +| T恤衫 | 1000 | 2000 | +| 打孔器 | 500 | 1000 | +| 运动T恤 | 4000 | 8000 | +| 菜刀 | 3000 | 6000 | +| 高压锅 | 6800 | 13600 | +| 叉子 | 500 | 1000 | +| 擦菜板 | 880 | 1760 | +| 圆珠笔 | 100 | 200 | ++--------------+------------+---------------+ +8 rows in set (0.00 sec) +``` + ++ 四则运算所使用的运算符**(+、-、*、/)**称为算术运算符。 + ++ 在运算表达式中,也可以使用**()**,括号中的运算表达式优先级会得到提升。 + ++ **NULL**的计算结果,仍然还是**NULL**。 + + + +## 5.2 比较运算符 + +在 `WHERE` 子句中通过使用比较运算符可以组合出各种各样的条件表达式。 + +```mysql +SELECT product_name, product_type + FROM Product + WHERE sale_price = 500; +``` + +常见比较运算符如下表: + +| 运算符 | 含义 | +| ------ | -------- | +| = | 相等 | +| <> | 不相等 | +| \>= | 大于等于 | +| \> | 大于 | +| <= | 小于等于 | +| < | 小于 | + ++ 不能对**NULL**使用任何比较运算符,只能通过`IS NULL`语句来判断: + +```mysql +SELECT + product_name, + purchase_price + FROM Product + WHERE purchase_price IS NULL; +``` + +​ 希望选取不是 NULL 的记录时,需要使用`IS NOT NULL`运算符。 + ++ 对字符串使用比较符 + +​ MySQL中字符串的排序与数字不同,典型的规则就是按照字典顺序进行比较,也就是像姓名那样,按照条目在字典中出现的顺序来进行排序。例如: + +```mysql +'1' < '10' < '11' < '2' < '222' < '3' +``` + + + +## 5.3 逻辑运算符 + +1. 使用`NOT`否认某一条件: + +```mysql +SELECT + product_name, + product_type, + sale_price + FROM Product + WHERE NOT sale_price >= 1000; +``` + +2. `AND`运算符合`OR`运算符 + +```mysql +SELECT product_type, sale_price + FROM Product + WHERE product_type = '厨房用具' + AND sale_price >= 3000; + +-- 结果如下 ++--------------+------------+ +| product_type | sale_price | ++--------------+------------+ +| 厨房用具 | 3000 | +| 厨房用具 | 6800 | ++--------------+------------+ +2 rows in set (0.00 sec) +``` + +```mysql +SELECT product_type, sale_price + FROM Product + WHERE product_type = '厨房用具' + OR sale_price >= 3000; + +-- 结果如下 ++--------------+------------+ +| product_type | sale_price | ++--------------+------------+ +| 衣服 | 4000 | +| 厨房用具 | 3000 | +| 厨房用具 | 6800 | +| 厨房用具 | 500 | +| 厨房用具 | 880 | ++--------------+------------+ +5 rows in set (0.00 sec) +``` + +3. 逻辑运算符和真值 + ++ 符**NOT**、**AND** 和 **OR** 称为逻辑运算符; ++ 真值就是值为**真(TRUE)**或**假 (FALSE)**; + ++ 在查询**NULL**时,SQL中存在第三种真值,**不确定(UNKNOWN)**,**NULL**和任何值做逻辑运算结果都是不确定; ++ 考虑 **NULL** 时的条件判断也会变得异常复杂,因此尽量给字段加上**NOT NULL**的约束。 + + + +# 六、分组查询 + +## 6.1 聚合函数 + +通过 SQL 对数据进行某种操作或计算时需要使用函数。 + ++ `COUNT`:计算表中的记录数(行数) + ++ `SUM`: 计算表中数值列中数据的合计值 + ++ `AVG`: 计算表中数值列中数据的平均值 + ++ `MAX`: 求出表中任意列中数据的最大值 + ++ `MIN`: 求出表中任意列中数据的最小值 + +示例: + +```mysql +-- 计算全部数据的行数 +SELECT COUNT(*) FROM Product; + +-- 结果如下 ++----------+ +| COUNT(*) | ++----------+ +| 8 | ++----------+ +1 row in set (0.00 sec) +``` + +**注意点1**:除了`COUNT`可以将`*`作为参数,其它的函数均不可以。 + +```mysql +-- 计算最高的销售价格 +SELECT MAX(sale_price) FROM Product; + +-- 结果如下 ++-----------------+ +| MAX(sale_price) | ++-----------------+ +| 680000 | ++-----------------+ +1 row in set (0.00 sec) +``` + +**注意点2:**当将字段名作为参数传递给函数时,只会计算不包含`NULL`的行。 + +示例: + +```mysql +-- purchase_price字段是包含NULL值的 +SELECT purchase_price FROM Product; + +-- 结果如下 ++----------------+ +| purchase_price | ++----------------+ +| 500 | +| 320 | +| 2800 | +| 700 | +| 1250 | +| NULL | +| 198 | +| NULL | ++----------------+ +8 rows in set (0.00 sec) +``` + +以*为参数传递给`COUNT`函数 + +```mysql +SELECT COUNT(*) FROM Product; + +-- 结果如下 ++----------+ +| COUNT(*) | ++----------+ +| 8 | ++----------+ +1 row in set (0.00 sec) +``` + +以purchase_price为参数传递给`COUNT`函数 + +```mysql +SELECT COUNT(purchase_price) FROM Product; + +-- 结果如下 ++-----------------------+ +| COUNT(purchase_price) | ++-----------------------+ +| 6 | ++-----------------------+ +1 row in set (0.00 sec) +``` + +可以看到结果并不一样,函数忽略了值为**NULL**的行。 + +`SUM`,`AVG`函数时也一样,计算时会直接忽略,**并不会当做0来处理!**特别注意`AVG`函数,计算时分母也不会算上`NULL`行。 + +**注意点3**:`MAX/MIN`函数几乎适用于所有数据类型的列,包括字符和日期。`SUM/AVG`函数只适用于数值类型的列。 + +**注意点4**:在聚合函数删除重复值 + +```mysql +SELECT COUNT(DISTINCT product_type) + FROM Product; + +-- 结果如下 ++------------------------------+ +| COUNT(DISTINCT product_type) | ++------------------------------+ +| 3 | ++------------------------------+ +1 row in set (0.01 sec) +``` + +`DISTINCT`必须写在括号中。这是因为必须要在计算行数之前删除 product_type 字段中的重复数据。 + + + +## 6.2 对表分组 + +如果对Python的Pandas熟悉,那么大家应该很了解`groupby`函数,可以根据指定的列名,对表进行分组。在MySQL中,也存在同样作用的函数,即`GROUP BY`。 + +语法结构如下: + +```mysql +SELECT <列名1>, <列名2>, <列名3>, …… + FROM <表名> + GROUP BY <列名1>, <列名2>, <列名3>, ……; +``` + +示例: + +```mysql +SELECT product_type, COUNT(*) + FROM Product + GROUP BY product_type; + +-- 结果如下 ++--------------+----------+ +| product_type | COUNT(*) | ++--------------+----------+ +| 衣服 | 2 | +| 办公用品 | 2 | +| 厨房用具 | 4 | ++--------------+----------+ +3 rows in set (0.01 sec) +``` + +1. 在该语句中,我们首先通过`GROUP BY`函数对指定的字段product_type进行分组。分组时,product_type字段中具有相同值的行会汇聚到同一组。 + +2. 最后通过`COUNT`函数,统计不同分组的包含的行数。 + +简单来理解: + ++ 例如做操时,老师将不同身高的同学进行分组,相同身高的同学会被分到同一组,分组后我们又统计了每个小组的学生数。 + ++ 将这里的同学可以理解为表中的一行数据,身高理解为表的某一字段。 ++ 分组操作就是`GROUP BY`,`GROUP BY`后面接的字段等价于按照身高分组,统计学生数就等价于在`SELECT`后用了`COUNT(*)`函数。 + +注意:`GROUP BY `子句的位置一定要写在`FROM` 语句之后(如果有 `WHERE` 子句的话需要写在 `WHERE` 子句之后) + +``` +1. SELECT → 2. FROM → 3. WHERE → 4. GROUP BY +``` + +当被聚合的键中,包含`NULL`时,在结果中会以“不确定”行(空行)的形式表现出来,也就是字段中为`NULL`的数据会被聚合为一组。 + +## 6.3 使用WHERE语句 + +在对表进行分组之前,也可以是先使用`WHERE`对表进行条件过滤,然后再进行分组处理。语法结构如下: + +```mysql +SELECT <列名1>, <列名2>, <列名3>, …… + FROM <表名> + WHERE + GROUP BY <列名1>, <列名2>, <列名3>, ……; +``` + +示例: + +```mysql +-- WHERE语句先将表中类型为衣服的行筛选出来 +-- 然后再按照purchase_price来进行分组 +SELECT purchase_price, COUNT(*) + FROM Product + WHERE product_type = '衣服' + GROUP BY purchase_price; + +-- 结果如下 ++----------------+----------+ +| purchase_price | COUNT(*) | ++----------------+----------+ +| 500 | 1 | +| 2800 | 1 | ++----------------+----------+ +2 rows in set (0.01 sec) +``` + +该语法实际的执行顺序为: + +``` +FROM → WHERE → GROUP BY → SELECT +``` + ++ 使用`GROUP BY`子句时,`SELECT`子句中不能出现聚合键之外的字段名。即,若`GROUP BY`选中purchase_price字段进行分组,则在`SELECT`语句中只能选中purchase_price字段,其它字段如product_id等均不行。 ++ `WHERE`语句中,不可以使用聚合函数。`WHERE`子句只能指定记录(行)的条件,而不能用来指定组的条件。即`WHERE MAX(purchase_price) > 1000`这样的语句是非法的。 + + + +## 6.4 为聚合结果指定条件 + +前面提到了`WHERE`语句中不能使用聚合函数,但是实际操作时需要通过聚合函数来进行过滤怎么办呢?这就要用到`HAVING`语句了。语法结构如下: + +```mysql +SELECT <列名1>, <列名2>, <列名3>, …… + FROM <表名> + GROUP BY <列名1>, <列名2>, <列名3>, …… +HAVING <分组结果对应的条件> +``` + +在`HAVING`的子句中能够使用的 3 种要素如下所示: + +● 常数 + +● 聚合函数 + +● `GROUP BY`子句中指定的字段名(即聚合键) + +示例: + +```mysql +-- 不使用HAVING语句 +SELECT product_type, AVG(sale_price) + FROM Product + GROUP BY product_type; + +-- 结果如下 ++--------------+-----------------+ +| product_type | AVG(sale_price) | ++--------------+-----------------+ +| 衣服 | 2500.0000 | +| 办公用品 | 300.0000 | +| 厨房用具 | 279500.0000 | ++--------------+-----------------+ +3 rows in set (0.00 sec) +``` + +```mysql +-- 使用HAVING语句 +-- 通过HAVING语句将销售平均价格大于等于2500的组给保留了 +SELECT product_type, AVG(sale_price) + FROM Product + GROUP BY product_type +HAVING AVG(sale_price) >= 2500; + +-- 结果如下 ++--------------+-----------------+ +| product_type | AVG(sale_price) | ++--------------+-----------------+ +| 衣服 | 2500.0000 | +| 厨房用具 | 279500.0000 | ++--------------+-----------------+ +2 rows in set (0.00 sec) +``` + +可以看到使用`HAVING`语句后,输出的结果有所变化。大致流程如下: + ++ 首先,`FROM`语句会选中表Product; ++ 然后,`GROUP BY`语句会选中字段product_type进行分组; ++ 之后,通过`HAVING`语句将销售平均价格大于等于2500的组保留下来; ++ 最后,通过`SELECT`语句将保留下的组的产品类型和平均价格显示出来; + + + +如果是对**表的行**进行条件指定,`WHERE`和`HAVING`都可以生效。 + +```mysql +-- 下面两条语句执行结果一致 +SELECT product_type, COUNT(*) + FROM Product + GROUP BY product_type + HAVING product_type = '衣服'; + +SELECT product_type, COUNT(*) + FROM Product + WHERE product_type = '衣服' + GROUP BY product_type; + +-- 结果如下 ++--------------+----------+ +| product_type | COUNT(*) | ++--------------+----------+ +| 衣服 | 2 | ++--------------+----------+ +1 row in set (0.01 sec) +``` + +但是,一般而言如果是对表的行进行条件指定,最好还是使用`WHERE`语句,因为`WHERE`的执行速度更快。 + + + +## 6.5 对表的查询结果进行排序 + +如果希望对表的查询结果根据某指定的字段进行排序,可以使用`ORDER BY`语句。语法结构如下: + +```mysql +SELECT <列名1>, <列名2>, <列名3>, …… + FROM <表名> + ORDER BY <排序基准列1>, <排序基准列2>, …… +``` + +示例: + +```mysql +SELECT product_id, product_name, sale_price, purchase_price + FROM Product; + +-- 结果如下 ++------------+--------------+------------+----------------+ +| product_id | product_name | sale_price | purchase_price | ++------------+--------------+------------+----------------+ +| 0001 | T恤衫 | 1000 | 500 | +| 0002 | 打孔器 | 500 | 320 | +| 0003 | 运动T恤 | 4000 | 2800 | +| 0004 | 菜刀 | 300000 | 700 | +| 0005 | 高压锅 | 680000 | 1250 | +| 0006 | 叉子 | 50000 | NULL | +| 0007 | 擦菜板 | 88000 | 198 | +| 0008 | 圆珠笔 | 100 | NULL | ++------------+--------------+------------+----------------+ +8 rows in set (0.01 sec) +``` + +```mysql +-- 根据字段sale_price的值进行排序 +SELECT product_id, product_name, sale_price, purchase_price + FROM Product +ORDER BY sale_price; + +-- 结果如下 ++------------+--------------+------------+----------------+ +| product_id | product_name | sale_price | purchase_price | ++------------+--------------+------------+----------------+ +| 0008 | 圆珠笔 | 100 | NULL | +| 0002 | 打孔器 | 500 | 320 | +| 0001 | T恤衫 | 1000 | 500 | +| 0003 | 运动T恤 | 4000 | 2800 | +| 0006 | 叉子 | 50000 | NULL | +| 0007 | 擦菜板 | 88000 | 198 | +| 0004 | 菜刀 | 300000 | 700 | +| 0005 | 高压锅 | 680000 | 1250 | ++------------+--------------+------------+----------------+ +8 rows in set (0.00 sec) +``` + +可以看到`ORDER BY`默认是按照升序的方式进行排序的,正式的书写方式应该是在字段后加上关键字`ASC`,即`ORDER BY sale_price ASC`。 + +如果我们希望按照降序的方式,可以通过`DESC`关键词进行指定。 + +```mysql +SELECT product_id, product_name, sale_price, purchase_price + FROM Product +ORDER BY sale_price DESC; + +-- 结果如下 ++------------+--------------+------------+----------------+ +| product_id | product_name | sale_price | purchase_price | ++------------+--------------+------------+----------------+ +| 0005 | 高压锅 | 680000 | 1250 | +| 0004 | 菜刀 | 300000 | 700 | +| 0007 | 擦菜板 | 88000 | 198 | +| 0006 | 叉子 | 50000 | NULL | +| 0003 | 运动T恤 | 4000 | 2800 | +| 0001 | T恤衫 | 1000 | 500 | +| 0002 | 打孔器 | 500 | 320 | +| 0008 | 圆珠笔 | 100 | NULL | ++------------+--------------+------------+----------------+ +8 rows in set (0.00 sec) +``` + +前面展示了指定一个字段来对表进行排序,实际上我们可以指定多个字段来进行排序。 + +示例: + +```mysql +SELECT regist_date, product_id, sale_price, purchase_price + FROM Product +ORDER BY regist_date, product_id; + +-- 结果如下 ++-------------+------------+------------+----------------+ +| regist_date | product_id | sale_price | purchase_price | ++-------------+------------+------------+----------------+ +| 2009-10-10 | 0002 | 500 | 320 | +| 2009-10-10 | 0003 | 4000 | 2800 | +| 2009-10-10 | 0004 | 300000 | 700 | +| 2009-10-10 | 0005 | 680000 | 1250 | +| 2009-10-10 | 0006 | 50000 | NULL | +| 2009-10-10 | 0007 | 88000 | 198 | +| 2009-10-10 | 0008 | 100 | NULL | +| 2021-10-30 | 0001 | 1000 | 500 | ++-------------+------------+------------+----------------+ +``` + +可以看到先按照`regist_date`的大小进行排序,在字段`regist_date`中具有相同的值的行,接着会按照`product_id`进行排序。 + +使用含有 NULL 的列作为排序键时,NULL 会在结果的开头或末尾汇总显示。 + +在`ORDER BY`子句中可以使用`SELECT`子句中定义的别名。 + +```mysql +-- 将product_id命名为ID,然后按照ID进行排序 +SELECT product_id as ID, product_name, sale_price, purchase_price + FROM Product +ORDER BY ID; + +-- 结果如下 ++------+--------------+------------+----------------+ +| ID | product_name | sale_price | purchase_price | ++------+--------------+------------+----------------+ +| 0001 | T恤衫 | 1000 | 500 | +| 0002 | 打孔器 | 500 | 320 | +| 0003 | 运动T恤 | 4000 | 2800 | +| 0004 | 菜刀 | 300000 | 700 | +| 0005 | 高压锅 | 680000 | 1250 | +| 0006 | 叉子 | 50000 | NULL | +| 0007 | 擦菜板 | 88000 | 198 | +| 0008 | 圆珠笔 | 100 | NULL | ++------+--------------+------------+----------------+ +8 rows in set (0.00 sec) +``` + +为什么`ORDER BY`中可以使用`SELECT`定义的别名呢? + +这是因为在MySQL中,`ORDER BY `的执行次序在`SELECT`之后。 + + + +# 七、数据的插入及更新 + +## 7.1 数据的插入 + +通过命令`INSERT`,可以向表中插入数据: + +```mysql +-- 往表中插入一行数据 +INSERT INTO <表名> (字段1, 字段2, 字段3, ……) VALUES (值1, 值2, 值3, ……); + +-- 往表中插入多行数据 +INSERT INTO <表名> (字段1, 字段2, 字段3, ……) VALUES + (值1, 值2, 值3, ……), + (值1, 值2, 值3, ……), + ... + ; +``` + +示例: + +1. 创建表并插入数据 + +```mysql +-- 创建表 +CREATE TABLE ProductIns +(product_id CHAR(4) NOT NULL, + product_name VARCHAR(100) NOT NULL, + product_type VARCHAR(32) NOT NULL, + sale_price INTEGER DEFAULT 0, -- DEFAULT 0:表示将字段sale_price的默认值设为0 + purchase_price INT , + regist_date DATE , + PRIMARY KEY (product_id)); + +-- 通过单行方式插入 +INSERT INTO + ProductIns(product_id, product_name, product_type, sale_price, purchase_price, regist_date) + VALUES ('0001', '打孔器', '办公用品', 500, 320, '2009-09-11'); + +-- 当对表插入全字段时,可以省略表后的字段清单 +INSERT INTO ProductIns VALUES('0002', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'); + +-- 通过多行方式插入 +INSERT INTO ProductIns VALUES + ('0003', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'), + ('0004', '订书机', '办公用品', 100, 50, '2009-09-11'), + ('0005', '裙子', '衣服', 4100, 3200, '2009-01-23'), + ('0006', '运动T恤', '衣服', 4000, 2800, NULL), + ('0007', '牙刷', '日用品', 20, 10, '2010-03-22'); +``` + +```mysql +SELECT * FROM ProductIns; + +-- 结果如下 ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | +| 0002 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | +| 0003 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | +| 0004 | 订书机 | 办公用品 | 100 | 50 | 2009-09-11 | +| 0005 | 裙子 | 衣服 | 4100 | 3200 | 2009-01-23 | +| 0006 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | +| 0007 | 牙刷 | 日用品 | 20 | 10 | 2010-03-22 | ++------------+--------------+--------------+------------+----------------+-------------+ +7 rows in set (0.00 sec) +``` + +2. 插入NULL + + `INSERT `语句中想给某一列赋予**NULL**值时,可以直接在` VALUES`子句的值清单中写入**NULL**。 + +```mysql +INSERT INTO ProductIns VALUES ('0008', '叉子', '厨房用具', 500, NULL, '2009-09-20'); +``` + +3. 插入默认值 + + 在前面我们创建表时,字段sale_price包含了一条约束条件,默认为0。我们在插入数据时,可以直接用`DEFAULT`对该字段赋值。前提是,该字段被指定了默认值。 + +```mysql +-- 通过显式方法设定默认值 +INSERT INTO + ProductIns (product_id, product_name, product_type, sale_price, purchase_price, regist_date) + VALUES ('0009', '擦菜板', '厨房用具', DEFAULT, 790, '2009-04-28'); + +-- 通过隐式方法插入默认值 +INSERT INTO + ProductIns (product_id, product_name, product_type, purchase_price, regist_date) + VALUES ('0010', '擦菜板', '厨房用具', 790, '2009-04-28'); +``` + + + +## 7.2 数据的删除 + +通过`DROP TABLE`或者`DELETE`语句,可以对表进行删除,但二者存在一定的区别。 + ++ `DROP TABLE` 语句可以将表完全删除。 ++ `DELETE` 语句会留下表结构,而删除表中的全部数据。 + +无论通过哪种方式删除,数据都是难以恢复的。 + +1. 通过`DROP`进行删除 + + 语法结构为: + +```mysql +DROP <表名>; +``` + +2. 通过`DELETE`进行删除 + + 语法结构如下,记得要加`FROM`: + +```mysql +DELETE FROM <表名>; +``` + +​ 同时,也可以通过`WHERE`语句来指定删除的条件: + +```mysql +DELETE FROM <表名> + WHERE <条件>; +``` + +​ 需要注意的是,`DELETE`语句的删除对象并不是表或者列,而是记录(行)。 + +示例: + +```mysql +SELECT * FROM Product; + +-- 结果如下 +mysql> SELECT * FROM Product; ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | +| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | +| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | +| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | +| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | +| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | +| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | +| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | ++------------+--------------+--------------+------------+----------------+-------------+ +8 rows in set (0.00 sec) + +-- 删除销售价格大于等于4000的行 +DELETE FROM Product + WHERE sale_price >= 4000; + +-- 结果如下 +mysql> SELECT * FROM Product; ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | +| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | +| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | +| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | +| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | +| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | ++------------+--------------+--------------+------------+----------------+-------------+ +6 rows in set (0.00 sec) +``` + +3. 通过`TRUNCATE`进行删除 + + 在MySQL中,还存在一种删除表的方式,就是利用`TRUNCATE`语句。它的功能和`DROP`类似,但是不能通过`WHERE`指定条件,优点是速度比`DROP`快得多。 + +```mysql +TRUNCATE Product; + +-- 结果如下 +mysql> SELECT * FROM Product; +Empty set (0.00 sec) + +mysql> DESC Product; ++----------------+--------------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++----------------+--------------+------+-----+---------+-------+ +| product_id | char(4) | NO | PRI | NULL | | +| product_name | varchar(100) | NO | | NULL | | +| product_type | varchar(32) | NO | | NULL | | +| sale_price | int | YES | | NULL | | +| purchase_price | int | YES | | NULL | | +| regist_date | date | YES | | NULL | | ++----------------+--------------+------+-----+---------+-------+ +6 rows in set (0.00 sec) +``` + + + +## 7.3 数据的更新 + +当我们使用`INSERT`语句插入错误的数据后,若我们不想删除后从新插入,那就要使用到`UPDATE`语句。 + +1. 基本用法 + + `UPDATE`的语法结构如下: + +```mysql +UPDATE <表名> + SET <字段名> = <表达式>; +``` + +​ 示例: + +```mysql +-- 由于前面演示删除语句时,表Product的内容已清空 +-- 所以,这里从新进行数据插入 +INSERT INTO Product VALUES + ('0001', 'T恤衫', '衣服', 1000, 500, '2009-09-20'), + ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11'), + ('0003', '运动T恤', '衣服', 4000, 2800, NULL), + ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'), + ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'), + ('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20'), + ('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28'), + ('0008', '圆珠笔', '办公用品', 100, NULL,'2009-11-11') + ; + +-- 修改表中所有行regist_date的值 +UPDATE Product + SET regist_date = '2009-10-10'; + +-- 结果如下 +mysql> SELECT * FROM Product; ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-10-10 | +| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-10-10 | +| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | 2009-10-10 | +| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-10-10 | +| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-10-10 | +| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-10-10 | +| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2009-10-10 | +| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-10-10 | ++------------+--------------+--------------+------------+----------------+-------------+ +8 rows in set (0.00 sec) +``` + +2. 指定条件 + +```mysql +UPDATE <表名> + SET <列名> = <表达式> + WHERE <条件>; +``` + +​ 示例: + +```mysql +UPDATE Product + SET regist_date = '2021-10-30' + WHERE product_id = '0001'; +``` + +​ 注意,你也可是使用**NULL**对表进行更新,不过更新的字段必须满足没有**主键**和**NOT NULL**的约束条件。 + +3. 多列更新 + + 多列更新只需要用逗号(,)连接更改的字段即可。 + +```mysql +UPDATE Product + SET + sale_price = sale_price * 10, + purchase_price = purchase_price / 2 + WHERE product_type = '厨房用具'; +``` + + + +# 八、Pymysql的使用 + +在正式介绍`pymysql`的用法之前,我们先思考一件事,我们希望借助`pymysql`完成什么事情? + +之前,我们在命令行下,通过输入SQL语句来完成对数据库和表的增删改查。那么,我们也希望能够在Python下能够完成同样的操作,并且能够返回相应的反馈。具体任务包括: + +1. 登陆并连接到MySQL下的用户; +2. 切换到相应的数据库下; +3. 完成对表的增删改查; + +接下来的内容将围绕这3部分来介绍。 + + + +## 8.1 安装pymysql + +通过`pip`,我们可以完成对`pymysql`的安装: + +```bash +python3 -m pip install PyMySQL +``` + + + +## 8.2 连接数据库 + +如果希望在Python中操作MySQL数据库,那么首先就要登陆到MySQL下的用户。 + +我们通过创建库pymysql下的类`connect`的一个实例来登陆到数据库。 + +示例: + +```python +import pymysql + +# 这里登陆到我之前创建的admin账户 +db = pymysql.connect( + host='localhost', + user='admin', + password='mysql123', + database='shop', + charset='utf8mb4', + cursorclass=pymysql.cursors.DictCursor +) +``` + +参数解释: + ++ `host`:数据库服务器地址,默认`localhost`; ++ `user`:所要登陆的用户名; ++ `password`:用户的登录密码; ++ `database`:所要连接的数据库库名; ++ `charset`:使用的字符类型; ++ `cursorclass`:定义游标使用的类型,通过指定游标使用的类型,在返回输出的结果时将按照指定的类型进行返回。例如,这里设置为字典游标。 + + + +## 8.3 创建游标 + +关于游标,可以理解为在命令行中的光标。在命令行中,我们是在光标处键入语句的。这里游标的起到类似作用。 + +```python +# 创建游标 +cursor = db.cursor() +``` + +实际上,除了在初始化`connect`的实例时指定游标类型,我们在初始化游标时也可以指定游标类型,默认为元组类型。 + +```python +cursor = db.cursor(cursor=pymysql.cursors.DictCursor) +``` + +`cursors`共支持四类游标: + ++ `Cursor`: 默认,元组类型 + ++ `DictCursor`: 字典类型 + ++ `SSCursor`: 无缓冲元组类型 + ++ `SSDictCursor`: 无缓冲字典类型 + + + +## 8.4 类方法 + +初始化完类`connect`和`cursor`的实例后,我们先来了解一下这两个类下包含的方法。了解这些方法有利于我们后面在python下操作mysql: + ++ `connect`下的类方法: + + `close()`:在完成操作后,需要关闭与数据库之间的连接; + + `commit()`:如果执行语句中发生了数据更改,需要提交更改到稳定的存储器; + + `cursor(cursor=None)`:创建一个游标,前面我们在初始化`connect`类是指定了游标类型,通过`cursor`初始化游标时,也可以进行游标类型指定; + + `rollback()`:事务回滚; + ++ `pymysql.cursors`下的类方法: + + `close()`:结束时,关闭游标; + + `execute()`:通过游标执行语句; + + `executemany()`:通过游标执行多条语句; + + `fetchone()`:获取单条数据; + + `fetchmany(size=None)`:获取size条数据; + + `fetchall()`:获取单条数据; + + `scroll(value, mode)`:数据的查询操作都是基于游标,可以通过`scroll`控制游标的位置。 + + `mode=absolute`:绝对位置移动,控制游标位置到上一次查询的第`value`条数据,最小值为`0`; + + `mode=relative`:相对位置移动,基于当前位置,跳过`value`条数据; + +更详细的资料,可参考官方的API或者Github: + +[pymysql github]: https://github.com/PyMySQL/PyMySQL + +[pymysql document]: https://pymysql.readthedocs.io/en/latest/modules/index.html# + + + +## 8.5 实战 + ++ 示例1: + +  在这个示例中,我们将做两件事情:创建表和插入数据。 + +```python +import pymysql + +# 以admin身份连接到数据库shop +connection = pymysql.connect( + host='localhost', + user='admin', + password='mysql123', + database='shop', + charset='utf8mb4', +) + +# 创建游标 +cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) + +# 1. 创建了一个表 +sql = """ +CREATE TABLE Employee( + id INT PRIMARY KEY, + name CHAR(15) NOT NULL + ) + """ + +# 提交执行 +cursor.execute(sql) + +# 2. 往表中插入数据 +sql = "INSERT INTO Employee (id, name) VALUES (%s, %s)" +values = [(1, 'XiaoBai'), + (2, 'XiaoHei'), + (3, 'XiaoHong'), + (4, 'XiaoMei'), + (5, 'XiaoLi')] + +try: + # 通过executemany可以插入多条数据 + cursor.executemany(sql, values) + # 提交事务 + connection.commit() +except: + connection.rollback() + + +# 3. 关闭光标及连接 +cursor.close() +connection.close() +``` + ++ 示例2 + + 在示例1的基础上,我们继续执行查询工作。 + +```python +import pymysql + +# 以admin身份连接到数据库shop +connection = pymysql.connect( + host='localhost', + user='admin', + password='mysql123', + database='shop', + charset='utf8mb4', +) + +with connection: + # 创建游标 + cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) + + # 1. 通过fetchone只查询一条 + cursor.execute("SHOW CREATE TABLE Employee") + result = cursor.fetchone() + print(f'查询结果1: \n{result}') + + # 2. 通过fetchmany查询size条 + cursor.execute("DESC Employee") + result = cursor.fetchmany(size=2) + print(f'查询结果2: \n{result}') + + # 3. 通过fetchall查询所有 + cursor.execute("SELECT * FROM Employee") + result = cursor.fetchall() + print(f'查询结果3: \n{result}') + + # 4. 通过scroll回滚到第0条进行查询 + cursor.scroll(0, mode='absolute') + result = cursor.fetchone() + print(f'查询结果4: \n{result}') + + # 5. 通过scroll跳过2条进行查询 + cursor.scroll(2, mode='relative') + result = cursor.fetchone() + print(f'查询结果5: \n{result}') + + cursor.close() +``` + +​ 控制台打印结果如下: + +```bash +查询结果1: +{'Table': 'Employee', 'Create Table': 'CREATE TABLE `Employee` (\n `id` int NOT NULL,\n `name` char(15) NOT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci'} +查询结果2: +[{'Field': 'id', 'Type': 'int', 'Null': 'NO', 'Key': 'PRI', 'Default': None, 'Extra': ''}, {'Field': 'name', 'Type': 'char(15)', 'Null': 'NO', 'Key': '', 'Default': None, 'Extra': ''}] +查询结果3: +[{'id': 1, 'name': 'XiaoBai'}, {'id': 2, 'name': 'XiaoHei'}, {'id': 3, 'name': 'XiaoHong'}, {'id': 4, 'name': 'XiaoMei'}, {'id': 5, 'name': 'XiaoLi'}] +查询结果4: +{'id': 1, 'name': 'XiaoBai'} +查询结果5: +{'id': 4, 'name': 'XiaoMei'} +``` + ++ 示例3: + + ​ 该示例将演示SQL注入的问题。先建立一个表并插入数据: + +```python +import pymysql + +# 以admin身份连接到数据库shop +connection = pymysql.connect( + host='localhost', + user='admin', + password='mysql123', + database='shop', + charset='utf8mb4', +) + +# 创建游标 +cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) + +sql = """ + CREATE TABLE UserInfo( + id INT PRIMARY KEY, + name VARCHAR(15), + password CHAR(15) NOT NULL + ) + """ + +cursor.execute(sql) + +sql = "INSERT INTO UserInfo (id, name, password) VALUES (%s, %s, %s)" +values = [(1, 'XiaoBai', '123'), + (2, 'XiaoHei', '234'), + (3, 'XiaoHong', '567'), + (4, 'XiaoMei', '321'), + (5, 'XiaoLi', '789')] + +cursor.executemany(sql, values) +connection.commit() +``` + +​ 再写一个程序,根据输入判定登陆是否成功: + +```python +import pymysql + +# 以admin身份连接到数据库shop +connection = pymysql.connect( + host='localhost', + user='admin', + password='mysql123', + database='shop', + charset='utf8mb4', +) + +# 创建游标 +cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) + +while True: + user = input("输入用户:").strip() + password = input("输入密码:").strip() + sql = "select name, password from UserInfo where name='%s' and password='%s' " % (user, password) + + cursor.execute(sql) + # 打印用户和密码 + result=cursor.fetchone() + print(result) + + if result: + print("成功登陆\n") + else: + print("登陆失败\n") +``` + +​ 在控制台下,我们进行了三组用户和密码的验证: + +```python +输入用户:XiaoBai +输入密码:123 +{'name': 'XiaoBai', 'password': '123'} +成功登陆 + +输入用户:XiaoBai +输入密码:321 +None +登陆失败 + +输入用户:XiaoBai' -- dsd +输入密码:321 +{'name': 'XiaoBai', 'password': '123'} +成功登陆 +``` + +​ 可以看出,第1组和第2组验证正常,但是第三组出现了异常,输入错误的密码却可以正确登陆。 + +​ 这是因为在MySQL中`--`的含义是注释,如果通过字符串进行拼接: + +```mysql +select name, password from UserInfo where name='XiaoBai' -- dsd' and password='321' +``` + +​ 实际等价于: + +```mysql +sselect name, password from UserInfo where name='XiaoBai' +``` + +​ 解决办法:通过`execute`或者`executemany`来进行拼接。将语句: + +```python +sql = "select name, password from UserInfo where name='%s' and password='%s' " % (user, password) +cursor.execute(sql) +``` + +​ 改为: + +```python +sql = "select name, password from UserInfo where name=%s and password=%s" +cursor.execute(sql, (user, password)) +``` + From 3a06bf9f82cac7ed959c44891c49c8e430476c7a Mon Sep 17 00:00:00 2001 From: RuyiLuo Date: Wed, 3 Nov 2021 19:33:02 +0800 Subject: [PATCH 07/16] Update readme.md --- readme.md | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/readme.md b/readme.md index ed158648..d4932ba4 100644 --- a/readme.md +++ b/readme.md @@ -54,34 +54,33 @@ - **2.2 新闻推荐系统实践** - **2.2.1 构建物料池** - - 2.2.1.1 Mysql基础 + - [2.2.1.1 Mysql基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/MySQL%E5%9F%BA%E7%A1%80.md) - [2.2.1.2 MongoDB基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/MongoDB%E5%9F%BA%E7%A1%80.md) - [2.2.1.3 Redis基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/Redis%E5%9F%BA%E7%A1%80.md) - - 2.2.1.4 scrapy框架基础 - - 2.2.1.5 scrapy新闻爬取 + - 2.2.1.4 scrapy基础及新闻爬取实战 - 2.2.1.6 新闻画像的构建 - - - **2.2.2 前后端交互** + + - **2.2.2 前后端交互** - 2.2.2.1 Vue简介及基础 - 2.2.2.2 flask简介及基础 - 2.2.2.3 前后端交互 - - - **2.2.3 数据收集及冷启动** - - - **2.2.4 特征工程** - - - **2.2.5 召回** + + - **2.2.3 数据收集及冷启动** + + - **2.2.4 特征工程** + + - **2.2.5 召回** - 2.2.5.1 规则类召回 - 2.2.5.2 模型类召回 - 2.2.5.3 召回评估 - - - **2.2.6 排序** + + - **2.2.6 排序** - 2.2.6.1 DeepFM排序模型 - 2.2.6.2 排序模型评估 - - - **2.2.7 规则与重排** - - - **2.2.8 任务监控与调度** + + - **2.2.7 规则与重排** + + - **2.2.8 任务监控与调度** - **第三章 推荐算法面经** From 0ebe92f43c3e515ba71be0f111bd12a9da483d43 Mon Sep 17 00:00:00 2001 From: RuyiLuo Date: Wed, 3 Nov 2021 19:34:32 +0800 Subject: [PATCH 08/16] Update readme.md --- readme.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/readme.md b/readme.md index d4932ba4..7be3e23e 100644 --- a/readme.md +++ b/readme.md @@ -59,28 +59,28 @@ - [2.2.1.3 Redis基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/Redis%E5%9F%BA%E7%A1%80.md) - 2.2.1.4 scrapy基础及新闻爬取实战 - 2.2.1.6 新闻画像的构建 - - - **2.2.2 前后端交互** + + - **2.2.2 前后端交互** - 2.2.2.1 Vue简介及基础 - 2.2.2.2 flask简介及基础 - 2.2.2.3 前后端交互 - - **2.2.3 数据收集及冷启动** + - **2.2.3 数据收集及冷启动** - - **2.2.4 特征工程** + - **2.2.4 特征工程** - - **2.2.5 召回** + - **2.2.5 召回** - 2.2.5.1 规则类召回 - 2.2.5.2 模型类召回 - 2.2.5.3 召回评估 - - **2.2.6 排序** + - **2.2.6 排序** - 2.2.6.1 DeepFM排序模型 - 2.2.6.2 排序模型评估 - - **2.2.7 规则与重排** + - **2.2.7 规则与重排** - - **2.2.8 任务监控与调度** + - **2.2.8 任务监控与调度** - **第三章 推荐算法面经** From f15b6842be1e2e2ac3e13396b3104d6e2599cede Mon Sep 17 00:00:00 2001 From: RuyiLuo Date: Wed, 3 Nov 2021 21:50:59 +0800 Subject: [PATCH 09/16] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20scrapy=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E5=8F=8A=E6=96=B0=E9=97=BB=E7=88=AC=E5=8F=96=E5=AE=9E?= =?UTF-8?q?=E6=88=98.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs/scrapy基础及新闻爬取实战.md | 509 ++++++++++++++++++ 1 file changed, 509 insertions(+) create mode 100644 docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/scrapy基础及新闻爬取实战.md diff --git a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/scrapy基础及新闻爬取实战.md b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/scrapy基础及新闻爬取实战.md new file mode 100644 index 00000000..30cbd5fe --- /dev/null +++ b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/scrapy基础及新闻爬取实战.md @@ -0,0 +1,509 @@ +## scrapy基础及新闻爬取实战 + +### python环境的安装 + +python 环境,使用miniconda搭建,安装miniconda的参考链接:https://blog.csdn.net/pdcfighting/article/details/111503057。 + +在安装完miniconda之后,创建一个新闻推荐的虚拟环境,我这边将其命名为news_rec_py3 + +```C++ +conda create -n news_rec_py3 python==3.8 +``` + +### Scrapy的简介与安装 + +Scrapy 是一种快速的高级 web crawling 和 web scraping 框架,**用于对网站内容进行爬取,并从其页面提取结构化数据**。 + +Ubuntu下安装Scrapy,需要先安装依赖Linux依赖 + +```C++ +sudo apt-get install python3 python3-dev python3-pip libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev +``` + +在新闻推荐系统虚拟conda环境中安装scrapy + +```C++ +pip install scrapy +``` + +#### scrapy项目结构 + +默认情况下,所有scrapy项目的项目结构都是相似的,在指定目录对应的命令行中输入如下命令,就会在当前目录创建一个scrapy项目 + +``` +scrapy startproject myproject +``` + +项目的目录结构如下: + +```C++ +myproject/ + scrapy.cfg + + myproject/ + __init__.py + items.py + middlewares.py + pipelines.py + settings.py + spiders/ + __init__.py +``` + +- scrapy.cfg: 项目配置文件 +- myproject/ : 项目python模块, 代码将从这里导入 +- **myproject/ items.py: 项目items文件,** +- **myproject/ pipelines.py: 项目管道文件,将爬取的数据进行持久化存储** +- myproject/ settings.py: 项目配置文件,可以配置数据库等 +- **myproject/ spiders/: 放置spider的目录,爬虫的具体逻辑就是在这里实现的(具体逻辑写在spider.py文件中),可以使用命令行创建spider,也可以直接在这个文件夹中创建spider相关的py文件** +- myproject/ middlewares:中间件,请求和响应都将经过他,可以配置请求头、代理、cookie、会话维持等 + +#### spider + +**spider是定义一个特定站点(或一组站点)如何被抓取的类,包括如何执行抓取(即跟踪链接)以及如何从页面中提取结构化数据(即抓取项)。换言之,spider是为特定站点(或者在某些情况下,一组站点)定义爬行和解析页面的自定义行为的地方。** + +爬行器是自己定义的类,Scrapy使用它从一个网站(或一组网站)中抓取信息。它们必须继承 `Spider` 并定义要做出的初始请求,可选的是如何跟随页面中的链接,以及如何解析下载的页面内容以提取数据。 + +对于spider来说,抓取周期是这样的: + +1. 首先生成对第一个URL进行爬网的初始请求,然后指定一个回调函数,该函数使用从这些请求下载的响应进行调用。要执行的第一个请求是通过调用 `start_requests()` 方法,该方法(默认情况下)生成 `Request` 中指定的URL的 `start_urls` 以及 `parse` 方法作为请求的回调函数。 +2. 在回调函数中,解析响应(网页)并返回 [item objects](https://www.osgeo.cn/scrapy/topics/items.html#topics-items) , `Request` 对象,或这些对象的可迭代。这些请求还将包含一个回调(可能相同),然后由Scrapy下载,然后由指定的回调处理它们的响应。 +3. 在回调函数中,解析页面内容,通常使用 [选择器](https://www.osgeo.cn/scrapy/topics/selectors.html#topics-selectors) (但您也可以使用beautifulsoup、lxml或任何您喜欢的机制)并使用解析的数据生成项。 +4. 最后,从spider返回的项目通常被持久化到数据库(在某些 [Item Pipeline](https://www.osgeo.cn/scrapy/topics/item-pipeline.html#topics-item-pipeline) )或者使用 [Feed 导出](https://www.osgeo.cn/scrapy/topics/feed-exports.html#topics-feed-exports) . + +**下面是官网给出的Demo:** + +```python +import scrapy + +class QuotesSpider(scrapy.Spider): + name = "quotes" # 表示一个spider 它在一个项目中必须是唯一的,即不能为不同的spider设置相同的名称。 + + # 必须返回请求的可迭代(您可以返回请求列表或编写生成器函数),spider将从该请求开始爬行。后续请求将从这些初始请求中相继生成。 + def start_requests(self): + urls = [ + 'http://quotes.toscrape.com/page/1/', + 'http://quotes.toscrape.com/page/2/', + ] + for url in urls: + yield scrapy.Request(url=url, callback=self.parse) # 注意,这里callback调用了下面定义的parse方法 + + # 将被调用以处理为每个请求下载的响应的方法。Response参数是 TextResponse 它保存页面内容,并具有进一步有用的方法来处理它。 + def parse(self, response): + # 下面是直接从response中获取内容,为了更方便的爬取内容,后面会介绍使用selenium来模拟人用浏览器,并且使用对应的方法来提取我们想要爬取的内容 + page = response.url.split("/")[-2] + filename = f'quotes-{page}.html' + with open(filename, 'wb') as f: + f.write(response.body) + self.log(f'Saved file {filename}') +``` + +#### Xpath + +**XPath 是一门在 XML 文档中查找信息的语言,XPath 可用来在 XML 文档中对元素和属性进行遍历。在爬虫的时候使用xpath来选择我们想要爬取的内容是非常方便的**,这里就提一下xpath中需要掌握的内容,参考资料中的内容更加的详细(建议花一个小时看看)。 + +要了解xpath, 需要先了解一下HTML(是用来描述网页的一种语言), 这个的细节就不详细展开 + +**划重点:** + +1. **xpath路径表达式:**XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。节点是通过沿着路径 (path) 或者步 (steps) 来选取的。 + +2. **了解如何使用xpath语法选取我们想要的内容,所以需要熟悉xpath的基本语法** + +#### scrapy爬取新闻内容实战 + +在介绍这个项目之前先说一下这个项目的基本逻辑。 + +**环境准备:** + +1. 首先Ubuntu系统里面需要安装好MongoDB数据库,这个可以参考开源项目MongoDB基础 +2. python环境中安装好了scrapy, pymongo包 + +**项目逻辑:** + +1. 每天定时从新浪新闻网站上爬取新闻数据存储到mongodb数据库中,并且需要监控每天爬取新闻的状态(比如某天爬取的数据特别少可能是哪里出了问题,需要进行排查) +2. 每天爬取新闻的时候只爬取当天日期的新闻,主要是为了防止相同的新闻重复爬取(当然这个也完全避免不了爬取重复的新闻,爬取新闻之后需要有一些单独的去重的逻辑) +3. 爬虫项目中实现三个核心文件,分别是sina.py(spider),items.py(抽取数据的规范化及字段的定义),pipelines.py(数据写入数据库) + +因为新闻爬取项目和新闻推荐系统是放在一起的,为了方便提前学习,下面直接给出项目的目录结构以及重要文件中的代码实现,最终的项目将会和新闻推荐系统一起开源出来 + +image-20211103214124327 + +1. **创建一个scrapy项目:** + +```shell +scrapy startproject sinanews +``` + +2. **实现item.py逻辑** + +```python +# Define here the models for your scraped items +# +# See documentation in: +# https://docs.scrapy.org/en/latest/topics/items.html + +import scrapy +from scrapy import Item, Field + +# 定义新闻数据的字段 +class SinanewsItem(scrapy.Item): + """数据格式化,数据不同字段的定义 + """ + title = Field() # 新闻标题 + ctime = Field() # 新闻发布时间 + url = Field() # 新闻原始url + raw_key_words = Field() # 新闻关键词(爬取的关键词) + content = Field() # 新闻的具体内容 + cate = Field() # 新闻类别 +``` + +3. **实现sina.py (spider)逻辑** + + 这里需要注意的一点,这里在爬取新闻的时候选择的是一个比较简洁的展示网站进行爬取的,相比直接去最新的新浪新闻观光爬取新闻简单很多,简洁的网站大概的链接:https://news.sina.com.cn/roll/#pageid=153&lid=2509&k=&num=50&page=1 + +image-20211103213354334 + +```python +# -*- coding: utf-8 -*- +import re +import json +import random +import scrapy +from scrapy import Request +from ..items import SinanewsItem +from datetime import datetime + + +class SinaSpider(scrapy.Spider): + # spider的名字 + name = 'sina_spider' + + def __init__(self, pages=None): + super(SinaSpider).__init__() + + self.total_pages = int(pages) + # base_url 对应的是新浪新闻的简洁版页面,方便爬虫,并且不同类别的新闻也很好区分 + self.base_url = 'https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid={}&k=&num=50&page={}&r={}' + # lid和分类映射字典 + self.cate_dict = { + "2510": "国内", + "2511": "国际", + "2669": "社会", + "2512": "体育", + "2513": "娱乐", + "2514": "军事", + "2515": "科技", + "2516": "财经", + "2517": "股市", + "2518": "美股" + } + + def start_requests(self): + """返回一个Request迭代器 + """ + # 遍历所有类型的论文 + for cate_id in self.cate_dict.keys(): + for page in range(1, self.total_pages + 1): + lid = cate_id + # 这里就是一个随机数,具体含义不是很清楚 + r = random.random() + # cb_kwargs 是用来往解析函数parse中传递参数的 + yield Request(self.base_url.format(lid, page, r), callback=self.parse, cb_kwargs={"cate_id": lid}) + + def parse(self, response, cate_id): + """解析网页内容,并提取网页中需要的内容 + """ + json_result = json.loads(response.text) # 将请求回来的页面解析成json + # 提取json中我们想要的字段 + # json使用get方法比直接通过字典的形式获取数据更方便,因为不需要处理异常 + data_list = json_result.get('result').get('data') + for data in data_list: + item = SinanewsItem() + + item['cate'] = self.cate_dict[cate_id] + item['title'] = data.get('title') + item['url'] = data.get('url') + item['raw_key_words'] = data.get('keywords') + + # ctime = datetime.fromtimestamp(int(data.get('ctime'))) + # ctime = datetime.strftime(ctime, '%Y-%m-%d %H:%M') + + # 保留的是一个时间戳 + item['ctime'] = data.get('ctime') + + # meta参数传入的是一个字典,在下一层可以将当前层的item进行复制 + yield Request(url=item['url'], callback=self.parse_content, meta={'item': item}) + + def parse_content(self, response): + """解析文章内容 + """ + item = response.meta['item'] + content = ''.join(response.xpath('//*[@id="artibody" or @id="article"]//p/text()').extract()) + content = re.sub(r'\u3000', '', content) + content = re.sub(r'[ \xa0?]+', ' ', content) + content = re.sub(r'\s*\n\s*', '\n', content) + content = re.sub(r'\s*(\s)', r'\1', content) + content = ''.join([x.strip() for x in content]) + item['content'] = content + yield item +``` + +4. **数据持久化实现,piplines.py** + + 这里需要注意的就是实现SinanewsPipeline类的时候,里面很多方法都是固定的,不是随便写的,不同的方法又不同的功能,这个可以参考scrapy官方文档。 + +```python +# Define your item pipelines here +# +# Don't forget to add your pipeline to the ITEM_PIPELINES setting +# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html +# useful for handling different item types with a single interface +import time +import datetime +import pymongo +from pymongo.errors import DuplicateKeyError +from sinanews.items import SinanewsItem +from itemadapter import ItemAdapter + + +# 新闻item持久化 +class SinanewsPipeline: + """数据持久化:将数据存放到mongodb中 + """ + def __init__(self, host, port, db_name, collection_name): + self.host = host + self.port = port + self.db_name = db_name + self.collection_name = collection_name + + @classmethod + def from_crawler(cls, crawler): + """自带的方法,这个方法可以重新返回一个新的pipline对象,并且可以调用配置文件中的参数 + """ + return cls( + host = crawler.settings.get("MONGO_HOST"), + port = crawler.settings.get("MONGO_PORT"), + db_name = crawler.settings.get("DB_NAME"), + # mongodb中数据的集合按照日期存储 + collection_name = crawler.settings.get("COLLECTION_NAME") + \ + "_" + time.strftime("%Y%m%d", time.localtime()) + ) + + def open_spider(self, spider): + """开始爬虫的操作,主要就是链接数据库及对应的集合 + """ + self.client = pymongo.MongoClient(self.host, self.port) + self.db = self.client[self.db_name] + self.collection = self.db[self.collection_name] + + def close_spider(self, spider): + """关闭爬虫操作的时候,需要将数据库断开 + """ + self.client.close() + + def process_item(self, item, spider): + """处理每一条数据,注意这里需要将item返回 + 注意:判断新闻是否是今天的,每天只保存当天产出的新闻,这样可以增量的添加新的新闻数据源 + """ + if isinstance(item, SinanewsItem): + try: + # TODO 物料去重逻辑,根据title进行去重,先读取物料池中的所有物料的title然后进行去重 + + cur_time = int(item['ctime']) + str_today = str(datetime.date.today()) + min_time = int(time.mktime(time.strptime(str_today + " 00:00:00", '%Y-%m-%d %H:%M:%S'))) + max_time = int(time.mktime(time.strptime(str_today + " 23:59:59", '%Y-%m-%d %H:%M:%S'))) + if cur_time > min_time and cur_time <= max_time: + self.collection.insert(dict(item)) + except DuplicateKeyError: + """ + 说明有重复 + """ + pass + return item +``` + +5. 配置文件,settings.py + +```python +# Scrapy settings for sinanews project +# +# For simplicity, this file contains only settings considered important or +# commonly used. You can find more settings consulting the documentation: +# +# https://docs.scrapy.org/en/latest/topics/settings.html +# https://docs.scrapy.org/en/latest/topics/downloader-middleware.html +# https://docs.scrapy.org/en/latest/topics/spider-middleware.html + +from typing import Collection + +BOT_NAME = 'sinanews' + +SPIDER_MODULES = ['sinanews.spiders'] +NEWSPIDER_MODULE = 'sinanews.spiders' + + +# Crawl responsibly by identifying yourself (and your website) on the user-agent +#USER_AGENT = 'sinanews (+http://www.yourdomain.com)' + +# Obey robots.txt rules +ROBOTSTXT_OBEY = True + +# Configure maximum concurrent requests performed by Scrapy (default: 16) +#CONCURRENT_REQUESTS = 32 + +# Configure a delay for requests for the same website (default: 0) +# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay +# See also autothrottle settings and docs +# DOWNLOAD_DELAY = 3 +# The download delay setting will honor only one of: +#CONCURRENT_REQUESTS_PER_DOMAIN = 16 +#CONCURRENT_REQUESTS_PER_IP = 16 + +# Disable cookies (enabled by default) +#COOKIES_ENABLED = False + +# Disable Telnet Console (enabled by default) +#TELNETCONSOLE_ENABLED = False + +# Override the default request headers: +#DEFAULT_REQUEST_HEADERS = { +# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', +# 'Accept-Language': 'en', +#} + +# Enable or disable spider middlewares +# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html +#SPIDER_MIDDLEWARES = { +# 'sinanews.middlewares.SinanewsSpiderMiddleware': 543, +#} + +# Enable or disable downloader middlewares +# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html +#DOWNLOADER_MIDDLEWARES = { +# 'sinanews.middlewares.SinanewsDownloaderMiddleware': 543, +#} + +# Enable or disable extensions +# See https://docs.scrapy.org/en/latest/topics/extensions.html +#EXTENSIONS = { +# 'scrapy.extensions.telnet.TelnetConsole': None, +#} + +# Configure item pipelines +# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html +# 如果需要使用itempipline来存储item的话需要将这段注释打开 +ITEM_PIPELINES = { + 'sinanews.pipelines.SinanewsPipeline': 300, +} + +# Enable and configure the AutoThrottle extension (disabled by default) +# See https://docs.scrapy.org/en/latest/topics/autothrottle.html +#AUTOTHROTTLE_ENABLED = True +# The initial download delay +#AUTOTHROTTLE_START_DELAY = 5 +# The maximum download delay to be set in case of high latencies +#AUTOTHROTTLE_MAX_DELAY = 60 +# The average number of requests Scrapy should be sending in parallel to +# each remote server +#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 +# Enable showing throttling stats for every response received: +#AUTOTHROTTLE_DEBUG = False + +# Enable and configure HTTP caching (disabled by default) +# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings +#HTTPCACHE_ENABLED = True +#HTTPCACHE_EXPIRATION_SECS = 0 +#HTTPCACHE_DIR = 'httpcache' +#HTTPCACHE_IGNORE_HTTP_CODES = [] +#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage' + +MONGO_HOST = "127.0.0.1" +MONGO_PORT = 27017 +DB_NAME = "SinaNews" +COLLECTION_NAME = "news" +``` + +6. 监控脚本,monitor_news.py + +```python +# -*- coding: utf-8 -*- +import sys, time +import pymongo +import scrapy +from sinanews.settings import MONGO_HOST, MONGO_PORT, DB_NAME, COLLECTION_NAME + +if __name__ == "__main__": + news_num = int(sys.argv[1]) + time_str = time.strftime("%Y%m%d", time.localtime()) + + # 实际的collection_name + collection_name = COLLECTION_NAME + "_" + time_str + + # 链接数据库 + client = pymongo.MongoClient(MONGO_HOST, MONGO_PORT) + db = client[DB_NAME] + collection = db[collection_name] + + # 查找当前集合中所有文档的数量 + cur_news_num = collection.count() + + print(cur_news_num) + if (cur_news_num < news_num): + print("the news nums of {}_{} collection is less then {}".\ + format(COLLECTION_NAME, time_str, news_num)) +``` + +7. 运行脚本,run_scrapy_sina.sh + +```python +# -*- coding: utf-8 -*- +""" +新闻爬取及监控脚本 +""" + +# 设置python环境 +python="/home/recsys/miniconda3/envs/news_rec_py3/bin/python" + +# 新浪新闻网站爬取的页面数量 +page="1" +min_news_num="1000" # 每天爬取的新闻数量少于500认为是异常 + +# 爬取数据 +scrapy crawl sina_spider -a pages=${page} +if [ $? -eq 0 ]; then + echo "scrapy crawl sina_spider --pages ${page} success." +else + echo "scrapy crawl sina_spider --pages ${page} fail." +fi + +# 检查今天爬取的数据是否少于min_news_num篇文章,这里也可以配置邮件报警 +python monitor_news.py ${min_news_num} +if [ $? -eq 0 ]; then + echo "run python monitor_news.py success." +else + echo "run python monitor_news.py fail." +fi +``` + +8. 运行项目命令 + +``` +sh run_scrapy_sina.sh +``` + +最终查看数据库中的数据: + +image-20211103214611171 + +### 参考资料 + +1. [MongoDB基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/MongoDB%E5%9F%BA%E7%A1%80.md) +2. [Scrapy框架新手入门教程](https://blog.csdn.net/sxf1061700625/article/details/106866547/) +3. [scrapy中文文档](https://www.osgeo.cn/scrapy/index.html) +4. [Xpath教程](https://www.w3school.com.cn/xpath/index.asp) +5. https://github.com/Ingram7/NewsinaSpider + +6. https://www.cnblogs.com/zlslch/p/6931838.html + From a1f650c91e4ba050873566589c5a5620ce8ecf01 Mon Sep 17 00:00:00 2001 From: RuyiLuo Date: Wed, 3 Nov 2021 21:54:35 +0800 Subject: [PATCH 10/16] =?UTF-8?q?add=20scrapy=E5=9F=BA=E7=A1=80=E5=8F=8A?= =?UTF-8?q?=E6=96=B0=E9=97=BB=E7=88=AC=E5=8F=96=E5=AE=9E=E6=88=98.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2.2新闻推荐系统实战/docs/scrapy基础及新闻爬取实战.md | 2 +- readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/scrapy基础及新闻爬取实战.md b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/scrapy基础及新闻爬取实战.md index 30cbd5fe..157e11e7 100644 --- a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/scrapy基础及新闻爬取实战.md +++ b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/scrapy基础及新闻爬取实战.md @@ -495,7 +495,7 @@ sh run_scrapy_sina.sh 最终查看数据库中的数据: -image-20211103214611171 +image-20211103214611171 ### 参考资料 diff --git a/readme.md b/readme.md index 7be3e23e..c648f68c 100644 --- a/readme.md +++ b/readme.md @@ -57,7 +57,7 @@ - [2.2.1.1 Mysql基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/MySQL%E5%9F%BA%E7%A1%80.md) - [2.2.1.2 MongoDB基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/MongoDB%E5%9F%BA%E7%A1%80.md) - [2.2.1.3 Redis基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/Redis%E5%9F%BA%E7%A1%80.md) - - 2.2.1.4 scrapy基础及新闻爬取实战 + - [2.2.1.4 scrapy基础及新闻爬取实战](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/scrapy%E5%9F%BA%E7%A1%80%E5%8F%8A%E6%96%B0%E9%97%BB%E7%88%AC%E5%8F%96%E5%AE%9E%E6%88%98.md) - 2.2.1.6 新闻画像的构建 - **2.2.2 前后端交互** From bce56b45cceeabcb61571294cd59f846f6945cb0 Mon Sep 17 00:00:00 2001 From: RuyiLuo Date: Wed, 3 Nov 2021 21:56:09 +0800 Subject: [PATCH 11/16] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs/{MySQL基础.md => 2.2.1.1 Mysql基础.md} | 4620 ++++++++--------- .../docs/{MongoDB基础.md => 2.2.1.2 MongoDB基础.md} | 2478 ++++----- .../docs/{Redis基础.md => 2.2.1.3 Redis基础.md} | 2476 ++++----- ...础及新闻爬取实战.md => 2.2.1.4 scrapy基础及新闻爬取实战.md} | 0 4 files changed, 4787 insertions(+), 4787 deletions(-) rename docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/{MySQL基础.md => 2.2.1.1 Mysql基础.md} (96%) rename docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/{MongoDB基础.md => 2.2.1.2 MongoDB基础.md} (96%) rename docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/{Redis基础.md => 2.2.1.3 Redis基础.md} (97%) rename docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/{scrapy基础及新闻爬取实战.md => 2.2.1.4 scrapy基础及新闻爬取实战.md} (100%) diff --git a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MySQL基础.md b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.1 Mysql基础.md similarity index 96% rename from docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MySQL基础.md rename to docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.1 Mysql基础.md index feccaa7b..e6ba6a07 100644 --- a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MySQL基础.md +++ b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.1 Mysql基础.md @@ -1,2310 +1,2310 @@ -# 前言 MySQL简介 - -​ MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。 - -​ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行的开源数据库,因此被广泛地应用在Internet上的中小型网站中。随着MySQL的不断成熟,它也逐渐用于更多大规模网站和应用,比如维基百科、Google和Facebook等网站。非常流行的开源软件组合LAMP中的“M”指的就是MySQL。 - -[百度百科]: https://baike.baidu.com/item/mySQL/471251 -[维基百科]: https://zh.wikipedia.org/wiki/MySQL - - - -# 一、 Ubuntu下安装MySQL - -安装教程是在`Ubuntu20.04`下进行的,安装的MySQL版本为`8.0.27`。 - -## 1.1 安装 - -```bash -sudo apt install mysql-server mysql-client -``` - -在输入密码后,再输入`yes`即可开始安装。 - -安装完成后,通过运行命令`mysql -V`查看版本号: - -```bash -lyons@ubuntu:~$ mysql -V -mysql Ver 8.0.27-0ubuntu0.20.04.1 for Linux on x86_64 ((Ubuntu)) -``` - -验证MySQL服务正在运行,命令行下输入: - -```bash -sudo service mysql status -``` - -如果正在运行,则会显示: - -```bash -● mysql.service - MySQL Community Server - Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled) - Active: active (running) since Wed 2021-10-27 10:27:59 CST; 9h ago - Main PID: 6179 (mysqld) - Status: "Server is operational" - Tasks: 39 (limit: 4599) - Memory: 348.9M - CGroup: /system.slice/mysql.service - └─6179 /usr/sbin/mysqld - -10月 27 10:27:59 ubuntu systemd[1]: Starting MySQL Community Server... -10月 27 10:27:59 ubuntu systemd[1]: Started MySQL Community Server. -``` - - - -## 1.2 配置MySQL的安全性 - -1. 首先,运行命令`mysql_secure_installation`: - - ```bash - sudo mysql_secure_installation - ``` - -2. `VALIDATE PASSWORD COMPONENT` - - 设置验证密码插件。它被用来测试`MySQL`用户的密码强度,并且提高安全性。如果想设置验证密码插件,请输入`y`: - - ```bash - Connecting to MySQL using a blank password. - - VALIDATE PASSWORD COMPONENT can be used to test passwords - and improve security. It checks the strength of password - and allows the users to set only those passwords which are - secure enough. Would you like to setup VALIDATE PASSWORD component? - - Press y|Y for Yes, any other key for No: y - ``` - - 接下来,将进行密码验证等级设置,根据数字设置对应等级,这里设置为0: - - ```bash - There are three levels of password validation policy: - - LOW Length >= 8 - MEDIUM Length >= 8, numeric, mixed case, and special characters - STRONG Length >= 8, numeric, mixed case, special characters and dictionary file - - Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 0 - ``` - -3. 设置密码 - - 为MySQL root用户设置密码,设置过程中密码不会显示。如果设置了验证密码插件,将会显示密码的强度。 - - ``` - Please set the password for root here. - New password: - - Re-enter new password: - - Estimated strength of the password: 25 - Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y - ``` - -4. 移除匿名用户 - - 默认情况下,MySQL安装有一个匿名用户,允许任何人登录MySQL,而不必为他们创建用户帐户。输入`y`进行删除: - - ``` - By default, a MySQL installation has an anonymous user, - allowing anyone to log into MySQL without having to have - a user account created for them. This is intended only for - testing, and to make the installation go a bit smoother. - You should remove them before moving into a production - environment. - - Remove anonymous users? (Press y|Y for Yes, any other key for No) : y - Success. - ``` - -5. 禁止远程root用户登录 - - 输入`y`后按`enter`,将会禁止`root`用户登录。 - - ``` - Normally, root should only be allowed to connect from - 'localhost'. This ensures that someone cannot guess at - the root password from the network. - - Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y - Success. - ``` - -6. 删除测试库 - - 输入`y`后按`enter`,将会删除测试库。 - - ``` - By default, MySQL comes with a database named 'test' that - anyone can access. This is also intended only for testing, - and should be removed before moving into a production - environment. - - - Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y - - Dropping test database... - Success. - ``` - -7. 重新加载特权表 - - 输入`y`后按`enter`,将会重新加载特权表。 - - ``` - - Removing privileges on test database... - Success. - - Reloading the privilege tables will ensure that all changes - made so far will take effect immediately. - - Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y - Success. - - All done! - ``` - - 至此,配置完成。 - - - -## 1.3 以root用户登录 - -在MySQL 8.0上,root 用户默认通过`auth_socket`插件授权。`auth_socket`插件通过 Unix socket 文件来验证所有连接到`localhost`的用户。 - -这意味着你不能通过提供密码,验证为 root。此时,输入`mysql -uroot -p`可能会被拒绝访问: - -```bash -lyons@ubuntu:~$ mysql -uroot -p -mysql: [Warning] Using a password on the command line interface can be insecure. -ERROR 1698 (28000): Access denied for user 'root'@'localhost' -``` - -若要以 root 用户身份登录 MySQL服务器,输入`sudo mysql`,如下: - -```bash -# 登录密码为linux系统用户的root密码 -lyons@ubuntu:~$ sudo mysql -[sudo] lyons 的密码: -Welcome to the MySQL monitor. Commands end with ; or \g. -Your MySQL connection id is 55 -Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu) - -Copyright (c) 2000, 2021, Oracle and/or its affiliates. - -Oracle is a registered trademark of Oracle Corporation and/or its -affiliates. Other names may be trademarks of their respective -owners. - -Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. - -mysql> -``` - -退出MySQL,请输入`exit`命令: - -```mysql -mysql> exit -Bye -lyons@ubuntu:~$ -``` - -如果你想以 root 身份登录 MySQL 服务器,便于使用其他的程序。可以将验证方法从`auth_socket`修改成`mysql_native_password`。 - -+ **方式1** - -你可以通过运行下面的命令实现: - -```bash --- 语法中的'你的密码’指的是你自己设置的登录密码,可设置为字母数字组合。 -ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; -FLUSH PRIVILEGES; -``` - -示例: - -```mysql --- 在mysql下,将密码设置为'mysql123' -mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'mysql123'; -Query OK, 0 rows affected (0.00 sec) - --- 刷新系统权限 -mysql> FLUSH PRIVILEGES; -Query OK, 0 rows affected (0.01 sec) - -mysql> exit -Bye - --- 现在便可以通过mysql -uroot -p登录 --- 登录密码为前面设置的'mysql123' -lyons@ubuntu:~$ mysql -uroot -p -Enter password: -Welcome to the MySQL monitor. Commands end with ; or \g. -Your MySQL connection id is 57 -Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu) - -Copyright (c) 2000, 2021, Oracle and/or its affiliates. - -Oracle is a registered trademark of Oracle Corporation and/or its -affiliates. Other names may be trademarks of their respective -owners. - -Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. - -mysql> -mysql> exit -Bye - --- 同时,命令sudo mysql会被拒绝访问 -lyons@ubuntu:~$ sudo mysql -ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO) -``` - -当然,若要再次修改回`sudo mysql`的方式来登录`root`用户,方法类似: - -```mysql -ALTER USER 'root'@'localhost' IDENTIFIED WITH auth_socket BY '你的密码'; - -FLUSH PRIVILEGES; -``` - -+ **方式2** - - 推荐的选项,就是创建一个新的独立管理用户,拥有所有数据库的访问权限: - -```mysql -# 创建用户 -CREATE USER '用户名'@'localhost' identified by '你的密码' - -# 赋予admin用户全部的权限,你也可以只授予部分权限 -GRANT ALL PRIVILEGES ON *.* TO '用户名'@'localhost'; -``` - -​ 示例: - -```mysql -# 创建名为admin的用户,密码为mysql123 -mysql> create user 'admin'@'localhost' identified by 'mysql123'; -Query OK, 0 rows affected (0.01 sec) - -# 将访问所有database以及表的权利授权用户admin -#with gran option表示该用户可给其它用户赋予权限,但不可能超过该用户已有的权限 -mysql> grant all privileges on *.* to 'admin'@'localhost' with grant option; -Query OK, 0 rows affected (0.00 sec) - -mysql> FLUSH PRIVILEGES; -Query OK, 0 rows affected (0.00 sec) - -# 查看已有的用户 -mysql> select user, host from mysql.user; -+------------------+-----------+ -| user | host | -+------------------+-----------+ -| admin | localhost | -| debian-sys-maint | localhost | -| mysql.infoschema | localhost | -| mysql.session | localhost | -| mysql.sys | localhost | -| root | localhost | -+------------------+-----------+ -6 rows in set (0.00 sec) - -# 退出root用户登录 -mysql> exit -Bye - -# 登录admin用户,输入密码mysql123即可登录成功 -lyons@ubuntu:~$ mysql -uadmin -p -Enter password: -Welcome to the MySQL monitor. Commands end with ; or \g. -Your MySQL connection id is 16 -Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu) - -Copyright (c) 2000, 2021, Oracle and/or its affiliates. - -Oracle is a registered trademark of Oracle Corporation and/or its -affiliates. Other names may be trademarks of their respective -owners. - -Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. - -mysql> -``` - -说明:`'admin'@'localhost'`中,`localhost`指本地才可连接,可以将其换成`%`指任意`ip`都能连接,也可以指定`ip`连接。 - - - -## 1.4 修改密码 - -将用户`admin`的登录密码修改为`mysql321`: - -```mysql -ALTER USER 'admin'@'localhost' IDENTIFIED WITH mysql_native_password BY 'mysql321'; -``` - - - -## 1.5 撤销用户授权 - -```mysql -# 查看用户的权限 -show grants for 'admin'@'localhost'; - -# 撤销用户的权限 -# 用户有什么权限就撤销什么 -revoke all privileges on *.* from 'admin'@'localhost'; -``` - - - -## 1.6 删除用户 - -```MYSQL -drop user 'admin'@'localhost'; -``` - - - -> 注:MySQL 8.0版本和5.0部分命令有所改掉,上述语法都是在8.0版本下运行通过的;请务必检查自己的MySQL版本号。 - - - -# 二、MySQL预备知识 - -在正式学习MySQL之前,我们先来了解一下SQL语句的书写规范以及命名规则等。 - -## 2.1 SQL书写规范 - -在写SQL语句时,要求按照如下规范进行: - -+ SQL 语句要以分号(;)结尾 - -+ SQL 不区分关键字的大小写 ,这对于表名和列名同样适用。 - -+ 插入到表中的数据是区分大小写的。例如,数据Computer、COMPUTER 或computer,三者是不一样的。 - -+ 常数的书写方式是固定的,在SQL 语句中直接书写的字符串、日期或者数字等称为常数。常数的书写方式如下所示。 - - + SQL 语句中含有字符串的时候,需要像'abc'这样,使用单引号(')将字符串括起来,用来标识这是一个字符串。 - + SQL 语句中含有日期的时候,同样需要使用单引号将其括起来。日期的格式有很多种('26 Jan 2010' 或者'10/01/26' 等)。 - + 在SQL 语句中书写数字的时候,不需要使用任何符号标识,直接写成1000 这样的数字即可。 - -+ 单词之间需要用半角空格或者换行来分隔。 - -+ SQL中的注释主要采用`--`和`/* ... */`的方式,第二种方式可以换行。在MySQL下,还可以通过`#`来进行注释。 - - - -## 2.2 命名规则 - -+ 在数据库中,只能使用半角英文字母、数字、下划线(_)作为数据库、表和列的名称 。 -+ 名称必须以半角英文字母作为开头。 -+ 名称不能重复,同一个数据库下不能有2张相同的表。 - - - -## 2.3. 数据类型 - -MySQL 支持所有标准 SQL 数值数据类型,包括: - -### (1)数值类型 - -数值包含的类型如下: - -+ 整型数据:`TINYINT`、`INTEGER`、`SMALLINT`、`MEDIUMINT`、`DECIMAL` 、`NUMERIC` 和`BIGINT`。 - -+ 浮点型数据:`DECIMAL`、`FLOAT`、`REAL` 和 `DOUBLE PRECISION`)。 - -其中,关键字`INT`是`INTEGER`的同义词,关键字DEC是的同义词。 - -不同关键字的主要区别就是表示的范围或精度不一样。具体如下表: - -| 类型 | 大小 | 范围(有符号) | 范围(无符号) | 用途 | -| :----------: | :--------------------------------------: | :----------------------------------------------------------- | :----------------------------------------------------------- | :-------------- | -| TINYINT | 1 Bytes | (-128,127) | (0,255) | 小整数值 | -| SMALLINT | 2 Bytes | (-32 768,32 767) | (0,65 535) | 大整数值 | -| MEDIUMINT | 3 Bytes | (-8 388 608,8 388 607) | (0,16 777 215) | 大整数值 | -| INT或INTEGER | 4 Bytes | (-2 147 483 648,2 147 483 647) | (0,4 294 967 295) | 大整数值 | -| BIGINT | 8 Bytes | (-9,223,372,036,854,775,808,9 223 372 036 854 775 807) | (0,18 446 744 073 709 551 615) | 极大整数值 | -| FLOAT | 4 Bytes | (-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) | 0,(1.175 494 351 E-38,3.402 823 466 E+38) | 单精度 浮点数值 | -| DOUBLE | 8 Bytes | (-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 双精度 浮点数值 | -| DECIMAL | 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 | 依赖于M和D的值 | 依赖于M和D的值 | 小数值 | - -### (2)日期和时间类型 - -表示时间值的日期和时间类型为`DATETIME`、`DATE`、`TIMESTAMP`、`TIME`和`YEAR`。具体如下表: - -| 类型 | 大小 ( bytes) | 范围 | 格式 | 用途 | -| :-------- | :------------ | :----------------------------------------------------------- | :------------------ | :----------------------- | -| DATE | 3 | 1000-01-01/9999-12-31 | YYYY-MM-DD | 日期值 | -| TIME | 3 | '-838:59:59'/'838:59:59' | HH:MM:SS | 时间值或持续时间 | -| YEAR | 1 | 1901/2155 | YYYY | 年份值 | -| DATETIME | 8 | 1000-01-01 00:00:00/9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS | 混合日期和时间值 | -| TIMESTAMP | 4 | 1970-01-01 00:00:00/2038结束时间是第 2147483647 秒,北京时间 2038-1-19 11:14:07,格林尼治时间 2038年1月19日 凌晨 03:14:07 | YYYYMMDD HHMMSS | 混合日期和时间值,时间戳 | - -### (3)字符串类型 - -字符串类型指`CHAR`、`VARCHAR`、`BINARY`、`VARBINARY`、`BLOB`、`TEXT`、`ENUM`和`SET`。具体如下表: - -| 类型 | 大小 | 用途 | -| :--------- | :-------------------- | :------------------------------ | -| CHAR | 0-255 bytes | 定长字符串 | -| VARCHAR | 0-65535 bytes | 变长字符串 | -| TINYBLOB | 0-255 bytes | 不超过 255 个字符的二进制字符串 | -| TINYTEXT | 0-255 bytes | 短文本字符串 | -| BLOB | 0-65 535 bytes | 二进制形式的长文本数据 | -| TEXT | 0-65 535 bytes | 长文本数据 | -| MEDIUMBLOB | 0-16 777 215 bytes | 二进制形式的中等长度文本数据 | -| MEDIUMTEXT | 0-16 777 215 bytes | 中等长度文本数据 | -| LONGBLOB | 0-4 294 967 295 bytes | 二进制形式的极大文本数据 | -| LONGTEXT | 0-4 294 967 295 bytes | 极大文本数据 | - -+ `char`声明的是定长字符串。若实际中字符串长度不足,则会在末尾使用空格进行填充至声明的长度。 - -+ `varchar`声明的是可变长字符串。存储过程中,只会按照字符串的实际长度来存储,但会多占用一位来存放实际字节的长度。 - - - -# 三、 数据库的基本操作 - -首先,我们来学习在MySQL下如何操作数据库。 - -## 3.1 数据库的创建 - -通过`CREATE`命令,可以创建指定名称的数据库,语法结构如下: - -```mysql -CREATE DATABASE [IF NOT EXISTS] <数据库名称>; -``` - -MySQL 的数据存储区将以目录方式表示 MySQL 数据库,因此数据库名称必须符合操作系统的文件夹命名规则,不能以数字开头,尽量要有实际意义。 - -MySQL下不运行存在两个相同名字的数据库,否则会报错。如果使用`IF NOT EXISTS`(可选项),可以避免此类错误。 - -示例: - -```mysql --- 创建名为shop的数据库。 -CREATE DATABASE shop; -``` - - - -## 3.2 数据库的查看 - -1. 查看所有存在的数据库 - -```MYSQL -SHOW DATABASES [LIKE '数据库名'];; -``` - -`LIKE`从句是可选项,用于匹配指定的数据库名称。`LIKE` 从句可以部分匹配,也可以完全匹配。 - -示例: - -```mysql -SHOW DATABASES; - --- 结果如下: -+--------------------+ -| Database | -+--------------------+ -| information_schema | -| mysql | -| performance_schema | -| shop | -| sys | -+--------------------+ -5 rows in set (0.01 sec) -``` - -```mysql --- %表示任意0个或多个字符,可匹配任意类型和长度的字符。 -SHOW DATABASES LIKE 'S%'; - --- 结果如下 -+---------------+ -| Database (S%) | -+---------------+ -| shop | -| sys | -+---------------+ -2 rows in set (0.00 sec) -``` - -2. 查看创建的数据库 - -```mysql -SHOW CREATE DATABASE <数据库名>; -``` - -示例: - -```mysql -SHOW CREATE DATABASE shop; - --- 或者 -SHOW CREATE DATABASE shop \G - --- 结果如下 -*************************** 1. row *************************** - Database: shop -Create Database: CREATE DATABASE `shop` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */ -1 row in set (0.00 sec) -``` - -`CHARACTER SET utf8mb4`表示编码字符集为`utf8mb4`。 - - - -## 3.3 选择数据库 - -在操作数据库前,必须指定所要操作的数据库。通过`USE`命令,可以切换到对应的数据库下。 - -```mysql -USE <数据库名> -``` - -示例: - -```mysql --- 切换到数据库shop下。 -USE shop; - --- 结果如下 -Database changed -``` - - - -## 3.4 删除数据库 - -通过`DROP`命令,可以将相应数据库进行删除。 - -```mysql -DROP DATABASE [IF EXISTS] <数据库名> -``` - -其中,`IF EXISTS`为可选性,用于防止数据库不存在时报错。 - -示例: - -```mysql -DROP DATABASE shop; - -SHOW DATABASES; -``` - -考虑到后面表的操作都是shop数据库下,在实验完`DROP`删除数据库命令后,请从新创建数据库shop并通过`USE`命令切换到该数据库下。 - - - -# 四、表的基本操作 - -表相当于文件,表中的一条记录就相当于文件的一行内容,不同的是,表中的一条记录有对应的标题,称为表的字段。 - -## 4.1 表的创建 - -创建表的语法结构如下: - -```mysql -CREATE TABLE <表名> (<字段1> <数据类型> <该列所需约束>, - <字段2> <数据类型> <该列所需约束>, - <字段3> <数据类型> <该列所需约束>, - <字段4> <数据类型> <该列所需约束>, - . - . - . - <该表的约束1>, <该表的约束2>,……); -``` - -示例: - -```mysql --- 创建一个名为Product的表 -CREATE TABLE Product( - product_id CHAR(4) NOT NULL, - product_name VARCHAR(100) NOT NULL, - product_type VARCHAR(32) NOT NULL, - sale_price INT, - purchase_price INT, - regist_date DATE, - PRIMARY KEY (product_id) -); -``` - -在第二章中,我们介绍过不同的数据类型: - -+ `CHAR`为定长字符,这里`CHAR`旁边括号里的数字表示该字段最长为多少字符,少于该数字将会使用空格进行填充。 - -+ `VARCHAR`表示变长字符,括号里的数字表示该字段最长为多少字符,存储时只会按照字符的实际长度来存储,但会使用额外的1-2字节来存储值长度。 - - - -简单介绍一下该语句中出现的约束条件,约束条件在后面会详细介绍: - -+ `PRIMARY KEY`:主键,表示该字段对应的内容唯一且不能为空。 -+ `NOT NULL`:在 `NULL` 之前加上了表示否定的` NOT`,表示该字段不能输入空白。 - -通过`SHOW TABLES`命令来查看当前数据库下的所有的表名: - -```mysql -SHOW TABLES; - --- 结果如下 -+----------------+ -| Tables_in_shop | -+----------------+ -| Product | -+----------------+ -1 rows in set (0.00 sec) -``` - -通过`DESC <表名>`来查看表的结构: - -```mysql -DESC Product; - --- 结果如下 -+----------------+--------------+------+-----+---------+-------+ -| Field | Type | Null | Key | Default | Extra | -+----------------+--------------+------+-----+---------+-------+ -| product_id | char(4) | NO | PRI | NULL | | -| product_name | varchar(100) | NO | | NULL | | -| product_type | varchar(32) | NO | | NULL | | -| sale_price | int | YES | | NULL | | -| purchase_price | int | YES | | NULL | | -| regist_date | date | YES | | NULL | | -+----------------+--------------+------+-----+---------+-------+ -6 rows in set (0.00 sec) -``` - - - -## 4.2 表的删除 - -删除表的语法结构如下: - -```mysql -DROP TABLE <表名>; - --- 例如:DROP TABLE Product; -``` - -说明:通过`DROP`删除的表示无法恢复的,在删除表的时候请谨慎。 - - - -## 4.3 表的更新 - -通过`ALTER TABLE`语句,我们可以对表字段进行不同的操作,下面通过示例来具体学习用法。 - -示例: - -1. 创建一张名为Student的表 - -```mysql -CREATE TABLE Student( - id INT PRIMARY KEY, - name CHAR(15) -); -``` - - - -```mysql -DESC student; - --- 结果如下 -+-------+----------+------+-----+---------+-------+ -| Field | Type | Null | Key | Default | Extra | -+-------+----------+------+-----+---------+-------+ -| id | int | NO | PRI | NULL | | -| name | char(15) | YES | | NULL | | -+-------+----------+------+-----+---------+-------+ -2 rows in set (0.00 sec) -``` - -2. 更改表名 - - 通过`RENAME`命令,将表名从Student => Students。 - -```mysql -ALTER TABLE Student RENAME Students; -``` - -3. 插入新的字段 - - 通过`ADD`命令,新增字段sex和age。 - -```mysql --- 不同的字段通过逗号分开 -ALTER TABLE Students ADD sex CHAR(1), ADD age INT; -``` - -​ 其它插入技巧: - -```mysql --- 通过FIRST在表首插入字段stu_num -ALTER TABLE Students ADD stu_num INT FIRST; - --- 指定在字段sex后插入字段height -ALTER TABLE Students ADD height INT AFTER sex; -``` - -```mysql -DESC Students; - --- 结果如下 -+---------+----------+------+-----+---------+-------+ -| Field | Type | Null | Key | Default | Extra | -+---------+----------+------+-----+---------+-------+ -| stu_num | int | YES | | NULL | | -| id | int | NO | PRI | NULL | | -| name | char(15) | YES | | NULL | | -| sex | char(1) | YES | | NULL | | -| height | int | YES | | NULL | | -| age | int | YES | | NULL | | -+---------+----------+------+-----+---------+-------+ -6 rows in set (0.00 sec) -``` - -4. 字段的删除 - - 通过`DROP`命令,可以对不在需要的字段进行删除。 - -```mysql --- 删除字段stu_num -ALTER TABLE Students DROP stu_num; -``` - -5. 字段的修改 - - 通过`MODIFY`修改字段的数据类型。 - -```mysql --- 修改字段age的数据类型 -ALTER TABLE Students MODIFY age CHAR(3); -``` - -​ 通过`CHANGE`命令,修改字段名或类型 - -```mysql --- 修改字段name为stu_name,不修改数据类型 -ALTER TABLE Students CHANGE name stu_name CHAR(15); - --- 修改字段sex为stu_sex,数据类型修改为int -ALTER TABLE Students CHANGE sex stu_sex INT; -``` - -```mysql -DESC Students; - --- 结果如下 -+----------+----------+------+-----+---------+-------+ -| Field | Type | Null | Key | Default | Extra | -+----------+----------+------+-----+---------+-------+ -| id | int | NO | PRI | NULL | | -| stu_name | char(20) | YES | | NULL | | -| stu_sex | int | YES | | NULL | | -| height | int | YES | | NULL | | -| age | char(3) | YES | | NULL | | -+----------+----------+------+-----+---------+-------+ -5 rows in set (0.00 sec) -``` - - - -## 4.4 表的查询 - -通过`SELECT`语句,可以从表中取出所要查看的字段的内容: - -```mysql -SELECT <字段名>, …… - FROM <表名>; -``` - -如要直接查询表的全部字段: - -```mysql -SELECT * - FROM <表名>; -``` - -其中,**星号(*)**代表全部字段的意思。 - -示例: - -1. 建表并插入数据 - - 在MySQL中,我们通过`INSERT`语句往表中插入数据,该语句在后面会详细介绍,该小节的重点是学会使用`SELECT`。 - -```mysql --- 向Product表中插入数据 -INSERT INTO Product VALUES - ('0001', 'T恤衫', '衣服', 1000, 500, '2009-09-20'), - ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11'), - ('0003', '运动T恤', '衣服', 4000, 2800, NULL), - ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'), - ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'), - ('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20'), - ('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28'), - ('0008', '圆珠笔', '办公用品', 100, NULL,'2009-11-11') - ; -``` - -2. 查看表的内容 - -```mysql --- 查看表的全部内容 -SELECT * - FROM Product; - --- 结果如下 -+------------+--------------+--------------+------------+----------------+-------------+ -| product_id | product_name | product_type | sale_price | purchase_price | regist_date | -+------------+--------------+--------------+------------+----------------+-------------+ -| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | -| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | -| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | -| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | -| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | -| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | -| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | -| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | -+------------+--------------+--------------+------------+----------------+-------------+ -8 rows in set (0.00 sec) -``` - -```mysql --- 查看部分字段包含的内容 -SELECT - product_id, - product_name, - sale_price - FROM Product; - --- 结果如下 -+------------+--------------+------------+ -| product_id | product_name | sale_price | -+------------+--------------+------------+ -| 0001 | T恤衫 | 1000 | -| 0002 | 打孔器 | 500 | -| 0003 | 运动T恤 | 4000 | -| 0004 | 菜刀 | 3000 | -| 0005 | 高压锅 | 6800 | -| 0006 | 叉子 | 500 | -| 0007 | 擦菜板 | 880 | -| 0008 | 圆珠笔 | 100 | -+------------+--------------+------------+ -8 rows in set (0.00 sec) -``` - -3. 对查看的字段从新命名 - - 通过`AS`语句对展示的字段另起别名,这不会修改表内字段的名字。 - -```mysql -SELECT - product_id AS ID, - product_type AS TYPE - FROM Product; - --- 结果如下 -+------+--------------+ -| ID | TYPE | -+------+--------------+ -| 0001 | 衣服 | -| 0002 | 办公用品 | -| 0003 | 衣服 | -| 0004 | 厨房用具 | -| 0005 | 厨房用具 | -| 0006 | 厨房用具 | -| 0007 | 厨房用具 | -| 0008 | 办公用品 | -+------+--------------+ -8 rows in set (0.00 sec) -``` - -​ 设定汉语别名时需要使用双引号(")括起来,英文字符则不需要。 - -```Mysql -SELECT - product_id AS "产品编号", - product_type AS "产品类型" - FROM Product; -``` - -4. 常数的查询 - - `SELECT`子句中,除了可以写字段外,还可以写常数。 - -```mysql -SELECT - '商品' AS string, - '2009-05-24' AS date, - product_id, - product_name - FROM Product; - --- 结果如下 -+--------+------------+------------+--------------+ -| string | date | product_id | product_name | -+--------+------------+------------+--------------+ -| 商品 | 2009-05-24 | 0001 | T恤衫 | -| 商品 | 2009-05-24 | 0002 | 打孔器 | -| 商品 | 2009-05-24 | 0003 | 运动T恤 | -| 商品 | 2009-05-24 | 0004 | 菜刀 | -| 商品 | 2009-05-24 | 0005 | 高压锅 | -| 商品 | 2009-05-24 | 0006 | 叉子 | -| 商品 | 2009-05-24 | 0007 | 擦菜板 | -| 商品 | 2009-05-24 | 0008 | 圆珠笔 | -+--------+------------+------------+--------------+ -8 rows in set (0.00 sec) -``` - -5. 删除重复行 - - 在`SELECT`语句中使用`DISTINCT`可以去除重复行。 - -```mysql -SELECT - DISTINCT regist_date - FROM Product; - --- 结果如下 -+-------------+ -| regist_date | -+-------------+ -| 2009-09-20 | -| 2009-09-11 | -| NULL | -| 2009-01-15 | -| 2008-04-28 | -| 2009-11-11 | -+-------------+ -6 rows in set (0.01 sec) -``` - -​ 在使用`DISTINCT` 时,`NULL `也被视为一类数据。`NULL `存在于多行中时,会被合并为一条`NULL `数据。 - -​ 还可以通过组合使用,来去除列组合重复的数据。`DISTINCT `关键字只能用在第一个列名之前。 - -```mysql -SELECT - DISTINCT product_type, regist_date - FROM Product; - --- 结果如下,列出了所有的组合 -+--------------+-------------+ -| product_type | regist_date | -+--------------+-------------+ -| 衣服 | 2009-09-20 | -| 办公用品 | 2009-09-11 | -| 衣服 | NULL | -| 厨房用具 | 2009-09-20 | -| 厨房用具 | 2009-01-15 | -| 厨房用具 | 2008-04-28 | -| 办公用品 | 2009-11-11 | -+--------------+-------------+ -7 rows in set (0.00 sec) -``` - -6. 指定查询条件 - - 首先通过`WHERE` 子句查询出符合指定条件的记录,然后再选取出` SELECT `语句指定的列,语法结构如下: - -```mysql -SELECT <字段名>, …… - FROM <表名> - WHERE <条件表达式>; -``` - -​ 示例: - -```mysql -SELECT product_name - FROM Product - WHERE product_type = '衣服'; - --- 结果如下 -+--------------+ -| product_name | -+--------------+ -| T恤衫 | -| 运动T恤 | -+--------------+ -2 rows in set (0.01 sec) -``` - -注意,`WHERE`子句要紧跟在`FROM`子句之后。 - - - -## 4.5 表的复制 - -表的复制可以将表结构与表中的数据全部复制,或者只复制表的结构。 - -```mysql --- 将整个表复制过来 -CREATE TABLE Product_COPY1 - SELECT * FROM Product; - -SELECT * FROM Product_COPY1; - --- 结果如下 -+------------+--------------+--------------+------------+----------------+-------------+ -| product_id | product_name | product_type | sale_price | purchase_price | regist_date | -+------------+--------------+--------------+------------+----------------+-------------+ -| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | -| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | -| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | -| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | -| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | -| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | -| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | -| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | -+------------+--------------+--------------+------------+----------------+-------------+ -8 rows in set (0.00 sec) -``` - -```mysql --- 通过LIKE复制表结构 -CREATE TABLE Product_COPY2 - LIKe Product; - -SELECT * FROM Product_COPY2; - --- 结果如下 -Empty set (0.00 sec) -- 表为空的 - -DESC Product_COPY2; - --- 结果如下 --- 表结构已复制过来 -+----------------+--------------+------+-----+---------+-------+ -| Field | Type | Null | Key | Default | Extra | -+----------------+--------------+------+-----+---------+-------+ -| product_id | char(4) | NO | PRI | NULL | | -| product_name | varchar(100) | NO | | NULL | | -| product_type | varchar(32) | NO | | NULL | | -| sale_price | int | YES | | 0 | | -| purchase_price | int | YES | | NULL | | -| regist_date | date | YES | | NULL | | -+----------------+--------------+------+-----+---------+-------+ -6 rows in set (0.01 sec) -``` - - - -# 五、运算符 - -## 5.1 算术运算符 - -我们可以在`SELECT`语句中使用计算表达式: - -```mysql -SELECT - product_name, - sale_price, - sale_price * 2 AS "sale_price_x2" - FROM Product; - --- 结果如下 -+--------------+------------+---------------+ -| product_name | sale_price | sale_price_x2 | -+--------------+------------+---------------+ -| T恤衫 | 1000 | 2000 | -| 打孔器 | 500 | 1000 | -| 运动T恤 | 4000 | 8000 | -| 菜刀 | 3000 | 6000 | -| 高压锅 | 6800 | 13600 | -| 叉子 | 500 | 1000 | -| 擦菜板 | 880 | 1760 | -| 圆珠笔 | 100 | 200 | -+--------------+------------+---------------+ -8 rows in set (0.00 sec) -``` - -+ 四则运算所使用的运算符**(+、-、*、/)**称为算术运算符。 - -+ 在运算表达式中,也可以使用**()**,括号中的运算表达式优先级会得到提升。 - -+ **NULL**的计算结果,仍然还是**NULL**。 - - - -## 5.2 比较运算符 - -在 `WHERE` 子句中通过使用比较运算符可以组合出各种各样的条件表达式。 - -```mysql -SELECT product_name, product_type - FROM Product - WHERE sale_price = 500; -``` - -常见比较运算符如下表: - -| 运算符 | 含义 | -| ------ | -------- | -| = | 相等 | -| <> | 不相等 | -| \>= | 大于等于 | -| \> | 大于 | -| <= | 小于等于 | -| < | 小于 | - -+ 不能对**NULL**使用任何比较运算符,只能通过`IS NULL`语句来判断: - -```mysql -SELECT - product_name, - purchase_price - FROM Product - WHERE purchase_price IS NULL; -``` - -​ 希望选取不是 NULL 的记录时,需要使用`IS NOT NULL`运算符。 - -+ 对字符串使用比较符 - -​ MySQL中字符串的排序与数字不同,典型的规则就是按照字典顺序进行比较,也就是像姓名那样,按照条目在字典中出现的顺序来进行排序。例如: - -```mysql -'1' < '10' < '11' < '2' < '222' < '3' -``` - - - -## 5.3 逻辑运算符 - -1. 使用`NOT`否认某一条件: - -```mysql -SELECT - product_name, - product_type, - sale_price - FROM Product - WHERE NOT sale_price >= 1000; -``` - -2. `AND`运算符合`OR`运算符 - -```mysql -SELECT product_type, sale_price - FROM Product - WHERE product_type = '厨房用具' - AND sale_price >= 3000; - --- 结果如下 -+--------------+------------+ -| product_type | sale_price | -+--------------+------------+ -| 厨房用具 | 3000 | -| 厨房用具 | 6800 | -+--------------+------------+ -2 rows in set (0.00 sec) -``` - -```mysql -SELECT product_type, sale_price - FROM Product - WHERE product_type = '厨房用具' - OR sale_price >= 3000; - --- 结果如下 -+--------------+------------+ -| product_type | sale_price | -+--------------+------------+ -| 衣服 | 4000 | -| 厨房用具 | 3000 | -| 厨房用具 | 6800 | -| 厨房用具 | 500 | -| 厨房用具 | 880 | -+--------------+------------+ -5 rows in set (0.00 sec) -``` - -3. 逻辑运算符和真值 - -+ 符**NOT**、**AND** 和 **OR** 称为逻辑运算符; -+ 真值就是值为**真(TRUE)**或**假 (FALSE)**; - -+ 在查询**NULL**时,SQL中存在第三种真值,**不确定(UNKNOWN)**,**NULL**和任何值做逻辑运算结果都是不确定; -+ 考虑 **NULL** 时的条件判断也会变得异常复杂,因此尽量给字段加上**NOT NULL**的约束。 - - - -# 六、分组查询 - -## 6.1 聚合函数 - -通过 SQL 对数据进行某种操作或计算时需要使用函数。 - -+ `COUNT`:计算表中的记录数(行数) - -+ `SUM`: 计算表中数值列中数据的合计值 - -+ `AVG`: 计算表中数值列中数据的平均值 - -+ `MAX`: 求出表中任意列中数据的最大值 - -+ `MIN`: 求出表中任意列中数据的最小值 - -示例: - -```mysql --- 计算全部数据的行数 -SELECT COUNT(*) FROM Product; - --- 结果如下 -+----------+ -| COUNT(*) | -+----------+ -| 8 | -+----------+ -1 row in set (0.00 sec) -``` - -**注意点1**:除了`COUNT`可以将`*`作为参数,其它的函数均不可以。 - -```mysql --- 计算最高的销售价格 -SELECT MAX(sale_price) FROM Product; - --- 结果如下 -+-----------------+ -| MAX(sale_price) | -+-----------------+ -| 680000 | -+-----------------+ -1 row in set (0.00 sec) -``` - -**注意点2:**当将字段名作为参数传递给函数时,只会计算不包含`NULL`的行。 - -示例: - -```mysql --- purchase_price字段是包含NULL值的 -SELECT purchase_price FROM Product; - --- 结果如下 -+----------------+ -| purchase_price | -+----------------+ -| 500 | -| 320 | -| 2800 | -| 700 | -| 1250 | -| NULL | -| 198 | -| NULL | -+----------------+ -8 rows in set (0.00 sec) -``` - -以*为参数传递给`COUNT`函数 - -```mysql -SELECT COUNT(*) FROM Product; - --- 结果如下 -+----------+ -| COUNT(*) | -+----------+ -| 8 | -+----------+ -1 row in set (0.00 sec) -``` - -以purchase_price为参数传递给`COUNT`函数 - -```mysql -SELECT COUNT(purchase_price) FROM Product; - --- 结果如下 -+-----------------------+ -| COUNT(purchase_price) | -+-----------------------+ -| 6 | -+-----------------------+ -1 row in set (0.00 sec) -``` - -可以看到结果并不一样,函数忽略了值为**NULL**的行。 - -`SUM`,`AVG`函数时也一样,计算时会直接忽略,**并不会当做0来处理!**特别注意`AVG`函数,计算时分母也不会算上`NULL`行。 - -**注意点3**:`MAX/MIN`函数几乎适用于所有数据类型的列,包括字符和日期。`SUM/AVG`函数只适用于数值类型的列。 - -**注意点4**:在聚合函数删除重复值 - -```mysql -SELECT COUNT(DISTINCT product_type) - FROM Product; - --- 结果如下 -+------------------------------+ -| COUNT(DISTINCT product_type) | -+------------------------------+ -| 3 | -+------------------------------+ -1 row in set (0.01 sec) -``` - -`DISTINCT`必须写在括号中。这是因为必须要在计算行数之前删除 product_type 字段中的重复数据。 - - - -## 6.2 对表分组 - -如果对Python的Pandas熟悉,那么大家应该很了解`groupby`函数,可以根据指定的列名,对表进行分组。在MySQL中,也存在同样作用的函数,即`GROUP BY`。 - -语法结构如下: - -```mysql -SELECT <列名1>, <列名2>, <列名3>, …… - FROM <表名> - GROUP BY <列名1>, <列名2>, <列名3>, ……; -``` - -示例: - -```mysql -SELECT product_type, COUNT(*) - FROM Product - GROUP BY product_type; - --- 结果如下 -+--------------+----------+ -| product_type | COUNT(*) | -+--------------+----------+ -| 衣服 | 2 | -| 办公用品 | 2 | -| 厨房用具 | 4 | -+--------------+----------+ -3 rows in set (0.01 sec) -``` - -1. 在该语句中,我们首先通过`GROUP BY`函数对指定的字段product_type进行分组。分组时,product_type字段中具有相同值的行会汇聚到同一组。 - -2. 最后通过`COUNT`函数,统计不同分组的包含的行数。 - -简单来理解: - -+ 例如做操时,老师将不同身高的同学进行分组,相同身高的同学会被分到同一组,分组后我们又统计了每个小组的学生数。 - -+ 将这里的同学可以理解为表中的一行数据,身高理解为表的某一字段。 -+ 分组操作就是`GROUP BY`,`GROUP BY`后面接的字段等价于按照身高分组,统计学生数就等价于在`SELECT`后用了`COUNT(*)`函数。 - -注意:`GROUP BY `子句的位置一定要写在`FROM` 语句之后(如果有 `WHERE` 子句的话需要写在 `WHERE` 子句之后) - -``` -1. SELECT → 2. FROM → 3. WHERE → 4. GROUP BY -``` - -当被聚合的键中,包含`NULL`时,在结果中会以“不确定”行(空行)的形式表现出来,也就是字段中为`NULL`的数据会被聚合为一组。 - -## 6.3 使用WHERE语句 - -在对表进行分组之前,也可以是先使用`WHERE`对表进行条件过滤,然后再进行分组处理。语法结构如下: - -```mysql -SELECT <列名1>, <列名2>, <列名3>, …… - FROM <表名> - WHERE - GROUP BY <列名1>, <列名2>, <列名3>, ……; -``` - -示例: - -```mysql --- WHERE语句先将表中类型为衣服的行筛选出来 --- 然后再按照purchase_price来进行分组 -SELECT purchase_price, COUNT(*) - FROM Product - WHERE product_type = '衣服' - GROUP BY purchase_price; - --- 结果如下 -+----------------+----------+ -| purchase_price | COUNT(*) | -+----------------+----------+ -| 500 | 1 | -| 2800 | 1 | -+----------------+----------+ -2 rows in set (0.01 sec) -``` - -该语法实际的执行顺序为: - -``` -FROM → WHERE → GROUP BY → SELECT -``` - -+ 使用`GROUP BY`子句时,`SELECT`子句中不能出现聚合键之外的字段名。即,若`GROUP BY`选中purchase_price字段进行分组,则在`SELECT`语句中只能选中purchase_price字段,其它字段如product_id等均不行。 -+ `WHERE`语句中,不可以使用聚合函数。`WHERE`子句只能指定记录(行)的条件,而不能用来指定组的条件。即`WHERE MAX(purchase_price) > 1000`这样的语句是非法的。 - - - -## 6.4 为聚合结果指定条件 - -前面提到了`WHERE`语句中不能使用聚合函数,但是实际操作时需要通过聚合函数来进行过滤怎么办呢?这就要用到`HAVING`语句了。语法结构如下: - -```mysql -SELECT <列名1>, <列名2>, <列名3>, …… - FROM <表名> - GROUP BY <列名1>, <列名2>, <列名3>, …… -HAVING <分组结果对应的条件> -``` - -在`HAVING`的子句中能够使用的 3 种要素如下所示: - -● 常数 - -● 聚合函数 - -● `GROUP BY`子句中指定的字段名(即聚合键) - -示例: - -```mysql --- 不使用HAVING语句 -SELECT product_type, AVG(sale_price) - FROM Product - GROUP BY product_type; - --- 结果如下 -+--------------+-----------------+ -| product_type | AVG(sale_price) | -+--------------+-----------------+ -| 衣服 | 2500.0000 | -| 办公用品 | 300.0000 | -| 厨房用具 | 279500.0000 | -+--------------+-----------------+ -3 rows in set (0.00 sec) -``` - -```mysql --- 使用HAVING语句 --- 通过HAVING语句将销售平均价格大于等于2500的组给保留了 -SELECT product_type, AVG(sale_price) - FROM Product - GROUP BY product_type -HAVING AVG(sale_price) >= 2500; - --- 结果如下 -+--------------+-----------------+ -| product_type | AVG(sale_price) | -+--------------+-----------------+ -| 衣服 | 2500.0000 | -| 厨房用具 | 279500.0000 | -+--------------+-----------------+ -2 rows in set (0.00 sec) -``` - -可以看到使用`HAVING`语句后,输出的结果有所变化。大致流程如下: - -+ 首先,`FROM`语句会选中表Product; -+ 然后,`GROUP BY`语句会选中字段product_type进行分组; -+ 之后,通过`HAVING`语句将销售平均价格大于等于2500的组保留下来; -+ 最后,通过`SELECT`语句将保留下的组的产品类型和平均价格显示出来; - - - -如果是对**表的行**进行条件指定,`WHERE`和`HAVING`都可以生效。 - -```mysql --- 下面两条语句执行结果一致 -SELECT product_type, COUNT(*) - FROM Product - GROUP BY product_type - HAVING product_type = '衣服'; - -SELECT product_type, COUNT(*) - FROM Product - WHERE product_type = '衣服' - GROUP BY product_type; - --- 结果如下 -+--------------+----------+ -| product_type | COUNT(*) | -+--------------+----------+ -| 衣服 | 2 | -+--------------+----------+ -1 row in set (0.01 sec) -``` - -但是,一般而言如果是对表的行进行条件指定,最好还是使用`WHERE`语句,因为`WHERE`的执行速度更快。 - - - -## 6.5 对表的查询结果进行排序 - -如果希望对表的查询结果根据某指定的字段进行排序,可以使用`ORDER BY`语句。语法结构如下: - -```mysql -SELECT <列名1>, <列名2>, <列名3>, …… - FROM <表名> - ORDER BY <排序基准列1>, <排序基准列2>, …… -``` - -示例: - -```mysql -SELECT product_id, product_name, sale_price, purchase_price - FROM Product; - --- 结果如下 -+------------+--------------+------------+----------------+ -| product_id | product_name | sale_price | purchase_price | -+------------+--------------+------------+----------------+ -| 0001 | T恤衫 | 1000 | 500 | -| 0002 | 打孔器 | 500 | 320 | -| 0003 | 运动T恤 | 4000 | 2800 | -| 0004 | 菜刀 | 300000 | 700 | -| 0005 | 高压锅 | 680000 | 1250 | -| 0006 | 叉子 | 50000 | NULL | -| 0007 | 擦菜板 | 88000 | 198 | -| 0008 | 圆珠笔 | 100 | NULL | -+------------+--------------+------------+----------------+ -8 rows in set (0.01 sec) -``` - -```mysql --- 根据字段sale_price的值进行排序 -SELECT product_id, product_name, sale_price, purchase_price - FROM Product -ORDER BY sale_price; - --- 结果如下 -+------------+--------------+------------+----------------+ -| product_id | product_name | sale_price | purchase_price | -+------------+--------------+------------+----------------+ -| 0008 | 圆珠笔 | 100 | NULL | -| 0002 | 打孔器 | 500 | 320 | -| 0001 | T恤衫 | 1000 | 500 | -| 0003 | 运动T恤 | 4000 | 2800 | -| 0006 | 叉子 | 50000 | NULL | -| 0007 | 擦菜板 | 88000 | 198 | -| 0004 | 菜刀 | 300000 | 700 | -| 0005 | 高压锅 | 680000 | 1250 | -+------------+--------------+------------+----------------+ -8 rows in set (0.00 sec) -``` - -可以看到`ORDER BY`默认是按照升序的方式进行排序的,正式的书写方式应该是在字段后加上关键字`ASC`,即`ORDER BY sale_price ASC`。 - -如果我们希望按照降序的方式,可以通过`DESC`关键词进行指定。 - -```mysql -SELECT product_id, product_name, sale_price, purchase_price - FROM Product -ORDER BY sale_price DESC; - --- 结果如下 -+------------+--------------+------------+----------------+ -| product_id | product_name | sale_price | purchase_price | -+------------+--------------+------------+----------------+ -| 0005 | 高压锅 | 680000 | 1250 | -| 0004 | 菜刀 | 300000 | 700 | -| 0007 | 擦菜板 | 88000 | 198 | -| 0006 | 叉子 | 50000 | NULL | -| 0003 | 运动T恤 | 4000 | 2800 | -| 0001 | T恤衫 | 1000 | 500 | -| 0002 | 打孔器 | 500 | 320 | -| 0008 | 圆珠笔 | 100 | NULL | -+------------+--------------+------------+----------------+ -8 rows in set (0.00 sec) -``` - -前面展示了指定一个字段来对表进行排序,实际上我们可以指定多个字段来进行排序。 - -示例: - -```mysql -SELECT regist_date, product_id, sale_price, purchase_price - FROM Product -ORDER BY regist_date, product_id; - --- 结果如下 -+-------------+------------+------------+----------------+ -| regist_date | product_id | sale_price | purchase_price | -+-------------+------------+------------+----------------+ -| 2009-10-10 | 0002 | 500 | 320 | -| 2009-10-10 | 0003 | 4000 | 2800 | -| 2009-10-10 | 0004 | 300000 | 700 | -| 2009-10-10 | 0005 | 680000 | 1250 | -| 2009-10-10 | 0006 | 50000 | NULL | -| 2009-10-10 | 0007 | 88000 | 198 | -| 2009-10-10 | 0008 | 100 | NULL | -| 2021-10-30 | 0001 | 1000 | 500 | -+-------------+------------+------------+----------------+ -``` - -可以看到先按照`regist_date`的大小进行排序,在字段`regist_date`中具有相同的值的行,接着会按照`product_id`进行排序。 - -使用含有 NULL 的列作为排序键时,NULL 会在结果的开头或末尾汇总显示。 - -在`ORDER BY`子句中可以使用`SELECT`子句中定义的别名。 - -```mysql --- 将product_id命名为ID,然后按照ID进行排序 -SELECT product_id as ID, product_name, sale_price, purchase_price - FROM Product -ORDER BY ID; - --- 结果如下 -+------+--------------+------------+----------------+ -| ID | product_name | sale_price | purchase_price | -+------+--------------+------------+----------------+ -| 0001 | T恤衫 | 1000 | 500 | -| 0002 | 打孔器 | 500 | 320 | -| 0003 | 运动T恤 | 4000 | 2800 | -| 0004 | 菜刀 | 300000 | 700 | -| 0005 | 高压锅 | 680000 | 1250 | -| 0006 | 叉子 | 50000 | NULL | -| 0007 | 擦菜板 | 88000 | 198 | -| 0008 | 圆珠笔 | 100 | NULL | -+------+--------------+------------+----------------+ -8 rows in set (0.00 sec) -``` - -为什么`ORDER BY`中可以使用`SELECT`定义的别名呢? - -这是因为在MySQL中,`ORDER BY `的执行次序在`SELECT`之后。 - - - -# 七、数据的插入及更新 - -## 7.1 数据的插入 - -通过命令`INSERT`,可以向表中插入数据: - -```mysql --- 往表中插入一行数据 -INSERT INTO <表名> (字段1, 字段2, 字段3, ……) VALUES (值1, 值2, 值3, ……); - --- 往表中插入多行数据 -INSERT INTO <表名> (字段1, 字段2, 字段3, ……) VALUES - (值1, 值2, 值3, ……), - (值1, 值2, 值3, ……), - ... - ; -``` - -示例: - -1. 创建表并插入数据 - -```mysql --- 创建表 -CREATE TABLE ProductIns -(product_id CHAR(4) NOT NULL, - product_name VARCHAR(100) NOT NULL, - product_type VARCHAR(32) NOT NULL, - sale_price INTEGER DEFAULT 0, -- DEFAULT 0:表示将字段sale_price的默认值设为0 - purchase_price INT , - regist_date DATE , - PRIMARY KEY (product_id)); - --- 通过单行方式插入 -INSERT INTO - ProductIns(product_id, product_name, product_type, sale_price, purchase_price, regist_date) - VALUES ('0001', '打孔器', '办公用品', 500, 320, '2009-09-11'); - --- 当对表插入全字段时,可以省略表后的字段清单 -INSERT INTO ProductIns VALUES('0002', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'); - --- 通过多行方式插入 -INSERT INTO ProductIns VALUES - ('0003', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'), - ('0004', '订书机', '办公用品', 100, 50, '2009-09-11'), - ('0005', '裙子', '衣服', 4100, 3200, '2009-01-23'), - ('0006', '运动T恤', '衣服', 4000, 2800, NULL), - ('0007', '牙刷', '日用品', 20, 10, '2010-03-22'); -``` - -```mysql -SELECT * FROM ProductIns; - --- 结果如下 -+------------+--------------+--------------+------------+----------------+-------------+ -| product_id | product_name | product_type | sale_price | purchase_price | regist_date | -+------------+--------------+--------------+------------+----------------+-------------+ -| 0001 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | -| 0002 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | -| 0003 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | -| 0004 | 订书机 | 办公用品 | 100 | 50 | 2009-09-11 | -| 0005 | 裙子 | 衣服 | 4100 | 3200 | 2009-01-23 | -| 0006 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | -| 0007 | 牙刷 | 日用品 | 20 | 10 | 2010-03-22 | -+------------+--------------+--------------+------------+----------------+-------------+ -7 rows in set (0.00 sec) -``` - -2. 插入NULL - - `INSERT `语句中想给某一列赋予**NULL**值时,可以直接在` VALUES`子句的值清单中写入**NULL**。 - -```mysql -INSERT INTO ProductIns VALUES ('0008', '叉子', '厨房用具', 500, NULL, '2009-09-20'); -``` - -3. 插入默认值 - - 在前面我们创建表时,字段sale_price包含了一条约束条件,默认为0。我们在插入数据时,可以直接用`DEFAULT`对该字段赋值。前提是,该字段被指定了默认值。 - -```mysql --- 通过显式方法设定默认值 -INSERT INTO - ProductIns (product_id, product_name, product_type, sale_price, purchase_price, regist_date) - VALUES ('0009', '擦菜板', '厨房用具', DEFAULT, 790, '2009-04-28'); - --- 通过隐式方法插入默认值 -INSERT INTO - ProductIns (product_id, product_name, product_type, purchase_price, regist_date) - VALUES ('0010', '擦菜板', '厨房用具', 790, '2009-04-28'); -``` - - - -## 7.2 数据的删除 - -通过`DROP TABLE`或者`DELETE`语句,可以对表进行删除,但二者存在一定的区别。 - -+ `DROP TABLE` 语句可以将表完全删除。 -+ `DELETE` 语句会留下表结构,而删除表中的全部数据。 - -无论通过哪种方式删除,数据都是难以恢复的。 - -1. 通过`DROP`进行删除 - - 语法结构为: - -```mysql -DROP <表名>; -``` - -2. 通过`DELETE`进行删除 - - 语法结构如下,记得要加`FROM`: - -```mysql -DELETE FROM <表名>; -``` - -​ 同时,也可以通过`WHERE`语句来指定删除的条件: - -```mysql -DELETE FROM <表名> - WHERE <条件>; -``` - -​ 需要注意的是,`DELETE`语句的删除对象并不是表或者列,而是记录(行)。 - -示例: - -```mysql -SELECT * FROM Product; - --- 结果如下 -mysql> SELECT * FROM Product; -+------------+--------------+--------------+------------+----------------+-------------+ -| product_id | product_name | product_type | sale_price | purchase_price | regist_date | -+------------+--------------+--------------+------------+----------------+-------------+ -| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | -| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | -| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | -| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | -| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | -| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | -| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | -| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | -+------------+--------------+--------------+------------+----------------+-------------+ -8 rows in set (0.00 sec) - --- 删除销售价格大于等于4000的行 -DELETE FROM Product - WHERE sale_price >= 4000; - --- 结果如下 -mysql> SELECT * FROM Product; -+------------+--------------+--------------+------------+----------------+-------------+ -| product_id | product_name | product_type | sale_price | purchase_price | regist_date | -+------------+--------------+--------------+------------+----------------+-------------+ -| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | -| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | -| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | -| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | -| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | -| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | -+------------+--------------+--------------+------------+----------------+-------------+ -6 rows in set (0.00 sec) -``` - -3. 通过`TRUNCATE`进行删除 - - 在MySQL中,还存在一种删除表的方式,就是利用`TRUNCATE`语句。它的功能和`DROP`类似,但是不能通过`WHERE`指定条件,优点是速度比`DROP`快得多。 - -```mysql -TRUNCATE Product; - --- 结果如下 -mysql> SELECT * FROM Product; -Empty set (0.00 sec) - -mysql> DESC Product; -+----------------+--------------+------+-----+---------+-------+ -| Field | Type | Null | Key | Default | Extra | -+----------------+--------------+------+-----+---------+-------+ -| product_id | char(4) | NO | PRI | NULL | | -| product_name | varchar(100) | NO | | NULL | | -| product_type | varchar(32) | NO | | NULL | | -| sale_price | int | YES | | NULL | | -| purchase_price | int | YES | | NULL | | -| regist_date | date | YES | | NULL | | -+----------------+--------------+------+-----+---------+-------+ -6 rows in set (0.00 sec) -``` - - - -## 7.3 数据的更新 - -当我们使用`INSERT`语句插入错误的数据后,若我们不想删除后从新插入,那就要使用到`UPDATE`语句。 - -1. 基本用法 - - `UPDATE`的语法结构如下: - -```mysql -UPDATE <表名> - SET <字段名> = <表达式>; -``` - -​ 示例: - -```mysql --- 由于前面演示删除语句时,表Product的内容已清空 --- 所以,这里从新进行数据插入 -INSERT INTO Product VALUES - ('0001', 'T恤衫', '衣服', 1000, 500, '2009-09-20'), - ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11'), - ('0003', '运动T恤', '衣服', 4000, 2800, NULL), - ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'), - ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'), - ('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20'), - ('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28'), - ('0008', '圆珠笔', '办公用品', 100, NULL,'2009-11-11') - ; - --- 修改表中所有行regist_date的值 -UPDATE Product - SET regist_date = '2009-10-10'; - --- 结果如下 -mysql> SELECT * FROM Product; -+------------+--------------+--------------+------------+----------------+-------------+ -| product_id | product_name | product_type | sale_price | purchase_price | regist_date | -+------------+--------------+--------------+------------+----------------+-------------+ -| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-10-10 | -| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-10-10 | -| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | 2009-10-10 | -| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-10-10 | -| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-10-10 | -| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-10-10 | -| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2009-10-10 | -| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-10-10 | -+------------+--------------+--------------+------------+----------------+-------------+ -8 rows in set (0.00 sec) -``` - -2. 指定条件 - -```mysql -UPDATE <表名> - SET <列名> = <表达式> - WHERE <条件>; -``` - -​ 示例: - -```mysql -UPDATE Product - SET regist_date = '2021-10-30' - WHERE product_id = '0001'; -``` - -​ 注意,你也可是使用**NULL**对表进行更新,不过更新的字段必须满足没有**主键**和**NOT NULL**的约束条件。 - -3. 多列更新 - - 多列更新只需要用逗号(,)连接更改的字段即可。 - -```mysql -UPDATE Product - SET - sale_price = sale_price * 10, - purchase_price = purchase_price / 2 - WHERE product_type = '厨房用具'; -``` - - - -# 八、Pymysql的使用 - -在正式介绍`pymysql`的用法之前,我们先思考一件事,我们希望借助`pymysql`完成什么事情? - -之前,我们在命令行下,通过输入SQL语句来完成对数据库和表的增删改查。那么,我们也希望能够在Python下能够完成同样的操作,并且能够返回相应的反馈。具体任务包括: - -1. 登陆并连接到MySQL下的用户; -2. 切换到相应的数据库下; -3. 完成对表的增删改查; - -接下来的内容将围绕这3部分来介绍。 - - - -## 8.1 安装pymysql - -通过`pip`,我们可以完成对`pymysql`的安装: - -```bash -python3 -m pip install PyMySQL -``` - - - -## 8.2 连接数据库 - -如果希望在Python中操作MySQL数据库,那么首先就要登陆到MySQL下的用户。 - -我们通过创建库pymysql下的类`connect`的一个实例来登陆到数据库。 - -示例: - -```python -import pymysql - -# 这里登陆到我之前创建的admin账户 -db = pymysql.connect( - host='localhost', - user='admin', - password='mysql123', - database='shop', - charset='utf8mb4', - cursorclass=pymysql.cursors.DictCursor -) -``` - -参数解释: - -+ `host`:数据库服务器地址,默认`localhost`; -+ `user`:所要登陆的用户名; -+ `password`:用户的登录密码; -+ `database`:所要连接的数据库库名; -+ `charset`:使用的字符类型; -+ `cursorclass`:定义游标使用的类型,通过指定游标使用的类型,在返回输出的结果时将按照指定的类型进行返回。例如,这里设置为字典游标。 - - - -## 8.3 创建游标 - -关于游标,可以理解为在命令行中的光标。在命令行中,我们是在光标处键入语句的。这里游标的起到类似作用。 - -```python -# 创建游标 -cursor = db.cursor() -``` - -实际上,除了在初始化`connect`的实例时指定游标类型,我们在初始化游标时也可以指定游标类型,默认为元组类型。 - -```python -cursor = db.cursor(cursor=pymysql.cursors.DictCursor) -``` - -`cursors`共支持四类游标: - -+ `Cursor`: 默认,元组类型 - -+ `DictCursor`: 字典类型 - -+ `SSCursor`: 无缓冲元组类型 - -+ `SSDictCursor`: 无缓冲字典类型 - - - -## 8.4 类方法 - -初始化完类`connect`和`cursor`的实例后,我们先来了解一下这两个类下包含的方法。了解这些方法有利于我们后面在python下操作mysql: - -+ `connect`下的类方法: - + `close()`:在完成操作后,需要关闭与数据库之间的连接; - + `commit()`:如果执行语句中发生了数据更改,需要提交更改到稳定的存储器; - + `cursor(cursor=None)`:创建一个游标,前面我们在初始化`connect`类是指定了游标类型,通过`cursor`初始化游标时,也可以进行游标类型指定; - + `rollback()`:事务回滚; - -+ `pymysql.cursors`下的类方法: - + `close()`:结束时,关闭游标; - + `execute()`:通过游标执行语句; - + `executemany()`:通过游标执行多条语句; - + `fetchone()`:获取单条数据; - + `fetchmany(size=None)`:获取size条数据; - + `fetchall()`:获取单条数据; - + `scroll(value, mode)`:数据的查询操作都是基于游标,可以通过`scroll`控制游标的位置。 - + `mode=absolute`:绝对位置移动,控制游标位置到上一次查询的第`value`条数据,最小值为`0`; - + `mode=relative`:相对位置移动,基于当前位置,跳过`value`条数据; - -更详细的资料,可参考官方的API或者Github: - -[pymysql github]: https://github.com/PyMySQL/PyMySQL - -[pymysql document]: https://pymysql.readthedocs.io/en/latest/modules/index.html# - - - -## 8.5 实战 - -+ 示例1: - -  在这个示例中,我们将做两件事情:创建表和插入数据。 - -```python -import pymysql - -# 以admin身份连接到数据库shop -connection = pymysql.connect( - host='localhost', - user='admin', - password='mysql123', - database='shop', - charset='utf8mb4', -) - -# 创建游标 -cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) - -# 1. 创建了一个表 -sql = """ -CREATE TABLE Employee( - id INT PRIMARY KEY, - name CHAR(15) NOT NULL - ) - """ - -# 提交执行 -cursor.execute(sql) - -# 2. 往表中插入数据 -sql = "INSERT INTO Employee (id, name) VALUES (%s, %s)" -values = [(1, 'XiaoBai'), - (2, 'XiaoHei'), - (3, 'XiaoHong'), - (4, 'XiaoMei'), - (5, 'XiaoLi')] - -try: - # 通过executemany可以插入多条数据 - cursor.executemany(sql, values) - # 提交事务 - connection.commit() -except: - connection.rollback() - - -# 3. 关闭光标及连接 -cursor.close() -connection.close() -``` - -+ 示例2 - - 在示例1的基础上,我们继续执行查询工作。 - -```python -import pymysql - -# 以admin身份连接到数据库shop -connection = pymysql.connect( - host='localhost', - user='admin', - password='mysql123', - database='shop', - charset='utf8mb4', -) - -with connection: - # 创建游标 - cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) - - # 1. 通过fetchone只查询一条 - cursor.execute("SHOW CREATE TABLE Employee") - result = cursor.fetchone() - print(f'查询结果1: \n{result}') - - # 2. 通过fetchmany查询size条 - cursor.execute("DESC Employee") - result = cursor.fetchmany(size=2) - print(f'查询结果2: \n{result}') - - # 3. 通过fetchall查询所有 - cursor.execute("SELECT * FROM Employee") - result = cursor.fetchall() - print(f'查询结果3: \n{result}') - - # 4. 通过scroll回滚到第0条进行查询 - cursor.scroll(0, mode='absolute') - result = cursor.fetchone() - print(f'查询结果4: \n{result}') - - # 5. 通过scroll跳过2条进行查询 - cursor.scroll(2, mode='relative') - result = cursor.fetchone() - print(f'查询结果5: \n{result}') - - cursor.close() -``` - -​ 控制台打印结果如下: - -```bash -查询结果1: -{'Table': 'Employee', 'Create Table': 'CREATE TABLE `Employee` (\n `id` int NOT NULL,\n `name` char(15) NOT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci'} -查询结果2: -[{'Field': 'id', 'Type': 'int', 'Null': 'NO', 'Key': 'PRI', 'Default': None, 'Extra': ''}, {'Field': 'name', 'Type': 'char(15)', 'Null': 'NO', 'Key': '', 'Default': None, 'Extra': ''}] -查询结果3: -[{'id': 1, 'name': 'XiaoBai'}, {'id': 2, 'name': 'XiaoHei'}, {'id': 3, 'name': 'XiaoHong'}, {'id': 4, 'name': 'XiaoMei'}, {'id': 5, 'name': 'XiaoLi'}] -查询结果4: -{'id': 1, 'name': 'XiaoBai'} -查询结果5: -{'id': 4, 'name': 'XiaoMei'} -``` - -+ 示例3: - - ​ 该示例将演示SQL注入的问题。先建立一个表并插入数据: - -```python -import pymysql - -# 以admin身份连接到数据库shop -connection = pymysql.connect( - host='localhost', - user='admin', - password='mysql123', - database='shop', - charset='utf8mb4', -) - -# 创建游标 -cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) - -sql = """ - CREATE TABLE UserInfo( - id INT PRIMARY KEY, - name VARCHAR(15), - password CHAR(15) NOT NULL - ) - """ - -cursor.execute(sql) - -sql = "INSERT INTO UserInfo (id, name, password) VALUES (%s, %s, %s)" -values = [(1, 'XiaoBai', '123'), - (2, 'XiaoHei', '234'), - (3, 'XiaoHong', '567'), - (4, 'XiaoMei', '321'), - (5, 'XiaoLi', '789')] - -cursor.executemany(sql, values) -connection.commit() -``` - -​ 再写一个程序,根据输入判定登陆是否成功: - -```python -import pymysql - -# 以admin身份连接到数据库shop -connection = pymysql.connect( - host='localhost', - user='admin', - password='mysql123', - database='shop', - charset='utf8mb4', -) - -# 创建游标 -cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) - -while True: - user = input("输入用户:").strip() - password = input("输入密码:").strip() - sql = "select name, password from UserInfo where name='%s' and password='%s' " % (user, password) - - cursor.execute(sql) - # 打印用户和密码 - result=cursor.fetchone() - print(result) - - if result: - print("成功登陆\n") - else: - print("登陆失败\n") -``` - -​ 在控制台下,我们进行了三组用户和密码的验证: - -```python -输入用户:XiaoBai -输入密码:123 -{'name': 'XiaoBai', 'password': '123'} -成功登陆 - -输入用户:XiaoBai -输入密码:321 -None -登陆失败 - -输入用户:XiaoBai' -- dsd -输入密码:321 -{'name': 'XiaoBai', 'password': '123'} -成功登陆 -``` - -​ 可以看出,第1组和第2组验证正常,但是第三组出现了异常,输入错误的密码却可以正确登陆。 - -​ 这是因为在MySQL中`--`的含义是注释,如果通过字符串进行拼接: - -```mysql -select name, password from UserInfo where name='XiaoBai' -- dsd' and password='321' -``` - -​ 实际等价于: - -```mysql -sselect name, password from UserInfo where name='XiaoBai' -``` - -​ 解决办法:通过`execute`或者`executemany`来进行拼接。将语句: - -```python -sql = "select name, password from UserInfo where name='%s' and password='%s' " % (user, password) -cursor.execute(sql) -``` - -​ 改为: - -```python -sql = "select name, password from UserInfo where name=%s and password=%s" -cursor.execute(sql, (user, password)) -``` - +# 前言 MySQL简介 + +​ MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。 + +​ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行的开源数据库,因此被广泛地应用在Internet上的中小型网站中。随着MySQL的不断成熟,它也逐渐用于更多大规模网站和应用,比如维基百科、Google和Facebook等网站。非常流行的开源软件组合LAMP中的“M”指的就是MySQL。 + +[百度百科]: https://baike.baidu.com/item/mySQL/471251 +[维基百科]: https://zh.wikipedia.org/wiki/MySQL + + + +# 一、 Ubuntu下安装MySQL + +安装教程是在`Ubuntu20.04`下进行的,安装的MySQL版本为`8.0.27`。 + +## 1.1 安装 + +```bash +sudo apt install mysql-server mysql-client +``` + +在输入密码后,再输入`yes`即可开始安装。 + +安装完成后,通过运行命令`mysql -V`查看版本号: + +```bash +lyons@ubuntu:~$ mysql -V +mysql Ver 8.0.27-0ubuntu0.20.04.1 for Linux on x86_64 ((Ubuntu)) +``` + +验证MySQL服务正在运行,命令行下输入: + +```bash +sudo service mysql status +``` + +如果正在运行,则会显示: + +```bash +● mysql.service - MySQL Community Server + Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled) + Active: active (running) since Wed 2021-10-27 10:27:59 CST; 9h ago + Main PID: 6179 (mysqld) + Status: "Server is operational" + Tasks: 39 (limit: 4599) + Memory: 348.9M + CGroup: /system.slice/mysql.service + └─6179 /usr/sbin/mysqld + +10月 27 10:27:59 ubuntu systemd[1]: Starting MySQL Community Server... +10月 27 10:27:59 ubuntu systemd[1]: Started MySQL Community Server. +``` + + + +## 1.2 配置MySQL的安全性 + +1. 首先,运行命令`mysql_secure_installation`: + + ```bash + sudo mysql_secure_installation + ``` + +2. `VALIDATE PASSWORD COMPONENT` + + 设置验证密码插件。它被用来测试`MySQL`用户的密码强度,并且提高安全性。如果想设置验证密码插件,请输入`y`: + + ```bash + Connecting to MySQL using a blank password. + + VALIDATE PASSWORD COMPONENT can be used to test passwords + and improve security. It checks the strength of password + and allows the users to set only those passwords which are + secure enough. Would you like to setup VALIDATE PASSWORD component? + + Press y|Y for Yes, any other key for No: y + ``` + + 接下来,将进行密码验证等级设置,根据数字设置对应等级,这里设置为0: + + ```bash + There are three levels of password validation policy: + + LOW Length >= 8 + MEDIUM Length >= 8, numeric, mixed case, and special characters + STRONG Length >= 8, numeric, mixed case, special characters and dictionary file + + Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 0 + ``` + +3. 设置密码 + + 为MySQL root用户设置密码,设置过程中密码不会显示。如果设置了验证密码插件,将会显示密码的强度。 + + ``` + Please set the password for root here. + New password: + + Re-enter new password: + + Estimated strength of the password: 25 + Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y + ``` + +4. 移除匿名用户 + + 默认情况下,MySQL安装有一个匿名用户,允许任何人登录MySQL,而不必为他们创建用户帐户。输入`y`进行删除: + + ``` + By default, a MySQL installation has an anonymous user, + allowing anyone to log into MySQL without having to have + a user account created for them. This is intended only for + testing, and to make the installation go a bit smoother. + You should remove them before moving into a production + environment. + + Remove anonymous users? (Press y|Y for Yes, any other key for No) : y + Success. + ``` + +5. 禁止远程root用户登录 + + 输入`y`后按`enter`,将会禁止`root`用户登录。 + + ``` + Normally, root should only be allowed to connect from + 'localhost'. This ensures that someone cannot guess at + the root password from the network. + + Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y + Success. + ``` + +6. 删除测试库 + + 输入`y`后按`enter`,将会删除测试库。 + + ``` + By default, MySQL comes with a database named 'test' that + anyone can access. This is also intended only for testing, + and should be removed before moving into a production + environment. + + + Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y + - Dropping test database... + Success. + ``` + +7. 重新加载特权表 + + 输入`y`后按`enter`,将会重新加载特权表。 + + ``` + - Removing privileges on test database... + Success. + + Reloading the privilege tables will ensure that all changes + made so far will take effect immediately. + + Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y + Success. + + All done! + ``` + + 至此,配置完成。 + + + +## 1.3 以root用户登录 + +在MySQL 8.0上,root 用户默认通过`auth_socket`插件授权。`auth_socket`插件通过 Unix socket 文件来验证所有连接到`localhost`的用户。 + +这意味着你不能通过提供密码,验证为 root。此时,输入`mysql -uroot -p`可能会被拒绝访问: + +```bash +lyons@ubuntu:~$ mysql -uroot -p +mysql: [Warning] Using a password on the command line interface can be insecure. +ERROR 1698 (28000): Access denied for user 'root'@'localhost' +``` + +若要以 root 用户身份登录 MySQL服务器,输入`sudo mysql`,如下: + +```bash +# 登录密码为linux系统用户的root密码 +lyons@ubuntu:~$ sudo mysql +[sudo] lyons 的密码: +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 55 +Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu) + +Copyright (c) 2000, 2021, Oracle and/or its affiliates. + +Oracle is a registered trademark of Oracle Corporation and/or its +affiliates. Other names may be trademarks of their respective +owners. + +Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. + +mysql> +``` + +退出MySQL,请输入`exit`命令: + +```mysql +mysql> exit +Bye +lyons@ubuntu:~$ +``` + +如果你想以 root 身份登录 MySQL 服务器,便于使用其他的程序。可以将验证方法从`auth_socket`修改成`mysql_native_password`。 + ++ **方式1** + +你可以通过运行下面的命令实现: + +```bash +-- 语法中的'你的密码’指的是你自己设置的登录密码,可设置为字母数字组合。 +ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; +FLUSH PRIVILEGES; +``` + +示例: + +```mysql +-- 在mysql下,将密码设置为'mysql123' +mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'mysql123'; +Query OK, 0 rows affected (0.00 sec) + +-- 刷新系统权限 +mysql> FLUSH PRIVILEGES; +Query OK, 0 rows affected (0.01 sec) + +mysql> exit +Bye + +-- 现在便可以通过mysql -uroot -p登录 +-- 登录密码为前面设置的'mysql123' +lyons@ubuntu:~$ mysql -uroot -p +Enter password: +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 57 +Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu) + +Copyright (c) 2000, 2021, Oracle and/or its affiliates. + +Oracle is a registered trademark of Oracle Corporation and/or its +affiliates. Other names may be trademarks of their respective +owners. + +Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. + +mysql> +mysql> exit +Bye + +-- 同时,命令sudo mysql会被拒绝访问 +lyons@ubuntu:~$ sudo mysql +ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO) +``` + +当然,若要再次修改回`sudo mysql`的方式来登录`root`用户,方法类似: + +```mysql +ALTER USER 'root'@'localhost' IDENTIFIED WITH auth_socket BY '你的密码'; + +FLUSH PRIVILEGES; +``` + ++ **方式2** + + 推荐的选项,就是创建一个新的独立管理用户,拥有所有数据库的访问权限: + +```mysql +# 创建用户 +CREATE USER '用户名'@'localhost' identified by '你的密码' + +# 赋予admin用户全部的权限,你也可以只授予部分权限 +GRANT ALL PRIVILEGES ON *.* TO '用户名'@'localhost'; +``` + +​ 示例: + +```mysql +# 创建名为admin的用户,密码为mysql123 +mysql> create user 'admin'@'localhost' identified by 'mysql123'; +Query OK, 0 rows affected (0.01 sec) + +# 将访问所有database以及表的权利授权用户admin +#with gran option表示该用户可给其它用户赋予权限,但不可能超过该用户已有的权限 +mysql> grant all privileges on *.* to 'admin'@'localhost' with grant option; +Query OK, 0 rows affected (0.00 sec) + +mysql> FLUSH PRIVILEGES; +Query OK, 0 rows affected (0.00 sec) + +# 查看已有的用户 +mysql> select user, host from mysql.user; ++------------------+-----------+ +| user | host | ++------------------+-----------+ +| admin | localhost | +| debian-sys-maint | localhost | +| mysql.infoschema | localhost | +| mysql.session | localhost | +| mysql.sys | localhost | +| root | localhost | ++------------------+-----------+ +6 rows in set (0.00 sec) + +# 退出root用户登录 +mysql> exit +Bye + +# 登录admin用户,输入密码mysql123即可登录成功 +lyons@ubuntu:~$ mysql -uadmin -p +Enter password: +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 16 +Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu) + +Copyright (c) 2000, 2021, Oracle and/or its affiliates. + +Oracle is a registered trademark of Oracle Corporation and/or its +affiliates. Other names may be trademarks of their respective +owners. + +Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. + +mysql> +``` + +说明:`'admin'@'localhost'`中,`localhost`指本地才可连接,可以将其换成`%`指任意`ip`都能连接,也可以指定`ip`连接。 + + + +## 1.4 修改密码 + +将用户`admin`的登录密码修改为`mysql321`: + +```mysql +ALTER USER 'admin'@'localhost' IDENTIFIED WITH mysql_native_password BY 'mysql321'; +``` + + + +## 1.5 撤销用户授权 + +```mysql +# 查看用户的权限 +show grants for 'admin'@'localhost'; + +# 撤销用户的权限 +# 用户有什么权限就撤销什么 +revoke all privileges on *.* from 'admin'@'localhost'; +``` + + + +## 1.6 删除用户 + +```MYSQL +drop user 'admin'@'localhost'; +``` + + + +> 注:MySQL 8.0版本和5.0部分命令有所改掉,上述语法都是在8.0版本下运行通过的;请务必检查自己的MySQL版本号。 + + + +# 二、MySQL预备知识 + +在正式学习MySQL之前,我们先来了解一下SQL语句的书写规范以及命名规则等。 + +## 2.1 SQL书写规范 + +在写SQL语句时,要求按照如下规范进行: + ++ SQL 语句要以分号(;)结尾 + ++ SQL 不区分关键字的大小写 ,这对于表名和列名同样适用。 + ++ 插入到表中的数据是区分大小写的。例如,数据Computer、COMPUTER 或computer,三者是不一样的。 + ++ 常数的书写方式是固定的,在SQL 语句中直接书写的字符串、日期或者数字等称为常数。常数的书写方式如下所示。 + + + SQL 语句中含有字符串的时候,需要像'abc'这样,使用单引号(')将字符串括起来,用来标识这是一个字符串。 + + SQL 语句中含有日期的时候,同样需要使用单引号将其括起来。日期的格式有很多种('26 Jan 2010' 或者'10/01/26' 等)。 + + 在SQL 语句中书写数字的时候,不需要使用任何符号标识,直接写成1000 这样的数字即可。 + ++ 单词之间需要用半角空格或者换行来分隔。 + ++ SQL中的注释主要采用`--`和`/* ... */`的方式,第二种方式可以换行。在MySQL下,还可以通过`#`来进行注释。 + + + +## 2.2 命名规则 + ++ 在数据库中,只能使用半角英文字母、数字、下划线(_)作为数据库、表和列的名称 。 ++ 名称必须以半角英文字母作为开头。 ++ 名称不能重复,同一个数据库下不能有2张相同的表。 + + + +## 2.3. 数据类型 + +MySQL 支持所有标准 SQL 数值数据类型,包括: + +### (1)数值类型 + +数值包含的类型如下: + ++ 整型数据:`TINYINT`、`INTEGER`、`SMALLINT`、`MEDIUMINT`、`DECIMAL` 、`NUMERIC` 和`BIGINT`。 + ++ 浮点型数据:`DECIMAL`、`FLOAT`、`REAL` 和 `DOUBLE PRECISION`)。 + +其中,关键字`INT`是`INTEGER`的同义词,关键字DEC是的同义词。 + +不同关键字的主要区别就是表示的范围或精度不一样。具体如下表: + +| 类型 | 大小 | 范围(有符号) | 范围(无符号) | 用途 | +| :----------: | :--------------------------------------: | :----------------------------------------------------------- | :----------------------------------------------------------- | :-------------- | +| TINYINT | 1 Bytes | (-128,127) | (0,255) | 小整数值 | +| SMALLINT | 2 Bytes | (-32 768,32 767) | (0,65 535) | 大整数值 | +| MEDIUMINT | 3 Bytes | (-8 388 608,8 388 607) | (0,16 777 215) | 大整数值 | +| INT或INTEGER | 4 Bytes | (-2 147 483 648,2 147 483 647) | (0,4 294 967 295) | 大整数值 | +| BIGINT | 8 Bytes | (-9,223,372,036,854,775,808,9 223 372 036 854 775 807) | (0,18 446 744 073 709 551 615) | 极大整数值 | +| FLOAT | 4 Bytes | (-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) | 0,(1.175 494 351 E-38,3.402 823 466 E+38) | 单精度 浮点数值 | +| DOUBLE | 8 Bytes | (-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 双精度 浮点数值 | +| DECIMAL | 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 | 依赖于M和D的值 | 依赖于M和D的值 | 小数值 | + +### (2)日期和时间类型 + +表示时间值的日期和时间类型为`DATETIME`、`DATE`、`TIMESTAMP`、`TIME`和`YEAR`。具体如下表: + +| 类型 | 大小 ( bytes) | 范围 | 格式 | 用途 | +| :-------- | :------------ | :----------------------------------------------------------- | :------------------ | :----------------------- | +| DATE | 3 | 1000-01-01/9999-12-31 | YYYY-MM-DD | 日期值 | +| TIME | 3 | '-838:59:59'/'838:59:59' | HH:MM:SS | 时间值或持续时间 | +| YEAR | 1 | 1901/2155 | YYYY | 年份值 | +| DATETIME | 8 | 1000-01-01 00:00:00/9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS | 混合日期和时间值 | +| TIMESTAMP | 4 | 1970-01-01 00:00:00/2038结束时间是第 2147483647 秒,北京时间 2038-1-19 11:14:07,格林尼治时间 2038年1月19日 凌晨 03:14:07 | YYYYMMDD HHMMSS | 混合日期和时间值,时间戳 | + +### (3)字符串类型 + +字符串类型指`CHAR`、`VARCHAR`、`BINARY`、`VARBINARY`、`BLOB`、`TEXT`、`ENUM`和`SET`。具体如下表: + +| 类型 | 大小 | 用途 | +| :--------- | :-------------------- | :------------------------------ | +| CHAR | 0-255 bytes | 定长字符串 | +| VARCHAR | 0-65535 bytes | 变长字符串 | +| TINYBLOB | 0-255 bytes | 不超过 255 个字符的二进制字符串 | +| TINYTEXT | 0-255 bytes | 短文本字符串 | +| BLOB | 0-65 535 bytes | 二进制形式的长文本数据 | +| TEXT | 0-65 535 bytes | 长文本数据 | +| MEDIUMBLOB | 0-16 777 215 bytes | 二进制形式的中等长度文本数据 | +| MEDIUMTEXT | 0-16 777 215 bytes | 中等长度文本数据 | +| LONGBLOB | 0-4 294 967 295 bytes | 二进制形式的极大文本数据 | +| LONGTEXT | 0-4 294 967 295 bytes | 极大文本数据 | + ++ `char`声明的是定长字符串。若实际中字符串长度不足,则会在末尾使用空格进行填充至声明的长度。 + ++ `varchar`声明的是可变长字符串。存储过程中,只会按照字符串的实际长度来存储,但会多占用一位来存放实际字节的长度。 + + + +# 三、 数据库的基本操作 + +首先,我们来学习在MySQL下如何操作数据库。 + +## 3.1 数据库的创建 + +通过`CREATE`命令,可以创建指定名称的数据库,语法结构如下: + +```mysql +CREATE DATABASE [IF NOT EXISTS] <数据库名称>; +``` + +MySQL 的数据存储区将以目录方式表示 MySQL 数据库,因此数据库名称必须符合操作系统的文件夹命名规则,不能以数字开头,尽量要有实际意义。 + +MySQL下不运行存在两个相同名字的数据库,否则会报错。如果使用`IF NOT EXISTS`(可选项),可以避免此类错误。 + +示例: + +```mysql +-- 创建名为shop的数据库。 +CREATE DATABASE shop; +``` + + + +## 3.2 数据库的查看 + +1. 查看所有存在的数据库 + +```MYSQL +SHOW DATABASES [LIKE '数据库名'];; +``` + +`LIKE`从句是可选项,用于匹配指定的数据库名称。`LIKE` 从句可以部分匹配,也可以完全匹配。 + +示例: + +```mysql +SHOW DATABASES; + +-- 结果如下: ++--------------------+ +| Database | ++--------------------+ +| information_schema | +| mysql | +| performance_schema | +| shop | +| sys | ++--------------------+ +5 rows in set (0.01 sec) +``` + +```mysql +-- %表示任意0个或多个字符,可匹配任意类型和长度的字符。 +SHOW DATABASES LIKE 'S%'; + +-- 结果如下 ++---------------+ +| Database (S%) | ++---------------+ +| shop | +| sys | ++---------------+ +2 rows in set (0.00 sec) +``` + +2. 查看创建的数据库 + +```mysql +SHOW CREATE DATABASE <数据库名>; +``` + +示例: + +```mysql +SHOW CREATE DATABASE shop; + +-- 或者 +SHOW CREATE DATABASE shop \G + +-- 结果如下 +*************************** 1. row *************************** + Database: shop +Create Database: CREATE DATABASE `shop` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */ +1 row in set (0.00 sec) +``` + +`CHARACTER SET utf8mb4`表示编码字符集为`utf8mb4`。 + + + +## 3.3 选择数据库 + +在操作数据库前,必须指定所要操作的数据库。通过`USE`命令,可以切换到对应的数据库下。 + +```mysql +USE <数据库名> +``` + +示例: + +```mysql +-- 切换到数据库shop下。 +USE shop; + +-- 结果如下 +Database changed +``` + + + +## 3.4 删除数据库 + +通过`DROP`命令,可以将相应数据库进行删除。 + +```mysql +DROP DATABASE [IF EXISTS] <数据库名> +``` + +其中,`IF EXISTS`为可选性,用于防止数据库不存在时报错。 + +示例: + +```mysql +DROP DATABASE shop; + +SHOW DATABASES; +``` + +考虑到后面表的操作都是shop数据库下,在实验完`DROP`删除数据库命令后,请从新创建数据库shop并通过`USE`命令切换到该数据库下。 + + + +# 四、表的基本操作 + +表相当于文件,表中的一条记录就相当于文件的一行内容,不同的是,表中的一条记录有对应的标题,称为表的字段。 + +## 4.1 表的创建 + +创建表的语法结构如下: + +```mysql +CREATE TABLE <表名> (<字段1> <数据类型> <该列所需约束>, + <字段2> <数据类型> <该列所需约束>, + <字段3> <数据类型> <该列所需约束>, + <字段4> <数据类型> <该列所需约束>, + . + . + . + <该表的约束1>, <该表的约束2>,……); +``` + +示例: + +```mysql +-- 创建一个名为Product的表 +CREATE TABLE Product( + product_id CHAR(4) NOT NULL, + product_name VARCHAR(100) NOT NULL, + product_type VARCHAR(32) NOT NULL, + sale_price INT, + purchase_price INT, + regist_date DATE, + PRIMARY KEY (product_id) +); +``` + +在第二章中,我们介绍过不同的数据类型: + ++ `CHAR`为定长字符,这里`CHAR`旁边括号里的数字表示该字段最长为多少字符,少于该数字将会使用空格进行填充。 + ++ `VARCHAR`表示变长字符,括号里的数字表示该字段最长为多少字符,存储时只会按照字符的实际长度来存储,但会使用额外的1-2字节来存储值长度。 + + + +简单介绍一下该语句中出现的约束条件,约束条件在后面会详细介绍: + ++ `PRIMARY KEY`:主键,表示该字段对应的内容唯一且不能为空。 ++ `NOT NULL`:在 `NULL` 之前加上了表示否定的` NOT`,表示该字段不能输入空白。 + +通过`SHOW TABLES`命令来查看当前数据库下的所有的表名: + +```mysql +SHOW TABLES; + +-- 结果如下 ++----------------+ +| Tables_in_shop | ++----------------+ +| Product | ++----------------+ +1 rows in set (0.00 sec) +``` + +通过`DESC <表名>`来查看表的结构: + +```mysql +DESC Product; + +-- 结果如下 ++----------------+--------------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++----------------+--------------+------+-----+---------+-------+ +| product_id | char(4) | NO | PRI | NULL | | +| product_name | varchar(100) | NO | | NULL | | +| product_type | varchar(32) | NO | | NULL | | +| sale_price | int | YES | | NULL | | +| purchase_price | int | YES | | NULL | | +| regist_date | date | YES | | NULL | | ++----------------+--------------+------+-----+---------+-------+ +6 rows in set (0.00 sec) +``` + + + +## 4.2 表的删除 + +删除表的语法结构如下: + +```mysql +DROP TABLE <表名>; + +-- 例如:DROP TABLE Product; +``` + +说明:通过`DROP`删除的表示无法恢复的,在删除表的时候请谨慎。 + + + +## 4.3 表的更新 + +通过`ALTER TABLE`语句,我们可以对表字段进行不同的操作,下面通过示例来具体学习用法。 + +示例: + +1. 创建一张名为Student的表 + +```mysql +CREATE TABLE Student( + id INT PRIMARY KEY, + name CHAR(15) +); +``` + + + +```mysql +DESC student; + +-- 结果如下 ++-------+----------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++-------+----------+------+-----+---------+-------+ +| id | int | NO | PRI | NULL | | +| name | char(15) | YES | | NULL | | ++-------+----------+------+-----+---------+-------+ +2 rows in set (0.00 sec) +``` + +2. 更改表名 + + 通过`RENAME`命令,将表名从Student => Students。 + +```mysql +ALTER TABLE Student RENAME Students; +``` + +3. 插入新的字段 + + 通过`ADD`命令,新增字段sex和age。 + +```mysql +-- 不同的字段通过逗号分开 +ALTER TABLE Students ADD sex CHAR(1), ADD age INT; +``` + +​ 其它插入技巧: + +```mysql +-- 通过FIRST在表首插入字段stu_num +ALTER TABLE Students ADD stu_num INT FIRST; + +-- 指定在字段sex后插入字段height +ALTER TABLE Students ADD height INT AFTER sex; +``` + +```mysql +DESC Students; + +-- 结果如下 ++---------+----------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++---------+----------+------+-----+---------+-------+ +| stu_num | int | YES | | NULL | | +| id | int | NO | PRI | NULL | | +| name | char(15) | YES | | NULL | | +| sex | char(1) | YES | | NULL | | +| height | int | YES | | NULL | | +| age | int | YES | | NULL | | ++---------+----------+------+-----+---------+-------+ +6 rows in set (0.00 sec) +``` + +4. 字段的删除 + + 通过`DROP`命令,可以对不在需要的字段进行删除。 + +```mysql +-- 删除字段stu_num +ALTER TABLE Students DROP stu_num; +``` + +5. 字段的修改 + + 通过`MODIFY`修改字段的数据类型。 + +```mysql +-- 修改字段age的数据类型 +ALTER TABLE Students MODIFY age CHAR(3); +``` + +​ 通过`CHANGE`命令,修改字段名或类型 + +```mysql +-- 修改字段name为stu_name,不修改数据类型 +ALTER TABLE Students CHANGE name stu_name CHAR(15); + +-- 修改字段sex为stu_sex,数据类型修改为int +ALTER TABLE Students CHANGE sex stu_sex INT; +``` + +```mysql +DESC Students; + +-- 结果如下 ++----------+----------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++----------+----------+------+-----+---------+-------+ +| id | int | NO | PRI | NULL | | +| stu_name | char(20) | YES | | NULL | | +| stu_sex | int | YES | | NULL | | +| height | int | YES | | NULL | | +| age | char(3) | YES | | NULL | | ++----------+----------+------+-----+---------+-------+ +5 rows in set (0.00 sec) +``` + + + +## 4.4 表的查询 + +通过`SELECT`语句,可以从表中取出所要查看的字段的内容: + +```mysql +SELECT <字段名>, …… + FROM <表名>; +``` + +如要直接查询表的全部字段: + +```mysql +SELECT * + FROM <表名>; +``` + +其中,**星号(*)**代表全部字段的意思。 + +示例: + +1. 建表并插入数据 + + 在MySQL中,我们通过`INSERT`语句往表中插入数据,该语句在后面会详细介绍,该小节的重点是学会使用`SELECT`。 + +```mysql +-- 向Product表中插入数据 +INSERT INTO Product VALUES + ('0001', 'T恤衫', '衣服', 1000, 500, '2009-09-20'), + ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11'), + ('0003', '运动T恤', '衣服', 4000, 2800, NULL), + ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'), + ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'), + ('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20'), + ('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28'), + ('0008', '圆珠笔', '办公用品', 100, NULL,'2009-11-11') + ; +``` + +2. 查看表的内容 + +```mysql +-- 查看表的全部内容 +SELECT * + FROM Product; + +-- 结果如下 ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | +| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | +| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | +| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | +| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | +| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | +| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | +| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | ++------------+--------------+--------------+------------+----------------+-------------+ +8 rows in set (0.00 sec) +``` + +```mysql +-- 查看部分字段包含的内容 +SELECT + product_id, + product_name, + sale_price + FROM Product; + +-- 结果如下 ++------------+--------------+------------+ +| product_id | product_name | sale_price | ++------------+--------------+------------+ +| 0001 | T恤衫 | 1000 | +| 0002 | 打孔器 | 500 | +| 0003 | 运动T恤 | 4000 | +| 0004 | 菜刀 | 3000 | +| 0005 | 高压锅 | 6800 | +| 0006 | 叉子 | 500 | +| 0007 | 擦菜板 | 880 | +| 0008 | 圆珠笔 | 100 | ++------------+--------------+------------+ +8 rows in set (0.00 sec) +``` + +3. 对查看的字段从新命名 + + 通过`AS`语句对展示的字段另起别名,这不会修改表内字段的名字。 + +```mysql +SELECT + product_id AS ID, + product_type AS TYPE + FROM Product; + +-- 结果如下 ++------+--------------+ +| ID | TYPE | ++------+--------------+ +| 0001 | 衣服 | +| 0002 | 办公用品 | +| 0003 | 衣服 | +| 0004 | 厨房用具 | +| 0005 | 厨房用具 | +| 0006 | 厨房用具 | +| 0007 | 厨房用具 | +| 0008 | 办公用品 | ++------+--------------+ +8 rows in set (0.00 sec) +``` + +​ 设定汉语别名时需要使用双引号(")括起来,英文字符则不需要。 + +```Mysql +SELECT + product_id AS "产品编号", + product_type AS "产品类型" + FROM Product; +``` + +4. 常数的查询 + + `SELECT`子句中,除了可以写字段外,还可以写常数。 + +```mysql +SELECT + '商品' AS string, + '2009-05-24' AS date, + product_id, + product_name + FROM Product; + +-- 结果如下 ++--------+------------+------------+--------------+ +| string | date | product_id | product_name | ++--------+------------+------------+--------------+ +| 商品 | 2009-05-24 | 0001 | T恤衫 | +| 商品 | 2009-05-24 | 0002 | 打孔器 | +| 商品 | 2009-05-24 | 0003 | 运动T恤 | +| 商品 | 2009-05-24 | 0004 | 菜刀 | +| 商品 | 2009-05-24 | 0005 | 高压锅 | +| 商品 | 2009-05-24 | 0006 | 叉子 | +| 商品 | 2009-05-24 | 0007 | 擦菜板 | +| 商品 | 2009-05-24 | 0008 | 圆珠笔 | ++--------+------------+------------+--------------+ +8 rows in set (0.00 sec) +``` + +5. 删除重复行 + + 在`SELECT`语句中使用`DISTINCT`可以去除重复行。 + +```mysql +SELECT + DISTINCT regist_date + FROM Product; + +-- 结果如下 ++-------------+ +| regist_date | ++-------------+ +| 2009-09-20 | +| 2009-09-11 | +| NULL | +| 2009-01-15 | +| 2008-04-28 | +| 2009-11-11 | ++-------------+ +6 rows in set (0.01 sec) +``` + +​ 在使用`DISTINCT` 时,`NULL `也被视为一类数据。`NULL `存在于多行中时,会被合并为一条`NULL `数据。 + +​ 还可以通过组合使用,来去除列组合重复的数据。`DISTINCT `关键字只能用在第一个列名之前。 + +```mysql +SELECT + DISTINCT product_type, regist_date + FROM Product; + +-- 结果如下,列出了所有的组合 ++--------------+-------------+ +| product_type | regist_date | ++--------------+-------------+ +| 衣服 | 2009-09-20 | +| 办公用品 | 2009-09-11 | +| 衣服 | NULL | +| 厨房用具 | 2009-09-20 | +| 厨房用具 | 2009-01-15 | +| 厨房用具 | 2008-04-28 | +| 办公用品 | 2009-11-11 | ++--------------+-------------+ +7 rows in set (0.00 sec) +``` + +6. 指定查询条件 + + 首先通过`WHERE` 子句查询出符合指定条件的记录,然后再选取出` SELECT `语句指定的列,语法结构如下: + +```mysql +SELECT <字段名>, …… + FROM <表名> + WHERE <条件表达式>; +``` + +​ 示例: + +```mysql +SELECT product_name + FROM Product + WHERE product_type = '衣服'; + +-- 结果如下 ++--------------+ +| product_name | ++--------------+ +| T恤衫 | +| 运动T恤 | ++--------------+ +2 rows in set (0.01 sec) +``` + +注意,`WHERE`子句要紧跟在`FROM`子句之后。 + + + +## 4.5 表的复制 + +表的复制可以将表结构与表中的数据全部复制,或者只复制表的结构。 + +```mysql +-- 将整个表复制过来 +CREATE TABLE Product_COPY1 + SELECT * FROM Product; + +SELECT * FROM Product_COPY1; + +-- 结果如下 ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | +| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | +| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | +| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | +| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | +| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | +| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | +| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | ++------------+--------------+--------------+------------+----------------+-------------+ +8 rows in set (0.00 sec) +``` + +```mysql +-- 通过LIKE复制表结构 +CREATE TABLE Product_COPY2 + LIKe Product; + +SELECT * FROM Product_COPY2; + +-- 结果如下 +Empty set (0.00 sec) -- 表为空的 + +DESC Product_COPY2; + +-- 结果如下 +-- 表结构已复制过来 ++----------------+--------------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++----------------+--------------+------+-----+---------+-------+ +| product_id | char(4) | NO | PRI | NULL | | +| product_name | varchar(100) | NO | | NULL | | +| product_type | varchar(32) | NO | | NULL | | +| sale_price | int | YES | | 0 | | +| purchase_price | int | YES | | NULL | | +| regist_date | date | YES | | NULL | | ++----------------+--------------+------+-----+---------+-------+ +6 rows in set (0.01 sec) +``` + + + +# 五、运算符 + +## 5.1 算术运算符 + +我们可以在`SELECT`语句中使用计算表达式: + +```mysql +SELECT + product_name, + sale_price, + sale_price * 2 AS "sale_price_x2" + FROM Product; + +-- 结果如下 ++--------------+------------+---------------+ +| product_name | sale_price | sale_price_x2 | ++--------------+------------+---------------+ +| T恤衫 | 1000 | 2000 | +| 打孔器 | 500 | 1000 | +| 运动T恤 | 4000 | 8000 | +| 菜刀 | 3000 | 6000 | +| 高压锅 | 6800 | 13600 | +| 叉子 | 500 | 1000 | +| 擦菜板 | 880 | 1760 | +| 圆珠笔 | 100 | 200 | ++--------------+------------+---------------+ +8 rows in set (0.00 sec) +``` + ++ 四则运算所使用的运算符**(+、-、*、/)**称为算术运算符。 + ++ 在运算表达式中,也可以使用**()**,括号中的运算表达式优先级会得到提升。 + ++ **NULL**的计算结果,仍然还是**NULL**。 + + + +## 5.2 比较运算符 + +在 `WHERE` 子句中通过使用比较运算符可以组合出各种各样的条件表达式。 + +```mysql +SELECT product_name, product_type + FROM Product + WHERE sale_price = 500; +``` + +常见比较运算符如下表: + +| 运算符 | 含义 | +| ------ | -------- | +| = | 相等 | +| <> | 不相等 | +| \>= | 大于等于 | +| \> | 大于 | +| <= | 小于等于 | +| < | 小于 | + ++ 不能对**NULL**使用任何比较运算符,只能通过`IS NULL`语句来判断: + +```mysql +SELECT + product_name, + purchase_price + FROM Product + WHERE purchase_price IS NULL; +``` + +​ 希望选取不是 NULL 的记录时,需要使用`IS NOT NULL`运算符。 + ++ 对字符串使用比较符 + +​ MySQL中字符串的排序与数字不同,典型的规则就是按照字典顺序进行比较,也就是像姓名那样,按照条目在字典中出现的顺序来进行排序。例如: + +```mysql +'1' < '10' < '11' < '2' < '222' < '3' +``` + + + +## 5.3 逻辑运算符 + +1. 使用`NOT`否认某一条件: + +```mysql +SELECT + product_name, + product_type, + sale_price + FROM Product + WHERE NOT sale_price >= 1000; +``` + +2. `AND`运算符合`OR`运算符 + +```mysql +SELECT product_type, sale_price + FROM Product + WHERE product_type = '厨房用具' + AND sale_price >= 3000; + +-- 结果如下 ++--------------+------------+ +| product_type | sale_price | ++--------------+------------+ +| 厨房用具 | 3000 | +| 厨房用具 | 6800 | ++--------------+------------+ +2 rows in set (0.00 sec) +``` + +```mysql +SELECT product_type, sale_price + FROM Product + WHERE product_type = '厨房用具' + OR sale_price >= 3000; + +-- 结果如下 ++--------------+------------+ +| product_type | sale_price | ++--------------+------------+ +| 衣服 | 4000 | +| 厨房用具 | 3000 | +| 厨房用具 | 6800 | +| 厨房用具 | 500 | +| 厨房用具 | 880 | ++--------------+------------+ +5 rows in set (0.00 sec) +``` + +3. 逻辑运算符和真值 + ++ 符**NOT**、**AND** 和 **OR** 称为逻辑运算符; ++ 真值就是值为**真(TRUE)**或**假 (FALSE)**; + ++ 在查询**NULL**时,SQL中存在第三种真值,**不确定(UNKNOWN)**,**NULL**和任何值做逻辑运算结果都是不确定; ++ 考虑 **NULL** 时的条件判断也会变得异常复杂,因此尽量给字段加上**NOT NULL**的约束。 + + + +# 六、分组查询 + +## 6.1 聚合函数 + +通过 SQL 对数据进行某种操作或计算时需要使用函数。 + ++ `COUNT`:计算表中的记录数(行数) + ++ `SUM`: 计算表中数值列中数据的合计值 + ++ `AVG`: 计算表中数值列中数据的平均值 + ++ `MAX`: 求出表中任意列中数据的最大值 + ++ `MIN`: 求出表中任意列中数据的最小值 + +示例: + +```mysql +-- 计算全部数据的行数 +SELECT COUNT(*) FROM Product; + +-- 结果如下 ++----------+ +| COUNT(*) | ++----------+ +| 8 | ++----------+ +1 row in set (0.00 sec) +``` + +**注意点1**:除了`COUNT`可以将`*`作为参数,其它的函数均不可以。 + +```mysql +-- 计算最高的销售价格 +SELECT MAX(sale_price) FROM Product; + +-- 结果如下 ++-----------------+ +| MAX(sale_price) | ++-----------------+ +| 680000 | ++-----------------+ +1 row in set (0.00 sec) +``` + +**注意点2:**当将字段名作为参数传递给函数时,只会计算不包含`NULL`的行。 + +示例: + +```mysql +-- purchase_price字段是包含NULL值的 +SELECT purchase_price FROM Product; + +-- 结果如下 ++----------------+ +| purchase_price | ++----------------+ +| 500 | +| 320 | +| 2800 | +| 700 | +| 1250 | +| NULL | +| 198 | +| NULL | ++----------------+ +8 rows in set (0.00 sec) +``` + +以*为参数传递给`COUNT`函数 + +```mysql +SELECT COUNT(*) FROM Product; + +-- 结果如下 ++----------+ +| COUNT(*) | ++----------+ +| 8 | ++----------+ +1 row in set (0.00 sec) +``` + +以purchase_price为参数传递给`COUNT`函数 + +```mysql +SELECT COUNT(purchase_price) FROM Product; + +-- 结果如下 ++-----------------------+ +| COUNT(purchase_price) | ++-----------------------+ +| 6 | ++-----------------------+ +1 row in set (0.00 sec) +``` + +可以看到结果并不一样,函数忽略了值为**NULL**的行。 + +`SUM`,`AVG`函数时也一样,计算时会直接忽略,**并不会当做0来处理!**特别注意`AVG`函数,计算时分母也不会算上`NULL`行。 + +**注意点3**:`MAX/MIN`函数几乎适用于所有数据类型的列,包括字符和日期。`SUM/AVG`函数只适用于数值类型的列。 + +**注意点4**:在聚合函数删除重复值 + +```mysql +SELECT COUNT(DISTINCT product_type) + FROM Product; + +-- 结果如下 ++------------------------------+ +| COUNT(DISTINCT product_type) | ++------------------------------+ +| 3 | ++------------------------------+ +1 row in set (0.01 sec) +``` + +`DISTINCT`必须写在括号中。这是因为必须要在计算行数之前删除 product_type 字段中的重复数据。 + + + +## 6.2 对表分组 + +如果对Python的Pandas熟悉,那么大家应该很了解`groupby`函数,可以根据指定的列名,对表进行分组。在MySQL中,也存在同样作用的函数,即`GROUP BY`。 + +语法结构如下: + +```mysql +SELECT <列名1>, <列名2>, <列名3>, …… + FROM <表名> + GROUP BY <列名1>, <列名2>, <列名3>, ……; +``` + +示例: + +```mysql +SELECT product_type, COUNT(*) + FROM Product + GROUP BY product_type; + +-- 结果如下 ++--------------+----------+ +| product_type | COUNT(*) | ++--------------+----------+ +| 衣服 | 2 | +| 办公用品 | 2 | +| 厨房用具 | 4 | ++--------------+----------+ +3 rows in set (0.01 sec) +``` + +1. 在该语句中,我们首先通过`GROUP BY`函数对指定的字段product_type进行分组。分组时,product_type字段中具有相同值的行会汇聚到同一组。 + +2. 最后通过`COUNT`函数,统计不同分组的包含的行数。 + +简单来理解: + ++ 例如做操时,老师将不同身高的同学进行分组,相同身高的同学会被分到同一组,分组后我们又统计了每个小组的学生数。 + ++ 将这里的同学可以理解为表中的一行数据,身高理解为表的某一字段。 ++ 分组操作就是`GROUP BY`,`GROUP BY`后面接的字段等价于按照身高分组,统计学生数就等价于在`SELECT`后用了`COUNT(*)`函数。 + +注意:`GROUP BY `子句的位置一定要写在`FROM` 语句之后(如果有 `WHERE` 子句的话需要写在 `WHERE` 子句之后) + +``` +1. SELECT → 2. FROM → 3. WHERE → 4. GROUP BY +``` + +当被聚合的键中,包含`NULL`时,在结果中会以“不确定”行(空行)的形式表现出来,也就是字段中为`NULL`的数据会被聚合为一组。 + +## 6.3 使用WHERE语句 + +在对表进行分组之前,也可以是先使用`WHERE`对表进行条件过滤,然后再进行分组处理。语法结构如下: + +```mysql +SELECT <列名1>, <列名2>, <列名3>, …… + FROM <表名> + WHERE + GROUP BY <列名1>, <列名2>, <列名3>, ……; +``` + +示例: + +```mysql +-- WHERE语句先将表中类型为衣服的行筛选出来 +-- 然后再按照purchase_price来进行分组 +SELECT purchase_price, COUNT(*) + FROM Product + WHERE product_type = '衣服' + GROUP BY purchase_price; + +-- 结果如下 ++----------------+----------+ +| purchase_price | COUNT(*) | ++----------------+----------+ +| 500 | 1 | +| 2800 | 1 | ++----------------+----------+ +2 rows in set (0.01 sec) +``` + +该语法实际的执行顺序为: + +``` +FROM → WHERE → GROUP BY → SELECT +``` + ++ 使用`GROUP BY`子句时,`SELECT`子句中不能出现聚合键之外的字段名。即,若`GROUP BY`选中purchase_price字段进行分组,则在`SELECT`语句中只能选中purchase_price字段,其它字段如product_id等均不行。 ++ `WHERE`语句中,不可以使用聚合函数。`WHERE`子句只能指定记录(行)的条件,而不能用来指定组的条件。即`WHERE MAX(purchase_price) > 1000`这样的语句是非法的。 + + + +## 6.4 为聚合结果指定条件 + +前面提到了`WHERE`语句中不能使用聚合函数,但是实际操作时需要通过聚合函数来进行过滤怎么办呢?这就要用到`HAVING`语句了。语法结构如下: + +```mysql +SELECT <列名1>, <列名2>, <列名3>, …… + FROM <表名> + GROUP BY <列名1>, <列名2>, <列名3>, …… +HAVING <分组结果对应的条件> +``` + +在`HAVING`的子句中能够使用的 3 种要素如下所示: + +● 常数 + +● 聚合函数 + +● `GROUP BY`子句中指定的字段名(即聚合键) + +示例: + +```mysql +-- 不使用HAVING语句 +SELECT product_type, AVG(sale_price) + FROM Product + GROUP BY product_type; + +-- 结果如下 ++--------------+-----------------+ +| product_type | AVG(sale_price) | ++--------------+-----------------+ +| 衣服 | 2500.0000 | +| 办公用品 | 300.0000 | +| 厨房用具 | 279500.0000 | ++--------------+-----------------+ +3 rows in set (0.00 sec) +``` + +```mysql +-- 使用HAVING语句 +-- 通过HAVING语句将销售平均价格大于等于2500的组给保留了 +SELECT product_type, AVG(sale_price) + FROM Product + GROUP BY product_type +HAVING AVG(sale_price) >= 2500; + +-- 结果如下 ++--------------+-----------------+ +| product_type | AVG(sale_price) | ++--------------+-----------------+ +| 衣服 | 2500.0000 | +| 厨房用具 | 279500.0000 | ++--------------+-----------------+ +2 rows in set (0.00 sec) +``` + +可以看到使用`HAVING`语句后,输出的结果有所变化。大致流程如下: + ++ 首先,`FROM`语句会选中表Product; ++ 然后,`GROUP BY`语句会选中字段product_type进行分组; ++ 之后,通过`HAVING`语句将销售平均价格大于等于2500的组保留下来; ++ 最后,通过`SELECT`语句将保留下的组的产品类型和平均价格显示出来; + + + +如果是对**表的行**进行条件指定,`WHERE`和`HAVING`都可以生效。 + +```mysql +-- 下面两条语句执行结果一致 +SELECT product_type, COUNT(*) + FROM Product + GROUP BY product_type + HAVING product_type = '衣服'; + +SELECT product_type, COUNT(*) + FROM Product + WHERE product_type = '衣服' + GROUP BY product_type; + +-- 结果如下 ++--------------+----------+ +| product_type | COUNT(*) | ++--------------+----------+ +| 衣服 | 2 | ++--------------+----------+ +1 row in set (0.01 sec) +``` + +但是,一般而言如果是对表的行进行条件指定,最好还是使用`WHERE`语句,因为`WHERE`的执行速度更快。 + + + +## 6.5 对表的查询结果进行排序 + +如果希望对表的查询结果根据某指定的字段进行排序,可以使用`ORDER BY`语句。语法结构如下: + +```mysql +SELECT <列名1>, <列名2>, <列名3>, …… + FROM <表名> + ORDER BY <排序基准列1>, <排序基准列2>, …… +``` + +示例: + +```mysql +SELECT product_id, product_name, sale_price, purchase_price + FROM Product; + +-- 结果如下 ++------------+--------------+------------+----------------+ +| product_id | product_name | sale_price | purchase_price | ++------------+--------------+------------+----------------+ +| 0001 | T恤衫 | 1000 | 500 | +| 0002 | 打孔器 | 500 | 320 | +| 0003 | 运动T恤 | 4000 | 2800 | +| 0004 | 菜刀 | 300000 | 700 | +| 0005 | 高压锅 | 680000 | 1250 | +| 0006 | 叉子 | 50000 | NULL | +| 0007 | 擦菜板 | 88000 | 198 | +| 0008 | 圆珠笔 | 100 | NULL | ++------------+--------------+------------+----------------+ +8 rows in set (0.01 sec) +``` + +```mysql +-- 根据字段sale_price的值进行排序 +SELECT product_id, product_name, sale_price, purchase_price + FROM Product +ORDER BY sale_price; + +-- 结果如下 ++------------+--------------+------------+----------------+ +| product_id | product_name | sale_price | purchase_price | ++------------+--------------+------------+----------------+ +| 0008 | 圆珠笔 | 100 | NULL | +| 0002 | 打孔器 | 500 | 320 | +| 0001 | T恤衫 | 1000 | 500 | +| 0003 | 运动T恤 | 4000 | 2800 | +| 0006 | 叉子 | 50000 | NULL | +| 0007 | 擦菜板 | 88000 | 198 | +| 0004 | 菜刀 | 300000 | 700 | +| 0005 | 高压锅 | 680000 | 1250 | ++------------+--------------+------------+----------------+ +8 rows in set (0.00 sec) +``` + +可以看到`ORDER BY`默认是按照升序的方式进行排序的,正式的书写方式应该是在字段后加上关键字`ASC`,即`ORDER BY sale_price ASC`。 + +如果我们希望按照降序的方式,可以通过`DESC`关键词进行指定。 + +```mysql +SELECT product_id, product_name, sale_price, purchase_price + FROM Product +ORDER BY sale_price DESC; + +-- 结果如下 ++------------+--------------+------------+----------------+ +| product_id | product_name | sale_price | purchase_price | ++------------+--------------+------------+----------------+ +| 0005 | 高压锅 | 680000 | 1250 | +| 0004 | 菜刀 | 300000 | 700 | +| 0007 | 擦菜板 | 88000 | 198 | +| 0006 | 叉子 | 50000 | NULL | +| 0003 | 运动T恤 | 4000 | 2800 | +| 0001 | T恤衫 | 1000 | 500 | +| 0002 | 打孔器 | 500 | 320 | +| 0008 | 圆珠笔 | 100 | NULL | ++------------+--------------+------------+----------------+ +8 rows in set (0.00 sec) +``` + +前面展示了指定一个字段来对表进行排序,实际上我们可以指定多个字段来进行排序。 + +示例: + +```mysql +SELECT regist_date, product_id, sale_price, purchase_price + FROM Product +ORDER BY regist_date, product_id; + +-- 结果如下 ++-------------+------------+------------+----------------+ +| regist_date | product_id | sale_price | purchase_price | ++-------------+------------+------------+----------------+ +| 2009-10-10 | 0002 | 500 | 320 | +| 2009-10-10 | 0003 | 4000 | 2800 | +| 2009-10-10 | 0004 | 300000 | 700 | +| 2009-10-10 | 0005 | 680000 | 1250 | +| 2009-10-10 | 0006 | 50000 | NULL | +| 2009-10-10 | 0007 | 88000 | 198 | +| 2009-10-10 | 0008 | 100 | NULL | +| 2021-10-30 | 0001 | 1000 | 500 | ++-------------+------------+------------+----------------+ +``` + +可以看到先按照`regist_date`的大小进行排序,在字段`regist_date`中具有相同的值的行,接着会按照`product_id`进行排序。 + +使用含有 NULL 的列作为排序键时,NULL 会在结果的开头或末尾汇总显示。 + +在`ORDER BY`子句中可以使用`SELECT`子句中定义的别名。 + +```mysql +-- 将product_id命名为ID,然后按照ID进行排序 +SELECT product_id as ID, product_name, sale_price, purchase_price + FROM Product +ORDER BY ID; + +-- 结果如下 ++------+--------------+------------+----------------+ +| ID | product_name | sale_price | purchase_price | ++------+--------------+------------+----------------+ +| 0001 | T恤衫 | 1000 | 500 | +| 0002 | 打孔器 | 500 | 320 | +| 0003 | 运动T恤 | 4000 | 2800 | +| 0004 | 菜刀 | 300000 | 700 | +| 0005 | 高压锅 | 680000 | 1250 | +| 0006 | 叉子 | 50000 | NULL | +| 0007 | 擦菜板 | 88000 | 198 | +| 0008 | 圆珠笔 | 100 | NULL | ++------+--------------+------------+----------------+ +8 rows in set (0.00 sec) +``` + +为什么`ORDER BY`中可以使用`SELECT`定义的别名呢? + +这是因为在MySQL中,`ORDER BY `的执行次序在`SELECT`之后。 + + + +# 七、数据的插入及更新 + +## 7.1 数据的插入 + +通过命令`INSERT`,可以向表中插入数据: + +```mysql +-- 往表中插入一行数据 +INSERT INTO <表名> (字段1, 字段2, 字段3, ……) VALUES (值1, 值2, 值3, ……); + +-- 往表中插入多行数据 +INSERT INTO <表名> (字段1, 字段2, 字段3, ……) VALUES + (值1, 值2, 值3, ……), + (值1, 值2, 值3, ……), + ... + ; +``` + +示例: + +1. 创建表并插入数据 + +```mysql +-- 创建表 +CREATE TABLE ProductIns +(product_id CHAR(4) NOT NULL, + product_name VARCHAR(100) NOT NULL, + product_type VARCHAR(32) NOT NULL, + sale_price INTEGER DEFAULT 0, -- DEFAULT 0:表示将字段sale_price的默认值设为0 + purchase_price INT , + regist_date DATE , + PRIMARY KEY (product_id)); + +-- 通过单行方式插入 +INSERT INTO + ProductIns(product_id, product_name, product_type, sale_price, purchase_price, regist_date) + VALUES ('0001', '打孔器', '办公用品', 500, 320, '2009-09-11'); + +-- 当对表插入全字段时,可以省略表后的字段清单 +INSERT INTO ProductIns VALUES('0002', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'); + +-- 通过多行方式插入 +INSERT INTO ProductIns VALUES + ('0003', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'), + ('0004', '订书机', '办公用品', 100, 50, '2009-09-11'), + ('0005', '裙子', '衣服', 4100, 3200, '2009-01-23'), + ('0006', '运动T恤', '衣服', 4000, 2800, NULL), + ('0007', '牙刷', '日用品', 20, 10, '2010-03-22'); +``` + +```mysql +SELECT * FROM ProductIns; + +-- 结果如下 ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | +| 0002 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | +| 0003 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | +| 0004 | 订书机 | 办公用品 | 100 | 50 | 2009-09-11 | +| 0005 | 裙子 | 衣服 | 4100 | 3200 | 2009-01-23 | +| 0006 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | +| 0007 | 牙刷 | 日用品 | 20 | 10 | 2010-03-22 | ++------------+--------------+--------------+------------+----------------+-------------+ +7 rows in set (0.00 sec) +``` + +2. 插入NULL + + `INSERT `语句中想给某一列赋予**NULL**值时,可以直接在` VALUES`子句的值清单中写入**NULL**。 + +```mysql +INSERT INTO ProductIns VALUES ('0008', '叉子', '厨房用具', 500, NULL, '2009-09-20'); +``` + +3. 插入默认值 + + 在前面我们创建表时,字段sale_price包含了一条约束条件,默认为0。我们在插入数据时,可以直接用`DEFAULT`对该字段赋值。前提是,该字段被指定了默认值。 + +```mysql +-- 通过显式方法设定默认值 +INSERT INTO + ProductIns (product_id, product_name, product_type, sale_price, purchase_price, regist_date) + VALUES ('0009', '擦菜板', '厨房用具', DEFAULT, 790, '2009-04-28'); + +-- 通过隐式方法插入默认值 +INSERT INTO + ProductIns (product_id, product_name, product_type, purchase_price, regist_date) + VALUES ('0010', '擦菜板', '厨房用具', 790, '2009-04-28'); +``` + + + +## 7.2 数据的删除 + +通过`DROP TABLE`或者`DELETE`语句,可以对表进行删除,但二者存在一定的区别。 + ++ `DROP TABLE` 语句可以将表完全删除。 ++ `DELETE` 语句会留下表结构,而删除表中的全部数据。 + +无论通过哪种方式删除,数据都是难以恢复的。 + +1. 通过`DROP`进行删除 + + 语法结构为: + +```mysql +DROP <表名>; +``` + +2. 通过`DELETE`进行删除 + + 语法结构如下,记得要加`FROM`: + +```mysql +DELETE FROM <表名>; +``` + +​ 同时,也可以通过`WHERE`语句来指定删除的条件: + +```mysql +DELETE FROM <表名> + WHERE <条件>; +``` + +​ 需要注意的是,`DELETE`语句的删除对象并不是表或者列,而是记录(行)。 + +示例: + +```mysql +SELECT * FROM Product; + +-- 结果如下 +mysql> SELECT * FROM Product; ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | +| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | +| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL | +| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | +| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 | +| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | +| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | +| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | ++------------+--------------+--------------+------------+----------------+-------------+ +8 rows in set (0.00 sec) + +-- 删除销售价格大于等于4000的行 +DELETE FROM Product + WHERE sale_price >= 4000; + +-- 结果如下 +mysql> SELECT * FROM Product; ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 | +| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 | +| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 | +| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-09-20 | +| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 | +| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-11-11 | ++------------+--------------+--------------+------------+----------------+-------------+ +6 rows in set (0.00 sec) +``` + +3. 通过`TRUNCATE`进行删除 + + 在MySQL中,还存在一种删除表的方式,就是利用`TRUNCATE`语句。它的功能和`DROP`类似,但是不能通过`WHERE`指定条件,优点是速度比`DROP`快得多。 + +```mysql +TRUNCATE Product; + +-- 结果如下 +mysql> SELECT * FROM Product; +Empty set (0.00 sec) + +mysql> DESC Product; ++----------------+--------------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++----------------+--------------+------+-----+---------+-------+ +| product_id | char(4) | NO | PRI | NULL | | +| product_name | varchar(100) | NO | | NULL | | +| product_type | varchar(32) | NO | | NULL | | +| sale_price | int | YES | | NULL | | +| purchase_price | int | YES | | NULL | | +| regist_date | date | YES | | NULL | | ++----------------+--------------+------+-----+---------+-------+ +6 rows in set (0.00 sec) +``` + + + +## 7.3 数据的更新 + +当我们使用`INSERT`语句插入错误的数据后,若我们不想删除后从新插入,那就要使用到`UPDATE`语句。 + +1. 基本用法 + + `UPDATE`的语法结构如下: + +```mysql +UPDATE <表名> + SET <字段名> = <表达式>; +``` + +​ 示例: + +```mysql +-- 由于前面演示删除语句时,表Product的内容已清空 +-- 所以,这里从新进行数据插入 +INSERT INTO Product VALUES + ('0001', 'T恤衫', '衣服', 1000, 500, '2009-09-20'), + ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11'), + ('0003', '运动T恤', '衣服', 4000, 2800, NULL), + ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20'), + ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15'), + ('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20'), + ('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28'), + ('0008', '圆珠笔', '办公用品', 100, NULL,'2009-11-11') + ; + +-- 修改表中所有行regist_date的值 +UPDATE Product + SET regist_date = '2009-10-10'; + +-- 结果如下 +mysql> SELECT * FROM Product; ++------------+--------------+--------------+------------+----------------+-------------+ +| product_id | product_name | product_type | sale_price | purchase_price | regist_date | ++------------+--------------+--------------+------------+----------------+-------------+ +| 0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-10-10 | +| 0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-10-10 | +| 0003 | 运动T恤 | 衣服 | 4000 | 2800 | 2009-10-10 | +| 0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-10-10 | +| 0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-10-10 | +| 0006 | 叉子 | 厨房用具 | 500 | NULL | 2009-10-10 | +| 0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2009-10-10 | +| 0008 | 圆珠笔 | 办公用品 | 100 | NULL | 2009-10-10 | ++------------+--------------+--------------+------------+----------------+-------------+ +8 rows in set (0.00 sec) +``` + +2. 指定条件 + +```mysql +UPDATE <表名> + SET <列名> = <表达式> + WHERE <条件>; +``` + +​ 示例: + +```mysql +UPDATE Product + SET regist_date = '2021-10-30' + WHERE product_id = '0001'; +``` + +​ 注意,你也可是使用**NULL**对表进行更新,不过更新的字段必须满足没有**主键**和**NOT NULL**的约束条件。 + +3. 多列更新 + + 多列更新只需要用逗号(,)连接更改的字段即可。 + +```mysql +UPDATE Product + SET + sale_price = sale_price * 10, + purchase_price = purchase_price / 2 + WHERE product_type = '厨房用具'; +``` + + + +# 八、Pymysql的使用 + +在正式介绍`pymysql`的用法之前,我们先思考一件事,我们希望借助`pymysql`完成什么事情? + +之前,我们在命令行下,通过输入SQL语句来完成对数据库和表的增删改查。那么,我们也希望能够在Python下能够完成同样的操作,并且能够返回相应的反馈。具体任务包括: + +1. 登陆并连接到MySQL下的用户; +2. 切换到相应的数据库下; +3. 完成对表的增删改查; + +接下来的内容将围绕这3部分来介绍。 + + + +## 8.1 安装pymysql + +通过`pip`,我们可以完成对`pymysql`的安装: + +```bash +python3 -m pip install PyMySQL +``` + + + +## 8.2 连接数据库 + +如果希望在Python中操作MySQL数据库,那么首先就要登陆到MySQL下的用户。 + +我们通过创建库pymysql下的类`connect`的一个实例来登陆到数据库。 + +示例: + +```python +import pymysql + +# 这里登陆到我之前创建的admin账户 +db = pymysql.connect( + host='localhost', + user='admin', + password='mysql123', + database='shop', + charset='utf8mb4', + cursorclass=pymysql.cursors.DictCursor +) +``` + +参数解释: + ++ `host`:数据库服务器地址,默认`localhost`; ++ `user`:所要登陆的用户名; ++ `password`:用户的登录密码; ++ `database`:所要连接的数据库库名; ++ `charset`:使用的字符类型; ++ `cursorclass`:定义游标使用的类型,通过指定游标使用的类型,在返回输出的结果时将按照指定的类型进行返回。例如,这里设置为字典游标。 + + + +## 8.3 创建游标 + +关于游标,可以理解为在命令行中的光标。在命令行中,我们是在光标处键入语句的。这里游标的起到类似作用。 + +```python +# 创建游标 +cursor = db.cursor() +``` + +实际上,除了在初始化`connect`的实例时指定游标类型,我们在初始化游标时也可以指定游标类型,默认为元组类型。 + +```python +cursor = db.cursor(cursor=pymysql.cursors.DictCursor) +``` + +`cursors`共支持四类游标: + ++ `Cursor`: 默认,元组类型 + ++ `DictCursor`: 字典类型 + ++ `SSCursor`: 无缓冲元组类型 + ++ `SSDictCursor`: 无缓冲字典类型 + + + +## 8.4 类方法 + +初始化完类`connect`和`cursor`的实例后,我们先来了解一下这两个类下包含的方法。了解这些方法有利于我们后面在python下操作mysql: + ++ `connect`下的类方法: + + `close()`:在完成操作后,需要关闭与数据库之间的连接; + + `commit()`:如果执行语句中发生了数据更改,需要提交更改到稳定的存储器; + + `cursor(cursor=None)`:创建一个游标,前面我们在初始化`connect`类是指定了游标类型,通过`cursor`初始化游标时,也可以进行游标类型指定; + + `rollback()`:事务回滚; + ++ `pymysql.cursors`下的类方法: + + `close()`:结束时,关闭游标; + + `execute()`:通过游标执行语句; + + `executemany()`:通过游标执行多条语句; + + `fetchone()`:获取单条数据; + + `fetchmany(size=None)`:获取size条数据; + + `fetchall()`:获取单条数据; + + `scroll(value, mode)`:数据的查询操作都是基于游标,可以通过`scroll`控制游标的位置。 + + `mode=absolute`:绝对位置移动,控制游标位置到上一次查询的第`value`条数据,最小值为`0`; + + `mode=relative`:相对位置移动,基于当前位置,跳过`value`条数据; + +更详细的资料,可参考官方的API或者Github: + +[pymysql github]: https://github.com/PyMySQL/PyMySQL + +[pymysql document]: https://pymysql.readthedocs.io/en/latest/modules/index.html# + + + +## 8.5 实战 + ++ 示例1: + +  在这个示例中,我们将做两件事情:创建表和插入数据。 + +```python +import pymysql + +# 以admin身份连接到数据库shop +connection = pymysql.connect( + host='localhost', + user='admin', + password='mysql123', + database='shop', + charset='utf8mb4', +) + +# 创建游标 +cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) + +# 1. 创建了一个表 +sql = """ +CREATE TABLE Employee( + id INT PRIMARY KEY, + name CHAR(15) NOT NULL + ) + """ + +# 提交执行 +cursor.execute(sql) + +# 2. 往表中插入数据 +sql = "INSERT INTO Employee (id, name) VALUES (%s, %s)" +values = [(1, 'XiaoBai'), + (2, 'XiaoHei'), + (3, 'XiaoHong'), + (4, 'XiaoMei'), + (5, 'XiaoLi')] + +try: + # 通过executemany可以插入多条数据 + cursor.executemany(sql, values) + # 提交事务 + connection.commit() +except: + connection.rollback() + + +# 3. 关闭光标及连接 +cursor.close() +connection.close() +``` + ++ 示例2 + + 在示例1的基础上,我们继续执行查询工作。 + +```python +import pymysql + +# 以admin身份连接到数据库shop +connection = pymysql.connect( + host='localhost', + user='admin', + password='mysql123', + database='shop', + charset='utf8mb4', +) + +with connection: + # 创建游标 + cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) + + # 1. 通过fetchone只查询一条 + cursor.execute("SHOW CREATE TABLE Employee") + result = cursor.fetchone() + print(f'查询结果1: \n{result}') + + # 2. 通过fetchmany查询size条 + cursor.execute("DESC Employee") + result = cursor.fetchmany(size=2) + print(f'查询结果2: \n{result}') + + # 3. 通过fetchall查询所有 + cursor.execute("SELECT * FROM Employee") + result = cursor.fetchall() + print(f'查询结果3: \n{result}') + + # 4. 通过scroll回滚到第0条进行查询 + cursor.scroll(0, mode='absolute') + result = cursor.fetchone() + print(f'查询结果4: \n{result}') + + # 5. 通过scroll跳过2条进行查询 + cursor.scroll(2, mode='relative') + result = cursor.fetchone() + print(f'查询结果5: \n{result}') + + cursor.close() +``` + +​ 控制台打印结果如下: + +```bash +查询结果1: +{'Table': 'Employee', 'Create Table': 'CREATE TABLE `Employee` (\n `id` int NOT NULL,\n `name` char(15) NOT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci'} +查询结果2: +[{'Field': 'id', 'Type': 'int', 'Null': 'NO', 'Key': 'PRI', 'Default': None, 'Extra': ''}, {'Field': 'name', 'Type': 'char(15)', 'Null': 'NO', 'Key': '', 'Default': None, 'Extra': ''}] +查询结果3: +[{'id': 1, 'name': 'XiaoBai'}, {'id': 2, 'name': 'XiaoHei'}, {'id': 3, 'name': 'XiaoHong'}, {'id': 4, 'name': 'XiaoMei'}, {'id': 5, 'name': 'XiaoLi'}] +查询结果4: +{'id': 1, 'name': 'XiaoBai'} +查询结果5: +{'id': 4, 'name': 'XiaoMei'} +``` + ++ 示例3: + + ​ 该示例将演示SQL注入的问题。先建立一个表并插入数据: + +```python +import pymysql + +# 以admin身份连接到数据库shop +connection = pymysql.connect( + host='localhost', + user='admin', + password='mysql123', + database='shop', + charset='utf8mb4', +) + +# 创建游标 +cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) + +sql = """ + CREATE TABLE UserInfo( + id INT PRIMARY KEY, + name VARCHAR(15), + password CHAR(15) NOT NULL + ) + """ + +cursor.execute(sql) + +sql = "INSERT INTO UserInfo (id, name, password) VALUES (%s, %s, %s)" +values = [(1, 'XiaoBai', '123'), + (2, 'XiaoHei', '234'), + (3, 'XiaoHong', '567'), + (4, 'XiaoMei', '321'), + (5, 'XiaoLi', '789')] + +cursor.executemany(sql, values) +connection.commit() +``` + +​ 再写一个程序,根据输入判定登陆是否成功: + +```python +import pymysql + +# 以admin身份连接到数据库shop +connection = pymysql.connect( + host='localhost', + user='admin', + password='mysql123', + database='shop', + charset='utf8mb4', +) + +# 创建游标 +cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) + +while True: + user = input("输入用户:").strip() + password = input("输入密码:").strip() + sql = "select name, password from UserInfo where name='%s' and password='%s' " % (user, password) + + cursor.execute(sql) + # 打印用户和密码 + result=cursor.fetchone() + print(result) + + if result: + print("成功登陆\n") + else: + print("登陆失败\n") +``` + +​ 在控制台下,我们进行了三组用户和密码的验证: + +```python +输入用户:XiaoBai +输入密码:123 +{'name': 'XiaoBai', 'password': '123'} +成功登陆 + +输入用户:XiaoBai +输入密码:321 +None +登陆失败 + +输入用户:XiaoBai' -- dsd +输入密码:321 +{'name': 'XiaoBai', 'password': '123'} +成功登陆 +``` + +​ 可以看出,第1组和第2组验证正常,但是第三组出现了异常,输入错误的密码却可以正确登陆。 + +​ 这是因为在MySQL中`--`的含义是注释,如果通过字符串进行拼接: + +```mysql +select name, password from UserInfo where name='XiaoBai' -- dsd' and password='321' +``` + +​ 实际等价于: + +```mysql +sselect name, password from UserInfo where name='XiaoBai' +``` + +​ 解决办法:通过`execute`或者`executemany`来进行拼接。将语句: + +```python +sql = "select name, password from UserInfo where name='%s' and password='%s' " % (user, password) +cursor.execute(sql) +``` + +​ 改为: + +```python +sql = "select name, password from UserInfo where name=%s and password=%s" +cursor.execute(sql, (user, password)) +``` + diff --git a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MongoDB基础.md b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.2 MongoDB基础.md similarity index 96% rename from docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MongoDB基础.md rename to docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.2 MongoDB基础.md index 186ee062..56d8d105 100644 --- a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/MongoDB基础.md +++ b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.2 MongoDB基础.md @@ -1,1239 +1,1239 @@ -# MongoDB简介 - -MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。 - -在高负载的情况下,添加更多的节点,可以保证服务器性能。 - -MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。 - -MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。 - -![img](https://i.loli.net/2021/11/02/sgy5CQIfnR9cmpO.png) - -## 主要特点 - -- MongoDB 是一个面向文档存储的数据库,操作起来比较简单和容易。 -- 你可以在MongoDB记录中设置任何属性的索引 (如:FirstName="Sameer",Address="8 Gandhi Road")来实现更快的排序。 -- 你可以通过本地或者网络创建数据镜像,这使得MongoDB有更强的扩展性。 -- 如果负载的增加(需要更多的存储空间和更强的处理能力) ,它可以分布在计算机网络中的其他节点上这就是所谓的分片。 -- Mongo支持丰富的查询表达式。查询指令使用JSON形式的标记,可轻易查询文档中内嵌的对象及数组。 -- MongoDb 使用update()命令可以实现替换完成的文档(数据)或者一些指定的数据字段 。 -- Mongodb中的Map/reduce主要是用来对数据进行批量处理和聚合操作。 -- Map和Reduce。Map函数调用emit(key,value)遍历集合中所有的记录,将key与value传给Reduce函数进行处理。 -- Map函数和Reduce函数是使用Javascript编写的,并可以通过db.runCommand或mapreduce命令来执行MapReduce操作。 -- GridFS是MongoDB中的一个内置功能,可以用于存放大量小文件。 -- MongoDB允许在服务端执行脚本,可以用Javascript编写某个函数,直接在服务端执行,也可以把函数的定义存储在服务端,下次直接调用即可。 -- MongoDB支持各种编程语言:RUBY,PYTHON,JAVA,C++,PHP,C#等多种语言。 -- MongoDB安装简单 - - - -# Linux平台安装MongoDB - -MongoDB 提供了 linux 各个发行版本 64 位的安装包,你可以在官网下载安装包。 - -MongoDB 源码下载地址:https://www.mongodb.com/download-center#community - -安装前我们需要安装各个 Linux 平台依赖包。 - -**Red Hat/CentOS:** - -``` -sudo yum install libcurl openssl -``` - -**Ubuntu 18.04 LTS ("Bionic")/Debian 10 "Buster":** - -``` -sudo apt-get install libcurl4 openssl -``` - -**Ubuntu 16.04 LTS ("Xenial")/Debian 9 "Stretch":** - -``` -sudo apt-get install libcurl3 openssl -``` - -查看ubuntu的版本 - -``` -lsb_release -a -``` - -![image-20211026193919108](https://i.loli.net/2021/11/02/4Ml1tYIbLimWS2X.png) - - - -![image-20211026201305053](https://i.loli.net/2021/11/02/cHV1hAf4s52ECUw.png) - -![image-20211026201645786](https://i.loli.net/2021/11/02/Imq9ZYdxrRXiGkl.png) - - - -这里我们选择 tgz 下载,下载完安装包,并解压 **tgz**(以下演示的是 64 位 Linux上的安装) 。 - -``` -wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.4.10.tgz #下载 -wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.4.10.tgz #解压 -``` - -MongoDB 的可执行文件位于 bin 目录下,所以可以将其添加到 **PATH** 路径中 - -``` -export PATH=/bin:$PATH -``` - -****为你 MongoDB 的安装路径。 - -## 创建数据库目录 - -默认情况下 MongoDB 启动后会初始化以下两个目录: - -- 数据存储目录:/var/lib/mongodb -- 日志文件目录:/var/log/mongodb - -我们在启动前可以先创建这两个目录: - -``` -sudo mkdir -p /var/lib/mongo -sudo mkdir -p /var/log/mongodb -``` - -接下来启动 Mongodb 服务: - -``` -mongod --dbpath /var/lib/mongo --logpath /var/log/mongodb/mongod.log --fork -``` - ---------------------------------------------------------- - -## MongoDB 后台管理 Shell - -如果你需要进入 mongodb 后台管理,由于已经将MongoDB可执行文件添加到PATH路径,所以可以直接执行 mongo 命令文件。 - -MongoDB Shell 是 MongoDB 自带的交互式 Javascript shell,用来对 MongoDB 进行操作和管理的交互式环境。 - -当你进入 mongoDB 后台后,它默认会链接到 test 文档(数据库): - -![image-20211027223343278](https://i.loli.net/2021/11/02/dDlZE71WqtsS2i8.png) - -# MongoDB 概念解析 - -在mongodb中基本的概念是文档、集合、数据库。下表将帮助您更容易理解Mongo中的一些概念: - -| SQL术语/概念 | MongoDB术语/概念 | 解释/说明 | -| :----------- | :--------------- | :---------------------------------- | -| database | database | 数据库 | -| table | collection | 数据库表/集合 | -| row | document | 数据记录行/文档 | -| column | field | 数据字段/域 | -| index | index | 索引 | -| table joins | | 表连接,MongoDB不支持 | -| primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 | - -## MongoDB 创建数据库 - -### 数据库 - -一个mongodb中可以建立多个数据库。 - -MongoDB的默认数据库为"db",该数据库存储在data目录中。 - -MongoDB的单个实例可以容纳多个独立的数据库,每一个都有自己的集合和权限,不同的数据库也放置在不同的文件中。 - -**"show dbs"** 命令可以显示所有数据的列表。 - -``` -toby@recsys:~$ mongo -MongoDB shell version: 2.6.10 -connecting to: test -> show dbs -admin (empty) -local 0.078GB -``` - -执行 **"db"** 命令可以显示当前数据库对象或集合。 - -``` -toby@recsys:~$ mongo -MongoDB shell version: 2.6.10 -connecting to: test -> db -test -``` - -运行"use"命令,可以连接到一个指定的数据库。 - -``` -toby@recsys:~$ mongo -MongoDB shell version: 2.6.10 -connecting to: test -> use admin -switched to db admin -> db -admin -> -``` - -### 语法 - -MongoDB 创建数据库的语法格式如下: - -``` -use DATABASE_NAME -``` - -如果数据库不存在,则创建数据库,否则切换到指定数据库。 - -### 实例 - -以下实例我们创建了数据库 tobytest: - -``` -toby@recsys:~$ mongo -MongoDB shell version: 2.6.10 -connecting to: test -> use tobytest -switched to db tobytest -> db -tobytest -> -``` - -如果你想查看所有数据库,可以使用 **show dbs** 命令: - -``` -> show dbs -admin (empty) -local 0.078GB -> -``` - -可以看到,我们刚创建的数据库 tobytest并不在数据库的列表中, 要显示它,我们需要向 tobytest数据库插入一些数据。 - -``` -> db.tobytest.insert({"name":"Toby"}) -WriteResult({ "nInserted" : 1 }) -> show dbs -admin (empty) -local 0.078GB -tobytest 0.078GB -> -``` - -MongoDB 中默认的数据库为 test,如果你没有创建新的数据库,集合将存放在 test 数据库中。 - -> **注意:** 在 MongoDB 中,集合只有在内容插入后才会创建! 就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建。 - -## MongoDB 创建集合 - -MongoDB 中使用 **createCollection()** 方法来创建集合。 - -语法格式: - -``` -db.createCollection(name, options) -``` - -参数说明: - -- name: 要创建的集合名称 -- options: 可选参数, 指定有关内存大小及索引的选项 - -options 可以是如下参数: - -| 字段 | 类型 | 描述 | -| :---------- | :--- | :----------------------------------------------------------- | -| capped | 布尔 | (可选)如果为 true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。 **当该值为 true 时,必须指定 size 参数。** | -| autoIndexId | 布尔 | 3.2 之后不再支持该参数。(可选)如为 true,自动在 _id 字段创建索引。默认为 false。 | -| size | 数值 | (可选)为固定集合指定一个最大值,即字节数。 **如果 capped 为 true,也需要指定该字段。** | -| max | 数值 | (可选)指定固定集合中包含文档的最大数量。 | - -在插入文档时,MongoDB 首先检查固定集合的 size 字段,然后检查 max 字段。 - -### 实例 - -在 tobytest 数据库中创建 runoob 集合: - -``` -> use tobytest -switched to db tobytest -> db.createCollection("tobycollection") -{ "ok" : 1 } -> -``` - -如果要查看已有集合,可以使用 **show collections** 或 **show tables** 命令: - -``` -> show tables -system.indexes -tobycollection -tobytest -> -``` - -## MongoDB 删除集合 - -MongoDB 中使用 drop() 方法来删除集合。 - -**语法格式:** - -``` -db.collection.drop() -``` - -参数说明: - -- 无 - -**返回值** - -如果成功删除选定集合,则 drop() 方法返回 true,否则返回 false。 - -### 实例 - -在数据库 tobytest中,我们可以先通过 **show collections** 命令查看已存在的集合: - -``` -> use tobytest -switched to db tobytest -> show collections -system.indexes -tobycollection -tobytest -> -``` - -接着删除集合 tobycollection: - -``` -> db.tobycollection.drop() -true -> -``` - -通过 show collections 再次查看数据库 tobytest中的集合: - -``` -> show collections -system.indexes -tobytest -> -``` - -从结果中可以看出 tobycollection集合已被删除。 - -## MongoDB 插入文档 - -文档的数据结构和 JSON 基本一样。 - -所有存储在集合中的数据都是 BSON 格式。 - -BSON 是一种类似 JSON 的二进制形式的存储格式,是 Binary JSON 的简称。 - -### 插入文档 - -MongoDB 使用 insert() 或 save() 方法向集合中插入文档,语法如下: - -``` -db.COLLECTION_NAME.insert(document) -或 -db.COLLECTION_NAME.save(document) -``` - -- save():如果 _id 主键存在则更新数据,如果不存在就插入数据。该方法新版本中已废弃,可以使用 **db.collection.insertOne()** 或 **db.collection.replaceOne()** 来代替。 -- insert(): 若插入的数据主键已经存在,则会抛 **org.springframework.dao.DuplicateKeyException** 异常,提示主键重复,不保存当前数据。 - -### 实例 - -以下文档可以存储在 MongoDB 的 tobytest 数据库 的 col 集合中: - -``` -> db.col.insert({title:'Toby MongoDB', -... description:'this is MongoDB', -... tags:['mongodb','database','NoSQL'], -... likes:1 -... }) -WriteResult({ "nInserted" : 1 }) -> -``` - -以上实例中 col 是我们的集合名,如果该集合不在该数据库中, MongoDB 会自动创建该集合并插入文档。 - -查看已插入文档: - -``` -> db.col.find() -{ "_id" : ObjectId("617970fc286e9ff2b1250d70"), "title" : "Toby MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } -> -``` - -我们也可以将数据定义为一个变量,如下所示: - -``` -> document=({title:'Toby another MongoDB', -... description:'this is another MongoDB', -... tags:['mongodb','database','NoSQL'], -... likes:2 -... }) -``` - -执行后显示结果如下: - -``` -{ - "title" : "Toby another MongoDB", - "description" : "this is another MongoDB", - "tags" : [ - "mongodb", - "database", - "NoSQL" - ], - "likes" : 2 -} -``` - -执行插入操作: - -``` -> db.col.insert(document) -WriteResult({ "nInserted" : 1 }) -> db.col.find() -{ "_id" : ObjectId("617970fc286e9ff2b1250d70"), "title" : "Toby MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } -{ "_id" : ObjectId("61797229286e9ff2b1250d71"), "title" : "Toby another MongoDB", "description" : "this is another MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 2 } -> -``` - -## MongoDB 更新文档 - -MongoDB 使用 **update()** 和 **save()** 方法来更新集合中的文档。接下来让我们详细来看下两个函数的应用及其区别。 - ------- - -### update() 方法 - -update() 方法用于更新已存在的文档。语法格式如下: - -``` -db.collection.update( - , - , - { - upsert: , - multi: , - writeConcern: - } -) -``` - -**参数说明:** - -- **query** : update的查询条件,类似sql update查询内where后面的。 -- **update** : update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的 -- **upsert** : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。 -- **multi** : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。 -- **writeConcern** :可选,抛出异常的级别。 - -### 实例 - -我们在集合 col 中插入如下数据: - -``` -> db.col.insert({title:'Toby MongoDB', -... description:'this is MongoDB', -... tags:['mongodb','database','NoSQL'], -... likes:1 -... }) -WriteResult({ "nInserted" : 1 }) -> -``` - -接着我们通过 update() 方法来更新标题(title): - -``` -> db.col.update({'title':'Toby MongoDB'},{$set:{'title':'MongoDB'}}) -WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) -> db.col.find().pretty() -{ - "_id" : ObjectId("617970fc286e9ff2b1250d70"), - "title" : "MongoDB", - "description" : "this is MongoDB", - "tags" : [ - "mongodb", - "database", - "NoSQL" - ], - "likes" : 1 -} -{ - "_id" : ObjectId("61797229286e9ff2b1250d71"), - "title" : "Toby another MongoDB", - "description" : "this is another MongoDB", - "tags" : [ - "mongodb", - "database", - "NoSQL" - ], - "likes" : 2 -} -> -``` - -可以看到标题(title)由原来的 "Toby MongoDB" 更新为了 "MongoDB"。 - -## MongoDB 删除文档 - -MongoDB remove() 函数是用来移除集合中的数据。 - -MongoDB 数据更新可以使用 update() 函数。在执行 remove() 函数前先执行 find() 命令来判断执行的条件是否正确,这是一个比较好的习惯。 - -### 语法 - -remove() 方法的基本语法格式如下所示: - -``` -db.collection.remove( - , - -) -``` - -如果你的 MongoDB 是 2.6 版本以后的,语法格式如下: - -``` -db.collection.remove( - , - { - justOne: , - writeConcern: - } -) -``` - -**参数说明:** - -- **query** :(可选)删除的文档的条件。 -- **justOne** : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。 -- **writeConcern** :(可选)抛出异常的级别。 - -### 实例 - -以下文档我们执行两次插入操作: - -``` -> db.col.insert({title:'Toby MongoDB', description:'this is MongoDB', tags:['mongodb','database','NoSQL'], likes:1 }) -WriteResult({ "nInserted" : 1 }) -> db.col.insert({title:'Toby MongoDB', description:'this is MongoDB', tags:['mongodb','database','NoSQL'], likes:1 }) -WriteResult({ "nInserted" : 1 }) -> -``` - -使用 find() 函数查询数据: - -``` -> db.col.find() -{ "_id" : ObjectId("617970fc286e9ff2b1250d70"), "title" : "MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } -{ "_id" : ObjectId("61797229286e9ff2b1250d71"), "title" : "Toby another MongoDB", "description" : "this is another MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 2 } -{ "_id" : ObjectId("6179747d286e9ff2b1250d72"), "title" : "Toby MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } -{ "_id" : ObjectId("61797481286e9ff2b1250d73"), "title" : "Toby MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } -> -``` - -接下来我们移除 title 为 'Toby MongoDB' 的文档: - -``` -> db.col.remove({'title':'Toby MongoDB'}) -WriteResult({ "nRemoved" : 2 }) # 删除了两个 -> db.col.find() -{ "_id" : ObjectId("617970fc286e9ff2b1250d70"), "title" : "MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } -{ "_id" : ObjectId("61797229286e9ff2b1250d71"), "title" : "Toby another MongoDB", "description" : "this is another MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 2 } -> -``` - -如果你只想删除第一条找到的记录可以设置 justOne 为 1,如下所示: - -``` ->db.COLLECTION_NAME.remove(DELETION_CRITERIA,1) -``` - -如果你想删除所有数据,可以使用以下方式(类似常规 SQL 的 truncate 命令): - -``` -> db.col.remove({}) -WriteResult({ "nRemoved" : 2 }) -> db.col.find() -> -``` - -## MongoDB 查询文档 - -MongoDB 查询文档使用 find() 方法。 - -find() 方法以非结构化的方式来显示所有文档。 - -### 语法 - -MongoDB 查询数据的语法格式如下: - -``` -db.collection.find(query, projection) -``` - -- **query** :可选,使用查询操作符指定查询条件 -- **projection** :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。 - -如果你需要以易读的方式来读取数据,可以使用 pretty() 方法,语法格式如下: - -``` ->db.col.find().pretty() -``` - -pretty() 方法以格式化的方式来显示所有文档。 - -### 实例 - -以下实例我们查询了集合 col 中的数据: - -``` -> db.col.insert({title:'Toby MongoDB', description:'this is MongoDB',by:'Toby', tags:['mongodb','database','NoSQL'], likes:100 }) -WriteResult({ "nInserted" : 1 }) -> db.col.find().pretty() -{ - "_id" : ObjectId("6179772f286e9ff2b1250d75"), - "title" : "Toby MongoDB", - "description" : "this is MongoDB", - "by" : "Toby", - "tags" : [ - "mongodb", - "database", - "NoSQL" - ], - "likes" : 100 -} -> -``` - -除了 find() 方法之外,还有一个 findOne() 方法,它只返回一个文档。 - -### MongoDB AND 条件 - -MongoDB 的 find() 方法可以传入多个键(key),每个键(key)以逗号隔开,即常规 SQL 的 AND 条件。 - -语法格式如下: - -``` ->db.col.find({key1:value1, key2:value2}).pretty() -``` - -#### 实例 - -以下实例通过 **by** 和 **title** 键来查询 **Toby** 中 **Toby MongoDB** 的数据 - -``` -> db.col.find({'by':'Toby','title':'Toby MongoDB'}).prettydb.col.find({'by':'Toby','title':'Toby MongoDB'}).pretty() -{ - "_id" : ObjectId("6179772f286e9ff2b1250d75"), - "title" : "Toby MongoDB", - "description" : "this is MongoDB", - "by" : "Toby", - "tags" : [ - "mongodb", - "database", - "NoSQL" - ], - "likes" : 100 -} -> -``` - -以上实例中类似于 WHERE 语句:**WHERE by='Toby' AND title='Toby MongoDB'** - ------- - -### MongoDB OR 条件 - -MongoDB OR 条件语句使用了关键字 **$or**,语法格式如下: - -``` ->db.col.find( - { - $or: [ - {key1: value1}, {key2:value2} - ] - } -).pretty() -``` - -#### 实例 - -以下实例中,我们演示了查询键 **by** 值为 **Toby**或键 **title** 值为 **Toby MongoDB** 的文档。 - -``` -> db.col.find({$or:[{"by":"Toby"},{"title":"Toby MongoDB"}]}).pretty() -{ - "_id" : ObjectId("6179772f286e9ff2b1250d75"), - "title" : "Toby MongoDB", - "description" : "this is MongoDB", - "by" : "Toby", - "tags" : [ - "mongodb", - "database", - "NoSQL" - ], - "likes" : 100 -} -> -``` - ------- - -### AND 和 OR 联合使用 - -以下实例演示了 AND 和 OR 联合使用,类似常规 SQL 语句为: **'where likes>50 AND (by = 'Toby' OR title = 'Toby MongoDB')'** - -``` -> db.col.find({"likes":{$gt:50},$or:[{"by":"Toby"},{"title":"Toby MongoDB"}]}).pretty() -{ - "_id" : ObjectId("6179772f286e9ff2b1250d75"), - "title" : "Toby MongoDB", - "description" : "this is MongoDB", - "by" : "Toby", - "tags" : [ - "mongodb", - "database", - "NoSQL" - ], - "likes" : 100 -} -> -``` - -## MongoDB 排序 - ------- - -### MongoDB sort() 方法 - -在 MongoDB 中使用 sort() 方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。 - -#### 语法 - -sort()方法基本语法如下所示: - -``` ->db.COLLECTION_NAME.find().sort({KEY:1}) -``` - -#### 实例 - -col 集合中的数据如下: - -``` -> db.col.find().pretty() -{ - "_id" : ObjectId("61797a56286e9ff2b1250d78"), - "title" : "Toby PHP", - "description" : "this is PHP", - "by" : "Toby", - "tags" : [ - "PHP", - "Language" - ], - "likes" : 100 -} -{ - "_id" : ObjectId("61797a62286e9ff2b1250d79"), - "title" : "Toby JAVA", - "description" : "this is JAVA", - "by" : "Toby", - "tags" : [ - "JAVA", - "Language" - ], - "likes" : 50 -} -{ - "_id" : ObjectId("61797a83286e9ff2b1250d7a"), - "title" : "Toby Python", - "description" : "this is Python", - "by" : "Toby", - "tags" : [ - "Python", - "Language" - ], - "likes" : 20 -} -> -``` - -以下实例演示了 col 集合中的数据按字段 likes 的降序排列: - -``` -> db.col.find({},{'title':1,_id:0}).sort({"likes":-1}) -{ "title" : "Toby PHP" } -{ "title" : "Toby JAVA" } -{ "title" : "Toby Python" } -> -``` - -# Python MongoDB - ------- - -## PyMongo - -Python 要连接 MongoDB 需要 MongoDB 驱动,这里我们使用 PyMongo 驱动来连接。 - -### pip 安装 - -pip 是一个通用的 Python 包管理工具,提供了对 Python 包的查找、下载、安装、卸载的功能。 - -安装 pymongo: - -``` -$ python3 -m pip install pymongo -``` - -### 测试 PyMongo - -接下来我们可以创建一个测试文件 demo_test_mongodb.py,代码如下: - -``` -import pymongo -``` - -执行以上代码文件,如果没有出现错误,表示安装成功。 - -## 创建数据库 - -### 创建一个数据库 - -创建数据库需要使用 MongoClient 对象,并且指定连接的 URL 地址和要创建的数据库名。 - -如下实例中,我们创建的数据库 pydb: - -#### 实例 - -```python -import pymongo -myclient=pymongo.MongoClient("mongodb://localhost:27017/") -mydb=myclient["pydb"] -``` - -> **注意:** 在 MongoDB 中,数据库只有在内容插入后才会创建! 就是说,数据库创建后要创建集合(数据表)并插入一个文档(记录),数据库才会真正创建。 - -### 判断数据库是否已存在 - -我们可以读取 MongoDB 中的所有数据库,并判断指定的数据库是否存在: - -#### 实例 - -```python -import pymongo -myclient=pymongo.MongoClient("mongodb://localhost:27017/") -mydb=myclient["pydb"] - -dblist = myclient.list_database_names() -# dblist = myclient.database_names() -if "pydb" in dblist: - print("数据库已存在!") -else: - print('数据库不存在') -``` - -> **注意:**database_names 在最新版本的 Python 中已废弃,Python3.7+ 之后的版本改为了 list_database_names()。 - - - -![image-20211030141217841](https://i.loli.net/2021/11/02/K4oZ3xvmiGXUWsQ.png) - -## 创建集合 - -MongoDB 中的集合类似 SQL 的表。 - -### 创建一个集合 - -MongoDB 使用数据库对象来创建集合,实例如下: - -#### 实例 - -```python -import pymongo -myclient=pymongo.MongoClient("mongodb://localhost:27017/") -mydb=myclient["pydb"] - -mycol=myclient["col_set"] -``` - - - -> **注意:** 在 MongoDB 中,集合只有在内容插入后才会创建! 就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建。 - -### 判断集合是否已存在 - -我们可以读取 MongoDB 数据库中的所有集合,并判断指定的集合是否存在: - -#### 实例 - -```python -import pymongo -myclient=pymongo.MongoClient("mongodb://localhost:27017/") -mydb=myclient["pydb"] - -mycol=myclient["col_set"] - -collist = mydb. list_collection_names() -if "col_set" in collist: # 判断 sites 集合是否存在 - print("集合已存在!") -else: - print('集合不存在') -``` - -![image-20211030141526295](https://i.loli.net/2021/11/02/K7mJARPe1dM2Yos.png) - -## Python Mongodb 插入文档 - -MongoDB 中的一个文档类似 SQL 表中的一条记录。 - -### 插入集合 - -集合中插入文档使用 **insert_one()** 方法,该方法的第一参数是字典 **name => value** 对。 - -以下实例向 **col_set** 集合中插入文档: - -#### 实例 - -```python -import pymongo - -myclient = pymongo.MongoClient("mongodb://localhost:27017/") -mydb = myclient["pydb"] -mycol = mydb["col_set"] - -mydict = { "name": "Toby", "age": "23", "url": "https://juejin.cn/user/3403743731649863" } - -x = mycol.insert_one(mydict) -print(x) -``` - -![image-20211030142137931](https://i.loli.net/2021/11/02/yY6EmCx4PfLolFQ.png) - -在命令行看一下是否插入成功 - -``` -> use pydb -switched to db pydb -> db.col_set.find() -{ "_id" : ObjectId("617ce42cbc6011eaf1529012"), "name" : "Toby", "url" : "https://juejin.cn/user/3403743731649863", "age" : "23" } -> -``` - -### 插入多个文档 - -集合中插入多个文档使用 **insert_many()** 方法,该方法的第一参数是字典列表。 - -#### 实例 - -```python -import pymongo - -myclient = pymongo.MongoClient("mongodb://localhost:27017/") -mydb = myclient["pydb"] -mycol = mydb["col_set"] - -mylist = [ - { "name": "Tom", "age": "100", "url": "https://juejin.cn/user/3403743731649863" }, - { "name": "Mary", "age": "101", "url": "https://juejin.cn/user/3403743731649863" }, - { "name": "Timi", "age": "10", "url": "https://juejin.cn/user/3403743731649863" }, -] - -x = mycol.insert_many(mylist) - -# 输出插入的所有文档对应的 _id 值 -print(x.inserted_ids) -``` - -![image-20211030142656115](https://i.loli.net/2021/11/02/7sS9XRKqUCFnrh6.png) - -在命令行看一下是否插入成功 - -``` -> use pydb -switched to db pydb -> db.col_set.find() -{ "_id" : ObjectId("617ce42cbc6011eaf1529012"), "name" : "Toby", "url" : "https://juejin.cn/user/3403743731649863", "age" : "23" } -{ "_id" : ObjectId("617ce591826d13d898f97890"), "name" : "Tom", "url" : "https://juejin.cn/user/3403743731649863", "age" : "100" } -{ "_id" : ObjectId("617ce591826d13d898f97891"), "name" : "Mary", "url" : "https://juejin.cn/user/3403743731649863", "age" : "101" } -{ "_id" : ObjectId("617ce591826d13d898f97892"), "name" : "Timi", "url" : "https://juejin.cn/user/3403743731649863", "age" : "10" } -> -``` - -## Python Mongodb 查询文档 - -MongoDB 中使用了 find 和 find_one 方法来查询集合中的数据,它类似于 SQL 中的 SELECT 语句。 - -### 查询一条数据 - -我们可以使用 **find_one()** 方法来查询集合中的一条数据。 - -查询 **col_set** 文档中的第一条数据: - -#### 实例 - -```python -import pymongo - -myclient = pymongo.MongoClient("mongodb://localhost:27017/") -mydb = myclient["pydb"] -mycol = mydb["col_set"] - -x = mycol.find_one() - -print(x) -``` - -![image-20211030142943707](https://i.loli.net/2021/11/02/F8GOH7PIiVUyA4J.png) - -### 查询集合中所有数据 - -**find()** 方法可以查询集合中的所有数据,类似 SQL 中的 **SELECT \*** 操作。 - -以下实例查找 **col_set** 集合中的所有数据: - -#### 实例 - -```python -import pymongo - -myclient = pymongo.MongoClient("mongodb://localhost:27017/") -mydb = myclient["pydb"] -mycol = mydb["col_set"] - -for x in mycol.find(): - print(x) -``` - -![image-20211030143207556](https://i.loli.net/2021/11/02/7kQH6zy5EjChqx1.png) - -### 查询指定字段的数据 - -我们可以使用 find() 方法来查询指定字段的数据,将要返回的字段对应值设置为 1。 - -#### 实例 - -```python -import pymongo - -myclient = pymongo.MongoClient("mongodb://localhost:27017/") -mydb = myclient["pydb"] -mycol = mydb["col_set"] - -for x in mycol.find({},{ "_id": 0, "name": 1, "age": 1 }): - print(x) -``` - -![image-20211030144042132](https://i.loli.net/2021/11/02/DbfneXgkLsFoIQJ.png) - -### 根据指定条件查询 - -我们可以在 **find()** 中设置参数来过滤数据。 - -以下实例查找 name 字段为 "Toby" 的数据: - -#### 实例 - -```python -import pymongo - -myclient = pymongo.MongoClient("mongodb://localhost:27017/") -mydb = myclient["pydb"] -mycol = mydb["col_set"] - -myquery = { "name": "Toby" } - -mydoc = mycol.find(myquery) - -for x in mydoc: - print(x) -``` - -![image-20211030144414902](https://i.loli.net/2021/11/02/nYx3mH5oZfNdLAu.png) - -### 返回指定条数记录 - -如果我们要对查询结果设置指定条数的记录可以使用 **limit()** 方法,该方法只接受一个数字参数。 - -以下实例返回 3 条文档记录: - -#### 实例 - -```python -import pymongo - -myclient = pymongo.MongoClient("mongodb://localhost:27017/") -mydb = myclient["pydb"] -mycol = mydb["col_set"] - -myresult = mycol.find().limit(3) - -# 输出结果 -for x in myresult: - print(x) -``` - -![image-20211030144609160](https://i.loli.net/2021/11/02/hpztCWj49APuIZr.png) - -## Python Mongodb 修改文档 - -我们可以在 MongoDB 中使用 **update_one()** 方法修改文档中的记录。该方法第一个参数为查询的条件,第二个参数为要修改的字段。 - -如果查找到的匹配数据多于一条,则只会修改第一条。 - -以下实例将 age字段的值 23改为 12345: - -#### 实例 - -```python -import pymongo - -myclient = pymongo.MongoClient("mongodb://localhost:27017/") -mydb = myclient["pydb"] -mycol = mydb["col_set"] - -myquery = { "age": "23" } -newvalues = { "$set": { "age": "12345" } } - -mycol.update_one(myquery, newvalues) - -# 输出修改后的 "sites" 集合 -for x in mycol.find(): - print(x) -``` - -![image-20211030144819907](https://i.loli.net/2021/11/02/Lun1miz7sFH6SJZ.png) - -## 排序 - -**sort()** 方法可以指定升序或降序排序。 - -**sort()** 方法第一个参数为要排序的字段,第二个字段指定排序规则,**1** 为升序,**-1** 为降序,默认为升序。 - -对字段 age 按升序排序: - -#### 实例 - -```python -import pymongo - -myclient = pymongo.MongoClient("mongodb://localhost:27017/") -mydb = myclient["pydb"] -mycol = mydb["col_set"] - -mydoc = mycol.find().sort("age") -for x in mydoc: - print(x) -``` - -![image-20211030145059219](https://i.loli.net/2021/11/02/QGZ6B4AsMqSei3W.png) - -对字段 age按降序排序: - -```python -import pymongo - -myclient = pymongo.MongoClient("mongodb://localhost:27017/") -mydb = myclient["pydb"] -mycol = mydb["col_set"] - -mydoc = mycol.find().sort("alexa", -1) - -for x in mydoc: - print(x) -``` - -![image-20211030145239034](https://i.loli.net/2021/11/02/B3v5Dkh6fYoQnTj.png) - -## Python Mongodb 删除数据 - -我们可以使用 **delete_one()** 方法来删除一个文档,该方法第一个参数为查询对象,指定要删除哪些数据。 - -以下实例删除 name 字段值为 "Timi" 的文档: - -#### 实例 - -```python -import pymongo - -myclient = pymongo.MongoClient("mongodb://localhost:27017/") -mydb = myclient["pydb"] -mycol = mydb["col_set"] - -myquery = { "name": "Timi" } - -mycol.delete_one(myquery) - -# 删除后输出 -for x in mycol.find(): - print(x) -``` - -![image-20211030145408484](https://i.loli.net/2021/11/02/crw3HJN2vQzyBW6.png) - -### 删除集合中的所有文档 - -**delete_many()** 方法如果传入的是一个空的查询对象,则会删除集合中的所有文档: - -#### 实例 - -```python -import pymongo - -myclient = pymongo.MongoClient("mongodb://localhost:27017/") -mydb = myclient["pydb"] -mycol = mydb["col_set"] - -x = mycol.delete_many({}) - -print(x.deleted_count, "个文档已删除") -``` - -![image-20211030145528857](https://i.loli.net/2021/11/02/a7l5NsKAJhVBcPk.png) - -## 删除集合 - -我们可以使用 **drop()** 方法来删除一个集合。 - -以下实例删除了 col_set集合: - -#### 实例 - -```python -import pymongo - -myclient = pymongo.MongoClient("mongodb://localhost:27017/") -mydb = myclient["pydb"] -mycol = mydb["col_set"] - -mycol.drop() - -``` - -我们在终端查看一下 - -``` -> use pydb -switched to db pydb -> show tables -system.indexes -> -``` - - - -参考链接: - -* https://www.runoob.com/python3/python-mongodb.html - -* https://www.runoob.com/mongodb/mongodb-tutorial.html - +# MongoDB简介 + +MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。 + +在高负载的情况下,添加更多的节点,可以保证服务器性能。 + +MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。 + +MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。 + +![img](https://i.loli.net/2021/11/02/sgy5CQIfnR9cmpO.png) + +## 主要特点 + +- MongoDB 是一个面向文档存储的数据库,操作起来比较简单和容易。 +- 你可以在MongoDB记录中设置任何属性的索引 (如:FirstName="Sameer",Address="8 Gandhi Road")来实现更快的排序。 +- 你可以通过本地或者网络创建数据镜像,这使得MongoDB有更强的扩展性。 +- 如果负载的增加(需要更多的存储空间和更强的处理能力) ,它可以分布在计算机网络中的其他节点上这就是所谓的分片。 +- Mongo支持丰富的查询表达式。查询指令使用JSON形式的标记,可轻易查询文档中内嵌的对象及数组。 +- MongoDb 使用update()命令可以实现替换完成的文档(数据)或者一些指定的数据字段 。 +- Mongodb中的Map/reduce主要是用来对数据进行批量处理和聚合操作。 +- Map和Reduce。Map函数调用emit(key,value)遍历集合中所有的记录,将key与value传给Reduce函数进行处理。 +- Map函数和Reduce函数是使用Javascript编写的,并可以通过db.runCommand或mapreduce命令来执行MapReduce操作。 +- GridFS是MongoDB中的一个内置功能,可以用于存放大量小文件。 +- MongoDB允许在服务端执行脚本,可以用Javascript编写某个函数,直接在服务端执行,也可以把函数的定义存储在服务端,下次直接调用即可。 +- MongoDB支持各种编程语言:RUBY,PYTHON,JAVA,C++,PHP,C#等多种语言。 +- MongoDB安装简单 + + + +# Linux平台安装MongoDB + +MongoDB 提供了 linux 各个发行版本 64 位的安装包,你可以在官网下载安装包。 + +MongoDB 源码下载地址:https://www.mongodb.com/download-center#community + +安装前我们需要安装各个 Linux 平台依赖包。 + +**Red Hat/CentOS:** + +``` +sudo yum install libcurl openssl +``` + +**Ubuntu 18.04 LTS ("Bionic")/Debian 10 "Buster":** + +``` +sudo apt-get install libcurl4 openssl +``` + +**Ubuntu 16.04 LTS ("Xenial")/Debian 9 "Stretch":** + +``` +sudo apt-get install libcurl3 openssl +``` + +查看ubuntu的版本 + +``` +lsb_release -a +``` + +![image-20211026193919108](https://i.loli.net/2021/11/02/4Ml1tYIbLimWS2X.png) + + + +![image-20211026201305053](https://i.loli.net/2021/11/02/cHV1hAf4s52ECUw.png) + +![image-20211026201645786](https://i.loli.net/2021/11/02/Imq9ZYdxrRXiGkl.png) + + + +这里我们选择 tgz 下载,下载完安装包,并解压 **tgz**(以下演示的是 64 位 Linux上的安装) 。 + +``` +wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.4.10.tgz #下载 +wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.4.10.tgz #解压 +``` + +MongoDB 的可执行文件位于 bin 目录下,所以可以将其添加到 **PATH** 路径中 + +``` +export PATH=/bin:$PATH +``` + +****为你 MongoDB 的安装路径。 + +## 创建数据库目录 + +默认情况下 MongoDB 启动后会初始化以下两个目录: + +- 数据存储目录:/var/lib/mongodb +- 日志文件目录:/var/log/mongodb + +我们在启动前可以先创建这两个目录: + +``` +sudo mkdir -p /var/lib/mongo +sudo mkdir -p /var/log/mongodb +``` + +接下来启动 Mongodb 服务: + +``` +mongod --dbpath /var/lib/mongo --logpath /var/log/mongodb/mongod.log --fork +``` + +--------------------------------------------------------- + +## MongoDB 后台管理 Shell + +如果你需要进入 mongodb 后台管理,由于已经将MongoDB可执行文件添加到PATH路径,所以可以直接执行 mongo 命令文件。 + +MongoDB Shell 是 MongoDB 自带的交互式 Javascript shell,用来对 MongoDB 进行操作和管理的交互式环境。 + +当你进入 mongoDB 后台后,它默认会链接到 test 文档(数据库): + +![image-20211027223343278](https://i.loli.net/2021/11/02/dDlZE71WqtsS2i8.png) + +# MongoDB 概念解析 + +在mongodb中基本的概念是文档、集合、数据库。下表将帮助您更容易理解Mongo中的一些概念: + +| SQL术语/概念 | MongoDB术语/概念 | 解释/说明 | +| :----------- | :--------------- | :---------------------------------- | +| database | database | 数据库 | +| table | collection | 数据库表/集合 | +| row | document | 数据记录行/文档 | +| column | field | 数据字段/域 | +| index | index | 索引 | +| table joins | | 表连接,MongoDB不支持 | +| primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 | + +## MongoDB 创建数据库 + +### 数据库 + +一个mongodb中可以建立多个数据库。 + +MongoDB的默认数据库为"db",该数据库存储在data目录中。 + +MongoDB的单个实例可以容纳多个独立的数据库,每一个都有自己的集合和权限,不同的数据库也放置在不同的文件中。 + +**"show dbs"** 命令可以显示所有数据的列表。 + +``` +toby@recsys:~$ mongo +MongoDB shell version: 2.6.10 +connecting to: test +> show dbs +admin (empty) +local 0.078GB +``` + +执行 **"db"** 命令可以显示当前数据库对象或集合。 + +``` +toby@recsys:~$ mongo +MongoDB shell version: 2.6.10 +connecting to: test +> db +test +``` + +运行"use"命令,可以连接到一个指定的数据库。 + +``` +toby@recsys:~$ mongo +MongoDB shell version: 2.6.10 +connecting to: test +> use admin +switched to db admin +> db +admin +> +``` + +### 语法 + +MongoDB 创建数据库的语法格式如下: + +``` +use DATABASE_NAME +``` + +如果数据库不存在,则创建数据库,否则切换到指定数据库。 + +### 实例 + +以下实例我们创建了数据库 tobytest: + +``` +toby@recsys:~$ mongo +MongoDB shell version: 2.6.10 +connecting to: test +> use tobytest +switched to db tobytest +> db +tobytest +> +``` + +如果你想查看所有数据库,可以使用 **show dbs** 命令: + +``` +> show dbs +admin (empty) +local 0.078GB +> +``` + +可以看到,我们刚创建的数据库 tobytest并不在数据库的列表中, 要显示它,我们需要向 tobytest数据库插入一些数据。 + +``` +> db.tobytest.insert({"name":"Toby"}) +WriteResult({ "nInserted" : 1 }) +> show dbs +admin (empty) +local 0.078GB +tobytest 0.078GB +> +``` + +MongoDB 中默认的数据库为 test,如果你没有创建新的数据库,集合将存放在 test 数据库中。 + +> **注意:** 在 MongoDB 中,集合只有在内容插入后才会创建! 就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建。 + +## MongoDB 创建集合 + +MongoDB 中使用 **createCollection()** 方法来创建集合。 + +语法格式: + +``` +db.createCollection(name, options) +``` + +参数说明: + +- name: 要创建的集合名称 +- options: 可选参数, 指定有关内存大小及索引的选项 + +options 可以是如下参数: + +| 字段 | 类型 | 描述 | +| :---------- | :--- | :----------------------------------------------------------- | +| capped | 布尔 | (可选)如果为 true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。 **当该值为 true 时,必须指定 size 参数。** | +| autoIndexId | 布尔 | 3.2 之后不再支持该参数。(可选)如为 true,自动在 _id 字段创建索引。默认为 false。 | +| size | 数值 | (可选)为固定集合指定一个最大值,即字节数。 **如果 capped 为 true,也需要指定该字段。** | +| max | 数值 | (可选)指定固定集合中包含文档的最大数量。 | + +在插入文档时,MongoDB 首先检查固定集合的 size 字段,然后检查 max 字段。 + +### 实例 + +在 tobytest 数据库中创建 runoob 集合: + +``` +> use tobytest +switched to db tobytest +> db.createCollection("tobycollection") +{ "ok" : 1 } +> +``` + +如果要查看已有集合,可以使用 **show collections** 或 **show tables** 命令: + +``` +> show tables +system.indexes +tobycollection +tobytest +> +``` + +## MongoDB 删除集合 + +MongoDB 中使用 drop() 方法来删除集合。 + +**语法格式:** + +``` +db.collection.drop() +``` + +参数说明: + +- 无 + +**返回值** + +如果成功删除选定集合,则 drop() 方法返回 true,否则返回 false。 + +### 实例 + +在数据库 tobytest中,我们可以先通过 **show collections** 命令查看已存在的集合: + +``` +> use tobytest +switched to db tobytest +> show collections +system.indexes +tobycollection +tobytest +> +``` + +接着删除集合 tobycollection: + +``` +> db.tobycollection.drop() +true +> +``` + +通过 show collections 再次查看数据库 tobytest中的集合: + +``` +> show collections +system.indexes +tobytest +> +``` + +从结果中可以看出 tobycollection集合已被删除。 + +## MongoDB 插入文档 + +文档的数据结构和 JSON 基本一样。 + +所有存储在集合中的数据都是 BSON 格式。 + +BSON 是一种类似 JSON 的二进制形式的存储格式,是 Binary JSON 的简称。 + +### 插入文档 + +MongoDB 使用 insert() 或 save() 方法向集合中插入文档,语法如下: + +``` +db.COLLECTION_NAME.insert(document) +或 +db.COLLECTION_NAME.save(document) +``` + +- save():如果 _id 主键存在则更新数据,如果不存在就插入数据。该方法新版本中已废弃,可以使用 **db.collection.insertOne()** 或 **db.collection.replaceOne()** 来代替。 +- insert(): 若插入的数据主键已经存在,则会抛 **org.springframework.dao.DuplicateKeyException** 异常,提示主键重复,不保存当前数据。 + +### 实例 + +以下文档可以存储在 MongoDB 的 tobytest 数据库 的 col 集合中: + +``` +> db.col.insert({title:'Toby MongoDB', +... description:'this is MongoDB', +... tags:['mongodb','database','NoSQL'], +... likes:1 +... }) +WriteResult({ "nInserted" : 1 }) +> +``` + +以上实例中 col 是我们的集合名,如果该集合不在该数据库中, MongoDB 会自动创建该集合并插入文档。 + +查看已插入文档: + +``` +> db.col.find() +{ "_id" : ObjectId("617970fc286e9ff2b1250d70"), "title" : "Toby MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } +> +``` + +我们也可以将数据定义为一个变量,如下所示: + +``` +> document=({title:'Toby another MongoDB', +... description:'this is another MongoDB', +... tags:['mongodb','database','NoSQL'], +... likes:2 +... }) +``` + +执行后显示结果如下: + +``` +{ + "title" : "Toby another MongoDB", + "description" : "this is another MongoDB", + "tags" : [ + "mongodb", + "database", + "NoSQL" + ], + "likes" : 2 +} +``` + +执行插入操作: + +``` +> db.col.insert(document) +WriteResult({ "nInserted" : 1 }) +> db.col.find() +{ "_id" : ObjectId("617970fc286e9ff2b1250d70"), "title" : "Toby MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } +{ "_id" : ObjectId("61797229286e9ff2b1250d71"), "title" : "Toby another MongoDB", "description" : "this is another MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 2 } +> +``` + +## MongoDB 更新文档 + +MongoDB 使用 **update()** 和 **save()** 方法来更新集合中的文档。接下来让我们详细来看下两个函数的应用及其区别。 + +------ + +### update() 方法 + +update() 方法用于更新已存在的文档。语法格式如下: + +``` +db.collection.update( + , + , + { + upsert: , + multi: , + writeConcern: + } +) +``` + +**参数说明:** + +- **query** : update的查询条件,类似sql update查询内where后面的。 +- **update** : update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的 +- **upsert** : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。 +- **multi** : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。 +- **writeConcern** :可选,抛出异常的级别。 + +### 实例 + +我们在集合 col 中插入如下数据: + +``` +> db.col.insert({title:'Toby MongoDB', +... description:'this is MongoDB', +... tags:['mongodb','database','NoSQL'], +... likes:1 +... }) +WriteResult({ "nInserted" : 1 }) +> +``` + +接着我们通过 update() 方法来更新标题(title): + +``` +> db.col.update({'title':'Toby MongoDB'},{$set:{'title':'MongoDB'}}) +WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) +> db.col.find().pretty() +{ + "_id" : ObjectId("617970fc286e9ff2b1250d70"), + "title" : "MongoDB", + "description" : "this is MongoDB", + "tags" : [ + "mongodb", + "database", + "NoSQL" + ], + "likes" : 1 +} +{ + "_id" : ObjectId("61797229286e9ff2b1250d71"), + "title" : "Toby another MongoDB", + "description" : "this is another MongoDB", + "tags" : [ + "mongodb", + "database", + "NoSQL" + ], + "likes" : 2 +} +> +``` + +可以看到标题(title)由原来的 "Toby MongoDB" 更新为了 "MongoDB"。 + +## MongoDB 删除文档 + +MongoDB remove() 函数是用来移除集合中的数据。 + +MongoDB 数据更新可以使用 update() 函数。在执行 remove() 函数前先执行 find() 命令来判断执行的条件是否正确,这是一个比较好的习惯。 + +### 语法 + +remove() 方法的基本语法格式如下所示: + +``` +db.collection.remove( + , + +) +``` + +如果你的 MongoDB 是 2.6 版本以后的,语法格式如下: + +``` +db.collection.remove( + , + { + justOne: , + writeConcern: + } +) +``` + +**参数说明:** + +- **query** :(可选)删除的文档的条件。 +- **justOne** : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。 +- **writeConcern** :(可选)抛出异常的级别。 + +### 实例 + +以下文档我们执行两次插入操作: + +``` +> db.col.insert({title:'Toby MongoDB', description:'this is MongoDB', tags:['mongodb','database','NoSQL'], likes:1 }) +WriteResult({ "nInserted" : 1 }) +> db.col.insert({title:'Toby MongoDB', description:'this is MongoDB', tags:['mongodb','database','NoSQL'], likes:1 }) +WriteResult({ "nInserted" : 1 }) +> +``` + +使用 find() 函数查询数据: + +``` +> db.col.find() +{ "_id" : ObjectId("617970fc286e9ff2b1250d70"), "title" : "MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } +{ "_id" : ObjectId("61797229286e9ff2b1250d71"), "title" : "Toby another MongoDB", "description" : "this is another MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 2 } +{ "_id" : ObjectId("6179747d286e9ff2b1250d72"), "title" : "Toby MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } +{ "_id" : ObjectId("61797481286e9ff2b1250d73"), "title" : "Toby MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } +> +``` + +接下来我们移除 title 为 'Toby MongoDB' 的文档: + +``` +> db.col.remove({'title':'Toby MongoDB'}) +WriteResult({ "nRemoved" : 2 }) # 删除了两个 +> db.col.find() +{ "_id" : ObjectId("617970fc286e9ff2b1250d70"), "title" : "MongoDB", "description" : "this is MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 1 } +{ "_id" : ObjectId("61797229286e9ff2b1250d71"), "title" : "Toby another MongoDB", "description" : "this is another MongoDB", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 2 } +> +``` + +如果你只想删除第一条找到的记录可以设置 justOne 为 1,如下所示: + +``` +>db.COLLECTION_NAME.remove(DELETION_CRITERIA,1) +``` + +如果你想删除所有数据,可以使用以下方式(类似常规 SQL 的 truncate 命令): + +``` +> db.col.remove({}) +WriteResult({ "nRemoved" : 2 }) +> db.col.find() +> +``` + +## MongoDB 查询文档 + +MongoDB 查询文档使用 find() 方法。 + +find() 方法以非结构化的方式来显示所有文档。 + +### 语法 + +MongoDB 查询数据的语法格式如下: + +``` +db.collection.find(query, projection) +``` + +- **query** :可选,使用查询操作符指定查询条件 +- **projection** :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。 + +如果你需要以易读的方式来读取数据,可以使用 pretty() 方法,语法格式如下: + +``` +>db.col.find().pretty() +``` + +pretty() 方法以格式化的方式来显示所有文档。 + +### 实例 + +以下实例我们查询了集合 col 中的数据: + +``` +> db.col.insert({title:'Toby MongoDB', description:'this is MongoDB',by:'Toby', tags:['mongodb','database','NoSQL'], likes:100 }) +WriteResult({ "nInserted" : 1 }) +> db.col.find().pretty() +{ + "_id" : ObjectId("6179772f286e9ff2b1250d75"), + "title" : "Toby MongoDB", + "description" : "this is MongoDB", + "by" : "Toby", + "tags" : [ + "mongodb", + "database", + "NoSQL" + ], + "likes" : 100 +} +> +``` + +除了 find() 方法之外,还有一个 findOne() 方法,它只返回一个文档。 + +### MongoDB AND 条件 + +MongoDB 的 find() 方法可以传入多个键(key),每个键(key)以逗号隔开,即常规 SQL 的 AND 条件。 + +语法格式如下: + +``` +>db.col.find({key1:value1, key2:value2}).pretty() +``` + +#### 实例 + +以下实例通过 **by** 和 **title** 键来查询 **Toby** 中 **Toby MongoDB** 的数据 + +``` +> db.col.find({'by':'Toby','title':'Toby MongoDB'}).prettydb.col.find({'by':'Toby','title':'Toby MongoDB'}).pretty() +{ + "_id" : ObjectId("6179772f286e9ff2b1250d75"), + "title" : "Toby MongoDB", + "description" : "this is MongoDB", + "by" : "Toby", + "tags" : [ + "mongodb", + "database", + "NoSQL" + ], + "likes" : 100 +} +> +``` + +以上实例中类似于 WHERE 语句:**WHERE by='Toby' AND title='Toby MongoDB'** + +------ + +### MongoDB OR 条件 + +MongoDB OR 条件语句使用了关键字 **$or**,语法格式如下: + +``` +>db.col.find( + { + $or: [ + {key1: value1}, {key2:value2} + ] + } +).pretty() +``` + +#### 实例 + +以下实例中,我们演示了查询键 **by** 值为 **Toby**或键 **title** 值为 **Toby MongoDB** 的文档。 + +``` +> db.col.find({$or:[{"by":"Toby"},{"title":"Toby MongoDB"}]}).pretty() +{ + "_id" : ObjectId("6179772f286e9ff2b1250d75"), + "title" : "Toby MongoDB", + "description" : "this is MongoDB", + "by" : "Toby", + "tags" : [ + "mongodb", + "database", + "NoSQL" + ], + "likes" : 100 +} +> +``` + +------ + +### AND 和 OR 联合使用 + +以下实例演示了 AND 和 OR 联合使用,类似常规 SQL 语句为: **'where likes>50 AND (by = 'Toby' OR title = 'Toby MongoDB')'** + +``` +> db.col.find({"likes":{$gt:50},$or:[{"by":"Toby"},{"title":"Toby MongoDB"}]}).pretty() +{ + "_id" : ObjectId("6179772f286e9ff2b1250d75"), + "title" : "Toby MongoDB", + "description" : "this is MongoDB", + "by" : "Toby", + "tags" : [ + "mongodb", + "database", + "NoSQL" + ], + "likes" : 100 +} +> +``` + +## MongoDB 排序 + +------ + +### MongoDB sort() 方法 + +在 MongoDB 中使用 sort() 方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。 + +#### 语法 + +sort()方法基本语法如下所示: + +``` +>db.COLLECTION_NAME.find().sort({KEY:1}) +``` + +#### 实例 + +col 集合中的数据如下: + +``` +> db.col.find().pretty() +{ + "_id" : ObjectId("61797a56286e9ff2b1250d78"), + "title" : "Toby PHP", + "description" : "this is PHP", + "by" : "Toby", + "tags" : [ + "PHP", + "Language" + ], + "likes" : 100 +} +{ + "_id" : ObjectId("61797a62286e9ff2b1250d79"), + "title" : "Toby JAVA", + "description" : "this is JAVA", + "by" : "Toby", + "tags" : [ + "JAVA", + "Language" + ], + "likes" : 50 +} +{ + "_id" : ObjectId("61797a83286e9ff2b1250d7a"), + "title" : "Toby Python", + "description" : "this is Python", + "by" : "Toby", + "tags" : [ + "Python", + "Language" + ], + "likes" : 20 +} +> +``` + +以下实例演示了 col 集合中的数据按字段 likes 的降序排列: + +``` +> db.col.find({},{'title':1,_id:0}).sort({"likes":-1}) +{ "title" : "Toby PHP" } +{ "title" : "Toby JAVA" } +{ "title" : "Toby Python" } +> +``` + +# Python MongoDB + +------ + +## PyMongo + +Python 要连接 MongoDB 需要 MongoDB 驱动,这里我们使用 PyMongo 驱动来连接。 + +### pip 安装 + +pip 是一个通用的 Python 包管理工具,提供了对 Python 包的查找、下载、安装、卸载的功能。 + +安装 pymongo: + +``` +$ python3 -m pip install pymongo +``` + +### 测试 PyMongo + +接下来我们可以创建一个测试文件 demo_test_mongodb.py,代码如下: + +``` +import pymongo +``` + +执行以上代码文件,如果没有出现错误,表示安装成功。 + +## 创建数据库 + +### 创建一个数据库 + +创建数据库需要使用 MongoClient 对象,并且指定连接的 URL 地址和要创建的数据库名。 + +如下实例中,我们创建的数据库 pydb: + +#### 实例 + +```python +import pymongo +myclient=pymongo.MongoClient("mongodb://localhost:27017/") +mydb=myclient["pydb"] +``` + +> **注意:** 在 MongoDB 中,数据库只有在内容插入后才会创建! 就是说,数据库创建后要创建集合(数据表)并插入一个文档(记录),数据库才会真正创建。 + +### 判断数据库是否已存在 + +我们可以读取 MongoDB 中的所有数据库,并判断指定的数据库是否存在: + +#### 实例 + +```python +import pymongo +myclient=pymongo.MongoClient("mongodb://localhost:27017/") +mydb=myclient["pydb"] + +dblist = myclient.list_database_names() +# dblist = myclient.database_names() +if "pydb" in dblist: + print("数据库已存在!") +else: + print('数据库不存在') +``` + +> **注意:**database_names 在最新版本的 Python 中已废弃,Python3.7+ 之后的版本改为了 list_database_names()。 + + + +![image-20211030141217841](https://i.loli.net/2021/11/02/K4oZ3xvmiGXUWsQ.png) + +## 创建集合 + +MongoDB 中的集合类似 SQL 的表。 + +### 创建一个集合 + +MongoDB 使用数据库对象来创建集合,实例如下: + +#### 实例 + +```python +import pymongo +myclient=pymongo.MongoClient("mongodb://localhost:27017/") +mydb=myclient["pydb"] + +mycol=myclient["col_set"] +``` + + + +> **注意:** 在 MongoDB 中,集合只有在内容插入后才会创建! 就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建。 + +### 判断集合是否已存在 + +我们可以读取 MongoDB 数据库中的所有集合,并判断指定的集合是否存在: + +#### 实例 + +```python +import pymongo +myclient=pymongo.MongoClient("mongodb://localhost:27017/") +mydb=myclient["pydb"] + +mycol=myclient["col_set"] + +collist = mydb. list_collection_names() +if "col_set" in collist: # 判断 sites 集合是否存在 + print("集合已存在!") +else: + print('集合不存在') +``` + +![image-20211030141526295](https://i.loli.net/2021/11/02/K7mJARPe1dM2Yos.png) + +## Python Mongodb 插入文档 + +MongoDB 中的一个文档类似 SQL 表中的一条记录。 + +### 插入集合 + +集合中插入文档使用 **insert_one()** 方法,该方法的第一参数是字典 **name => value** 对。 + +以下实例向 **col_set** 集合中插入文档: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +mydict = { "name": "Toby", "age": "23", "url": "https://juejin.cn/user/3403743731649863" } + +x = mycol.insert_one(mydict) +print(x) +``` + +![image-20211030142137931](https://i.loli.net/2021/11/02/yY6EmCx4PfLolFQ.png) + +在命令行看一下是否插入成功 + +``` +> use pydb +switched to db pydb +> db.col_set.find() +{ "_id" : ObjectId("617ce42cbc6011eaf1529012"), "name" : "Toby", "url" : "https://juejin.cn/user/3403743731649863", "age" : "23" } +> +``` + +### 插入多个文档 + +集合中插入多个文档使用 **insert_many()** 方法,该方法的第一参数是字典列表。 + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +mylist = [ + { "name": "Tom", "age": "100", "url": "https://juejin.cn/user/3403743731649863" }, + { "name": "Mary", "age": "101", "url": "https://juejin.cn/user/3403743731649863" }, + { "name": "Timi", "age": "10", "url": "https://juejin.cn/user/3403743731649863" }, +] + +x = mycol.insert_many(mylist) + +# 输出插入的所有文档对应的 _id 值 +print(x.inserted_ids) +``` + +![image-20211030142656115](https://i.loli.net/2021/11/02/7sS9XRKqUCFnrh6.png) + +在命令行看一下是否插入成功 + +``` +> use pydb +switched to db pydb +> db.col_set.find() +{ "_id" : ObjectId("617ce42cbc6011eaf1529012"), "name" : "Toby", "url" : "https://juejin.cn/user/3403743731649863", "age" : "23" } +{ "_id" : ObjectId("617ce591826d13d898f97890"), "name" : "Tom", "url" : "https://juejin.cn/user/3403743731649863", "age" : "100" } +{ "_id" : ObjectId("617ce591826d13d898f97891"), "name" : "Mary", "url" : "https://juejin.cn/user/3403743731649863", "age" : "101" } +{ "_id" : ObjectId("617ce591826d13d898f97892"), "name" : "Timi", "url" : "https://juejin.cn/user/3403743731649863", "age" : "10" } +> +``` + +## Python Mongodb 查询文档 + +MongoDB 中使用了 find 和 find_one 方法来查询集合中的数据,它类似于 SQL 中的 SELECT 语句。 + +### 查询一条数据 + +我们可以使用 **find_one()** 方法来查询集合中的一条数据。 + +查询 **col_set** 文档中的第一条数据: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +x = mycol.find_one() + +print(x) +``` + +![image-20211030142943707](https://i.loli.net/2021/11/02/F8GOH7PIiVUyA4J.png) + +### 查询集合中所有数据 + +**find()** 方法可以查询集合中的所有数据,类似 SQL 中的 **SELECT \*** 操作。 + +以下实例查找 **col_set** 集合中的所有数据: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +for x in mycol.find(): + print(x) +``` + +![image-20211030143207556](https://i.loli.net/2021/11/02/7kQH6zy5EjChqx1.png) + +### 查询指定字段的数据 + +我们可以使用 find() 方法来查询指定字段的数据,将要返回的字段对应值设置为 1。 + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +for x in mycol.find({},{ "_id": 0, "name": 1, "age": 1 }): + print(x) +``` + +![image-20211030144042132](https://i.loli.net/2021/11/02/DbfneXgkLsFoIQJ.png) + +### 根据指定条件查询 + +我们可以在 **find()** 中设置参数来过滤数据。 + +以下实例查找 name 字段为 "Toby" 的数据: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +myquery = { "name": "Toby" } + +mydoc = mycol.find(myquery) + +for x in mydoc: + print(x) +``` + +![image-20211030144414902](https://i.loli.net/2021/11/02/nYx3mH5oZfNdLAu.png) + +### 返回指定条数记录 + +如果我们要对查询结果设置指定条数的记录可以使用 **limit()** 方法,该方法只接受一个数字参数。 + +以下实例返回 3 条文档记录: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +myresult = mycol.find().limit(3) + +# 输出结果 +for x in myresult: + print(x) +``` + +![image-20211030144609160](https://i.loli.net/2021/11/02/hpztCWj49APuIZr.png) + +## Python Mongodb 修改文档 + +我们可以在 MongoDB 中使用 **update_one()** 方法修改文档中的记录。该方法第一个参数为查询的条件,第二个参数为要修改的字段。 + +如果查找到的匹配数据多于一条,则只会修改第一条。 + +以下实例将 age字段的值 23改为 12345: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +myquery = { "age": "23" } +newvalues = { "$set": { "age": "12345" } } + +mycol.update_one(myquery, newvalues) + +# 输出修改后的 "sites" 集合 +for x in mycol.find(): + print(x) +``` + +![image-20211030144819907](https://i.loli.net/2021/11/02/Lun1miz7sFH6SJZ.png) + +## 排序 + +**sort()** 方法可以指定升序或降序排序。 + +**sort()** 方法第一个参数为要排序的字段,第二个字段指定排序规则,**1** 为升序,**-1** 为降序,默认为升序。 + +对字段 age 按升序排序: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +mydoc = mycol.find().sort("age") +for x in mydoc: + print(x) +``` + +![image-20211030145059219](https://i.loli.net/2021/11/02/QGZ6B4AsMqSei3W.png) + +对字段 age按降序排序: + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +mydoc = mycol.find().sort("alexa", -1) + +for x in mydoc: + print(x) +``` + +![image-20211030145239034](https://i.loli.net/2021/11/02/B3v5Dkh6fYoQnTj.png) + +## Python Mongodb 删除数据 + +我们可以使用 **delete_one()** 方法来删除一个文档,该方法第一个参数为查询对象,指定要删除哪些数据。 + +以下实例删除 name 字段值为 "Timi" 的文档: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +myquery = { "name": "Timi" } + +mycol.delete_one(myquery) + +# 删除后输出 +for x in mycol.find(): + print(x) +``` + +![image-20211030145408484](https://i.loli.net/2021/11/02/crw3HJN2vQzyBW6.png) + +### 删除集合中的所有文档 + +**delete_many()** 方法如果传入的是一个空的查询对象,则会删除集合中的所有文档: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +x = mycol.delete_many({}) + +print(x.deleted_count, "个文档已删除") +``` + +![image-20211030145528857](https://i.loli.net/2021/11/02/a7l5NsKAJhVBcPk.png) + +## 删除集合 + +我们可以使用 **drop()** 方法来删除一个集合。 + +以下实例删除了 col_set集合: + +#### 实例 + +```python +import pymongo + +myclient = pymongo.MongoClient("mongodb://localhost:27017/") +mydb = myclient["pydb"] +mycol = mydb["col_set"] + +mycol.drop() + +``` + +我们在终端查看一下 + +``` +> use pydb +switched to db pydb +> show tables +system.indexes +> +``` + + + +参考链接: + +* https://www.runoob.com/python3/python-mongodb.html + +* https://www.runoob.com/mongodb/mongodb-tutorial.html + diff --git a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/Redis基础.md b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.3 Redis基础.md similarity index 97% rename from docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/Redis基础.md rename to docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.3 Redis基础.md index 0f659bc1..a546f433 100644 --- a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/Redis基础.md +++ b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.3 Redis基础.md @@ -1,1238 +1,1238 @@ -## ***Redis* 基础** - -### 简介: - -Redis(**Re**mote **Di**ctionary **S**erver ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库。由于是内存数据库,读写非常高速,可达10w/s的评率,所以一般应用于数据变化快、实时通讯、缓存等。但内存数据库通常要考虑机器的内存大小。Redis 是完全开源免费的,遵守 BSD 协议,是一个灵活的高性能 key-value 数据结构存储,可以用来作为数据库、缓存和消息队列。相比于其他的 key-value 缓存产品有以下三个特点: - -- Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载到内存使用。 -- Redis 不仅支持简单的 key-value 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。 -- Redis 支持主从复制,即 master-slave 模式的数据备份。 - -### 安装: - -本项目是基于Ubuntu环境进行开发,因此接下来都以Ubuntu的环境为基础,对于其他开发环境,大家可以参考相关的[资料](https://www.redis.com.cn/redis-installation.html)进行学习。 - -**安装Redis服务器:** - -```shell -sudo apt-get install redis-server -``` - -下载完成的结果 - -![image-20211030164414594](http://ryluo.oss-cn-chengdu.aliyuncs.com/图片image-20211030164414594.png) - -**启动Redis服务:** - -一般来说,当安装完成后,Redis服务器会自动启动,可以通过以下命令检查是否启动成功。(ps:如果Active显示为 active(running) 状态:表示redis已在运行,启动成功) - -```shell -service redis-server status -``` - -![image-20211030164432589](http://ryluo.oss-cn-chengdu.aliyuncs.com/图片image-20211030164432589.png) - -检查当前进程,查看redis是否启动。(ps: 可以看到redis服务正在监听6379端口) - -```shell -ps -aux|grep redis-server -``` - -![image-20211030164448713](http://ryluo.oss-cn-chengdu.aliyuncs.com/图片image-20211030164448713.png) - -或者进入redis客户端,与服务器进行通信,当输入ping命令,如果返回 PONG 表示Redis已成功安装。 - -```shell -redis-cli -``` - -![image-20211030164455928](http://ryluo.oss-cn-chengdu.aliyuncs.com/图片image-20211030164455928.png) - -上面的127.0.0.1 是redis服务器的 IP 地址,6379 是 Redis 服务器运行的端口。 - - - -### 命令: - -下面简单介绍一些常用的Redis命令: - -**1、基本操作命令:** - -- **启动Redis** - - ```shell - redis-server [--daemonize yes][--port 6379] - ``` - - 可以通过带参数方式来启动,如果参数过多,可以使用/etc/redis/redis.conf下面的配置文件来启动Redis。 - - ```shell - redis-server /etc/redis/redis.conf - ``` - -- **连接Redis** - - ```shell - redis-cli [-h host -p port -a password] - ``` - - 其中上面参数默认的是redis-server的默认地址和端口号,password可以在服务启动时采用参数的方式或者配置文件方式都可进行设置。因此可以通过redis-cli,可以连上我们服务器端的redis服务。 - -- **停止Redis** - - 停止Redis有两种方法,一种是通过 redis-cli 停止,另一种是通过杀掉redis服务进程 - - ```shell - > redis-cli shutdown - - > kill redis-pid - ``` - -- **切换库指令** - - redis.conf配置中默认16个库,下标从0~15。进入客服端默认选中第0个库,可以通过select命令进行切换,index表示库的小标。 - - ```shell - 127.0.0.1:6379> SELECT index - ``` - -- **删除当前库的数据** - - 删除当前选择的数据库中的所有数据,这个命令永远不会出现失败。 - - ```shell - 127.0.0.1:6379[1]> FLUSHDB - ``` - -- **删除所有库的数据** - - 删除所有数据库里面的数据,注意是所有数据库,这个命令永远不会出现失败。 - - ```shell - 127.0.0.1:6379[1]> FLUSHALL - ``` - -- **查看key的数量** - - 查看当前选择的库中key的数量 - - ```shell - 127.0.0.1:6379> DBSIZE - ``` - - 测试以上命令 - - ```shell - neu@neu:~$ redis-server --daemonize yes --port 6378 --requirepass 123456 - 28518:C 26 Oct 20:52:56.389 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo - 28518:C 26 Oct 20:52:56.389 # Redis version=4.0.9, bits=64, commit=00000000, modified=0, pid=28518, just started - 28518:C 26 Oct 20:52:56.389 # Configuration loaded - neu@neu:~$ redis-cli -p 6378 -a 123456 - 127.0.0.1:6378> set name jiangyou # 在第0个数据库插入一个值 - OK - 127.0.0.1:6378> select 1 # 选择第1个数据库 - OK - 127.0.0.1:6378[1]> set age 26 # 在第1个数据库插入一个值 - OK - 127.0.0.1:6378[1]> DBSIZE # 第1个数据库当前的key值数量 - (integer) 1 - 127.0.0.1:6378[1]> FLUSHDB # 删除第1个数据库的所有值 - OK - 127.0.0.1:6378[1]> DBSIZE # 删除后数据库中没有值 - (integer) 0 - 127.0.0.1:6378[1]> SELECT 0 # 切换到第0个数据库 - OK - 127.0.0.1:6378> DBSIZE # 第1个数据库的值存在,因此FLUSHDB只删除第1个数据库的所有值 - (integer) 1 - 127.0.0.1:6378> SELECT 1 - OK - 127.0.0.1:6378[1]> FLUSHALL # 切换到第1个数据库,使用FLUSHALL删除所有数据库的值 - OK - 127.0.0.1:6378[1]> SELECT 0 - OK - 127.0.0.1:6378> DBSIZE # 切换到第0个数据库,发现所有的值已被删除 - (integer) 0 - ``` - - - -**2、Key的操作命令:** - -​ 该部分指令主要是为了对数据库中的key进行增删改查等一些列操作,下面主要介绍几个常用的命令。 - -- **查找符合模板的Key** - - ```shell - KEYS pattern - ``` - - 该指令查找数据库所有符合pattern的key,其中pattern可以为?、* 、[abc...]、[a-d]等方式。其中?代表一个任意一个字符,*代表任意0个或多个字符,[abc...]代表只能是[]中的值,[a-d]代表a到d范围内总的值。 - - ```shell - 127.0.0.1:6378> keys * - 1) "age" - 2) "school" - 3) "home" - 4) "name" - 127.0.0.1:6378> keys *e - 1) "age" - 2) "home" - 3) "name" - 127.0.0.1:6378> keys a?e - 1) "age" - 127.0.0.1:6378> keys [a-n][ao]me - 1) "home" - 2) "name" - 127.0.0.1:6378> keys [a-n][a]me - 1) "name" - ``` - -- **查找存在key的数量** - - ```shell - EXISTS key or [key…] - ``` - - 该指令为了查找一个或多个key,返回存在key值的数量。 - - ```shell - 127.0.0.1:6378> exists name - (integer) 1 - 127.0.0.1:6378> exists name home id - (integer) 2 - ``` - -- **设置过期时间** - - ```shell - EXPIRE key seconds - ``` - - expire 设置 key 的过期时间,时间过期后,key 会被自动删除,设置成功返回1,key不存在返回0。 - - ```shell - TTL key - ``` - - ttl 命令以秒为单位返回key的剩余过期时间,如果key不存在返回 -2 key 存在但没有关联超时时间则返回 -1 。 - - ```shell - 127.0.0.1:6378> expire name 30 - (integer) 1 - 127.0.0.1:6378> ttl name - (integer) 26 - 127.0.0.1:6378> ttl name - (integer) -2 - 127.0.0.1:6378> ttl age - (integer) -1 - 127.0.0.1:6378> ttl id - (integer) -2 - ``` - -- **Key所属类型** - - ```shell - TYPE key - ``` - - type命令以字符串的形式返回存储在 `key` 中的值的类型,可返回的类型有:`string`, `list`, `set`, `zset`,`hash` 和 `stream`,如果key值不存在返回`none`。 - - ```shell - 127.0.0.1:6378> set key1 "value" - OK - 127.0.0.1:6378> lpush key2 "value" - (integer) 1 - 127.0.0.1:6378> SADD key3 "value" - (integer) 1 - 127.0.0.1:6378> type key1 - string - 127.0.0.1:6378> type key2 - list - 127.0.0.1:6378> type key3 - set - 127.0.0.1:6378> type key - none - ``` - -- **删除Key** - - ```shell - DEL key or [key…] - ``` - - del命令删除指定的key,不存在的key忽略,返回0,如果key存在,返回删除的key的个数。 - - ```shell - 127.0.0.1:6378> del key - (integer) 0 - 127.0.0.1:6378> del key1 key2 - (integer) 2 - ``` - -**3、字符串类型—string命令:** - -​ 字符串是Redis中最常见的数据类型,它能够存储任何形式的字符串,其中包括二进制格式,JSON格式,序列化格式等数据。而string相关的命令则是用于管理redis字符串值,下面介绍一些常见命令。 - -**基础命令** - -- **SET** - - set命令将key是定为指定的字符串,如果key存在,则会覆盖原来的值。 - - ```shell - SET key value [EX seconds] [PX milliseconds] [NX|XX] - ``` - - 其中set可以为设定的值设置过期时间,EX表示秒数,PX表示毫秒。参数NX表示只有键key不存在的时候才会设置key的值,XX表示只有键key存在的时候才会设置key的值。 - -- **GET** - - get命令返回与键 `key` 相关联的字符串值。 - - ```shell - GET key - ``` - - 如果key不存在,返回nil,如果key的值是非字符串类型,那么返回一个错误。 - -- **APPEND** - - append命令将指定的key追加值。如果key存在,并且是字符串,则会将value追加到key原值的末尾,如果key值是非字符串则会报错,当key不存在时候,改命令类似于set,简单将key设定为value。 - - ```shell - APPEND KEY_NAME NEW_VALUE - ``` - -- **INCR** - - incr 命令将 key 中储存的数字值增一。如果key不存在,key值会被初始化为0,在进行incr操作。如果字符串类型的值不能表示为数字,则会报错。 - - ```shell - INCR KEY_NAME - ``` - -- **DECR** - - decr命令将 key 中储存的数字值减一,和incr命令相似。 - - ```shell - DECR KEY_NAME - ``` - - - -**常用命令** - -- **STRLEN** - - Strlen 命令将获取指定 key 所储存的字符串值的长度,如果key存储的不是字符串类型或不存在时,返回错误。 - - ```shell - STRLEN KEY_NAME - ``` - -- **SETRANG** - - Setrange命令是将从偏移量 `offset` 开始, 用 `value` 参数覆盖键 `key` 储存的字符串值。 - - ```shell - SETRANGE key offset value - ``` - - 不存在的键 `key` 当作空白字符串处理,如果键 `key` 原来储存的字符串长度比偏移量小,那么原字符和偏移量之间的空白将用零字节("\x00" )进行填充。 - -- **GETRANG** - - Getrange命令返回存储在 key 中的字符串的子串,由 `start` 和 `end` 偏移决定(都包括在内)。负数偏移提供相对字符串结尾的偏移。并且该命令会通过将结果范围限制为字符串的实际长度来处理超出范围的请求。 - - ```shell - GETRANGE key start end - ``` - - 当key不存在返回空字符串。 - -- **MSET** - - 命令设置多个 `key` 的值为各自对应的 value。如果key存在,则会用新值替换旧值,如果key不存在,会重新创建,该命令总是返回“OK”,因为 MSET不会失败。 - - ```shell - MSET key value [key value ...] - ``` - -- **MGET** - - 命令返回所有(一个或多个)给定 key 的值,值的类型是字符串。 如果给定的 key 里面有某个 key 不存在或者值不是字符串,那么这个 key 返回特殊值 `nil` 。 - - ```shell - MGET key [key ...] - ``` - -测试以上命令 - -```shell -127.0.0.1:6379> set name jiang XX # XX表示只有键key存在的时候才会设置key的值 -(nil) -127.0.0.1:6379> set name jiang NX # NX表示只有键key不存在的时候才会设置key的值 -OK -127.0.0.1:6379> get name # 返回与键 `key` 相关联的字符串值。 -"jiangyou" -127.0.0.1:6379> get age # 键key不存在的时候返回nil -(nil) -127.0.0.1:6379> APPEND name # 将value追加到key原值的末尾,返回值的总长度 -(integer) 14 -127.0.0.1:6379> get name -"jiangyou" -127.0.0.1:6379> set age 24 EX 30 # 设置age 的值,并设置了过期时间 EX表示秒 -OK -127.0.0.1:6379> incr age # 在age上进行增 1 -(integer) 25 -127.0.0.1:6379> get age -"25" -127.0.0.1:6379> decr age # 在age上进行减 1 -(integer) 24 -127.0.0.1:6379> get age -"24" -127.0.0.1:6379> incr name # 由于name值不能表示数字,无法增1 -(error) ERR value is not an integer or out of range -127.0.0.1:6379> STRLEN name # name对应的string的长度 -(integer) 8 -127.0.0.1:6379> SETRANGE name 10 hahaha # 从偏移量为10 的位置开始加入hahaha -(integer) 16 -127.0.0.1:6379> get name # 不足的用\x00 补充 -"jiangyou\x00\x00hahaha" -127.0.0.1:6379> GETRANGE name 0 -1 # 获取name的值,改方式类似于python的数组查找 -"jiangyou\x00\x00hahaha" -127.0.0.1:6379> MSET age 26 home liaoning # 为多个key赋值 -OK -127.0.0.1:6379> MGET age home addr # 查找多个key对应的值,不存在的key返回nil。 -1) "26" -2) "liaoning" -3) (nil) -``` - - - -**4、列表—list命令:** - -**基本命令** - -- **LPUSH** - - Lpush 将一个或多个值插入到列表`key` 的头部。如果 key 不存在,那么在进行 push 操作前会创建一个空列表。如果 key 对应的值不是 list 类型,那么会返回一个错误。可以使用一个命令把多个元素 push 进入列表。 - - ```shell - LPUSH key value [value ...] - ``` - - - -- **RPUSH** - - Rpush 将向存储在 key 中的列表的尾部插入所有指定的值。如果 key 不存在,那么会创建一个空的列表然后再进行 push 操作。 当 key 保存的不是列表,那么会返回一个错误。 - - ```shell - RPUSH key value [value ...] - ``` - - - -- **LRANGE** - - Lrange将返回列表中指定区间内的元素(闭区间),区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。如果start大于最大小标,那么叫返回空列表。 - - ```shell - LRANGE key start stop - ``` - -- **LINDEX** - - Lindex 将返回列表 key 里索引 index 位置存储的元素。 index 下标是从 0 开始索引的,所以 0 是表示第一个元素, 1 表示第二个元素,并以此类推。 负数索引用于指定从列表尾部开始索引的元素,在这种方法下,-1 表示最后一个元素,-2 表示倒数第二个元素,并以此往前推。当 key 值不是列表的时候,会返回错误。 - - ```shell - LINDEX key index - ``` - -- **LLEN** - - Llen 将用于返回存储在 `key` 中的列表长度。 如果 `key` 不存在,则 `key` 被解释为一个空列表,返回 `0` 。 如果 `key` 不是列表类型,返回一个错误。 - - ```shell - LLEN key - ``` - - - -**常用命令** - -- **LREM** - - Lrem将用于从列表 key 中删除前 count 个值等于 `element` 的元素。 这个 count 参数通过下面几种方式影响这个操作,如果count > 0, 从头到尾删除值为 value 的元素;如果count < 0,将从尾到头删除值为 value 的元素;如果 count = 0 将移除所有值为 value 的元素 - - ```shell - LREM key count value - ``` - - - -- **LSET** - - Lset 将用于设置列表 key 中 index 位置的元素值为 `element`。 - - ```shell - LSET key index value - ``` - -- **LINSERT** - - Linsert 将用于把 `element` 插入到列表 `key` 的前面或后面。当 `key` 不存在时,这个list会被看作是空list,什么都不执行;当 `key` 存在,值不是列表类型时,返回错误。 - - ```shell - LINSERT key BEFORE|AFTER pivot value - ``` - -测试上面命令: - -```shell -127.0.0.1:6379> RPUSH myarrs 1 1 1 1 2 2 # 从list的右边开始往myarrs里面添加值 -(integer) 6 -127.0.0.1:6379> LRANGE myarrs 0 -1 # 返回myarrs的List中所有值 -1) "1" -2) "1" -3) "1" -4) "1" -5) "2" -6) "2" -127.0.0.1:6379> LPUSH myarrs 0 0 -1 # 从list的左边开始往myarrs里面添加值 -(integer) 9 -127.0.0.1:6379> LRANGE myarrs 0 -1 -1) "-1" -2) "0" -3) "0" -4) "1" -5) "1" -6) "1" -7) "1" -8) "2" -9) "2" -127.0.0.1:6379> LINDEX myarrs -2 # 根据索引返回List中的值 -"2" -127.0.0.1:6379> LLEN myarrs # 返回List中元素个数 -(integer) 9 -127.0.0.1:6379> LREM myarrs 2 1 # 删除myarrs中的1 count为2 所以从头往尾删除两个1 -(integer) 2 -127.0.0.1:6379> LRANGE myarrs 0 -1 -1) "-1" -2) "0" -3) "0" -4) "1" -5) "1" -6) "2" -7) "2" -127.0.0.1:6379> LREM myarrs -1 1 # 删除myarrs中的1 count为-1 所以从尾往头删除1个1 -(integer) 1 -127.0.0.1:6379> LRANGE myarrs 0 -1 -1) "-1" -2) "0" -3) "0" -4) "1" -5) "2" -6) "2" -127.0.0.1:6379> LREM myarrs 0 2 # 删除myarrs中的2 count为0 删除所有等于2的元素 -(integer) 2 -127.0.0.1:6379> LRANGE myarrs 0 -1 -1) "-1" -2) "0" -3) "0" -4) "1" -127.0.0.1:6379> LSET myarrs 2 5 # 根据索引设置myarrs的值,将索引为2 的位置赋值为5 -OK -127.0.0.1:6379> LRANGE myarrs 0 -1 -1) "-1" -2) "0" -3) "5" -4) "1" -127.0.0.1:6379> LINSERT myarrs before 5 4 # 在第一个值为5的位置的前面插入一个4 -(integer) 5 -127.0.0.1:6379> LRANGE myarrs 0 -1 -1) "-1" -2) "0" -3) "4" -4) "5" -5) "1" -``` - - - -**5、哈希类型—hash命令:** - -hash类似于java中的HashMap,在Reids中做了更多的优化。此外hash是一个sytring类型的field和value的映射表,特别适合用于存储对象。例如我们可以借用hash数据结构来存储用户信息,商品信息等。 - -**基本命令** - -- HSET - - Hset 命令用于为存储在 `key` 中的哈希表的 `field` 字段赋值 `value` 。如果哈希表不存在,一个新的哈希表被创建并进行 HSET 操作。如果字段(`field`)已经存在于哈希表中,旧值将被覆盖。 - - ```shell - HSET key field value - ``` - -- HGET - - Hget 命令用于返回哈希表中指定字段 `field` 的值。如果给定的字段或 key 不存在时,返回 nil 。 - - ```shell - HGET key field - ``` - -- HMSET - - Hmset 命令用于同时将多个 field-value (字段-值)对设置到哈希表中。此命令会覆盖哈希表中已存在的字段,如果哈希表不存在,会创建一个空哈希表,并执行 HMSET 操作。 - - ```shell - HMSET key field value [field value ...] - ``` - -- HGETALL - - Hgetall 命令用于返回存储在 `key` 中的哈希表中所有的域和值。返回值以列表形式返回哈希表的字段及字段值,若 key 不存在,返回空列表。 - - ```shell - HGETALL key - ``` - -- HDEL - - Hdel 命令用于删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。 如果 `key` 不存在,会被当作空哈希表处理并返回 `0` 。 - - ```shell - HDEL key field [field ...] - ``` - -**常用命令** - -- HEXISTS - - Hexists 命令用于查看哈希表的指定字段`field` 是否存在。如果表含有给定字段`field`会返回1,否则返回0。 - - ```shell - HEXISTS key field - ``` - -- HKEYS - - Hkeys返回存储在 `key` 中哈希表的所有域。当 key 不存在时,返回空表。 - - ```shell - HKEYS key - ``` - -- HVALS - - Hvals 命令返回哈希表所有域(field)的值。当 key 不存在时,返回空表。 - - ```shell - HVALS key - ``` - -测试以上命令 - -```shell -127.0.0.1:6379> HSET userinfo name jiangyou # 创建新的hash表,并存入对象userinfo的name属性 -(integer) 1 # 返回赋值成功域的个数 -127.0.0.1:6379> HSET userinfo age 26 home liaoming school neu # 设置userinfo对象的多个域的值 -(integer) 3 # 返回赋值成功域的个数 -127.0.0.1:6379> HKEYS userinfo # 查看userinfo的所有域的名 -1) "name" -2) "age" -3) "home" -4) "school" -127.0.0.1:6379> HKEYS users # 当key不存在时,返回空 -(empty list or set) -127.0.0.1:6379> HVALS userinfo # 返回key值的所有域的值 -1) "jiangyou" -2) "26" -3) "liaoming" -4) "neu" -127.0.0.1:6379> HEXISTS userinfo name # 查看哈希表的指定字段`name` 该字段存在,返回1 -(integer) 1 -127.0.0.1:6379> HEXISTS userinfo addr # 查看哈希表的指定字段`addr` 该字段存在,返回0 -(integer) 0 -127.0.0.1:6379> HGETALL userinfo # 查看哈希表中存储在 `key` 中的所有的域和值 -1) "name" -2) "jiangyou" -3) "age" -4) "26" -5) "home" -6) "liaoming" -7) "school" -8) "neu" -127.0.0.1:6379> HGETALL users # `key` 不存在,会被当作空哈希表处理并返回。 -(empty list or set) -127.0.0.1:6379> HDEL userinfo school home # 删除哈希表 key 中的一个或多个指定域,返回的为成功删除的域的个数。 -(integer) 2 -127.0.0.1:6379> HGETALL userinfo -1) "name" -2) "jiangyou" -3) "age" -4) "26" -``` - - - -**6、集合类型—set命令:** - -**基本命令** - -- **SADD** - - Sadd 将命令将一个或多个成员元素加入到集合中,已经存在于集合的成员元素将被忽略。假如集合 key 不存在,则创建一个只包含被添加的元素作为成员的集合。当集合 key 不是集合类型时,返回一个错误。 - - ```shell - SADD key member [member ...] - ``` - -- **SMEMBERS** - - Smembers 将返回存储在 `key` 中的集合的所有的成员。 不存在的集合被视为空集合。 - - ```shell - SMEMBERS key - ``` - -- **SISMEMBER** - - Sismember 将用于判断元素 `member` 是否集合 `key` 的成员。如果成员元素是集合的成员,返回 1 ;如果成员元素不是集合的成员,或 `key` 不存在,返回0。 - - ```shell - SISMEMBER key member - ``` - -- **SCARD** - - Scard 将返回集合中元素的数量。 - - ```shell - SCARD key - ``` - -- **SREM** - - Srem将在集合中删除指定的元素。如果指定的元素不是集合成员则被忽略。如果集合 `key` 不存在则被视为一个空的集合,该命令返回0。如果key的类型不是一个集合,则返回错误。 - - ```shell - SCARD key member [member ...] - ``` - - -**常用命令** - -- **SRANDMEMBER** - - Srandmember 将仅使用`key` 参数,那么随机返回集合`key` 中的一个随机元素。如果count是整数且小于元素的个数,返回含有 count 个不同的元素的数组,如果count是个整数且大于集合中元素的个数时,返回整个集合的所有元素,当count是负数,则会返回一个包含count的绝对值的个数元素的数组,如果count的绝对值大于元素的个数,则返回的结果集里会出现一个元素出现多次的情况。 - - ```shell - SRANDMEMBER key [count] - ``` - -- **SPOP** - - Spop 将从集合 `key`中删除并返回一个或多个随机元素。这个命令和 SRANDMEMBER相似, SRANDMEMBER 只返回随机成员但是不删除这些返回的成员。 - - ```shell - SRANDMEMBER key [count] - ``` - - -测试以上命令 - -```shell -127.0.0.1:6379> SADD name zhangsan lisi wangwu # 赋值key为name的set集合,返回赋值成功的个数 -(integer) 3 -127.0.0.1:6379> SMEMBERS name # 查看存储在name中的集合的所有的成员。 -1) "zhangsan" -2) "lisi" -3) "wangwu" -127.0.0.1:6379> SISMEMBER name zhangsan # 判断元素 zhangsan 是否集合 name 的成员,如果是 返回1 -(integer) 1 -127.0.0.1:6379> SISMEMBER name xuliu # 判断元素 xuliu 是否集合 name 的成员,如果不是 返回0 -(integer) 0 -127.0.0.1:6379> SCARD name -(integer) 3 -127.0.0.1:6379> SREM name zhangsan xuliu # 删除 name 的成员,如果存在直接删除,否则忽略。返回删除成功的元素个数 -(integer) 1 -127.0.0.1:6379> SMEMBERS name -1) "lisi" -2) "wangwu" -127.0.0.1:6379> SRANDMEMBER name 5 # 随机返回集合name中的一个随机元素,count为5 大于集合个数,返回整个集合元素 -1) "lisi" -2) "wangwu" -127.0.0.1:6379> SRANDMEMBER name 1 # 随机返回集合name中的一个随机元素,count为1 随机返回集合中任意一个元素 -1) "wangwu" -127.0.0.1:6379> SRANDMEMBER name -5 # 随机返回集合name中的一个随机元素,count为-5 返回的结果集里会出现一个元素出现多次 -1) "wangwu" -2) "lisi" -3) "lisi" -4) "lisi" -5) "wangwu" -127.0.0.1:6379> SPOP name 0 # 随机删除并返回集合name中的一个或多个随机元素,count为0 返回的结果集里不会出现任何元素 -(empty array) -127.0.0.1:6379> SPOP name 1 # 随机删除并返回集合name中的一个或多个随机元素,count为1 返回的结果集里会出现一个元素出现多次 -1) "lisi" -127.0.0.1:6379> SPOP name -5 # 随机删除并返回集合name中的一个或多个随机元素,count 不能为负数。 -(error) ERR value is out of range, must be positive -``` - - - -**7、有序集合类型—sortedset命令:** - -**基本命令** - -- **ZADD** - - Zadd 将一个或多个 `member` 元素及其 `score` 值加入到有序集 `key` 当中。如果某个 `member` 已经是有序集的成员,那么更新这个 `member` 的 `score` 值,并通过重新插入这个 `member` 元素,来保证该 `member` 在正确的位置上。如果有序集合 `key` 不存在,则创建一个空的有序集并执行 ZADD操作。当 `key` 存在但不是有序集类型时,返回一个错误。`score` 值可以是整数值或双精度浮点数,`score` 可为正也可以为负。 - - ```shell - ZADD key [NX|XX] [CH] [INCR] score member [score member ...] - ``` - - - **XX**: 仅更新存在的成员,不添加新成员。 - - **NX**: 不更新存在的成员。只添加新成员。 - - **LT**: 更新新的分值比当前分值小的成员,不存在则新增。 - - **GT**: 更新新的分值比当前分值大的成员,不存在则新增。 - - **CH**: 返回变更成员的数量。变更的成员是指 **新增成员** 和 **score值更新**的成员,命令指明的和之前score值相同的成员不计在内。 注意: 在通常情况下,ZADD返回值只计算新添加成员的数量。 - - **INCR**: [ZADD](https://www.redis.com.cn/commands/zadd.html) 使用该参数与 [ZINCRBY](https://www.redis.com.cn/commands/zincrby.html) 功能一样。一次只能操作一个score-element对。 - - 举例子: - - ```shell - ``` - - - -- **ZRANG** - - Zrange将返回有序集中,指定区间内(闭区间)的成员,其中成员的按分数值递增(从小到大)来排序,具有相同分数值的成员按字典序(lexicographical order )来排列。如果你需要成员按值递减(从大到小)来排列,可以使用 `ZREVRANGE`命令。下标参数 `start` 和 `stop` 都以 `0` 为底,也就是说,以 `0` 表示有序集第一个成员,以 `1` 表示有序集第二个成员,以此类推。其中 start和stop参数的细节同 `ZRANG`命令。 - - ```shell - ZRANGE key start stop [WITHSCORES] - ``` - - - -- **ZREVRANGE** - - Zervrange 将返回有序集`key`中,指定区间内的成员。其中成员的位置按score值递减(从高到低)来排列。具有相同score值的成员按字典序的反序排列。 除了成员排序相反外,`ZREVRANGE`命令的其他方面和`ZRANGE`命令一样。 - - ```shell - ZREVRANGE key start stop [WITHSCORES] - ``` - - - -- **ZREM** - - Zrem 将从有序集合`key`中删除指定的成员`member`。如果`member`不存在则被忽略。当key存在,但是不是有序集合类型时,返回类型错误。返回的是从有序集合中删除的成员个数,不包括不存在的成员。 - - ```shell - ZREM key member [member ...] - ``` - - - -- **ZCARD** - - Zcard 将返回有序集的成员个数。 当 `key` 不存在时,返回 `0` 。 - - ```shell - ZCARD key - ``` - - - -**常用命令** - -- **ZRANGEBYSCORE** - - 该指令将返回有序集 `key` 中,所有 `score` 值介于 `min` 和 `max` 之间(包括等于 `min` 或 `max` )的成员。有序集成员按 `score` 值递增(从小到大)次序排列。具有相同 `score` 值的成员按字典序来排列(该属性是有序集提供的,不需要额外的计算)。可选的 `LIMIT` 参数指定返回结果的数量及区间(就像SQL中的 `SELECT LIMIT offset, count` ),注意当 `offset` 很大时,定位 `offset` 的操作可能需要遍历整个有序集,此过程最坏复杂度为 O(N) 时间。可选的 `WITHSCORES` 参数决定结果集是单单返回有序集的成员,还是将有序集成员及其 `score` 值一起返回。 - - ```shell - ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] - ``` - - - -- **ZREVRANGEBYSCORE** - - 该指令将返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大)次序排列。具有相同分数值的成员按字典序来排列(该属性是有序集提供的,不需要额外的计算)。默认情况下,区间的取值使用闭区间 (小于等于或大于等于),你也可以通过给参数前增加 ( 符号来使用可选的开区间 (小于或大于)。可选的LIMIT参数指定返回结果的数量及区间(类似SQL中SELECT LIMIT offset, count)。注意,如果offset太大,定位offset就可能遍历整个有序集合,这会增加O(N)的复杂度。可选参数WITHSCORES会返回元素和其分数,而不只是元素。 - - ```shell - ZREVRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] - ``` - - - -- **ZCOUNT** - - Zcount 将返回有序集 `key` 中, `score` 值在 `min` 和 `max` 之间(默认包括 `score` 值等于 `min` 或 `max` )的成员的数量。 - - ```shell - ZCOUNT key min max - ``` - - - -## Python调用Redis - -在Python中,目前可以通过一个redis模块来实现操控Redis,下面我们简单的介绍一下关于使用redis模块。 - -### 安装Redis模块 - -如果是在Windows 系统,安装 redis 模块可以使用以下命令: - -```shell -python -m pip install redis -``` - -如果是 Linux 系统,需要执行以下命令来安装: - -```shell -sudo pip3 install redis -``` - -如果是使用Anaconda管理环境,也可以使用以下命令安装: - -```shell -conda install redis -``` - - - -### Python连接Redis - -Redis模块提供了两种连接的模式:直连模式和连接词模式。 - -**直连模式** - -直连模式的方式简单方便,适合少量长期连接的场景。其中host参数是ip地址,如果Redis服务存在于本地,可以使用127.0.0.1,或者换成Redis服务所在的ip地址。db表示当前选择的库,其参数值可以是 0-15;如果设置连接数据库的密码,那么就需要使用password进行验证。 - -```python -import redis - -r = redis.Redis(host='127.0.0.1',port=6379,db=0,password='') -r.set('name':'jiangyou') -print(r.get('name')) -``` - -**连接池模式** - -连接池模式是使用 connection pool(连接池)来管理 redis server 的所有连接,每个Redis实例会维护自己的连接池来管理管理对一个 redis server 所有的连接,避免每次建立,释放连接的开销。 - -```python -import redis - -pool = redis.ConnectionPool(host="127.0.0.1",port=6379,db=0,password="",decode_responses=True, max_connections=10) -r1 = redis.Redis(connection_pool=pool) # 第一个客户端访问 -r2 = redis.Redis(connection_pool=pool) # 第二个客户端访问 -``` - -上面的参数,decode_responses=True 可以使得redis取出的结果改成字符串,其默认的是字节, max_connections参数可以设置最大连接数量,这样当有新的客户端请求连接时,只需要去连接池获取即可,这样就可以把一个连接共享给多个客服端,减少每次连接所消耗的时间以及资源。 - - - -**基本操作** - -在Redis模块中,提供了**Redis**和**StrictRedis**来支持Redis访问和操作。其中 **StrictRedis** 使用python基于Redis协议实现了所有官方的Redis操作命令,也就是说其实对于python操作redis的API接口和上面提到的Redis官方接口一样。因此下面我们就简单介绍一些常用的方法。 - -1. **String操作** - - ```python - import redis - - pool = redis.ConnectionPool(host="127.0.0.1",port=6379,db=0,password="",decode_responses=True,max_connections=10) - r = redis.StrictRedis(connection_pool=pool) - - r.set('name','jiang') - r.append("name","you") # 在redis name对应的值后面追加内容 - - r.mset({'age':'26','home':'liaoning'}) - print(r.mget('name','age','home')) - print("name 长度:%d"%r.strlen('name')) #查看ame对应值的长度 - - r.incrby('age',5) #数值操作 将age对应的值 加5 - print(r.get('age')) - r.decrby('age',5) #数值操作 将age对应的值 减5 - print(r.get('age')) - r.incrbyfloat('age',5.2) #将age对应的值 加5.2 - print(r.get('age')) - r.incrbyfloat('age',-10.5) #将age对应的值 减10.5 - print(r.get('age')) - - r.setrange('name',5,'hahaha') # 修改字符串内容,从指定字符串索引开始向后替换。 - print(r.getrange('name',0,6)) # 获取子序列(根据字节获取,非字符),闭区间 - - r.delete('name') #删除key - ``` - - 运行结果 - - ```python - ['jiangyou', '26', 'liaoning'] - name 长度:8 - 31 - 26 - 31.2 - 20.7 - jiangha - ``` - -2. **Hash操作** - - ```python - import redis - - pool = redis.ConnectionPool(host="127.0.0.1",port=6379,db=0,password="",decode_responses=True,max_connections=10) - r = redis.StrictRedis(connection_pool=pool) - - r.hset('user1','name','zhangsan') # user1对应的hash中设置一个键值对(不存在,则创建;否则,修改) - r.hset('user1','age','22') # user1对应的hash中设置一个键值对(不存在,则创建;否则,修改) - r.hincrbyfloat('user1','age',0.5) # 自增user1对应的hash中的指定key的值,不存在则创建key=amount - print(r.hmget('user1','name','age')) # 在user1对应的hash中获取多个key的值 - - # 一次性设置多个field和value - user_dict = { - 'password':'123', - 'gender':'M', - 'home':'辽宁' - } - r.hmset('user1',user_dict) # 在user1对应的hash中批量设置键值对 - - print("user1中存在键值对的个数:%d "%r.hlen('user1')) # 获取所有数据,字典类型 - print("user1中存在键值对的具体信息:%s"%r.hgetall('user1')) # 获取所有数据,字典类型 - print(r.hkeys("user1")) # 获取所有fields字段 - print(r.hvals("user1")) # 获取所有fields字段的values值 - - if r.hexists("user1","home"): # 检查user1对应的hash是否存在当前传入的home - r.hdel("user1",'home') # 将user1对应的hash中指定key的键值对删除 - print("已删除该键!!!") - else: - print("不存在该键!!!") - ``` - - 运行结果 - - ```python - ['zhangsan', '22.5'] - user1中存在键值对的个数:5 - user1中存在键值对的具体信息:{'name': 'zhangsan', 'age': '22.5', 'password': '123', 'gender': 'M', 'home': '辽宁'} - ['name', 'age', 'password', 'gender', 'home'] - ['zhangsan', '22.5', '123', 'M', '辽宁'] - 已删除该键!! - ``` - - - -3. **List操作** - - ```python - import redis - - pool = redis.ConnectionPool(host="127.0.0.1",port=6379,db=0,password="",decode_responses=True,max_connections=10) - r = redis.StrictRedis(connection_pool=pool) - - r.lpush('database','sql','mysql','redis') # 在database对应的list中添加元素,每个新的元素都添加到列表的最左边 - print(r.lrange('database',0,-1)) - - r.linsert('database','before','mysql','mongodb') # 在database对应的列表的某一个值前或后插入一个新值,其含义为在第三个参数的前(before)或后(after) 插入参数四 - - print(r.lrange('database',0,-1)) # 在database对应的列表分片获取数据 - - print("database中元素个数:%d"%r.llen('database')) # database对应的list元素的个数 - - print("database中第2个元素:%s"%r.lindex('database',2)) #在database对应的列表中根据索引获取列表元素 - - r.lset('database', 0, 'redisdb') # 对database对应的list中的某一个索引位置重新赋值 - print(r.lrange('database',0,-1)) - - print(r.rpop('database')) # 在database对应的列表的右侧获取第一个元素并在列表中移除,返回值则是第一个元素 - - print(r.ltrim('database',0,1)) # 在database对应的列表中移除没有在start-end索引之间的值 - - while True: - result = r.brpop('database',1) # 从一个列表的右侧移除一个元素并将其添加到另一个列表的左侧 [如果列表中为空时,则返回None] - if result: - print(result) - else: - break - r.delete('database') - ``` - - 运行结果 - - ```python - ['redis', 'mysql', 'sql'] - ['redis', 'mongodb', 'mysql', 'sql'] - database中元素个数:4 - database中第2个元素:mysql - ['redisdb', 'mongodb', 'mysql', 'sql'] - sql - True - ('database', 'mongodb') - ('database', 'redisdb') - ``` - - - -4. **Set操作** - - ```python - import redis - - pool = redis.ConnectionPool(host="127.0.0.1",port=6379,db=0,password="",decode_responses=True,max_connections=10) - r = redis.StrictRedis(connection_pool=pool) - - - r.sadd("name","zhangsan") # 给name对应的集合中添加元素 - r.sadd("name","zhangsan","lisi","wangwu") - - - print(r.smembers('name')) # 获取name对应的集合的所有成员 - - print(r.scard("name") ) # 获取name对应的集合中的元素个数 - - print(r.sismember('name','zhangsan')) # 检查value是否是name对应的集合内的元素,返回值为True或False - - print(r.spop('name')) # 随机删除并返回指定集合的一个元素 - print(r.smembers('name')) - - # srem(name, value) - print(r.srem("name", "zhangsan")) # 删除集合中的某个元素 - print(r.smembers('name')) - - r.sadd("name","a","b") - r.sadd("name1","b","c") - r.sadd("name2","b","c","d") - - print(r.sinter("name","name1","name2")) # 获取多个name对应集合的交集 - - print(r.sunion("name","name1","name2")) # 获取多个name对应的集合的并集 - - print(r.sdiff("name","name1","name2")) # 在第一个name对应的集合中且不在其他name对应的集合的元素集合 - - r.flushall() - ``` - - 运行结果 - - ```python - {'zhangsan', 'lisi', 'wangwu'} - 3 - True - lisi - {'zhangsan', 'wangwu'} - 1 - {'wangwu'} - {'b'} - {'d', 'c', 'b', 'wangwu', 'a'} - {'wangwu', 'a'} - ``` - - - -5. **SortedSet操作** - - ```python - import redis - - pool = redis.ConnectionPool(host="127.0.0.1",port=6379,db=0,decode_responses=True,max_connections=10) - r = redis.StrictRedis(connection_pool=pool) - - mapping = { - 'zhangsan':85, - 'lisi':92, - 'wangwu':76 - } - r.zadd('C++',mapping,nx=True) # 在C++对应的有序集合中添加元素 - print(r.zrange('C++',0,-1,withscores=True)) # 获取C++对应的有序集合的所有元素 - - print(r.zcard("C++")) # 获取C++对应的有序集合元素的数量 - print(r.zcount('C++',min=0,max=90)) # 获取C++对应的有序集合中分数 在 [min,max] 之间的个数 - - r.zincrby(name='C++',value='lisi',amount=3) # 增加C++对应的有序集合的lisi对应的分数 - print(r.zrange('C++',0,-1,desc=False,withscores=True)) # 按照索引范围获取C++对应的有序集合的元素,排序规则,默认按照分数从小到大排序 - print(r.zrevrange('C++',0,-1,withscores=True)) # 按照索引范围获取C++对应的有序集合的元素,排序规则,默认按照分数从大到小排序 - - print(r.zrangebyscore('C++',70,90)) # 按照分数范围获取C++对应的有序集合的元素,排序规则,默认按照分数从小到大排序 - print(r.zrevrangebyscore('C++',90,70)) # 按照分数范围获取C++对应的有序集合的元素,排序规则,默认按照分数从大到小排序 - - print(r.zrank('C++','lisi')) # Zrank 返回有序集中指定成员的排名,有序集成员按分数值递增(从小到大)顺序排列。 - print(r.zrevrank('C++','lisi')) # Zrevrank 返回有序集中指定成员的排名,有序集成员按分数值递增(从大到小)顺序排列。 - - mapping = { - 'xuliu':74, - 'lisi':82, - 'wangwu':87 - } - r.zadd('python',mapping,nx=True) - r.zinterstore('sum_score_i',['C++','python'],aggregate='sum') # 获取两个有序集合的交集,如果遇到相同值不同分数,则按照aggregate进行操作 - print(r.zrange('sum_score_i',0,-1,withscores=True)) - print(r.zunionstore('sum_score_u',['C++','python'],'min')) # 获取两个有序集合的并集,如果遇到相同值不同分数,则按照aggregate进行操作 - print(r.zrange('sum_score_u',0,-1,withscores=True)) - - r.zrem('C++', 'zhangsan') # 删除C++对应的有序集合中值是zhangsan的成员 - print(r.zrange('C++',0,-1,withscores=True)) - - r.zremrangebyscore('C++', min=80, max=100) # 删除C++对应的有序集合中值是zhangsan的成员 - print(r.zrange('C++',0,-1,withscores=True)) - - r.zremrangebyrank('python', min=1, max=3) # 根据排行范围删除 - print(r.zrange('python',0,-1,withscores=True)) - ``` - - 运行结果 - - ```python - [('wangwu', 76.0), ('zhangsan', 85.0), ('lisi', 92.0)] - 3 - 2 - [('wangwu', 76.0), ('zhangsan', 85.0), ('lisi', 95.0)] - [('lisi', 95.0), ('zhangsan', 85.0), ('wangwu', 76.0)] - ['wangwu', 'zhangsan'] - ['zhangsan', 'wangwu'] - 2 - 0 - [('wangwu', 163.0), ('lisi', 177.0)] - 4 - [('xuliu', 74.0), ('wangwu', 76.0), ('lisi', 82.0), ('zhangsan', 85.0)] - [('wangwu', 76.0), ('lisi', 95.0)] - [('wangwu', 76.0)] - [('xuliu', 74.0)] - ``` - -6. **管道操作** - - Redis 模块默认在执行每次请求都会向连接池请求创建连接和断开申请操作,如果想要在一次请求中指定多个命令,则可以使用pipline实现一次请求指定多个命令,并且默认情况下一次pipline 是原子性操作(即为一次操作)。 - - ```python - import redis - - pool = redis.ConnectionPool(host="127.0.0.1",port=6379,db=0,decode_responses=True,max_connections=10) - r = redis.StrictRedis(connection_pool=pool) - - pipe = r.pipeline(transaction=True) - - pipe.set('name', 'jiangyou') - pipe.set('age', 'age') - pipe.execute() - - print(r.mget("name","age")) - ``` - - 运行结果 - - ```python - ['jiangyou', 'age'] - ``` - - +## ***Redis* 基础** + +### 简介: + +Redis(**Re**mote **Di**ctionary **S**erver ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库。由于是内存数据库,读写非常高速,可达10w/s的评率,所以一般应用于数据变化快、实时通讯、缓存等。但内存数据库通常要考虑机器的内存大小。Redis 是完全开源免费的,遵守 BSD 协议,是一个灵活的高性能 key-value 数据结构存储,可以用来作为数据库、缓存和消息队列。相比于其他的 key-value 缓存产品有以下三个特点: + +- Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载到内存使用。 +- Redis 不仅支持简单的 key-value 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。 +- Redis 支持主从复制,即 master-slave 模式的数据备份。 + +### 安装: + +本项目是基于Ubuntu环境进行开发,因此接下来都以Ubuntu的环境为基础,对于其他开发环境,大家可以参考相关的[资料](https://www.redis.com.cn/redis-installation.html)进行学习。 + +**安装Redis服务器:** + +```shell +sudo apt-get install redis-server +``` + +下载完成的结果 + +![image-20211030164414594](http://ryluo.oss-cn-chengdu.aliyuncs.com/图片image-20211030164414594.png) + +**启动Redis服务:** + +一般来说,当安装完成后,Redis服务器会自动启动,可以通过以下命令检查是否启动成功。(ps:如果Active显示为 active(running) 状态:表示redis已在运行,启动成功) + +```shell +service redis-server status +``` + +![image-20211030164432589](http://ryluo.oss-cn-chengdu.aliyuncs.com/图片image-20211030164432589.png) + +检查当前进程,查看redis是否启动。(ps: 可以看到redis服务正在监听6379端口) + +```shell +ps -aux|grep redis-server +``` + +![image-20211030164448713](http://ryluo.oss-cn-chengdu.aliyuncs.com/图片image-20211030164448713.png) + +或者进入redis客户端,与服务器进行通信,当输入ping命令,如果返回 PONG 表示Redis已成功安装。 + +```shell +redis-cli +``` + +![image-20211030164455928](http://ryluo.oss-cn-chengdu.aliyuncs.com/图片image-20211030164455928.png) + +上面的127.0.0.1 是redis服务器的 IP 地址,6379 是 Redis 服务器运行的端口。 + + + +### 命令: + +下面简单介绍一些常用的Redis命令: + +**1、基本操作命令:** + +- **启动Redis** + + ```shell + redis-server [--daemonize yes][--port 6379] + ``` + + 可以通过带参数方式来启动,如果参数过多,可以使用/etc/redis/redis.conf下面的配置文件来启动Redis。 + + ```shell + redis-server /etc/redis/redis.conf + ``` + +- **连接Redis** + + ```shell + redis-cli [-h host -p port -a password] + ``` + + 其中上面参数默认的是redis-server的默认地址和端口号,password可以在服务启动时采用参数的方式或者配置文件方式都可进行设置。因此可以通过redis-cli,可以连上我们服务器端的redis服务。 + +- **停止Redis** + + 停止Redis有两种方法,一种是通过 redis-cli 停止,另一种是通过杀掉redis服务进程 + + ```shell + > redis-cli shutdown + + > kill redis-pid + ``` + +- **切换库指令** + + redis.conf配置中默认16个库,下标从0~15。进入客服端默认选中第0个库,可以通过select命令进行切换,index表示库的小标。 + + ```shell + 127.0.0.1:6379> SELECT index + ``` + +- **删除当前库的数据** + + 删除当前选择的数据库中的所有数据,这个命令永远不会出现失败。 + + ```shell + 127.0.0.1:6379[1]> FLUSHDB + ``` + +- **删除所有库的数据** + + 删除所有数据库里面的数据,注意是所有数据库,这个命令永远不会出现失败。 + + ```shell + 127.0.0.1:6379[1]> FLUSHALL + ``` + +- **查看key的数量** + + 查看当前选择的库中key的数量 + + ```shell + 127.0.0.1:6379> DBSIZE + ``` + + 测试以上命令 + + ```shell + neu@neu:~$ redis-server --daemonize yes --port 6378 --requirepass 123456 + 28518:C 26 Oct 20:52:56.389 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo + 28518:C 26 Oct 20:52:56.389 # Redis version=4.0.9, bits=64, commit=00000000, modified=0, pid=28518, just started + 28518:C 26 Oct 20:52:56.389 # Configuration loaded + neu@neu:~$ redis-cli -p 6378 -a 123456 + 127.0.0.1:6378> set name jiangyou # 在第0个数据库插入一个值 + OK + 127.0.0.1:6378> select 1 # 选择第1个数据库 + OK + 127.0.0.1:6378[1]> set age 26 # 在第1个数据库插入一个值 + OK + 127.0.0.1:6378[1]> DBSIZE # 第1个数据库当前的key值数量 + (integer) 1 + 127.0.0.1:6378[1]> FLUSHDB # 删除第1个数据库的所有值 + OK + 127.0.0.1:6378[1]> DBSIZE # 删除后数据库中没有值 + (integer) 0 + 127.0.0.1:6378[1]> SELECT 0 # 切换到第0个数据库 + OK + 127.0.0.1:6378> DBSIZE # 第1个数据库的值存在,因此FLUSHDB只删除第1个数据库的所有值 + (integer) 1 + 127.0.0.1:6378> SELECT 1 + OK + 127.0.0.1:6378[1]> FLUSHALL # 切换到第1个数据库,使用FLUSHALL删除所有数据库的值 + OK + 127.0.0.1:6378[1]> SELECT 0 + OK + 127.0.0.1:6378> DBSIZE # 切换到第0个数据库,发现所有的值已被删除 + (integer) 0 + ``` + + + +**2、Key的操作命令:** + +​ 该部分指令主要是为了对数据库中的key进行增删改查等一些列操作,下面主要介绍几个常用的命令。 + +- **查找符合模板的Key** + + ```shell + KEYS pattern + ``` + + 该指令查找数据库所有符合pattern的key,其中pattern可以为?、* 、[abc...]、[a-d]等方式。其中?代表一个任意一个字符,*代表任意0个或多个字符,[abc...]代表只能是[]中的值,[a-d]代表a到d范围内总的值。 + + ```shell + 127.0.0.1:6378> keys * + 1) "age" + 2) "school" + 3) "home" + 4) "name" + 127.0.0.1:6378> keys *e + 1) "age" + 2) "home" + 3) "name" + 127.0.0.1:6378> keys a?e + 1) "age" + 127.0.0.1:6378> keys [a-n][ao]me + 1) "home" + 2) "name" + 127.0.0.1:6378> keys [a-n][a]me + 1) "name" + ``` + +- **查找存在key的数量** + + ```shell + EXISTS key or [key…] + ``` + + 该指令为了查找一个或多个key,返回存在key值的数量。 + + ```shell + 127.0.0.1:6378> exists name + (integer) 1 + 127.0.0.1:6378> exists name home id + (integer) 2 + ``` + +- **设置过期时间** + + ```shell + EXPIRE key seconds + ``` + + expire 设置 key 的过期时间,时间过期后,key 会被自动删除,设置成功返回1,key不存在返回0。 + + ```shell + TTL key + ``` + + ttl 命令以秒为单位返回key的剩余过期时间,如果key不存在返回 -2 key 存在但没有关联超时时间则返回 -1 。 + + ```shell + 127.0.0.1:6378> expire name 30 + (integer) 1 + 127.0.0.1:6378> ttl name + (integer) 26 + 127.0.0.1:6378> ttl name + (integer) -2 + 127.0.0.1:6378> ttl age + (integer) -1 + 127.0.0.1:6378> ttl id + (integer) -2 + ``` + +- **Key所属类型** + + ```shell + TYPE key + ``` + + type命令以字符串的形式返回存储在 `key` 中的值的类型,可返回的类型有:`string`, `list`, `set`, `zset`,`hash` 和 `stream`,如果key值不存在返回`none`。 + + ```shell + 127.0.0.1:6378> set key1 "value" + OK + 127.0.0.1:6378> lpush key2 "value" + (integer) 1 + 127.0.0.1:6378> SADD key3 "value" + (integer) 1 + 127.0.0.1:6378> type key1 + string + 127.0.0.1:6378> type key2 + list + 127.0.0.1:6378> type key3 + set + 127.0.0.1:6378> type key + none + ``` + +- **删除Key** + + ```shell + DEL key or [key…] + ``` + + del命令删除指定的key,不存在的key忽略,返回0,如果key存在,返回删除的key的个数。 + + ```shell + 127.0.0.1:6378> del key + (integer) 0 + 127.0.0.1:6378> del key1 key2 + (integer) 2 + ``` + +**3、字符串类型—string命令:** + +​ 字符串是Redis中最常见的数据类型,它能够存储任何形式的字符串,其中包括二进制格式,JSON格式,序列化格式等数据。而string相关的命令则是用于管理redis字符串值,下面介绍一些常见命令。 + +**基础命令** + +- **SET** + + set命令将key是定为指定的字符串,如果key存在,则会覆盖原来的值。 + + ```shell + SET key value [EX seconds] [PX milliseconds] [NX|XX] + ``` + + 其中set可以为设定的值设置过期时间,EX表示秒数,PX表示毫秒。参数NX表示只有键key不存在的时候才会设置key的值,XX表示只有键key存在的时候才会设置key的值。 + +- **GET** + + get命令返回与键 `key` 相关联的字符串值。 + + ```shell + GET key + ``` + + 如果key不存在,返回nil,如果key的值是非字符串类型,那么返回一个错误。 + +- **APPEND** + + append命令将指定的key追加值。如果key存在,并且是字符串,则会将value追加到key原值的末尾,如果key值是非字符串则会报错,当key不存在时候,改命令类似于set,简单将key设定为value。 + + ```shell + APPEND KEY_NAME NEW_VALUE + ``` + +- **INCR** + + incr 命令将 key 中储存的数字值增一。如果key不存在,key值会被初始化为0,在进行incr操作。如果字符串类型的值不能表示为数字,则会报错。 + + ```shell + INCR KEY_NAME + ``` + +- **DECR** + + decr命令将 key 中储存的数字值减一,和incr命令相似。 + + ```shell + DECR KEY_NAME + ``` + + + +**常用命令** + +- **STRLEN** + + Strlen 命令将获取指定 key 所储存的字符串值的长度,如果key存储的不是字符串类型或不存在时,返回错误。 + + ```shell + STRLEN KEY_NAME + ``` + +- **SETRANG** + + Setrange命令是将从偏移量 `offset` 开始, 用 `value` 参数覆盖键 `key` 储存的字符串值。 + + ```shell + SETRANGE key offset value + ``` + + 不存在的键 `key` 当作空白字符串处理,如果键 `key` 原来储存的字符串长度比偏移量小,那么原字符和偏移量之间的空白将用零字节("\x00" )进行填充。 + +- **GETRANG** + + Getrange命令返回存储在 key 中的字符串的子串,由 `start` 和 `end` 偏移决定(都包括在内)。负数偏移提供相对字符串结尾的偏移。并且该命令会通过将结果范围限制为字符串的实际长度来处理超出范围的请求。 + + ```shell + GETRANGE key start end + ``` + + 当key不存在返回空字符串。 + +- **MSET** + + 命令设置多个 `key` 的值为各自对应的 value。如果key存在,则会用新值替换旧值,如果key不存在,会重新创建,该命令总是返回“OK”,因为 MSET不会失败。 + + ```shell + MSET key value [key value ...] + ``` + +- **MGET** + + 命令返回所有(一个或多个)给定 key 的值,值的类型是字符串。 如果给定的 key 里面有某个 key 不存在或者值不是字符串,那么这个 key 返回特殊值 `nil` 。 + + ```shell + MGET key [key ...] + ``` + +测试以上命令 + +```shell +127.0.0.1:6379> set name jiang XX # XX表示只有键key存在的时候才会设置key的值 +(nil) +127.0.0.1:6379> set name jiang NX # NX表示只有键key不存在的时候才会设置key的值 +OK +127.0.0.1:6379> get name # 返回与键 `key` 相关联的字符串值。 +"jiangyou" +127.0.0.1:6379> get age # 键key不存在的时候返回nil +(nil) +127.0.0.1:6379> APPEND name # 将value追加到key原值的末尾,返回值的总长度 +(integer) 14 +127.0.0.1:6379> get name +"jiangyou" +127.0.0.1:6379> set age 24 EX 30 # 设置age 的值,并设置了过期时间 EX表示秒 +OK +127.0.0.1:6379> incr age # 在age上进行增 1 +(integer) 25 +127.0.0.1:6379> get age +"25" +127.0.0.1:6379> decr age # 在age上进行减 1 +(integer) 24 +127.0.0.1:6379> get age +"24" +127.0.0.1:6379> incr name # 由于name值不能表示数字,无法增1 +(error) ERR value is not an integer or out of range +127.0.0.1:6379> STRLEN name # name对应的string的长度 +(integer) 8 +127.0.0.1:6379> SETRANGE name 10 hahaha # 从偏移量为10 的位置开始加入hahaha +(integer) 16 +127.0.0.1:6379> get name # 不足的用\x00 补充 +"jiangyou\x00\x00hahaha" +127.0.0.1:6379> GETRANGE name 0 -1 # 获取name的值,改方式类似于python的数组查找 +"jiangyou\x00\x00hahaha" +127.0.0.1:6379> MSET age 26 home liaoning # 为多个key赋值 +OK +127.0.0.1:6379> MGET age home addr # 查找多个key对应的值,不存在的key返回nil。 +1) "26" +2) "liaoning" +3) (nil) +``` + + + +**4、列表—list命令:** + +**基本命令** + +- **LPUSH** + + Lpush 将一个或多个值插入到列表`key` 的头部。如果 key 不存在,那么在进行 push 操作前会创建一个空列表。如果 key 对应的值不是 list 类型,那么会返回一个错误。可以使用一个命令把多个元素 push 进入列表。 + + ```shell + LPUSH key value [value ...] + ``` + + + +- **RPUSH** + + Rpush 将向存储在 key 中的列表的尾部插入所有指定的值。如果 key 不存在,那么会创建一个空的列表然后再进行 push 操作。 当 key 保存的不是列表,那么会返回一个错误。 + + ```shell + RPUSH key value [value ...] + ``` + + + +- **LRANGE** + + Lrange将返回列表中指定区间内的元素(闭区间),区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。如果start大于最大小标,那么叫返回空列表。 + + ```shell + LRANGE key start stop + ``` + +- **LINDEX** + + Lindex 将返回列表 key 里索引 index 位置存储的元素。 index 下标是从 0 开始索引的,所以 0 是表示第一个元素, 1 表示第二个元素,并以此类推。 负数索引用于指定从列表尾部开始索引的元素,在这种方法下,-1 表示最后一个元素,-2 表示倒数第二个元素,并以此往前推。当 key 值不是列表的时候,会返回错误。 + + ```shell + LINDEX key index + ``` + +- **LLEN** + + Llen 将用于返回存储在 `key` 中的列表长度。 如果 `key` 不存在,则 `key` 被解释为一个空列表,返回 `0` 。 如果 `key` 不是列表类型,返回一个错误。 + + ```shell + LLEN key + ``` + + + +**常用命令** + +- **LREM** + + Lrem将用于从列表 key 中删除前 count 个值等于 `element` 的元素。 这个 count 参数通过下面几种方式影响这个操作,如果count > 0, 从头到尾删除值为 value 的元素;如果count < 0,将从尾到头删除值为 value 的元素;如果 count = 0 将移除所有值为 value 的元素 + + ```shell + LREM key count value + ``` + + + +- **LSET** + + Lset 将用于设置列表 key 中 index 位置的元素值为 `element`。 + + ```shell + LSET key index value + ``` + +- **LINSERT** + + Linsert 将用于把 `element` 插入到列表 `key` 的前面或后面。当 `key` 不存在时,这个list会被看作是空list,什么都不执行;当 `key` 存在,值不是列表类型时,返回错误。 + + ```shell + LINSERT key BEFORE|AFTER pivot value + ``` + +测试上面命令: + +```shell +127.0.0.1:6379> RPUSH myarrs 1 1 1 1 2 2 # 从list的右边开始往myarrs里面添加值 +(integer) 6 +127.0.0.1:6379> LRANGE myarrs 0 -1 # 返回myarrs的List中所有值 +1) "1" +2) "1" +3) "1" +4) "1" +5) "2" +6) "2" +127.0.0.1:6379> LPUSH myarrs 0 0 -1 # 从list的左边开始往myarrs里面添加值 +(integer) 9 +127.0.0.1:6379> LRANGE myarrs 0 -1 +1) "-1" +2) "0" +3) "0" +4) "1" +5) "1" +6) "1" +7) "1" +8) "2" +9) "2" +127.0.0.1:6379> LINDEX myarrs -2 # 根据索引返回List中的值 +"2" +127.0.0.1:6379> LLEN myarrs # 返回List中元素个数 +(integer) 9 +127.0.0.1:6379> LREM myarrs 2 1 # 删除myarrs中的1 count为2 所以从头往尾删除两个1 +(integer) 2 +127.0.0.1:6379> LRANGE myarrs 0 -1 +1) "-1" +2) "0" +3) "0" +4) "1" +5) "1" +6) "2" +7) "2" +127.0.0.1:6379> LREM myarrs -1 1 # 删除myarrs中的1 count为-1 所以从尾往头删除1个1 +(integer) 1 +127.0.0.1:6379> LRANGE myarrs 0 -1 +1) "-1" +2) "0" +3) "0" +4) "1" +5) "2" +6) "2" +127.0.0.1:6379> LREM myarrs 0 2 # 删除myarrs中的2 count为0 删除所有等于2的元素 +(integer) 2 +127.0.0.1:6379> LRANGE myarrs 0 -1 +1) "-1" +2) "0" +3) "0" +4) "1" +127.0.0.1:6379> LSET myarrs 2 5 # 根据索引设置myarrs的值,将索引为2 的位置赋值为5 +OK +127.0.0.1:6379> LRANGE myarrs 0 -1 +1) "-1" +2) "0" +3) "5" +4) "1" +127.0.0.1:6379> LINSERT myarrs before 5 4 # 在第一个值为5的位置的前面插入一个4 +(integer) 5 +127.0.0.1:6379> LRANGE myarrs 0 -1 +1) "-1" +2) "0" +3) "4" +4) "5" +5) "1" +``` + + + +**5、哈希类型—hash命令:** + +hash类似于java中的HashMap,在Reids中做了更多的优化。此外hash是一个sytring类型的field和value的映射表,特别适合用于存储对象。例如我们可以借用hash数据结构来存储用户信息,商品信息等。 + +**基本命令** + +- HSET + + Hset 命令用于为存储在 `key` 中的哈希表的 `field` 字段赋值 `value` 。如果哈希表不存在,一个新的哈希表被创建并进行 HSET 操作。如果字段(`field`)已经存在于哈希表中,旧值将被覆盖。 + + ```shell + HSET key field value + ``` + +- HGET + + Hget 命令用于返回哈希表中指定字段 `field` 的值。如果给定的字段或 key 不存在时,返回 nil 。 + + ```shell + HGET key field + ``` + +- HMSET + + Hmset 命令用于同时将多个 field-value (字段-值)对设置到哈希表中。此命令会覆盖哈希表中已存在的字段,如果哈希表不存在,会创建一个空哈希表,并执行 HMSET 操作。 + + ```shell + HMSET key field value [field value ...] + ``` + +- HGETALL + + Hgetall 命令用于返回存储在 `key` 中的哈希表中所有的域和值。返回值以列表形式返回哈希表的字段及字段值,若 key 不存在,返回空列表。 + + ```shell + HGETALL key + ``` + +- HDEL + + Hdel 命令用于删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。 如果 `key` 不存在,会被当作空哈希表处理并返回 `0` 。 + + ```shell + HDEL key field [field ...] + ``` + +**常用命令** + +- HEXISTS + + Hexists 命令用于查看哈希表的指定字段`field` 是否存在。如果表含有给定字段`field`会返回1,否则返回0。 + + ```shell + HEXISTS key field + ``` + +- HKEYS + + Hkeys返回存储在 `key` 中哈希表的所有域。当 key 不存在时,返回空表。 + + ```shell + HKEYS key + ``` + +- HVALS + + Hvals 命令返回哈希表所有域(field)的值。当 key 不存在时,返回空表。 + + ```shell + HVALS key + ``` + +测试以上命令 + +```shell +127.0.0.1:6379> HSET userinfo name jiangyou # 创建新的hash表,并存入对象userinfo的name属性 +(integer) 1 # 返回赋值成功域的个数 +127.0.0.1:6379> HSET userinfo age 26 home liaoming school neu # 设置userinfo对象的多个域的值 +(integer) 3 # 返回赋值成功域的个数 +127.0.0.1:6379> HKEYS userinfo # 查看userinfo的所有域的名 +1) "name" +2) "age" +3) "home" +4) "school" +127.0.0.1:6379> HKEYS users # 当key不存在时,返回空 +(empty list or set) +127.0.0.1:6379> HVALS userinfo # 返回key值的所有域的值 +1) "jiangyou" +2) "26" +3) "liaoming" +4) "neu" +127.0.0.1:6379> HEXISTS userinfo name # 查看哈希表的指定字段`name` 该字段存在,返回1 +(integer) 1 +127.0.0.1:6379> HEXISTS userinfo addr # 查看哈希表的指定字段`addr` 该字段存在,返回0 +(integer) 0 +127.0.0.1:6379> HGETALL userinfo # 查看哈希表中存储在 `key` 中的所有的域和值 +1) "name" +2) "jiangyou" +3) "age" +4) "26" +5) "home" +6) "liaoming" +7) "school" +8) "neu" +127.0.0.1:6379> HGETALL users # `key` 不存在,会被当作空哈希表处理并返回。 +(empty list or set) +127.0.0.1:6379> HDEL userinfo school home # 删除哈希表 key 中的一个或多个指定域,返回的为成功删除的域的个数。 +(integer) 2 +127.0.0.1:6379> HGETALL userinfo +1) "name" +2) "jiangyou" +3) "age" +4) "26" +``` + + + +**6、集合类型—set命令:** + +**基本命令** + +- **SADD** + + Sadd 将命令将一个或多个成员元素加入到集合中,已经存在于集合的成员元素将被忽略。假如集合 key 不存在,则创建一个只包含被添加的元素作为成员的集合。当集合 key 不是集合类型时,返回一个错误。 + + ```shell + SADD key member [member ...] + ``` + +- **SMEMBERS** + + Smembers 将返回存储在 `key` 中的集合的所有的成员。 不存在的集合被视为空集合。 + + ```shell + SMEMBERS key + ``` + +- **SISMEMBER** + + Sismember 将用于判断元素 `member` 是否集合 `key` 的成员。如果成员元素是集合的成员,返回 1 ;如果成员元素不是集合的成员,或 `key` 不存在,返回0。 + + ```shell + SISMEMBER key member + ``` + +- **SCARD** + + Scard 将返回集合中元素的数量。 + + ```shell + SCARD key + ``` + +- **SREM** + + Srem将在集合中删除指定的元素。如果指定的元素不是集合成员则被忽略。如果集合 `key` 不存在则被视为一个空的集合,该命令返回0。如果key的类型不是一个集合,则返回错误。 + + ```shell + SCARD key member [member ...] + ``` + + +**常用命令** + +- **SRANDMEMBER** + + Srandmember 将仅使用`key` 参数,那么随机返回集合`key` 中的一个随机元素。如果count是整数且小于元素的个数,返回含有 count 个不同的元素的数组,如果count是个整数且大于集合中元素的个数时,返回整个集合的所有元素,当count是负数,则会返回一个包含count的绝对值的个数元素的数组,如果count的绝对值大于元素的个数,则返回的结果集里会出现一个元素出现多次的情况。 + + ```shell + SRANDMEMBER key [count] + ``` + +- **SPOP** + + Spop 将从集合 `key`中删除并返回一个或多个随机元素。这个命令和 SRANDMEMBER相似, SRANDMEMBER 只返回随机成员但是不删除这些返回的成员。 + + ```shell + SRANDMEMBER key [count] + ``` + + +测试以上命令 + +```shell +127.0.0.1:6379> SADD name zhangsan lisi wangwu # 赋值key为name的set集合,返回赋值成功的个数 +(integer) 3 +127.0.0.1:6379> SMEMBERS name # 查看存储在name中的集合的所有的成员。 +1) "zhangsan" +2) "lisi" +3) "wangwu" +127.0.0.1:6379> SISMEMBER name zhangsan # 判断元素 zhangsan 是否集合 name 的成员,如果是 返回1 +(integer) 1 +127.0.0.1:6379> SISMEMBER name xuliu # 判断元素 xuliu 是否集合 name 的成员,如果不是 返回0 +(integer) 0 +127.0.0.1:6379> SCARD name +(integer) 3 +127.0.0.1:6379> SREM name zhangsan xuliu # 删除 name 的成员,如果存在直接删除,否则忽略。返回删除成功的元素个数 +(integer) 1 +127.0.0.1:6379> SMEMBERS name +1) "lisi" +2) "wangwu" +127.0.0.1:6379> SRANDMEMBER name 5 # 随机返回集合name中的一个随机元素,count为5 大于集合个数,返回整个集合元素 +1) "lisi" +2) "wangwu" +127.0.0.1:6379> SRANDMEMBER name 1 # 随机返回集合name中的一个随机元素,count为1 随机返回集合中任意一个元素 +1) "wangwu" +127.0.0.1:6379> SRANDMEMBER name -5 # 随机返回集合name中的一个随机元素,count为-5 返回的结果集里会出现一个元素出现多次 +1) "wangwu" +2) "lisi" +3) "lisi" +4) "lisi" +5) "wangwu" +127.0.0.1:6379> SPOP name 0 # 随机删除并返回集合name中的一个或多个随机元素,count为0 返回的结果集里不会出现任何元素 +(empty array) +127.0.0.1:6379> SPOP name 1 # 随机删除并返回集合name中的一个或多个随机元素,count为1 返回的结果集里会出现一个元素出现多次 +1) "lisi" +127.0.0.1:6379> SPOP name -5 # 随机删除并返回集合name中的一个或多个随机元素,count 不能为负数。 +(error) ERR value is out of range, must be positive +``` + + + +**7、有序集合类型—sortedset命令:** + +**基本命令** + +- **ZADD** + + Zadd 将一个或多个 `member` 元素及其 `score` 值加入到有序集 `key` 当中。如果某个 `member` 已经是有序集的成员,那么更新这个 `member` 的 `score` 值,并通过重新插入这个 `member` 元素,来保证该 `member` 在正确的位置上。如果有序集合 `key` 不存在,则创建一个空的有序集并执行 ZADD操作。当 `key` 存在但不是有序集类型时,返回一个错误。`score` 值可以是整数值或双精度浮点数,`score` 可为正也可以为负。 + + ```shell + ZADD key [NX|XX] [CH] [INCR] score member [score member ...] + ``` + + - **XX**: 仅更新存在的成员,不添加新成员。 + - **NX**: 不更新存在的成员。只添加新成员。 + - **LT**: 更新新的分值比当前分值小的成员,不存在则新增。 + - **GT**: 更新新的分值比当前分值大的成员,不存在则新增。 + - **CH**: 返回变更成员的数量。变更的成员是指 **新增成员** 和 **score值更新**的成员,命令指明的和之前score值相同的成员不计在内。 注意: 在通常情况下,ZADD返回值只计算新添加成员的数量。 + - **INCR**: [ZADD](https://www.redis.com.cn/commands/zadd.html) 使用该参数与 [ZINCRBY](https://www.redis.com.cn/commands/zincrby.html) 功能一样。一次只能操作一个score-element对。 + + 举例子: + + ```shell + ``` + + + +- **ZRANG** + + Zrange将返回有序集中,指定区间内(闭区间)的成员,其中成员的按分数值递增(从小到大)来排序,具有相同分数值的成员按字典序(lexicographical order )来排列。如果你需要成员按值递减(从大到小)来排列,可以使用 `ZREVRANGE`命令。下标参数 `start` 和 `stop` 都以 `0` 为底,也就是说,以 `0` 表示有序集第一个成员,以 `1` 表示有序集第二个成员,以此类推。其中 start和stop参数的细节同 `ZRANG`命令。 + + ```shell + ZRANGE key start stop [WITHSCORES] + ``` + + + +- **ZREVRANGE** + + Zervrange 将返回有序集`key`中,指定区间内的成员。其中成员的位置按score值递减(从高到低)来排列。具有相同score值的成员按字典序的反序排列。 除了成员排序相反外,`ZREVRANGE`命令的其他方面和`ZRANGE`命令一样。 + + ```shell + ZREVRANGE key start stop [WITHSCORES] + ``` + + + +- **ZREM** + + Zrem 将从有序集合`key`中删除指定的成员`member`。如果`member`不存在则被忽略。当key存在,但是不是有序集合类型时,返回类型错误。返回的是从有序集合中删除的成员个数,不包括不存在的成员。 + + ```shell + ZREM key member [member ...] + ``` + + + +- **ZCARD** + + Zcard 将返回有序集的成员个数。 当 `key` 不存在时,返回 `0` 。 + + ```shell + ZCARD key + ``` + + + +**常用命令** + +- **ZRANGEBYSCORE** + + 该指令将返回有序集 `key` 中,所有 `score` 值介于 `min` 和 `max` 之间(包括等于 `min` 或 `max` )的成员。有序集成员按 `score` 值递增(从小到大)次序排列。具有相同 `score` 值的成员按字典序来排列(该属性是有序集提供的,不需要额外的计算)。可选的 `LIMIT` 参数指定返回结果的数量及区间(就像SQL中的 `SELECT LIMIT offset, count` ),注意当 `offset` 很大时,定位 `offset` 的操作可能需要遍历整个有序集,此过程最坏复杂度为 O(N) 时间。可选的 `WITHSCORES` 参数决定结果集是单单返回有序集的成员,还是将有序集成员及其 `score` 值一起返回。 + + ```shell + ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] + ``` + + + +- **ZREVRANGEBYSCORE** + + 该指令将返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大)次序排列。具有相同分数值的成员按字典序来排列(该属性是有序集提供的,不需要额外的计算)。默认情况下,区间的取值使用闭区间 (小于等于或大于等于),你也可以通过给参数前增加 ( 符号来使用可选的开区间 (小于或大于)。可选的LIMIT参数指定返回结果的数量及区间(类似SQL中SELECT LIMIT offset, count)。注意,如果offset太大,定位offset就可能遍历整个有序集合,这会增加O(N)的复杂度。可选参数WITHSCORES会返回元素和其分数,而不只是元素。 + + ```shell + ZREVRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] + ``` + + + +- **ZCOUNT** + + Zcount 将返回有序集 `key` 中, `score` 值在 `min` 和 `max` 之间(默认包括 `score` 值等于 `min` 或 `max` )的成员的数量。 + + ```shell + ZCOUNT key min max + ``` + + + +## Python调用Redis + +在Python中,目前可以通过一个redis模块来实现操控Redis,下面我们简单的介绍一下关于使用redis模块。 + +### 安装Redis模块 + +如果是在Windows 系统,安装 redis 模块可以使用以下命令: + +```shell +python -m pip install redis +``` + +如果是 Linux 系统,需要执行以下命令来安装: + +```shell +sudo pip3 install redis +``` + +如果是使用Anaconda管理环境,也可以使用以下命令安装: + +```shell +conda install redis +``` + + + +### Python连接Redis + +Redis模块提供了两种连接的模式:直连模式和连接词模式。 + +**直连模式** + +直连模式的方式简单方便,适合少量长期连接的场景。其中host参数是ip地址,如果Redis服务存在于本地,可以使用127.0.0.1,或者换成Redis服务所在的ip地址。db表示当前选择的库,其参数值可以是 0-15;如果设置连接数据库的密码,那么就需要使用password进行验证。 + +```python +import redis + +r = redis.Redis(host='127.0.0.1',port=6379,db=0,password='') +r.set('name':'jiangyou') +print(r.get('name')) +``` + +**连接池模式** + +连接池模式是使用 connection pool(连接池)来管理 redis server 的所有连接,每个Redis实例会维护自己的连接池来管理管理对一个 redis server 所有的连接,避免每次建立,释放连接的开销。 + +```python +import redis + +pool = redis.ConnectionPool(host="127.0.0.1",port=6379,db=0,password="",decode_responses=True, max_connections=10) +r1 = redis.Redis(connection_pool=pool) # 第一个客户端访问 +r2 = redis.Redis(connection_pool=pool) # 第二个客户端访问 +``` + +上面的参数,decode_responses=True 可以使得redis取出的结果改成字符串,其默认的是字节, max_connections参数可以设置最大连接数量,这样当有新的客户端请求连接时,只需要去连接池获取即可,这样就可以把一个连接共享给多个客服端,减少每次连接所消耗的时间以及资源。 + + + +**基本操作** + +在Redis模块中,提供了**Redis**和**StrictRedis**来支持Redis访问和操作。其中 **StrictRedis** 使用python基于Redis协议实现了所有官方的Redis操作命令,也就是说其实对于python操作redis的API接口和上面提到的Redis官方接口一样。因此下面我们就简单介绍一些常用的方法。 + +1. **String操作** + + ```python + import redis + + pool = redis.ConnectionPool(host="127.0.0.1",port=6379,db=0,password="",decode_responses=True,max_connections=10) + r = redis.StrictRedis(connection_pool=pool) + + r.set('name','jiang') + r.append("name","you") # 在redis name对应的值后面追加内容 + + r.mset({'age':'26','home':'liaoning'}) + print(r.mget('name','age','home')) + print("name 长度:%d"%r.strlen('name')) #查看ame对应值的长度 + + r.incrby('age',5) #数值操作 将age对应的值 加5 + print(r.get('age')) + r.decrby('age',5) #数值操作 将age对应的值 减5 + print(r.get('age')) + r.incrbyfloat('age',5.2) #将age对应的值 加5.2 + print(r.get('age')) + r.incrbyfloat('age',-10.5) #将age对应的值 减10.5 + print(r.get('age')) + + r.setrange('name',5,'hahaha') # 修改字符串内容,从指定字符串索引开始向后替换。 + print(r.getrange('name',0,6)) # 获取子序列(根据字节获取,非字符),闭区间 + + r.delete('name') #删除key + ``` + + 运行结果 + + ```python + ['jiangyou', '26', 'liaoning'] + name 长度:8 + 31 + 26 + 31.2 + 20.7 + jiangha + ``` + +2. **Hash操作** + + ```python + import redis + + pool = redis.ConnectionPool(host="127.0.0.1",port=6379,db=0,password="",decode_responses=True,max_connections=10) + r = redis.StrictRedis(connection_pool=pool) + + r.hset('user1','name','zhangsan') # user1对应的hash中设置一个键值对(不存在,则创建;否则,修改) + r.hset('user1','age','22') # user1对应的hash中设置一个键值对(不存在,则创建;否则,修改) + r.hincrbyfloat('user1','age',0.5) # 自增user1对应的hash中的指定key的值,不存在则创建key=amount + print(r.hmget('user1','name','age')) # 在user1对应的hash中获取多个key的值 + + # 一次性设置多个field和value + user_dict = { + 'password':'123', + 'gender':'M', + 'home':'辽宁' + } + r.hmset('user1',user_dict) # 在user1对应的hash中批量设置键值对 + + print("user1中存在键值对的个数:%d "%r.hlen('user1')) # 获取所有数据,字典类型 + print("user1中存在键值对的具体信息:%s"%r.hgetall('user1')) # 获取所有数据,字典类型 + print(r.hkeys("user1")) # 获取所有fields字段 + print(r.hvals("user1")) # 获取所有fields字段的values值 + + if r.hexists("user1","home"): # 检查user1对应的hash是否存在当前传入的home + r.hdel("user1",'home') # 将user1对应的hash中指定key的键值对删除 + print("已删除该键!!!") + else: + print("不存在该键!!!") + ``` + + 运行结果 + + ```python + ['zhangsan', '22.5'] + user1中存在键值对的个数:5 + user1中存在键值对的具体信息:{'name': 'zhangsan', 'age': '22.5', 'password': '123', 'gender': 'M', 'home': '辽宁'} + ['name', 'age', 'password', 'gender', 'home'] + ['zhangsan', '22.5', '123', 'M', '辽宁'] + 已删除该键!! + ``` + + + +3. **List操作** + + ```python + import redis + + pool = redis.ConnectionPool(host="127.0.0.1",port=6379,db=0,password="",decode_responses=True,max_connections=10) + r = redis.StrictRedis(connection_pool=pool) + + r.lpush('database','sql','mysql','redis') # 在database对应的list中添加元素,每个新的元素都添加到列表的最左边 + print(r.lrange('database',0,-1)) + + r.linsert('database','before','mysql','mongodb') # 在database对应的列表的某一个值前或后插入一个新值,其含义为在第三个参数的前(before)或后(after) 插入参数四 + + print(r.lrange('database',0,-1)) # 在database对应的列表分片获取数据 + + print("database中元素个数:%d"%r.llen('database')) # database对应的list元素的个数 + + print("database中第2个元素:%s"%r.lindex('database',2)) #在database对应的列表中根据索引获取列表元素 + + r.lset('database', 0, 'redisdb') # 对database对应的list中的某一个索引位置重新赋值 + print(r.lrange('database',0,-1)) + + print(r.rpop('database')) # 在database对应的列表的右侧获取第一个元素并在列表中移除,返回值则是第一个元素 + + print(r.ltrim('database',0,1)) # 在database对应的列表中移除没有在start-end索引之间的值 + + while True: + result = r.brpop('database',1) # 从一个列表的右侧移除一个元素并将其添加到另一个列表的左侧 [如果列表中为空时,则返回None] + if result: + print(result) + else: + break + r.delete('database') + ``` + + 运行结果 + + ```python + ['redis', 'mysql', 'sql'] + ['redis', 'mongodb', 'mysql', 'sql'] + database中元素个数:4 + database中第2个元素:mysql + ['redisdb', 'mongodb', 'mysql', 'sql'] + sql + True + ('database', 'mongodb') + ('database', 'redisdb') + ``` + + + +4. **Set操作** + + ```python + import redis + + pool = redis.ConnectionPool(host="127.0.0.1",port=6379,db=0,password="",decode_responses=True,max_connections=10) + r = redis.StrictRedis(connection_pool=pool) + + + r.sadd("name","zhangsan") # 给name对应的集合中添加元素 + r.sadd("name","zhangsan","lisi","wangwu") + + + print(r.smembers('name')) # 获取name对应的集合的所有成员 + + print(r.scard("name") ) # 获取name对应的集合中的元素个数 + + print(r.sismember('name','zhangsan')) # 检查value是否是name对应的集合内的元素,返回值为True或False + + print(r.spop('name')) # 随机删除并返回指定集合的一个元素 + print(r.smembers('name')) + + # srem(name, value) + print(r.srem("name", "zhangsan")) # 删除集合中的某个元素 + print(r.smembers('name')) + + r.sadd("name","a","b") + r.sadd("name1","b","c") + r.sadd("name2","b","c","d") + + print(r.sinter("name","name1","name2")) # 获取多个name对应集合的交集 + + print(r.sunion("name","name1","name2")) # 获取多个name对应的集合的并集 + + print(r.sdiff("name","name1","name2")) # 在第一个name对应的集合中且不在其他name对应的集合的元素集合 + + r.flushall() + ``` + + 运行结果 + + ```python + {'zhangsan', 'lisi', 'wangwu'} + 3 + True + lisi + {'zhangsan', 'wangwu'} + 1 + {'wangwu'} + {'b'} + {'d', 'c', 'b', 'wangwu', 'a'} + {'wangwu', 'a'} + ``` + + + +5. **SortedSet操作** + + ```python + import redis + + pool = redis.ConnectionPool(host="127.0.0.1",port=6379,db=0,decode_responses=True,max_connections=10) + r = redis.StrictRedis(connection_pool=pool) + + mapping = { + 'zhangsan':85, + 'lisi':92, + 'wangwu':76 + } + r.zadd('C++',mapping,nx=True) # 在C++对应的有序集合中添加元素 + print(r.zrange('C++',0,-1,withscores=True)) # 获取C++对应的有序集合的所有元素 + + print(r.zcard("C++")) # 获取C++对应的有序集合元素的数量 + print(r.zcount('C++',min=0,max=90)) # 获取C++对应的有序集合中分数 在 [min,max] 之间的个数 + + r.zincrby(name='C++',value='lisi',amount=3) # 增加C++对应的有序集合的lisi对应的分数 + print(r.zrange('C++',0,-1,desc=False,withscores=True)) # 按照索引范围获取C++对应的有序集合的元素,排序规则,默认按照分数从小到大排序 + print(r.zrevrange('C++',0,-1,withscores=True)) # 按照索引范围获取C++对应的有序集合的元素,排序规则,默认按照分数从大到小排序 + + print(r.zrangebyscore('C++',70,90)) # 按照分数范围获取C++对应的有序集合的元素,排序规则,默认按照分数从小到大排序 + print(r.zrevrangebyscore('C++',90,70)) # 按照分数范围获取C++对应的有序集合的元素,排序规则,默认按照分数从大到小排序 + + print(r.zrank('C++','lisi')) # Zrank 返回有序集中指定成员的排名,有序集成员按分数值递增(从小到大)顺序排列。 + print(r.zrevrank('C++','lisi')) # Zrevrank 返回有序集中指定成员的排名,有序集成员按分数值递增(从大到小)顺序排列。 + + mapping = { + 'xuliu':74, + 'lisi':82, + 'wangwu':87 + } + r.zadd('python',mapping,nx=True) + r.zinterstore('sum_score_i',['C++','python'],aggregate='sum') # 获取两个有序集合的交集,如果遇到相同值不同分数,则按照aggregate进行操作 + print(r.zrange('sum_score_i',0,-1,withscores=True)) + print(r.zunionstore('sum_score_u',['C++','python'],'min')) # 获取两个有序集合的并集,如果遇到相同值不同分数,则按照aggregate进行操作 + print(r.zrange('sum_score_u',0,-1,withscores=True)) + + r.zrem('C++', 'zhangsan') # 删除C++对应的有序集合中值是zhangsan的成员 + print(r.zrange('C++',0,-1,withscores=True)) + + r.zremrangebyscore('C++', min=80, max=100) # 删除C++对应的有序集合中值是zhangsan的成员 + print(r.zrange('C++',0,-1,withscores=True)) + + r.zremrangebyrank('python', min=1, max=3) # 根据排行范围删除 + print(r.zrange('python',0,-1,withscores=True)) + ``` + + 运行结果 + + ```python + [('wangwu', 76.0), ('zhangsan', 85.0), ('lisi', 92.0)] + 3 + 2 + [('wangwu', 76.0), ('zhangsan', 85.0), ('lisi', 95.0)] + [('lisi', 95.0), ('zhangsan', 85.0), ('wangwu', 76.0)] + ['wangwu', 'zhangsan'] + ['zhangsan', 'wangwu'] + 2 + 0 + [('wangwu', 163.0), ('lisi', 177.0)] + 4 + [('xuliu', 74.0), ('wangwu', 76.0), ('lisi', 82.0), ('zhangsan', 85.0)] + [('wangwu', 76.0), ('lisi', 95.0)] + [('wangwu', 76.0)] + [('xuliu', 74.0)] + ``` + +6. **管道操作** + + Redis 模块默认在执行每次请求都会向连接池请求创建连接和断开申请操作,如果想要在一次请求中指定多个命令,则可以使用pipline实现一次请求指定多个命令,并且默认情况下一次pipline 是原子性操作(即为一次操作)。 + + ```python + import redis + + pool = redis.ConnectionPool(host="127.0.0.1",port=6379,db=0,decode_responses=True,max_connections=10) + r = redis.StrictRedis(connection_pool=pool) + + pipe = r.pipeline(transaction=True) + + pipe.set('name', 'jiangyou') + pipe.set('age', 'age') + pipe.execute() + + print(r.mget("name","age")) + ``` + + 运行结果 + + ```python + ['jiangyou', 'age'] + ``` + + diff --git a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/scrapy基础及新闻爬取实战.md b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.4 scrapy基础及新闻爬取实战.md similarity index 100% rename from docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/scrapy基础及新闻爬取实战.md rename to docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.4 scrapy基础及新闻爬取实战.md From 28876be058ff5811ff8dfded0eef926c56ea2a1c Mon Sep 17 00:00:00 2001 From: RuyiLuo Date: Wed, 3 Nov 2021 21:57:46 +0800 Subject: [PATCH 12/16] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2.2新闻推荐系统实战/docs/2.2.1.4 scrapy基础及新闻爬取实战.md | 2 +- readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.4 scrapy基础及新闻爬取实战.md b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.4 scrapy基础及新闻爬取实战.md index 157e11e7..8a6f6674 100644 --- a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.4 scrapy基础及新闻爬取实战.md +++ b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.4 scrapy基础及新闻爬取实战.md @@ -1,4 +1,4 @@ -## scrapy基础及新闻爬取实战 +## Scrapy基础及新闻爬取实战 ### python环境的安装 diff --git a/readme.md b/readme.md index c648f68c..ec68c8f1 100644 --- a/readme.md +++ b/readme.md @@ -57,7 +57,7 @@ - [2.2.1.1 Mysql基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/MySQL%E5%9F%BA%E7%A1%80.md) - [2.2.1.2 MongoDB基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/MongoDB%E5%9F%BA%E7%A1%80.md) - [2.2.1.3 Redis基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/Redis%E5%9F%BA%E7%A1%80.md) - - [2.2.1.4 scrapy基础及新闻爬取实战](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/scrapy%E5%9F%BA%E7%A1%80%E5%8F%8A%E6%96%B0%E9%97%BB%E7%88%AC%E5%8F%96%E5%AE%9E%E6%88%98.md) + - [2.2.1.4 Scrapy基础及新闻爬取实战](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/scrapy%E5%9F%BA%E7%A1%80%E5%8F%8A%E6%96%B0%E9%97%BB%E7%88%AC%E5%8F%96%E5%AE%9E%E6%88%98.md) - 2.2.1.6 新闻画像的构建 - **2.2.2 前后端交互** From d6080b94c5492a2854685b8652fb31342b0e3e3a Mon Sep 17 00:00:00 2001 From: RuyiLuo Date: Wed, 3 Nov 2021 22:00:49 +0800 Subject: [PATCH 13/16] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E7=AB=A0?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index ec68c8f1..52a1d09b 100644 --- a/readme.md +++ b/readme.md @@ -57,7 +57,7 @@ - [2.2.1.1 Mysql基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/MySQL%E5%9F%BA%E7%A1%80.md) - [2.2.1.2 MongoDB基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/MongoDB%E5%9F%BA%E7%A1%80.md) - [2.2.1.3 Redis基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/Redis%E5%9F%BA%E7%A1%80.md) - - [2.2.1.4 Scrapy基础及新闻爬取实战](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/scrapy%E5%9F%BA%E7%A1%80%E5%8F%8A%E6%96%B0%E9%97%BB%E7%88%AC%E5%8F%96%E5%AE%9E%E6%88%98.md) + - [2.2.1.4 Scrapy基础及新闻爬取实战](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/2.2.1.4%20scrapy%E5%9F%BA%E7%A1%80%E5%8F%8A%E6%96%B0%E9%97%BB%E7%88%AC%E5%8F%96%E5%AE%9E%E6%88%98.md) - 2.2.1.6 新闻画像的构建 - **2.2.2 前后端交互** From 1ce08d0401bf3463d34878c177b388a0ef83775a Mon Sep 17 00:00:00 2001 From: RuyiLuo Date: Thu, 4 Nov 2021 10:01:51 +0800 Subject: [PATCH 14/16] Update readme.md --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 52a1d09b..7759f48d 100644 --- a/readme.md +++ b/readme.md @@ -54,9 +54,9 @@ - **2.2 新闻推荐系统实践** - **2.2.1 构建物料池** - - [2.2.1.1 Mysql基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/MySQL%E5%9F%BA%E7%A1%80.md) - - [2.2.1.2 MongoDB基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/MongoDB%E5%9F%BA%E7%A1%80.md) - - [2.2.1.3 Redis基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/Redis%E5%9F%BA%E7%A1%80.md) + - [2.2.1.1 Mysql基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/2.2.1.1%20Mysql%E5%9F%BA%E7%A1%80.md) + - [2.2.1.2 MongoDB基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/2.2.1.2%20MongoDB%E5%9F%BA%E7%A1%80.md) + - [2.2.1.3 Redis基础](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/2.2.1.3%20Redis%E5%9F%BA%E7%A1%80.md) - [2.2.1.4 Scrapy基础及新闻爬取实战](https://github.com/datawhalechina/fun-rec/blob/master/docs/%E7%AC%AC%E4%BA%8C%E7%AB%A0%20%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/2.2%E6%96%B0%E9%97%BB%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%AE%9E%E6%88%98/docs/2.2.1.4%20scrapy%E5%9F%BA%E7%A1%80%E5%8F%8A%E6%96%B0%E9%97%BB%E7%88%AC%E5%8F%96%E5%AE%9E%E6%88%98.md) - 2.2.1.6 新闻画像的构建 From e06f1762fd87f13acafac2d6e7feede513f82bb0 Mon Sep 17 00:00:00 2001 From: RuyiLuo Date: Fri, 5 Nov 2021 21:03:22 +0800 Subject: [PATCH 15/16] =?UTF-8?q?Update=202.2.1.2=20MongoDB=E5=9F=BA?= =?UTF-8?q?=E7=A1=80.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2.2新闻推荐系统实战/docs/2.2.1.2 MongoDB基础.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.2 MongoDB基础.md b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.2 MongoDB基础.md index 56d8d105..5b40d6ec 100644 --- a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.2 MongoDB基础.md +++ b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.2 MongoDB基础.md @@ -74,7 +74,7 @@ lsb_release -a ``` wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.4.10.tgz #下载 -wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.4.10.tgz #解压 +tar -zxvf mongodb-linux-x86_64-ubuntu1604-4.4.10.tgz #解压 ``` MongoDB 的可执行文件位于 bin 目录下,所以可以将其添加到 **PATH** 路径中 From 390cc4cadcf148b3e1ad0caf16e7b4747d7f1566 Mon Sep 17 00:00:00 2001 From: RuyiLuo Date: Sun, 7 Nov 2021 14:28:47 +0800 Subject: [PATCH 16/16] =?UTF-8?q?Update=202.2.1.1=20Mysql=E5=9F=BA?= =?UTF-8?q?=E7=A1=80.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2.2新闻推荐系统实战/docs/2.2.1.1 Mysql基础.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.1 Mysql基础.md b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.1 Mysql基础.md index e6ba6a07..86e3fd0b 100644 --- a/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.1 Mysql基础.md +++ b/docs/第二章 推荐系统实战/2.2新闻推荐系统实战/docs/2.2.1.1 Mysql基础.md @@ -1,8 +1,10 @@ +本文属于新闻推荐实战—数据层—构建物料池之MySQL。MySQL数据库在该项目中会用来存储结构化的数据(用户、新闻特征),作为算法工程师需要了解常用的MySQL语法(比如增删改查,排序等),因为在实际的工作经常会用来统计相关数据或者抽取相关特征。本着这个目的,本文对MySQL常见的语法及Python操作MySQL进行了总结,方便大家快速了解。 + # 前言 MySQL简介 -​ MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。 + MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。 -​ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行的开源数据库,因此被广泛地应用在Internet上的中小型网站中。随着MySQL的不断成熟,它也逐渐用于更多大规模网站和应用,比如维基百科、Google和Facebook等网站。非常流行的开源软件组合LAMP中的“M”指的就是MySQL。 + MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行的开源数据库,因此被广泛地应用在Internet上的中小型网站中。随着MySQL的不断成熟,它也逐渐用于更多大规模网站和应用,比如维基百科、Google和Facebook等网站。非常流行的开源软件组合LAMP中的“M”指的就是MySQL。 [百度百科]: https://baike.baidu.com/item/mySQL/471251 [维基百科]: https://zh.wikipedia.org/wiki/MySQL @@ -466,7 +468,7 @@ MySQL 支持所有标准 SQL 数值数据类型,包括: -# 三、 数据库的基本操作 +# 三、数据库的基本操作 首先,我们来学习在MySQL下如何操作数据库。