基本概念
数据库(DataBase, DB)
:是存放数据的仓库,只不过这些数据存在一定的关联,并按一定的格式存放在计算机上。
数据库管理系统(DataBase Management System, DBMS)
:是管理数据库的系统,它按一定的数据模型组织数据。DBMS应提供如下功能:
(1)数据定义功能可定义数据库中的数据对象。
(2)数据操纵功能可对数据库表进行基本操作,如插入、删除、修改、查询。
(3)数据的完整性检查功能保证用户输入的数据应满足相应的约束条件。
(4)数据库的安全保护功能保证只有赋予权限的用户才能访问数据库中的数据。
(5)数据库的并发控制功能使多个应用程序可在同一时刻并发地访问数据库的数据。
(6)数据库系统的故障恢复功能使数据库运行出现故障时进行数据库恢复,以保证数据库可靠运行。
(7)在网络环境下访问数据库的功能。
(8)方便、有效地存取数据库信息的接口和工具。编程人员通过程序开发工具与数据库的接口编写数据库应用程序。数据库管理员(DataBase Adminitrator,DBA)
通过提供的工具对数据库进行管理。
数据库系统(Database System,DBS)
:数据、数据库、数据库管理系统与操作数据库的应用程序,加上支撑它们的硬件平台、软件平台和与数据库有关的人员一起构成了一个完整的数据库系统.
从上面的这些概念中,我们可以知道平时我们讲的SQL Server、Oracle、MySQL、DB2、SyBase等,本身并不是数据库,他们是数据库管理系统。我们通过这个数据库管理系统创建的那个可以存储表的东西才是数据库。数据库中存储这一个或多个表,表之间存在着特定的关系。一个表由行和列组成,每一列代表一个字段(Field),每一行则代表着一条具有某种特别意义的记录。
作为程序员,我们所要知道的就是要怎样使用数据库管理系统?怎样通过数据库管理系统去创建和删除数据库?怎样在数据库中创建和删除表,怎样在表中实现数据的增删改查?下面我就以mysql这一个关系型数据库管理系统为例来讲解这些操作。
mysql客户端工具基本操作
连接并登录
1 | $ mysql -u <NAME> -h <HOSTNAME> -P <PORT> -p |
在终端中输入上述命令,接着输入密码即可连接并登录指定主机指定端口的mysql服务器。(注意密码不回显)
编码的修改
在Mac或Linux上,需要编辑MySQL的配置文件,把数据库默认的编码全部改为UTF-8。MySQL的配置文件默认存放在/etc/my.cnf或者/etc/mysql/my.cnf:
1 | [client] |
重启MySQL
后,可以通过MySQL的客户端命令行检查编码:
1 | $ mysql -u root -p |
看到utf8字样就表示编码设置正确。
基本操作
查看当前所有的数据库:
1 | $ show databases; |
切换数据库:
1 | $ use <DB NAME>; |
创建数据库:
1 | $ CREATE DATABASE 数据库名; |
建立数据表:
1 | $ USE 库名; |
删除数据库:
1 | $ DROP DATABASE 库名; |
删除数据表:
1 | $ DROP TABLE 表名; |
查看当前数据库的所有的表:
1 | $ show tables; |
查看某一个表的结构:
1 | $ desc <Table name>; |
查看某一张表的前10条数据:
1 | $ select * from <Table name> limit 10; |
基本上上面的几条命令会是大家以后在mysql客户端中使用的最多的命令了。
创建和管理用户
mysql支持数据库的多用户管理,并且可以对每个用户的权限进行管理。下面我们来看看怎样创建用户和管理用户的权限。
创建用户
命令:
1 | $ CREATE USER 'username'@'host' IDENTIFIED BY 'password'; |
说明:
- username:你将创建的用户名
- host:指定该用户在哪个主机上可以登陆,如果是本地用户可用localhost,如果想让该用户可以从任意远程主机登陆,可以使用通配符%
- password:该用户的登陆密码,密码可以为空,如果为空则该用户可以不需要密码登陆服务器
例子:
1 | CREATE USER 'dog'@'localhost' IDENTIFIED BY '123456'; |
授权
命令:
1 | $ GRANT privileges ON databasename.tablename TO 'username'@'host' |
说明:
- privileges:用户的操作权限,如SELECT,INSERT,UPDATE等,如果要授予所的权限则使用ALL
- databasename:数据库名
- tablename:表名,如果要授予该用户对所有数据库和表的相应操作权限则可用表示,如.*
例子:1
2$ GRANT SELECT, INSERT ON test.user TO 'pig'@'%';
$ GRANT ALL ON *.* TO 'pig'@'%';
注意:
用以上命令授权的用户不能给其它用户授权,如果想让该用户可以授权,用以下命令:
1 | $ GRANT privileges ON databasename.tablename TO 'username'@'host' WITH GRANT OPTION; |
设置和更改用户密码
命令:
1 | $ SET PASSWORD FOR 'username'@'host' = PASSWORD('newpassword'); |
例子:
1 | $ SET PASSWORD FOR 'pig'@'%' = PASSWORD("123456"); |
插销用户权限
命令:
1 | $ REVOKE privilege ON databasename.tablename FROM 'username'@'host'; |
说明:
privilege, databasename, tablename:同授权部分
例子:
1 | $ REVOKE SELECT ON *.* FROM 'pig'@'%'; |
注意:
假如你在给用户‘pig’@’%’授权的时候是这样的(或类似的):GRANT SELECT ON test.user TO ‘pig’@’%’,则在使用REVOKE SELECT ON . FROM ‘pig’@’%’;命令并不能撤销该用户对test数据库中user表的SELECT 操作。相反,如果授权使用的是GRANT SELECT ON . TO ‘pig’@’%’;则REVOKE SELECT ON test.user FROM ‘pig’@’%’;命令也不能撤销该用户对test数据库中user表的Select权限。
删除用户
1 | $ DROP USER 'username'@'host'; |
SQL
SQL 是一门 ANSI 的标准计算机语言,用来访问和操作数据库系统。SQL 语句用于取回和更新数据库中的数据。SQL 可与数据库程序协同工作。虽然是一门标准的语言,不幸地是,存在着很多不同版本的 SQL 语言,但是为了与 ANSI 标准相兼容,它们必须以相似的方式共同地来支持一些主要的关键词(比如 SELECT、UPDATE、DELETE、INSERT、WHERE 等等)。
select
语法:SELECT 列名称 FROM 表名称;
SELECT 语句用于从表中选取数据。结果被存储在一个结果表中(称为结果集)。
1 | SELECT LastName,FirstName FROM Persons; |
distinct
语法:SELECT DISTINCT 列名称 FROM 表名称;
在表中,可能会包含重复值。这并不成问题,不过,有时您也许希望仅仅列出不同(distinct)的值。
关键词 DISTINCT 用于返回唯一不同的值
1 | SELECT DISTINCT LastName FROM Persons; |
where
语法:SELECT 列名称 FROM 表名称 WHERE 列 运算符 值;
如需有条件地从表中选取数据,可将 WHERE 子句添加到 SELECT 语句。
下面的运算符可以在where子句中使用:
操作符 | 描述 |
---|---|
= | 等于 |
<> | 不等于 |
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
BETWEEN | 在某个范围内 |
LIKE | 搜索某种模式 |
1 | SELECT * FROM Persons WHERE City='Beijing'; |
and & or
语法:AND 和 OR 可在 WHERE 子语句中把两个或多个条件结合起来。如果第一个条件和第二个条件都成立,则 AND 运算符显示一条记录。如果第一个条件和第二个条件中只要有一个成立,则 OR 运算符显示一条记录。
1 | SELECT * FROM Persons WHERE FirstName='Thomas' AND LastName='Carter'; |
order by
语法:ORDER BY 语句用于根据指定的列对结果集进行排序。ORDER BY 语句默认按照升序对记录进行排序。如果您希望按照降序对记录进行排序,可以使用 DESC 关键字。
1 | 以字母顺序显示公司名称: |
insert into
语法:INSERT INTO 表名称 VALUES (值1, 值2,….);
我们也可以指定所要插入数据的列:INSERT INTO table_name (列1, 列2,…) VALUES (值1, 值2,….);
1 | INSERT INTO Persons VALUES ('Gates', 'Bill', 'Xuanwumen 10', 'Beijing'); |
update
语法:UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值;
Update 语句用于修改表中的数据。
1 | UPDATE Person SET FirstName = 'Fred' WHERE LastName = 'Wilson' ; |
delete
DELETE 语句用于删除表中的行。
语法:DELETE FROM 表名称 WHERE 列名称 = 值;
1 | DELETE FROM Person WHERE LastName = 'Wilson' ; |
原生SQL在python代码中的使用
要想在你的python代码中操作mysql,那么据需要使用python链接mysql的驱动程序。MysqlDB是最常用的mysql驱动,但是至今没有支持python3.下表列出了python3中链接mysql的几个驱动程序:
名称 | 安装 | 导入 |
---|---|---|
MYSQL Connector | pip install mysql-connector-python | mysql.connctor |
PYMYSQL | pip install pymysql | pymysql |
下面以MySQL Connector为例讲解:
1 | import mysql.connector |
ORM
SQLAlchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API之上,使用关系对象映射进行数据库操作,简言之便是:将对象转换成SQL,然后使用数据库API执行SQL并获取执行结果。这是分了三层,上层和下面两层。ORM是用类来封装的,SQLALCHEMY Core 是用函数封装的,是核心层,不是执行数据库语句,是把写成的类,翻译成sql语句。DBAPI执行SQL从而获取执行结果或将数据持久化进磁盘。
SQLAlchemy本身无法操作数据库,其必须依赖pymysql,mysql-connector等第三方插件.Dialect用于和数据库API进行交流,根据配置文件的不同调用不同的数据库API,从而实现对数据库的操作。SQLAlchemy为我们屏蔽了SQL语句的繁琐,统一了各种不同数据库的操作,极大的方便了数据库应用的开发。
SQLAlchemy的基本使用
连接配置
dialect+driver://user:password@host:port/dbname
不同数据库和驱动条件下的连接配置:
1 | MySQL-Python |
创建表
1 | import sqlalchemy |
除了上面的创建方法之外,还有一种创建方式:
1 | from sqlalchemy import Table, MetaData, Column, Integer, String |
在使用的时候建议使用第一种创建方式。
Column构造函数常用参数
参数 | 含义 | 可选值 | 默认值 |
---|---|---|---|
autoincrement | 是否自增 | Ture,False | False |
primary_key | 主键 | Ture,False | False |
default | 指定默认值 | 自定义值 | 无 |
index | 是否建索引 | True,False | False |
nullable | 字段可否可空 | Ture,False | False |
插入一条数据
1 | from sqlalchemy import create_engine |
对象状态
对象实例有四种状态,分别是:
- Transient(瞬时的):这个状态的对象还不在session中,也不会保存到数据库中,主键为None(不是绝对的,如果Persistent对象rollback后虽然主键id有值,但还是Transient状态的)。
- Pending(挂起的):调用session.add()后,Transient对象就会变成Pending,这个时候它还是不会保存到数据库中,只有等到触发了flush动作才会存在数据库,比如query操作就可以出发flush。同样这个时候的实例的主键一样为None
- Persistent(持久的):session中,数据库中都有对应的一条记录存在,主键有值了。
- Detached(游离的):数据库中有记录,但是session中不存在,对这个状态的对象进行操作时,不会触发任何SQL语句。
Session对象缓存清理
Session对象在commit()方法调用时,在执行查询时和显式调用session的flush方法时清理缓存,从而保证查询结果能反映对象的最新状态。
Session是真正与数据库通信的handler,你还可以把他理解一个容器,add就是往容器中添加对象
执行完add方法后,ed_user对象处于pending状态,不会触发INSERT语句,当然ed_uesr.id也为None,如果在add方后有查询(session.query),那么会flush一下,把数据刷一遍,把所有的pending信息先flush再执行query。
查询
1 | my_user = Session.query(User).filter_by(name="fgf").first() # 查询 |
不过刚才显示的内存对象对址没办法分清返回的是什么数据的,除非打印具体字段看一下,如果想让它变的可读,只需在定义表的类下面加上这样的代码:
1 | def __repr__(self): |
完整的查询代码:
1 | from sqlalchemy import create_engine |
多条件查询
filter_by与filter
1 | my_user1 = Session.query(User).filter(User.id>2).all() |
建立在SQLAlchemy上的几种常见的SQL查询实例:
1 | 几种常见sqlalchemy查询: |
修改
1 | my_user = Session.query(User).filter_by(name="fgf").first() |
删除
1 | query = Session.query(User).filter_by(name="hhh").delete() |
回滚
1 | my_user = Session.query(User).filter_by(id=1).first() |
表与表之间的关系
SQLAlchemy中的映射关系有四种,分别是一对一,一对多,多对一,多对多。
一对多和多对一
因为外键(ForeignKey)始终定义在多的一方.如果relationship定义在多的一方,那就是多对一,一对多与多对一的区别在于其关联(relationship)的属性在多的一方还是一的一方,如果relationship定义在一的一方那就是一对多.
1 | from sqlalchemy import create_engine |
多对多关系
多对多关系需要一个中间关联表,通过参数secondary来指定,这个中间关联表只需要创建即可,不需要操作它,SQLAlchemy自己会管理这张表。
1 | post_keywords = Table('student_class',Base.metadata, |
1 | from sqlalchemy import Integer, ForeignKey, String, Column |
Flask-SQLAlchemy的使用
Flask-SQLAlchemy 在 SQLAlchemy 的基础上,提供了一些常用的工具,并预设了一些默认值,帮助你更轻松地完成常见任务。flask-sqlalchemy 用起来比直接用 sqlalchemy 方便、省事,不过有些高级一点的功能如果不了解 sqlalchemy 的话会用不好。
安装
1 | $ pip install flask-sqlalchemy |
配置数据库
1 | from flask_sqlalchemy import SQLAlchemy |
db对象是SQLAlchemy类的实例,表示程序使用的数据库,同时还获得了Flask-SQLAlchemy提供的所有功能。
定义模型
1 | class Role(db.Model): |
tablename定义在数据库中使用的表名,如果没有定义tablename,Flask-SQLAlchemy会使用一个默认的名字,但是默认的表名没有遵循复数形式进行命名的约定。
常使用的列类型
类型名称 | python类型 | 描述 |
---|---|---|
Integer | int | 常规整形,通常为32位 |
SmallInteger | int | 短整形,通常为16位 |
BigInteger | int或long | 精度不受限整形 |
Float | float | 浮点数 |
Numeric | decimal.Decimal | 定点数 |
String | str | 可变长度字符串 |
Text | str | 可变长度字符串,适合大量文本 |
Unicode | unicode | 可变长度Unicode字符串 |
UnicodeText | unicode | 可变长度Unicode字符串,对较长或不限长度的字符串做了优化 |
Boolean | bool | 布尔型 |
Date | datetime.date | 日期类型 |
Time | datetime.time | 时间类型 |
DataTime | datetime.datatime | 日期和时间 |
Interval | datetime.timedelta | 时间间隔 |
Enum | str | 字符列表 |
PickleType | 任意Python对象 | 自动Pickle序列化 |
LargeBinary | str | 二进制 |
常使用的列选项
参数 | 含义 | 可选值 | 默认值 |
---|---|---|---|
primary_key | 主键 | True,False | False |
default | 指定默认值 | 自定义值 | 无 |
index | 是否建索引 | True,False | False |
nullable | 字段可否可空 | True,False | False |
unique | 是否允许出现重复值 | True,False | False |
关系
一对多和多对一
1 | class Role(db.Model): |
两张表之间的关系事实上是由ForeignKey建立起来的,relationship在两张表之间建立了一个虚拟关系,帮助我们更方便的操作这种关系。注意:在一对多或多对一的关系中,ForeignKey只会出现在多的一方,并且它所关联的一定是另一张表的primary_key。 relationship的一个必须参数是要建立虚拟关系的表所对应类的名字,如果这个类还没有定义,可以用一个字符串代替,它的其他更多可选参数见下图:
数据库操作
创建
1 | #创建表 |
插入行
1 | from models import Role,User |
注意这里创建Role和User实例时,都没有传id属性,这是因为primary_key是由Flask-SQLAlchemy管理的。relationship建立的虚拟字段role和users也是可以使用的,虽然他们不是真正的数据库列,但却是一对多关系的高级表示。此时这些对象的状态是暂时的,并没有写入数据库中,此时打印对象的id值:
1 | print(admin_role.id) #->None |
添加如会话:
1 | db.session.add(admin_role) |
修改行
1 | admin_role.name="Administrator" |
删除行
1 | db.session.delete(mod_role) |
查询行
1 | Role.query.all() |
Flask-SQLAlchemy为每个模型都提供了一个query对象,模型名.query就可以拿到这个query对象,在query对象的后面可以加查询过滤器,查询过滤器返回的仍然是query对象,这意味着我们还可以继续在后面追加查询过滤器,从而精确的筛选出我们需要的数据。query对象建立好之后,Flask-SQLAlchemy并没有执行SQL查询,要想真正的得到数据,我们还必须要在query对象的后面执行查询执行函数。下面是常用的查询过滤器和查询执行函数:
过滤器 | 说明 |
---|---|
filter() | 把过滤器添加到原查询上,返回一个新查询 |
filter_by() | 把等值过滤器添加到原查询上,返回一个新查询 |
limit() | 使用指定的值限制原查询返回的结果数量,返回一个新查询 |
offset() | 偏移原查询返回的结果,返回一个新查询 |
order_by() | 根据指定条件对原查询结果进行排序,返回一个新查询 |
group_by() | 根据指定条件对原查询结果进行分组,返回一个新查询 |
查询执行方法 | 说明 | ||
---|---|---|---|
all() | 以列表形式返回查询的所有结果 | ||
first() | 返回查询的第一个结果,如果没有结果,则返回None | ||
first_or_404() | 返回查询的第一个结果,如果没有结果,则终止请求,返回404错误响应 | ||
get() | 返回指定主键对应的行,如果没有对应的行,则返回None | ||
get_or_404 | 返回指定主键对应的行,如果没有找到指定的主键,则终止请求,返回404错误响应 | ||
count() | 返回查询结果的数量 | ||
paginate() | 返回一个Paginate对象,它包含指定范围内的结果 | ||
下面我们看看relationship建立的虚拟字段role,users | |||
|
这里有一个小问题,我们用users虚拟字段的时候,默认我们就得到了当前角色的所有用户的列表,也就是得到了查询执行函数执行之后的结果,但是如果我们想要做更加精细的筛选呢?
事实上,我们更期待它返回的是一个query对象,而不是结果列表。这是可以做得到的。
多对多
1 | registrations=db.Table("registrations", |
自引用
1 | class Follow(db.Model): |
参考
参考内容过多,在此不一一列出,默默感谢那些走在前面的大佬。