# SQLAlchemy

## 1. MySQL数据库

在网站开发中，数据库是网站的重要组成部分。只有提供数据库，数据才能够动态的展示，而不是在网页中显示一个静态的页面。数据库有很多，比如有`SQL Server`、`Oracle`、`PostgreSQL`以及`MySQL`等等。`MySQL`由于价格实惠、简单易用、不受平台限制、灵活度高等特性，目前已经取得了绝大多数的市场份额。因此我们在`Flask`中，也是使用`MySQL`来作为数据存储。

### 1.1 MySQL数据库安装

1. 在`MySQL`的官网下载`MySQL`数据库安装文件：`https://dev.mysql.com/downloads/mysql/`。
2. 然后双击安装，如果出现以下错误，则到`http://www.microsoft.com/en-us/download/details.aspx?id=17113`下载`.net framework`。
   ![QQ截图20171026122939.png](04.SQLAlchemy/rHS8Ny8FHDrwhvbUWv4XAC.png)
3. 在安装过程中，如果提示没有`Microsoft C++ 2013`，那么就到以下网址下载安装即可：`http://download.microsoft.com/download/9/0/5/905DBD86-D1B8-4D4B-8A50-CB0E922017B9/vcredist_x64.exe`。
   ![QQ截图20171026144632.png](04.SQLAlchemy/XYQtENVVzPvVVARq7Qt74D.png)
4. 接下来就是做好用户名和密码的配置即可。

### 1.2 navicat数据库操作软件

安装完`MySQL`数据库以后，就可以使用`MySQL`提供的终端客户端软件来操作数据库。如下：
![mysql客户端.png](04.SQLAlchemy/p2YQ6oveDpNYrYQsnejFyV.png)
这个软件所有的操作都是基于`sql`语言，对于想要熟练`sql`语言的同学来讲是非常合适的。但是对于在企业中可能不是一款好用的工具。在企业中我们推荐使用`mysql workbench`以及`navicat`这种图形化操作的软件。而`mysql workbench`是`mysql`官方提供的一个免费的软件，正因为是免费，所以在一些功能上不及`navicat`。`navicat for mysql`是一款收费的软件。官网地址如下：`https://www.navicat.com.cn/products`。使用的截图如下：
![navicat.png](04.SQLAlchemy/eyw6PKK6tH8uAXPtd9AwiD.png)

### 1.3 MySQL驱动程序安装

我们使用`Django`来操作`MySQL`，实际上底层还是通过`Python`来操作的。因此我们想要用`Flask`来操作`MySQL`，首先还是需要安装一个驱动程序。在`Python3`中，驱动程序有多种选择。比如有`pymysql`以及`mysqlclient`等。这里我们就使用`mysqlclient`来操作。`mysqlclient`安装非常简单。只需要通过`pip install mysqlclient`即可安装。

常见`MySQL`驱动介绍：

1. `MySQL-python`：也就是`MySQLdb`。是对`C`语言操作`MySQL`数据库的一个简单封装。遵循了`Python DB API v2`。但是只支持`Python2`，目前还不支持`Python3`。
2. `mysqlclient`：是`MySQL-python`的另外一个分支。支持`Python3`并且修复了一些`bug`。
3. `pymysql`：纯`Python`实现的一个驱动。因为是纯`Python`编写的，因此执行效率不如`MySQL-python`。并且也因为是纯`Python`编写的，因此可以和`Python`代码无缝衔接。
4. `MySQL Connector/Python`：`MySQL`官方推出的使用纯`Python`连接`MySQL`的驱动。因为是纯`Python`开发的。效率不高。

## 2. SQLAlchemy介绍

### 2.1 安装：

`SQLAlchemy`是一个数据库的`ORM`框架，让我们操作数据库的时候不要再用`SQL`语句了，跟直接操作模型一样。安装命令为：`pip install SQLAlchemy`。

### 2.2 通过`SQLAlchemy`连接数据库：

首先来看一段代码：

```python
from sqlalchemy import create_engine

# 数据库的配置变量
HOSTNAME = '127.0.0.1'
PORT     = '3306'
DATABASE = 'xt_flask'
USERNAME = 'root'
PASSWORD = 'root'
DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)

# 创建数据库引擎
engine = create_engine(DB_URI)

#创建连接
with engine.connect() as con:
    rs = con.execute('SELECT 1')
    print rs.fetchone()
```

首先从`sqlalchemy`中导入`create_engine`，用这个函数来创建引擎，然后用`engine.connect()`来连接数据库。其中一个比较重要的一点是，通过`create_engine`函数的时候，需要传递一个满足某种格式的字符串，对这个字符串的格式来进行解释：

```
dialect+driver://username:password@host:port/database?charset=utf8
```

`dialect`是数据库的实现，比如`MySQL`、`PostgreSQL`、`SQLite`，并且转换成小写。`driver`是`Python`对应的驱动，如果不指定，会选择默认的驱动，比如MySQL的默认驱动是`MySQLdb`。`username`是连接数据库的用户名，`password`是连接数据库的密码，`host`是连接数据库的域名，`port`是数据库监听的端口号，`database`是连接哪个数据库的名字。

如果以上输出了`1`，说明`SQLAlchemy`能成功连接到数据库。

### 2.3 用SQLAlchemy执行原生SQL：

我们将上一个例子中的数据库配置选项单独放在一个`constants.py`的文件中，看以下例子：

```python
from sqlalchemy import create_engine
from constants import DB_URI

#连接数据库
engine = create_engine(DB_URI,echo=True)

# 使用with语句连接数据库，如果发生异常会被捕获
with engine.connect() as con:
    # 先删除users表
    con.execute('drop table if exists authors')
    # 创建一个users表，有自增长的id和name
    con.execute('create table authors(id int primary key auto_increment,'name varchar(25))')
    # 插入两条数据到表中
    con.execute('insert into persons(name) values("abc")')
    con.execute('insert into persons(name) values("xiaotuo")')
    # 执行查询操作
    results = con.execute('select * from persons')
    # 从查找的结果中遍历
    for result in results:
        print(result)
```

## 3. SQLAlchemy基本使用

### 3.1 使用SQLAlchemy：

#### 3.1.1 创建`ORM`模型：

要使用`ORM`来操作数据库，首先需要创建一个类来与对应的表进行映射。现在以`User表`来做为例子，它有`自增长的id`、`name`、`fullname`、`password`这些字段，那么对应的类为：

```python
from sqlalchemy import Column,Integer,String
from constants import DB_URI
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine(DB_URI,echo=True)

# 所有的类都要继承自`declarative_base`这个函数生成的基类
Base = declarative_base(engine)
class User(Base):
    # 定义表名为users
    __tablename__ = 'users'

    # 将id设置为主键，并且默认是自增长的
    id = Column(Integer,primary_key=True)
    # name字段，字符类型，最大的长度是50个字符
    name = Column(String(50))
    fullname = Column(String(50))
    password = Column(String(100))

    # 让打印出来的数据更好看，可选的
    def __repr__(self):
        return "<User(id='%s',name='%s',fullname='%s',password='%s')>" % (self.id,self.name,self.fullname,self.password)
```

#### 3.1.2 映射到数据库中：

`SQLAlchemy`会自动的设置第一个`Integer`的主键并且没有被标记为外键的字段添加自增长的属性。因此以上例子中`id`自动的变成自增长的。以上创建完和表映射的类后，还没有真正的映射到数据库当中，执行以下代码将类映射到数据库中：

```python
Base.metadata.create_all()
```

#### 3.1.3 添加数据到表中：

在创建完数据表，并且做完和数据库的映射后，接下来让我们添加数据进去：

```python
ed_user = User(name='ed',fullname='Ed Jones',password='edspassword')
# 打印名字
print ed_user.name
> ed
# 打印密码
print ed_user.password
> edspassword
# 打印id
print(ed_user.id)
> None
```

可以看到，name和password都能正常的打印，唯独`id`为`None`，这是因为`id`是一个自增长的主键，还未插入到数据库中，`id`是不存在的。接下来让我们把创建的数据插入到数据库中。和数据库打交道的，是一个叫做`Session`的对象：

```python
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
# 或者
# Session = sessionmaker()
# Session.configure(bind=engine)
session = Session()
ed_user = User(name='ed',fullname='Ed Jones',password='edspassword')
session.add(ed_user)
```

现在只是把数据添加到`session`中，但是并没有真正的把数据存储到数据库中。如果需要把数据存储到数据库中，还要做一次`commit`操作：

```python
session.commit()
# 打印ed_user的id
print ed_user.id
> 1
```

#### 3.1.4 回滚：

这时候，`ed_user`就已经有id。 说明已经插入到数据库中了。有人肯定有疑问了，为什么添加到`session`中后还要做一次`commit`操作呢，这是因为，在`SQLAlchemy`的`ORM`实现中，在做`commit`操作之前，所有的操作都是在事务中进行的，因此如果你要将事务中的操作真正的映射到数据库中，还需要做`commit`操作。既然用到了事务，这里就并不能避免的提到一个回滚操作了，那么看以下代码展示了如何使用回滚（接着以上示例代码）：

```python
# 修改ed_user的用户名
ed_user.name = 'Edwardo'
# 创建一个新的用户
fake_user = User(name='fakeuser',fullname='Invalid',password='12345')
# 将新创建的fake_user添加到session中
session.add(fake_user)
# 判断`fake_user`是否在`session`中存在
print fake_user in session
> True
# 从数据库中查找name=Edwardo的用户
tmp_user = session.query(User).filter_by(name='Edwardo')
# 打印tmp_user的name
print tmp_user
# 打印出查找到的tmp_user对象，注意这个对象的name属性已经在事务中被修改为Edwardo了。
> <User(name='Edwardo', fullname='Ed Jones', password='edspassword')>
# 刚刚所有的操作都是在事务中进行的，现在来做回滚操作
session.rollback()
# 再打印tmp_user
print tmp_user
> <User(name='ed', fullname='Ed Jones', password='edspassword')>
# 再看fake_user是否还在session中
print fake_user in session
> False
```

#### 3.1.5 查找数据：

接下来看下如何进行查找操作，查找操作是通过`session.query()`方法实现的，这个方法会返回一个`Query`对象，`Query`对象相当于一个数组，装载了查找出来的数据，并且可以进行迭代。具体里面装的什么数据，就要看向`session.query()`方法传的什么参数了，如果只是传一个`ORM`的类名作为参数，那么提取出来的数据就是都是这个类的实例，比如：

```python
for instance in session.query(User).order_by(User.id):
    print instance
# 输出所有的user实例
> <User (id=2,name='ed',fullname='Ed Json',password='12345')>
> <User (id=3,name='be',fullname='Be Engine',password='123456')>
```

如果传递了两个及其两个以上的对象，或者是传递的是`ORM`类的属性，那么查找出来的就是元组，例如：

```python
for instance in session.query(User.name):
    print instance
# 输出所有的查找结果
> ('ed',)
> ('be',)
```

以及：

```python
for instance in session.query(User.name,User.fullname):
    print instance
# 输出所有的查找结果
> ('ed', 'Ed Json')
> ('be', 'Be Engine')
```

或者是：

```python
for instance in session.query(User,User.name).all():
    print instance
# 输出所有的查找结果
> (<User (id=2,name='ed',fullname='Ed Json',password='12345')>, 'Ed Json')
> (<User (id=3,name='be',fullname='Be Engine',password='123456')>, 'Be Engine')
```

另外，还可以对查找的结果（`Query`）做切片操作：

```python
for instance in session.query(User).order_by(User.id)[1:3]
    instance
```

如果想对结果进行过滤，可以使用`filter_by`和`filter`两个方法，这两个方法都是用来做过滤的，区别在于，`filter_by`是传入关键字参数，`filter`是传入条件判断，并且`filter`能够传入的条件更多更灵活，请看以下例子：

```python
# 第一种：使用filter_by过滤：
for name in session.query(User.name).filter_by(fullname='Ed Jones'):
    print name
# 输出结果：
> ('ed',)

# 第二种：使用filter过滤：
for name in session.query(User.name).filter(User.fullname=='Ed Jones'):
    print name
# 输出结果：
> ('ed',)
```

### 3.2 Column常用参数：

- `default`：默认值。
- `nullable`：是否可空。
- `primary_key`：是否为主键。
- `unique`：是否唯一。
- `autoincrement`：是否自动增长。
- `onupdate`：更新的时候执行的函数。
- `name`：该属性在数据库中的字段映射。

### 3.3 sqlalchemy常用数据类型：

- `Integer`：整形。
- `Float`：浮点类型。
- `Boolean`：传递`True/False`进去。
- `DECIMAL`：定点类型。
- `enum`：枚举类型。
- `Date`：传递`datetime.date()`进去。
- `DateTime`：传递`datetime.datetime()`进去。
- `Time`：传递`datetime.time()`进去。
- `String`：字符类型，使用时需要指定长度，区别于`Text`类型。
- `Text`：文本类型。
- `LONGTEXT`：长文本类型。

## 4. 查找操作

### 4.1 query可用参数：

1. 模型对象。指定查找这个模型中所有的对象。
2. 模型中的属性。可以指定只查找某个模型的其中几个属性。
3. 聚合函数。
   - `func.count`：统计行的数量。
   - `func.avg`：求平均值。
   - `func.max`：求最大值。
   - `func.min`：求最小值。
   - `func.sum`：求和。

### 4.2 过滤条件：

过滤是数据提取的一个很重要的功能，以下对一些常用的过滤条件进行解释，并且这些过滤条件都是只能通过`filter`方法实现的：

1. `equals`：

```python
query.filter(User.name == 'ed')
```

2. `not equals`:

```python
query.filter(User.name != 'ed')
```

3. `like`：

```python
query.filter(User.name.like('%ed%'))
```

4. `in`：

```python
query.filter(User.name.in_(['ed','wendy','jack']))
# 同时，in也可以作用于一个Query
query.filter(User.name.in_(session.query(User.name).filter(User.name.like('%ed%'))))
```

5. `not in`：

```python
query.filter(~User.name.in_(['ed','wendy','jack']))
```

6. `is null`：

```python
query.filter(User.name==None)
# 或者是
query.filter(User.name.is_(None))
```

7. `is not null`:

```python
query.filter(User.name != None)
# 或者是
query.filter(User.name.isnot(None))
```

8. `and`：

```python
from sqlalchemy import and_
query.filter(and_(User.name=='ed',User.fullname=='Ed Jones'))
# 或者是传递多个参数
query.filter(User.name=='ed',User.fullname=='Ed Jones')
# 或者是通过多次filter操作
query.filter(User.name=='ed').filter(User.fullname=='Ed Jones')
```

9. `or`：

```python
from sqlalchemy import or_  query.filter(or_(User.name=='ed',User.name=='wendy'))
```

### 4.3 查找方法：

介绍完过滤条件后，有一些经常用到的查找数据的方法也需要解释一下：

1. `all()`：返回一个`Python`列表（`list`）：

```python
query = session.query(User).filter(User.name.like('%ed%').order_by(User.id)
# 输出query的类型
print type(query)
> <type 'list'>
# 调用all方法
query = query.all()
# 输出query的类型
print type(query)
> <class 'sqlalchemy.orm.query.Query'>
```

2. `first()`：返回`Query`中的第一个值：

```python
user = session.query(User).first()
print user
> <User(name='ed', fullname='Ed Jones', password='f8s7ccs')>
```

3. `one()`：查找所有行作为一个结果集，如果结果集中只有一条数据，则会把这条数据提取出来，如果这个结果集少于或者多于一条数据，则会抛出异常。总结一句话：有且只有一条数据的时候才会正常的返回，否则抛出异常：

```python
# 多于一条数据
user = query.one()
> Traceback (most recent call last):
> ...
> MultipleResultsFound: Multiple rows were found for one()
# 少于一条数据
user = query.filter(User.id == 99).one()
> Traceback (most recent call last):
> ...
> NoResultFound: No row was found for one()
# 只有一条数据
> query(User).filter_by(name='ed').one()
```

4. `one_or_none()`：跟`one()`方法类似，但是在结果集中没有数据的时候也不会抛出异常。

5. `scalar()`：底层调用`one()`方法，并且如果`one()`方法没有抛出异常，会返回查询到的第一列的数据：

```python
session.query(User.name,User.fullname).filter_by(name='ed').scalar()
```

### 4.4 文本SQL：

`SQLAlchemy`还提供了使用**文本SQL**的方式来进行查询，这种方式更加的灵活。而文本SQL要装在一个`text()`方法中，看以下例子：

```python
from sqlalchemy import text
for user in session.query(User).filter(text("id<244")).order_by(text("id")).all():
    print user.name
```

如果过滤条件比如上例中的244存储在变量中，这时候就可以通过传递参数的形式进行构造：

```python
session.query(User).filter(text("id<:value and name=:name")).params(value=224,name='ed').order_by(User.id)
```

在文本SQL中的变量前面使用了`:`来区分，然后使用`params`方法，指定需要传入进去的参数。另外，使用`from_statement`方法可以把过滤的函数和条件函数都给去掉，使用纯文本的SQL:

```python
sesseion.query(User).from_statement(text("select * from users where name=:name")).params(name='ed').all()
```

使用`from_statement`方法一定要注意，`from_statement`返回的是一个`text`里面的查询语句，一定要记得调用`all()`方法来获取所有的值。

### 4.5 计数（Count）：

`Query`对象有一个非常方便的方法来计算里面装了多少数据：

```python
session.query(User).filter(User.name.like('%ed%')).count()
```

当然，有时候你想明确的计数，比如要统计`users`表中有多少个不同的姓名，那么简单粗暴的采用以上`count`是不行的，因为姓名有可能会重复，但是处于两条不同的数据上，如果在原生数据库中，可以使用`distinct`关键字，那么在`SQLAlchemy`中，可以通过`func.count()`方法来实现：

```python
from sqlalchemy import func
session.query(func.count(User.name),User.name).group_by(User.name).all()
## 输出的结果
> [(1, u'ed'), (1, u'fred'), (1, u'mary'), (1, u'wendy')]
```

另外，如果想实现`select count(*) from users`，可以通过以下方式来实现：

```python
session.query(func.count(*)).select_from(User).scalar()
```

当然，如果指定了要查找的表的字段，可以省略`select_from()`方法：

```python
session.query(func.count(User.id)).scalar()
```

## 5. 表关系：

表之间的关系存在三种：一对一、一对多、多对多。而`SQLAlchemy`中的`ORM`也可以模拟这三种关系。因为一对一其实在`SQLAlchemy`中底层是通过一对多的方式模拟的，所以先来看下一对多的关系：

### 5.1 外键：

在Mysql中，外键可以让表之间的关系更加紧密。而SQLAlchemy同样也支持外键。通过ForeignKey类来实现，并且可以指定表的外键约束。相关示例代码如下：

```python
class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)
    content = Column(Text,nullable=False)
    uid = Column(Integer,ForeignKey('user.id'))

    def __repr__(self):
        return "<Article(title:%s)>" % self.title

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer,primary_key=True,autoincrement=True)
    username = Column(String(50),nullable=False)
```

外键约束有以下几项：

1. `RESTRICT`：父表数据被删除，会阻止删除。默认就是这一项。
2. `NO ACTION`：在MySQL中，同`RESTRICT`。
3. `CASCADE`：级联删除。
4. `SET NULL`：父表数据被删除，子表数据会设置为NULL。

### 5.2 一对多：

拿之前的`User`表为例，假如现在要添加一个功能，要保存用户的邮箱帐号，并且邮箱帐号可以有多个，这时候就必须创建一个新的表，用来存储用户的邮箱，然后通过`user.id`来作为外键进行引用，先来看下邮箱表的实现：

```python
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship

class Address(Base):
    __tablename__ = 'address'
    id = Column(Integer,primary_key=True)
    email_address = Column(String,nullable=False)
    # User表的外键，指定外键的时候，是使用的是数据库表的名称，而不是类名
    user_id = Column(Integer,ForeignKey('users.id'))
    # 在ORM层面绑定两者之间的关系，第一个参数是绑定的表的类名，
    # 第二个参数back_populates是通过User反向访问时的字段名称
    user = relationship('User',back_populates="addresses")

    def __repr__(self):
        return "<Address(email_address='%s')>" % self.email_address

# 重新修改User表，添加了addresses字段，引用了Address表的主键
class User(Base):
    __tablename__ = 'users'
    id = Column(Integer,primary_key=True)
    name = Column(String(50))
    fullname = Column(String(50))
    password = Column(String(100))
    # 在ORM层面绑定和`Address`表的关系
    addresses = relationship("Address",order_by=Address.id,back_populates="user")
```

其中，在`User`表中添加的`addresses`字段，可以通过`User.addresses`来访问和这个user相关的所有address。在`Address`表中的`user`字段，可以通过`Address.user`来访问这个user。达到了双向绑定。表关系已经建立好以后，接下来就应该对其进行操作，先看以下代码：

```python
jack = User(name='jack',fullname='Jack Bean',password='gjffdd')
jack.addresses = [Address(email_address='jack@google.com'),
                Address(email_address='j25@yahoo.com')]
session.add(jack)
session.commit()
```

首先，创建一个用户，然后对这个`jack`用户添加两个邮箱，最后再提交到数据库当中，可以看到这里操作`Address`并没有直接进行保存，而是先添加到用户里面，再保存。

### 5.3 一对一：

一对一其实就是一对多的特殊情况，从以上的一对多例子中不难发现，一对应的是`User`表，而多对应的是`Address`，也就是说一个`User`对象有多个`Address`。因此要将一对多转换成一对一，只要设置一个`User`对象对应一个`Address`对象即可，看以下示例：

```python
class User(Base):
  __tablename__ = 'users'
  id = Column(Integer,primary_key=True)
  name = Column(String(50))
  fullname = Column(String(50))
  password = Column(String(100))
  # 设置uselist关键字参数为False
  addresses = relationship("Address",back_populates='addresses',uselist=False)
class Address(Base):
  __tablename__ = 'addresses'
  id = Column(Integer,primary_key=True)
  email_address = Column(String(50))
  user_id = Column(Integer,ForeignKey('users.id')
  user = relationship('Address',back_populates='user')
```

从以上例子可以看到，只要在`User`表中的`addresses`字段上添加`uselist=False`就可以达到一对一的效果。设置了一对一的效果后，就不能添加多个邮箱到`user.addresses`字段了，只能添加一个：

```python
user.addresses = Address(email_address='ed@google.com')
```

### 5.4 多对多：

多对多需要一个中间表来作为连接，同理在`sqlalchemy`中的`orm`也需要一个中间表。假如现在有一个`Teacher`和一个`Classes`表，即老师和班级，一个老师可以教多个班级，一个班级有多个老师，是一种典型的多对多的关系，那么通过`sqlalchemy`的`ORM`的实现方式如下：

```python
association_table = Table('teacher_classes',Base.metadata,
	Column('teacher_id',Integer,ForeignKey('teacher.id')),
	Column('classes_id',Integer,ForeignKey('classes.id'))
)

class Teacher(Base):
    __tablename__ = 'teacher'
    id = Column(Integer,primary_key=True)
    tno = Column(String(10))
    name = Column(String(50))
    age = Column(Integer)
    classes = relationship('Classes',secondary=association_table,back_populates='teachers')

class Classes(Base):
    __tablename__ = 'classes'
    id = Column(Integer,primary_key=True)
    cno = Column(String(10))
    name = Column(String(50))
    teachers = relationship('Teacher',secondary=association_table,back_populates='classes')
```

要创建一个多对多的关系表，首先需要一个中间表，通过`Table`来创建一个中间表。上例中第一个参数`teacher_classes`代表的是中间表的表名，第二个参数是`Base`的元类，第三个和第四个参数就是要连接的两个表，其中`Column`第一个参数是表示的是连接表的外键名，第二个参数表示这个外键的类型，第三个参数表示要外键的表名和字段。
创建完中间表以后，还需要在两个表中进行绑定，比如在`Teacher`中有一个`classes`属性，来绑定`Classes`表，并且通过`secondary`参数来连接中间表。同理，`Classes`表连接`Teacher`表也是如此。定义完类后，之后就是添加数据，请看以下示例：

```python
teacher1 = Teacher(tno='t1111',name='xiaotuo',age=10)
teacher2 = Teacher(tno='t2222',name='datuo',age=10)
classes1 = Classes(cno='c1111',name='english')
classes2 = Classes(cno='c2222',name='math')
teacher1.classes = [classes1,classes2]
teacher2.classes = [classes1,classes2]
classes1.teachers = [teacher1,teacher2]
classes2.teachers = [teacher1,teacher2]
session.add(teacher1)
session.add(teacher2)
session.add(classes1)
session.add(classes2)
```

### 5.5 `ORM`层面的`CASCADE`：

如果将数据库的外键设置为`RESTRICT`，那么在`ORM`层面，删除了父表中的数据，那么从表中的数据将会`NULL`。如果不想要这种情况发生，那么应该将这个值的`nullable=False`。

在`SQLAlchemy`，只要将一个数据添加到`session`中，和他相关联的数据都可以一起存入到数据库中了。这些是怎么设置的呢？其实是通过`relationship`的时候，有一个关键字参数`cascade`可以设置这些属性：

1. `save-update`：默认选项。在添加一条数据的时候，会把其他和他相关联的数据都添加到数据库中。这种行为就是`save-update`属性影响的。
2. `delete`：表示当删除某一个模型中的数据的时候，是否也删掉使用`relationship`和他关联的数据。
3. `delete-orphan`：表示当对一个ORM对象解除了父表中的关联对象的时候，自己便会被删除掉。当然如果父表中的数据被删除，自己也会被删除。这个选项只能用在一对多上，不能用在多对多以及多对一上。并且还需要在子模型中的`relationship`中，增加一个`single_parent=True`的参数。
4. `merge`：默认选项。当在使用`session.merge`，合并一个对象的时候，会将使用了`relationship`相关联的对象也进行`merge`操作。
5. `expunge`：移除操作的时候，会将相关联的对象也进行移除。这个操作只是从session中移除，并不会真正的从数据库中删除。
6. `all`：是对`save-update, merge, refresh-expire, expunge, delete`几种的缩写。

## 6. 查询高级

### 6.1 排序：

1. `order_by`：可以指定根据这个表中的某个字段进行排序，如果在前面加了一个`-`，代表的是降序排序。

2. 在模型定义的时候指定默认排序：有些时候，不想每次在查询的时候都指定排序的方式，可以在定义模型的时候就指定排序的方式。有以下两种方式：

   - relationship的order_by参数：在指定`relationship`的时候，传递`order_by`参数来指定排序的字段。

   - 在模型定义中，添加以下代码：

     ```
      __mapper_args__ = {
          "order_by": title
        }
     ```

     即可让文章使用标题来进行排序。

3. 正向排序和反向排序：默认情况是从小到大，从前到后排序的，如果想要反向排序，可以调用排序的字段的`desc`方法。

### 6.2 limit、offset和切片：

1. `limit`：可以限制每次查询的时候只查询几条数据。
2. `offset`：可以限制查找数据的时候过滤掉前面多少条。
3. 切片：可以对`Query`对象使用切片操作，来获取想要的数据。

### 6.3 懒加载：

在一对多，或者多对多的时候，如果想要获取多的这一部分的数据的时候，往往能通过一个属性就可以全部获取了。比如有一个作者，想要或者这个作者的所有文章，那么可以通过`user.articles`就可以获取所有的。但有时候我们不想获取所有的数据，比如只想获取这个作者今天发表的文章，那么这时候我们可以给`relationship`传递一个`lazy='dynamic'`，以后通过`user.articles`获取到的就不是一个列表，而是一个`AppendQuery`对象了。这样就可以对这个对象再进行一层过滤和排序等操作。

### 6.4 查询高级：

1. group_by：

根据某个字段进行分组。比如想要根据性别进行分组，来统计每个分组分别有多少人，那么可以使用以下代码来完成：

```python
session.query(User.gender,func.count(User.id)).group_by(User.gender).all()
```

2. having：

`having`是对查找结果进一步过滤。比如只想要看未成年人的数量，那么可以首先对年龄进行分组统计人数，然后再对分组进行`having`过滤。示例代码如下：

```python
result = session.query(User.age,func.count(User.id)).group_by(User.age).having(User.age >= 18).all()
```

3. join方法：

`join`查询分为两种，一种是`inner join`，另一种是`outer join`。默认的是`inner join`，如果指定`left join`或者是`right join`则为`outer join`。如果想要查询`User`及其对应的`Address`，则可以通过以下方式来实现：

```python
for u,a in session.query(User,Address).filter(User.id==Address.user_id).all():
  print(u)
  print(a)
# 输出结果：
> <User (id=1,name='ed',fullname='Ed Jason',password='123456')>
> <Address id=4,email=ed@google.com,user_id=1>
```

这是通过普通方式的实现，也可以通过`join`的方式实现，更加简单：

```python
for u,a in session.query(User,Address).join(Address).all():
  print(u)
  print(a)
# 输出结果：
> <User (id=1,name='ed',fullname='Ed Jason',password='123456')>
> <Address id=4,email=ed@google.com,user_id=1>
```

当然，如果采用`outerjoin`，可以获取所有`user`，而不用在乎这个`user`是否有`address`对象，并且`outerjoin`默认为左外查询：

```python
for instance in session.query(User,Address).outerjoin(Address).all():
  print(instance)

# 输出结果：
(<User (id=1,name='ed',fullname='Ed Jason',password='123456')>, <Address id=4,email=ed@google.com,user_id=1>)
(<User (id=2,name='xt',fullname='xiaotuo',password='123')>, None)
```

4. 别名：

当多表查询的时候，有时候同一个表要用到多次，这时候用别名就可以方便的解决命名冲突的问题了：

```python
from sqlalchemy.orm import aliased
adalias1 = aliased(Address)
adalias2 = aliased(Address)
for username,email1,email2 in session.query(User.name,adalias1.email_address,adalias2.email_address).join(adalias1).join(adalias2).all():
  print(username,email1,email2)
```

5. 子查询：

`sqlalchemy`也支持子查询，比如现在要查找一个用户的用户名以及该用户的邮箱地址数量。要满足这个需求，可以在子查询中找到所有用户的邮箱数（通过group by合并同一用户），然后再将结果放在父查询中进行使用：

```python
from sqlalchemy.sql import func
# 构造子查询
stmt = session.query(Address.user_id.label('user_id'),func.count(*).label('address_count')).group_by(Address.user_id).subquery()

# 将子查询放到父查询中
for u,count in session.query(User,stmt.c.address_count).outerjoin(stmt,User.id==stmt.c.user_id).order_by(User.id):
  print(u,count)
```

从上面我们可以看到，一个查询如果想要变为子查询，则是通过`subquery()`方法实现，变成子查询后，通过`子查询.c`属性来访问查询出来的列。以上方式只能查询某个对象的具体字段，如果要查找整个实体，则需要通过`aliased`方法，示例如下：

```python
stmt = session.query(Address)
adalias = aliased(Address,stmt)
for user,address in session.query(User,stmt).join(stmt,User.addresses):
  print(user,address)
```

## 7. Flask-SQLAlchemy库

另外一个库，叫做`Flask-SQLAlchemy`，`Flask-SQLAlchemy`是对`SQLAlchemy`进行了一个简单的封装，使得我们在`flask`中使用`sqlalchemy`更加的简单。可以通过`pip install flask-sqlalchemy`。使用`Flask-SQLAlchemy`的流程如下：

1. 数据库初始化：数据库初始化不再是通过`create_engine`，请看以下示例：

```python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from constants import DB_URI
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
db = SQLAlchemy(app)
```

2. `ORM`类：之前都是通过`Base = declarative_base()`来初始化一个基类，然后再继承，在`Flask-SQLAlchemy`中更加简单了（代码依赖以上示例）：

```python
class User(db.Model):
	id = db.Column(db.Integer,primary_key=True)
	username = db.Column(db.String(80),unique=True)
	email = db.Column(db.String(120),unique=True)
	def __init__(self,username,email):
		self.username = username
		self.email = email
	def __repr__(self):
		return '<User %s>' % self.username
```

3. 映射模型到数据库表：使用`Flask-SQLAlchemy`所有的类都是继承自`db.Model`，并且所有的`Column`和数据类型也都成为`db`的一个属性。但是有个好处是不用写表名了，`Flask-SQLAlchemy`会自动将类名小写化，然后映射成表名。
   写完类模型后，要将模型映射到数据库的表中，使用以下代码创建所有的表：

```python
db.create_all()
```

4. 添加数据：这时候就可以在数据库中看到已经生成了一个`user`表了。接下来添加数据到表中：

```python
admin = User('admin','admin@example.com')
guest = User('guest','guest@example.com')
db.session.add(admin)
db.session.add(guest)
db.session.commit()
```

   添加数据和之前的没有区别，只是`session`成为了一个`db`的属性。

5. 查询数据：查询数据不再是之前的`session.query`了，而是将`query`属性放在了`db.Model`上，所以查询就是通过`Model.query`的方式进行查询了：

```python
users = User.query.all()
# 再如：
admin = User.query.filter_by(username='admin').first()
# 或者：
admin = User.query.filter(User.username='admin').first()
```

6. 删除数据：删除数据跟添加数据类似，只不过`session`是`db`的一个属性而已：

```python
db.session.delete(admin)
db.session.commit()
```

## 8. MySQL8图文教程

### 8.1 下载：

在以下链接中下载`MySQL8 Community`：https://dev.mysql.com/downloads/windows/installer/8.0.html
![image.png](04.SQLAlchemy/JkerUJzxSe9gkEZM8kq4td.png)

### 8.2 安装：

下载完成后，就可以双击`msi`文件进行安装了。安装步骤如下。

1. 第一步：选择`Developer Default`：
   ![image.png](04.SQLAlchemy/Yfgrhaxrx6NCj9gSHFbHuV.png)
2. 点击Execute：
   ![image.png](04.SQLAlchemy/PYHwMTRXxKjfBKyYG6b2Vi.png)
   然后再点击`Next`执行下一步
   ![image.png](04.SQLAlchemy/8P5sFSmXzjYHNiN76MStNS.png)
3. 点击Next：
   ![image.png](04.SQLAlchemy/6jGy66928WdhmKVvX6PtgY.png)
4. 保持默认值，点击Next：
   ![image.png](04.SQLAlchemy/wkgYVxahsubMtRv4NrMk9h.png)
5. 设置密码加密方式，用默认的即可，然后点击Next：
   ![image.png](04.SQLAlchemy/axbEskKZYY2XaaKfPgrcnD.png)
6. 设置密码，两次密码输入保持一致，以后登录`MySQL`就要用这个密码了，所以要记住：
   ![image.png](04.SQLAlchemy/ZhBkRdPgvRrnfAQrseTfs3.png)
7. 保持默认值，点击Next：
   ![image.png](04.SQLAlchemy/2wrbfuJzg3RVPL8UaLnw8U.png)
8. 点击Next：
   ![image.png](04.SQLAlchemy/mZEdUQoEPJuyXBtZbWsyRF.png)
   执行完后点击Finish
   ![image.png](04.SQLAlchemy/TCiEyKKyv5b9iJRT48HbMY.png)
9. 重新点击Next：
   ![image.png](04.SQLAlchemy/MW2uxnkekaQG5SiWFcLcfX.png)
10. 点击Next：
    ![image.png](04.SQLAlchemy/hT2fsYR8NemfdcVhUpjYSQ.png)
11. 输入密码，点击Check：
    ![image.png](04.SQLAlchemy/JeVsr6NJbo73HJhN2qEC5o.png)
12. 点击Finish，完成安装：
    ![image.png](04.SQLAlchemy/wYbVmHwu4Crndi4mMMjH9Z.png)

















































