Web后端学习笔记 Flask(7)数据库

高级查询:

1. group_by

根据某个字段进行分组,比如说需要根据某个字段分组,来统计每组有多少人。

2. having

having是对查询结果进一步过滤,比如只想看到未成年人的数量,那么首先可以对年龄进行分组统计人数,然后再对分组进行having过滤。

3. join查询分为两种,一种是inner join, 另一种是outer join。默认的是inner join,如果指定的是left join或者right join则为outer join。如果想要查询user及其对应的address.

在数据库中插入测试数据:

# -*- coding: utf-8 -*-

from sqlalchemy import create_engine, Column, Integer, String, DateTime, Float, func, Enum
from sqlalchemy import Text, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, backref
from datetime import datetime
import random
from sqlalchemy import Table
import time


HOST_NAME = "127.0.0.1"
PORT = "3306"
DATABASE = "cms"
USERNAME = "root"
PASSWORD = "root1234"

# dialect+driver://username:password@host:port/database

DB_URI = "mysql+pymysql://{username}:{password}@{host}:{port}/{database}".format(
    username=USERNAME, password=PASSWORD, host=HOST_NAME, port=PORT, database=DATABASE
)

# 创建数据库引擎
engine = create_engine(DB_URI)
Base = declarative_base(engine)   # 通过继承Base创建ORM模型/ 创建基类

db_session = sessionmaker(engine)()   # 创建会话,才能实现增删改查
# 1. 创建ORM模型,必须继承自SQLAlchemy:
# 2. 在ORM中创建一些属性,跟表中的字段一一映射,这些属性必须是SQLAlchemy提供的数据类型
# 3. 将创建好的ORM模型映射到数据库中


class User(Base):
    __tablename__ = "user"
    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), nullable=False)
    age = Column(Integer, default=0)
    gender = Column(Enum("male", "female", "secret"), default="secret")

    def __repr__(self):
        return str(self.id) + " | " + self.username


# Base.metadata.drop_all()
# Base.metadata.create_all()
#
# user1 = User(username="tom", age=88, gender="male")
# user2 = User(username="kit", age=22, gender="female")
# user3 = User(username="jane", age=23, gender="female")
# user4 = User(username="janny", age=45, gender="male")
# user5 = User(username="Austing", age=12, gender="male")
# user6 = User(username="Cart", age=34, gender="female")
# user7 = User(username="Buck", age=34, gender="secret")
# user8 = User(username="Buck", age=34, gender="secret")


# db_session.add_all([user1, user2, user3, user4,
#                     user5, user6, user7, user8])
#
# db_session.commit()

# 统计每个年龄段的人数 group_by
result = db_session.query(User.age, func.count(User.id)).group_by(User.age).all()
print(result)

# having,针对产找出来的数据进行再一层的过滤 having
result = db_session.query(User.age, func.count(User.id)).group_by(User.age).having(User.age > 20).all()
print(result)

join操作:

result = db_session.query(User.username, func.count(Article.id)).join(Article, User.id == Article.uid).group_by(User.id).order_by(func.count(Article.id)).desc().all()
print(result)

subquery()实现复杂的查询:
在数据库中插入测试数据:

例如,需要查找和一个人城市相同的人,或者年龄相同的人

原生的sql语句如下所示:

多次查询虽然也可以实现子查询的功能,但是多次查询会影响数据库的性能,如果网站的访问量过大的话,会造成网站性能不好。因此应该尽量采用子查询对数据库查询进行优化。子查询能够一次完成查询,避免多次查询数据库。

在sqlalchemy中实现子查询需要通过以下几个步骤:

1. 将子查询按照传统的方式写好查询代码,然后在query对象后面执行subquery方法,将这个查询变成一个子查询

2. 在子查询中,将需要用到的字段同构label取一个别名。

3. 在父查询中,如果需要使用子查询的字段,那么可以通过子查询的变量上的c属性获得。

示例代码:

# -*- coding: utf-8 -*-

from sqlalchemy import create_engine, Column, Integer, String, DateTime, Float, func, Enum
from sqlalchemy import Text, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, backref
from datetime import datetime
import random
from sqlalchemy import Table
import time


HOST_NAME = "127.0.0.1"
PORT = "3306"
DATABASE = "cms"
USERNAME = "root"
PASSWORD = "root1234"

# dialect+driver://username:password@host:port/database

DB_URI = "mysql+pymysql://{username}:{password}@{host}:{port}/{database}".format(
    username=USERNAME, password=PASSWORD, host=HOST_NAME, port=PORT, database=DATABASE
)

# 创建数据库引擎
engine = create_engine(DB_URI)
Base = declarative_base(engine)   # 通过继承Base创建ORM模型/ 创建基类

db_session = sessionmaker(engine)()   # 创建会话,才能实现增删改查
# 1. 创建ORM模型,必须继承自SQLAlchemy:
# 2. 在ORM中创建一些属性,跟表中的字段一一映射,这些属性必须是SQLAlchemy提供的数据类型
# 3. 将创建好的ORM模型映射到数据库中


class User(Base):
    __tablename__ = "user"
    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), nullable=False)
    age = Column(Integer, default=0)
    city = Column(String(50), nullable=False)

    def __repr__(self):
        return str(self.id) + " | " + self.username + " | " + str(self.age) + " | " + self.city


# Base.metadata.drop_all()
# Base.metadata.create_all()
#
# user1 = User(username="tom", age=88, city="北京")
# user2 = User(username="kit", age=22, city="上海")
# user3 = User(username="jane", age=23, city="常山")
# user4 = User(username="janny", age=45, city="长沙")
# user5 = User(username="Austing", age=12, city="香港")
# user6 = User(username="Cart", age=34, city="西安")
# user7 = User(username="Buck", age=34, city="上海")
# user8 = User(username="Buck", age=34, city="北京")


# db_session.add_all([user1, user2, user3, user4,
#                     user5, user6, user7, user8])
#
# db_session.commit()

# 两次查询的方式得到结果,效率低
user = db_session.query(User).filter(User.username == "tom").first()
users = db_session.query(User).filter(User.city == user.city).all()
print(users)

# 通过子查询的方式实现
# 通过.label()给查询到的字段起一个新的名字
sub_res = db_session.query(User.city.label("city")).filter(User.username == "tom").subquery()
res = db_session.query(User).filter(User.city == sub_res.c.city)
print(res)

Flask-sqlalchemy插件介绍:

flask-sqlalchemy相当于将sqlalchemy结合到flask中,并且对初始化数据,连接数据库,以及一些包的导入统一做了封装,进行进一步的简化,使得更加易于使用。

1. 数据库连接

定义好数据库连接字符串:

from flask import Flask, url_for
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

HOST_NAME = "127.0.0.1"
PORT = "3306"
DATABASE = "cms"
USERNAME = "root"
PASSWORD = "root1234"
# dialect+driver://username:password@host:port/database
DB_URI = "mysql+pymysql://{username}:{password}@{host}:{port}/{database}".format(
    username=USERNAME, password=PASSWORD, host=HOST_NAME, port=PORT, database=DATABASE
)
app.config["SQLALCHEMY_DATABASE_URI"] = DB_URI

db = SQLAlchemy(app)    # 初始化数据库 

app.config["TEMPLATE_AUTO_RELOAD"] = True

2. 创建orm模型

    不需要再创建基类,不需要再导入各种数据类型,直接使用db下面相应的属性名即可

3. 将orm模型映射到数据库

    省略

4. 使用session

    省略

5. 查询数据

from flask import Flask, url_for
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

HOST_NAME = "127.0.0.1"
PORT = "3306"
DATABASE = "flask_sqlalchemy"
USERNAME = "root"
PASSWORD = "root1234"
# dialect+driver://username:password@host:port/database
DB_URI = "mysql+pymysql://{username}:{password}@{host}:{port}/{database}".format(
    username=USERNAME, password=PASSWORD, host=HOST_NAME, port=PORT, database=DATABASE
)
app.config["SQLALCHEMY_DATABASE_URI"] = DB_URI
app.config["TEMPLATE_AUTO_RELOAD"] = True

db = SQLAlchemy(app)    # 初始化数据库


class Person(db.Model):
    __tablename__ = "person"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(50), nullable=False)

    def __repr__(self):
        return str(self.id) + " | " + self.name


db.drop_all()
db.create_all()


p1 = Person(name="zhang")
p2 = Person(name="wang")

db.session.add(p2)
db.session.commit()

res = db.session.query(Person).all()
print([p for p in res])


@app.route('/')
def hello_world():
    print(url_for("news.news_list"))
    return 'Hello World!'


if __name__ == '__main__':
    app.run(debug=True)

在flask-sqlalchemy中,__tablename__也可以省略不写,会自动进行添加(但是不推荐这种写法)

alembic数据库迁移工具基本使用:

        在使用sqlalchemy的过程中,在定义好orm模型之后,是通过.create_all()方法进行数据表的创建,创建好之后的不能再进行变更。但是在实际的开发中,可能需要在已经创建好的数据表中删除或者增加字段,而alembic就可以解决表之间的映射问题。对于orm模型新增的字段,如果要将新增字段映射到数据表中,就需要通过alembic工具进行。

        alembic的使用方式优点类似于git,第一:所有命令以alembic开头,第二:alembic的迁移文件也是通过版本进行控制的。通过pip install alembic进行安装。

alembic使用:

1. 初始化alembic仓库,在pycharm中右键项目文件,选择open in terminate, 打开项目终端

初始化alembic仓库之后,会在项目文件夹中生成一个alembic文件夹:

2. 创建模型类。创建一个models.py文件,然后定义模型类:

# -*- coding: utf-8 -*-
from sqlalchemy import Column, String, Integer, create_engine, Text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, backref


HOST_NAME = "127.0.0.1"
PORT = "3306"
DATABASE = "alembic_demon"
USERNAME = "root"
PASSWORD = "root1234"

# dialect+driver://username:password@host:port/database

DB_URI = "mysql+pymysql://{username}:{password}@{host}:{port}/{database}".format(
    username=USERNAME, password=PASSWORD, host=HOST_NAME, port=PORT, database=DATABASE
)

# 创建数据库引擎
engine = create_engine(DB_URI)
Base = declarative_base(engine)   # 通过继承Base创建ORM模型/ 创建基类

db_session = sessionmaker(engine)()   # 创建会话,才能实现增删改查
# 1. 创建ORM模型,必须继承自SQLAlchemy:
# 2. 在ORM中创建一些属性,跟表中的字段一一映射,这些属性必须是SQLAlchemy提供的数据类型
# 3. 将创建好的ORM模型映射到数据库中


class User(Base):
    __tablename__ = "user"
    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), nullable=False)
    password = Column(String(100), nullable=False)
    age = Column(Integer, default=0)
    country = Column(String(50), nullable=False)

    def __repr__(self):
        return str(self.id) + " | " + self.username


# class Article(Base):
#     __tablename__ = "article"
#     id = Column(Integer, primary_key=True, autoincrement=True)
#     title = Column(String(100), nullable=False)
#     content = Column(Text, nullable=False)


# ROM->迁移脚本->映射到数据库中


3. 修改配置文件:

在alembic.ini中设置数据库连接,设置的格式为:

sqlalchemy.url = driver://user:pass@localhost/dbname

sqlalchemy.url = mysql+pymysql://root:root1234@localhost/alembic_demon?charset=utf8
# 指定连接数据的方式

为了使得模型类能够更新到数据库当中,需要在env.py中设置target_metadata,默认target_metadata=None,需要使用sys啊把当前的项目路基那个导入到path中:

import sys,os
sys.path.append(os.path.dirname(os.path.dirname(__file__)))   # 添加models的路径
import models

target_metadata = models.Base.metadata

4. 自动生成迁移脚本,通过下面的命令:

alembic revision --autogenerate -m "message"

在生成迁移脚本的时候,出现了如下的报错:

应该是某处的编码问题,查找报错的文件,发现在下面这个地方调用了一个read()方法:

需要将read()方法的encoding参数修改为utf8.

还有这个地方,也调用了read()方法

将encoding参数修改为utf8.

anaconda/Lib/configeparser中定义了这个read()方法:这里的read()方法有一个参数encoding:

此时,调用迁移脚本生成命令:

此时在alembic/versions目录下会生成迁移脚本:

5. 更新数据库:
alembic upgrade head

6. 如果需要在数据表中添加新的字段,则首先对orm模型进行修改,重复4,5步骤

命令和参数:

1. init:创建一个alembic仓库。
2. revision:创建一个新的版本文件。
3. --autogenerate:自动将当前模型的修改,生成迁移脚本。
4. -m:本次迁移做了哪些修改,用户可以指定这个参数,方便回顾。
5. upgrade:将指定版本的迁移文件映射到数据库中,会执行版本文件中的upgrade函数。如果有多个迁移脚本没有被映射到数据库中,那么会执行多个迁移脚本。
6. [head]:代表最新的迁移脚本的版本号。
7. downgrade:会执行指定版本的迁移文件中的downgrade函数。
8. heads:展示head指向的脚本文件版本号。
9. history:列出所有的迁移版本及其信息。
10. current:展示当前数据库中的版本号。

alembic中的经典错误:
1. FAILED: Target database is not up to date.
    原因:主要是heads和current不相同。current落后于heads的版本。
    解决办法:将current移动到head上。alembic upgrade head
2. FAILED: Can't locate revision identified by '77525ee61b5b'
    原因:数据库中存的版本号不在迁移脚本文件中
    解决办法:删除数据库的alembic_version表中的数据,重新执行alembic upgrade head
3. 执行`upgrade head`时报某个表已经存在的错误:
    原因:执行这个命令的时候,会执行所有的迁移脚本,因为数据库中已经存在了这个表。然后迁移脚本中又包含了创建表的代      码。
    解决办法:(1)删除versions中所有的迁移文件。(2)修改迁移脚本中创建表的代码,使用pass语句

Flask-SqlAlchemy下使用alembic:

使用alembic管理Flask项目下的数据库:

1. 在Flask项目下打开终端

2. 创建初始的alembic仓库

3. 修改配置文件

    alembic.ini文件修改方法不变

    env.py文件中导入app文件的路径,以及app文件:

import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
import app

target_metadata = app.db.Model.metadata     # 完成绑定

后面生成迁移脚本以及映射数据库的方法不变。

Fask-script插件介绍:

Falsk-script的作用是可以通过命令行的形式来操作flask,例如通过命令跑一个开发版的服务器,设置数据库,定时任务等。可以通过pip install flask-script来安装。

flask-script使用:

通过新建manager.py文件,导入Manager和新建manager

from flask_script import Manager

manager = Manager(app)


# 没有参数
@manager.command
def greet():
    print("Hello")


if __name__ == "__main__":
    manager.run()

在命令行中通过如下的命令直接运行greet()函数:

如果函数中有参数,需要使用option装饰器进行传参,有几个参数就用几个option装饰器

# 如果需要传递参数,则需要使用option装饰器
@manager.option("-u", "--username", dest="username")
@manager.option("-a", "--age", dest="age")
def info_list(username, age):
    print("The name is {}, and the age is".format(username, age))

option中,第一个参数为命令行中参数的简写,第二个为命令行中的参数,第三个参数是接受参数值的变量。

通过如下的命令运行info_list()函数:

实际应用,例如要给CMS管理系统添加一个管理员:

1. 首先创建数据库,这里使用alembic实现,回顾一下前面的知识

from flask import Flask, url_for
from flask_sqlalchemy import SQLAlchemy
import config

app = Flask(__name__)
app.config.from_object(config)
db = SQLAlchemy(app)    # 初始化数据库


class BackEndUser(db.Model):
    __tablename__ = "back_user"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(50), nullable=False)
    email = db.Column(db.String(50), nullable=False)


@app.route('/')
def hello_world():
    print(url_for("news.news_list"))
    return 'Hello World!'


if __name__ == '__main__':
    app.run(debug=True)

2. 使用flask-script定义数据插入的函数:

# -*- coding: utf-8 -*-
from app import app, BackEndUser, db
from flask_script import Manager

manager = Manager(app)


@manager.option("-u", "--username", dest="username")
@manager.option("-e", "--email", dest="email")
def add_backend_user(username, email):
    user = BackEndUser(username=username, email=email)
    db.session.add(user)
    db.session.commit()


if __name__ == "__main__":
    manager.run()

3. 通过命令调用:

查看插入的数据:

这种添加管理员的方式,更加的安全与优雅

-------------------------------------------------------------------------------------------------------------------------------------------------

可以用flask-script对数据库的操作进行统一的管理,例如,创建db_manage.py文件,用于对数据库操作的方法做统一的封装:

# -*- coding: utf-8 -*-
from flask_script import Manager

db_manager = Manager()


@db_manager.command
def init():
    print("数据库初始化创建完成")


@db_manager.command
def revision():
    print("迁移脚本初始化完成")


@db_manager.command
def upgrade():
    print("数据库映射完成")

再将创建的db_manager添加到主Manager中,在manager.py文件中加入如下的代码即可:

from db_manager import db_manager

manager = Manager(app)
manager.add_command("db", db_manager)    # 添加子命令

然后通过下面的命令就可以调用db下面的子命令:

项目结构重构:

flask-migrate插件,底层基于alembic,相当于对alembic进行了一层封装,并集成到flask中,使用起来更加方便,类似于SQLAlchemy和flask-sqlalchemy。

python中的循环引用:创建一个第三方的文件,避免循环引用

例如,下面的app文件和models文件,models文件需要导入app文件中的db,app文件需要到models文件中User,就会造成文件之间循环引用的问题:

app.py文件:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import config
from models import User

app = Flask(__name__)
app.config.from_object(config)
db = SQLAlchemy(app)


@app.route('/')
def hello_world():
    return 'Hello World!'


@app.route('/profile/')
def profile():
    return "profile page"


if __name__ == '__main__':
    app.run()

models.py文件:

# -*- coding: utf-8 -*-
from app import db


class User(db.Model):
    __tablename__ = "user"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(50), nullable=False)

这样就会报错:

解决方案:

创建一个第三方的文件:

例如新建exts.py文件,就可以解决循环引用的问题:

exts.py文件:

# -*- coding: utf-8 -*-

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()      # db没有绑定app,此时db就拿不到app中数据库的配置文件

models.py文件:

# -*- coding: utf-8 -*-
from exts import db


class User(db.Model):
    __tablename__ = "user"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(50), nullable=False)

app.py文件:

from flask import Flask
import config
from models import User
from exts import db

app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)    # db获取app中数据库的连接方式


@app.route('/')
def hello_world():
    return 'Hello World!'


@app.route('/profile/')
def profile():
    return "profile page"


if __name__ == '__main__':
    app.run()

Flask-migrate详细介绍:

在实际的开发环境中,经常需要去修改数据库的行为,一般不回去直接修改数据库,而是将数据库的ORM模型进行修改,然后再把模型映射到数据库中。利用前面介绍的alembic可以实现此功能。在flask中,通过flask-migrate实现此功能,flask-migrate是基于alembic的一个封装,并将其集成到flask中。因此所有的数据库迁移操作以及上都是通过alembic完成的。

安装:pip install flask-migrate

实际使用:

基于上面的工程,新建一个manager.py的文件:

# -*- coding: utf-8 -*-
from flask_script import Manager
from app import app
from flask_migrate import MigrateCommand, Migrate    # 用来绑定app以及db
from exts import db

manager = Manager(app)    # 绑定app,db到flask-migrate
Migrate(app=app, db=db)   

# 添加falsk-migrate中子命令到db下:
manager.add_command("db", MigrateCommand)


if __name__ == "__main__":
    manager.run()


flask-migrate 常用命令:

1. python manager.py db init          

    初始化一个alembic仓库在项目文件夹下会生成一个Migration文件夹:其中的文件与alembic init alembic命令生成的类似

2. python manager.py db migrate    生成数据库的迁移文件

3. pyhon manager.py db upgrade    将迁移文件映射到数据库当中

如果需要修改模型,则重复2,3步骤即可

可以通过python manager.py db --help查看所有命令

flask-migrate注意事项:

from models import User   # 将需要映射的模型直接导入manager.py中即可

 

posted @ 2020-04-14 11:38  Alpha205  阅读(170)  评论(0编辑  收藏  举报