当前位置:   article > 正文

Web后端学习笔记 Flask(7)数据库_db.session.query

db.session.query

高级查询:

1. group_by

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

2. having

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

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

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

  1. # -*- coding: utf-8 -*-
  2. from sqlalchemy import create_engine, Column, Integer, String, DateTime, Float, func, Enum
  3. from sqlalchemy import Text, ForeignKey
  4. from sqlalchemy.ext.declarative import declarative_base
  5. from sqlalchemy.orm import sessionmaker, relationship, backref
  6. from datetime import datetime
  7. import random
  8. from sqlalchemy import Table
  9. import time
  10. HOST_NAME = "127.0.0.1"
  11. PORT = "3306"
  12. DATABASE = "cms"
  13. USERNAME = "root"
  14. PASSWORD = "root1234"
  15. # dialect+driver://username:password@host:port/database
  16. DB_URI = "mysql+pymysql://{username}:{password}@{host}:{port}/{database}".format(
  17. username=USERNAME, password=PASSWORD, host=HOST_NAME, port=PORT, database=DATABASE
  18. )
  19. # 创建数据库引擎
  20. engine = create_engine(DB_URI)
  21. Base = declarative_base(engine) # 通过继承Base创建ORM模型/ 创建基类
  22. db_session = sessionmaker(engine)() # 创建会话,才能实现增删改查
  23. # 1. 创建ORM模型,必须继承自SQLAlchemy:
  24. # 2. 在ORM中创建一些属性,跟表中的字段一一映射,这些属性必须是SQLAlchemy提供的数据类型
  25. # 3. 将创建好的ORM模型映射到数据库中
  26. class User(Base):
  27. __tablename__ = "user"
  28. id = Column(Integer, primary_key=True, autoincrement=True)
  29. username = Column(String(50), nullable=False)
  30. age = Column(Integer, default=0)
  31. gender = Column(Enum("male", "female", "secret"), default="secret")
  32. def __repr__(self):
  33. return str(self.id) + " | " + self.username
  34. # Base.metadata.drop_all()
  35. # Base.metadata.create_all()
  36. #
  37. # user1 = User(username="tom", age=88, gender="male")
  38. # user2 = User(username="kit", age=22, gender="female")
  39. # user3 = User(username="jane", age=23, gender="female")
  40. # user4 = User(username="janny", age=45, gender="male")
  41. # user5 = User(username="Austing", age=12, gender="male")
  42. # user6 = User(username="Cart", age=34, gender="female")
  43. # user7 = User(username="Buck", age=34, gender="secret")
  44. # user8 = User(username="Buck", age=34, gender="secret")
  45. # db_session.add_all([user1, user2, user3, user4,
  46. # user5, user6, user7, user8])
  47. #
  48. # db_session.commit()
  49. # 统计每个年龄段的人数 group_by
  50. result = db_session.query(User.age, func.count(User.id)).group_by(User.age).all()
  51. print(result)
  52. # having,针对产找出来的数据进行再一层的过滤 having
  53. result = db_session.query(User.age, func.count(User.id)).group_by(User.age).having(User.age > 20).all()
  54. print(result)

join操作:

  1. 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()
  2. print(result)

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

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

原生的sql语句如下所示:

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

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

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

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

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

示例代码:

  1. # -*- coding: utf-8 -*-
  2. from sqlalchemy import create_engine, Column, Integer, String, DateTime, Float, func, Enum
  3. from sqlalchemy import Text, ForeignKey
  4. from sqlalchemy.ext.declarative import declarative_base
  5. from sqlalchemy.orm import sessionmaker, relationship, backref
  6. from datetime import datetime
  7. import random
  8. from sqlalchemy import Table
  9. import time
  10. HOST_NAME = "127.0.0.1"
  11. PORT = "3306"
  12. DATABASE = "cms"
  13. USERNAME = "root"
  14. PASSWORD = "root1234"
  15. # dialect+driver://username:password@host:port/database
  16. DB_URI = "mysql+pymysql://{username}:{password}@{host}:{port}/{database}".format(
  17. username=USERNAME, password=PASSWORD, host=HOST_NAME, port=PORT, database=DATABASE
  18. )
  19. # 创建数据库引擎
  20. engine = create_engine(DB_URI)
  21. Base = declarative_base(engine) # 通过继承Base创建ORM模型/ 创建基类
  22. db_session = sessionmaker(engine)() # 创建会话,才能实现增删改查
  23. # 1. 创建ORM模型,必须继承自SQLAlchemy:
  24. # 2. 在ORM中创建一些属性,跟表中的字段一一映射,这些属性必须是SQLAlchemy提供的数据类型
  25. # 3. 将创建好的ORM模型映射到数据库中
  26. class User(Base):
  27. __tablename__ = "user"
  28. id = Column(Integer, primary_key=True, autoincrement=True)
  29. username = Column(String(50), nullable=False)
  30. age = Column(Integer, default=0)
  31. city = Column(String(50), nullable=False)
  32. def __repr__(self):
  33. return str(self.id) + " | " + self.username + " | " + str(self.age) + " | " + self.city
  34. # Base.metadata.drop_all()
  35. # Base.metadata.create_all()
  36. #
  37. # user1 = User(username="tom", age=88, city="北京")
  38. # user2 = User(username="kit", age=22, city="上海")
  39. # user3 = User(username="jane", age=23, city="常山")
  40. # user4 = User(username="janny", age=45, city="长沙")
  41. # user5 = User(username="Austing", age=12, city="香港")
  42. # user6 = User(username="Cart", age=34, city="西安")
  43. # user7 = User(username="Buck", age=34, city="上海")
  44. # user8 = User(username="Buck", age=34, city="北京")
  45. # db_session.add_all([user1, user2, user3, user4,
  46. # user5, user6, user7, user8])
  47. #
  48. # db_session.commit()
  49. # 两次查询的方式得到结果,效率低
  50. user = db_session.query(User).filter(User.username == "tom").first()
  51. users = db_session.query(User).filter(User.city == user.city).all()
  52. print(users)
  53. # 通过子查询的方式实现
  54. # 通过.label()给查询到的字段起一个新的名字
  55. sub_res = db_session.query(User.city.label("city")).filter(User.username == "tom").subquery()
  56. res = db_session.query(User).filter(User.city == sub_res.c.city)
  57. print(res)

Flask-sqlalchemy插件介绍:

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

1. 数据库连接

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

  1. from flask import Flask, url_for
  2. from flask_sqlalchemy import SQLAlchemy
  3. app = Flask(__name__)
  4. HOST_NAME = "127.0.0.1"
  5. PORT = "3306"
  6. DATABASE = "cms"
  7. USERNAME = "root"
  8. PASSWORD = "root1234"
  9. # dialect+driver://username:password@host:port/database
  10. DB_URI = "mysql+pymysql://{username}:{password}@{host}:{port}/{database}".format(
  11. username=USERNAME, password=PASSWORD, host=HOST_NAME, port=PORT, database=DATABASE
  12. )
  13. app.config["SQLALCHEMY_DATABASE_URI"] = DB_URI
  14. db = SQLAlchemy(app) # 初始化数据库
  15. app.config["TEMPLATE_AUTO_RELOAD"] = True

2. 创建orm模型

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

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

    省略

4. 使用session

    省略

5. 查询数据

  1. from flask import Flask, url_for
  2. from flask_sqlalchemy import SQLAlchemy
  3. app = Flask(__name__)
  4. HOST_NAME = "127.0.0.1"
  5. PORT = "3306"
  6. DATABASE = "flask_sqlalchemy"
  7. USERNAME = "root"
  8. PASSWORD = "root1234"
  9. # dialect+driver://username:password@host:port/database
  10. DB_URI = "mysql+pymysql://{username}:{password}@{host}:{port}/{database}".format(
  11. username=USERNAME, password=PASSWORD, host=HOST_NAME, port=PORT, database=DATABASE
  12. )
  13. app.config["SQLALCHEMY_DATABASE_URI"] = DB_URI
  14. app.config["TEMPLATE_AUTO_RELOAD"] = True
  15. db = SQLAlchemy(app) # 初始化数据库
  16. class Person(db.Model):
  17. __tablename__ = "person"
  18. id = db.Column(db.Integer, primary_key=True, autoincrement=True)
  19. name = db.Column(db.String(50), nullable=False)
  20. def __repr__(self):
  21. return str(self.id) + " | " + self.name
  22. db.drop_all()
  23. db.create_all()
  24. p1 = Person(name="zhang")
  25. p2 = Person(name="wang")
  26. db.session.add(p2)
  27. db.session.commit()
  28. res = db.session.query(Person).all()
  29. print([p for p in res])
  30. @app.route('/')
  31. def hello_world():
  32. print(url_for("news.news_list"))
  33. return 'Hello World!'
  34. if __name__ == '__main__':
  35. 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文件,然后定义模型类:

  1. # -*- coding: utf-8 -*-
  2. from sqlalchemy import Column, String, Integer, create_engine, Text
  3. from sqlalchemy.ext.declarative import declarative_base
  4. from sqlalchemy.orm import sessionmaker, relationship, backref
  5. HOST_NAME = "127.0.0.1"
  6. PORT = "3306"
  7. DATABASE = "alembic_demon"
  8. USERNAME = "root"
  9. PASSWORD = "root1234"
  10. # dialect+driver://username:password@host:port/database
  11. DB_URI = "mysql+pymysql://{username}:{password}@{host}:{port}/{database}".format(
  12. username=USERNAME, password=PASSWORD, host=HOST_NAME, port=PORT, database=DATABASE
  13. )
  14. # 创建数据库引擎
  15. engine = create_engine(DB_URI)
  16. Base = declarative_base(engine) # 通过继承Base创建ORM模型/ 创建基类
  17. db_session = sessionmaker(engine)() # 创建会话,才能实现增删改查
  18. # 1. 创建ORM模型,必须继承自SQLAlchemy:
  19. # 2. 在ORM中创建一些属性,跟表中的字段一一映射,这些属性必须是SQLAlchemy提供的数据类型
  20. # 3. 将创建好的ORM模型映射到数据库中
  21. class User(Base):
  22. __tablename__ = "user"
  23. id = Column(Integer, primary_key=True, autoincrement=True)
  24. username = Column(String(50), nullable=False)
  25. password = Column(String(100), nullable=False)
  26. age = Column(Integer, default=0)
  27. country = Column(String(50), nullable=False)
  28. def __repr__(self):
  29. return str(self.id) + " | " + self.username
  30. # class Article(Base):
  31. # __tablename__ = "article"
  32. # id = Column(Integer, primary_key=True, autoincrement=True)
  33. # title = Column(String(100), nullable=False)
  34. # content = Column(Text, nullable=False)
  35. # ROM->迁移脚本->映射到数据库中

3. 修改配置文件:

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

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

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

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

  1. import sys,os
  2. sys.path.append(os.path.dirname(os.path.dirname(__file__))) # 添加models的路径
  3. 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文件:

  1. import os
  2. import sys
  3. sys.path.append(os.path.dirname(os.path.dirname(__file__)))
  4. import app
  5. target_metadata = app.db.Model.metadata # 完成绑定

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

Fask-script插件介绍:

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

flask-script使用:

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

  1. from flask_script import Manager
  2. manager = Manager(app)
  3. # 没有参数
  4. @manager.command
  5. def greet():
  6. print("Hello")
  7. if __name__ == "__main__":
  8. manager.run()

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

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

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

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

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

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

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

  1. from flask import Flask, url_for
  2. from flask_sqlalchemy import SQLAlchemy
  3. import config
  4. app = Flask(__name__)
  5. app.config.from_object(config)
  6. db = SQLAlchemy(app) # 初始化数据库
  7. class BackEndUser(db.Model):
  8. __tablename__ = "back_user"
  9. id = db.Column(db.Integer, primary_key=True, autoincrement=True)
  10. username = db.Column(db.String(50), nullable=False)
  11. email = db.Column(db.String(50), nullable=False)
  12. @app.route('/')
  13. def hello_world():
  14. print(url_for("news.news_list"))
  15. return 'Hello World!'
  16. if __name__ == '__main__':
  17. app.run(debug=True)

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

  1. # -*- coding: utf-8 -*-
  2. from app import app, BackEndUser, db
  3. from flask_script import Manager
  4. manager = Manager(app)
  5. @manager.option("-u", "--username", dest="username")
  6. @manager.option("-e", "--email", dest="email")
  7. def add_backend_user(username, email):
  8. user = BackEndUser(username=username, email=email)
  9. db.session.add(user)
  10. db.session.commit()
  11. if __name__ == "__main__":
  12. manager.run()

3. 通过命令调用:

查看插入的数据:

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

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

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

  1. # -*- coding: utf-8 -*-
  2. from flask_script import Manager
  3. db_manager = Manager()
  4. @db_manager.command
  5. def init():
  6. print("数据库初始化创建完成")
  7. @db_manager.command
  8. def revision():
  9. print("迁移脚本初始化完成")
  10. @db_manager.command
  11. def upgrade():
  12. print("数据库映射完成")

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

  1. from db_manager import db_manager
  2. manager = Manager(app)
  3. 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文件:

  1. from flask import Flask
  2. from flask_sqlalchemy import SQLAlchemy
  3. import config
  4. from models import User
  5. app = Flask(__name__)
  6. app.config.from_object(config)
  7. db = SQLAlchemy(app)
  8. @app.route('/')
  9. def hello_world():
  10. return 'Hello World!'
  11. @app.route('/profile/')
  12. def profile():
  13. return "profile page"
  14. if __name__ == '__main__':
  15. app.run()

models.py文件:

  1. # -*- coding: utf-8 -*-
  2. from app import db
  3. class User(db.Model):
  4. __tablename__ = "user"
  5. id = db.Column(db.Integer, primary_key=True, autoincrement=True)
  6. username = db.Column(db.String(50), nullable=False)

这样就会报错:

解决方案:

创建一个第三方的文件:

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

exts.py文件:

  1. # -*- coding: utf-8 -*-
  2. from flask_sqlalchemy import SQLAlchemy
  3. db = SQLAlchemy() # db没有绑定app,此时db就拿不到app中数据库的配置文件

models.py文件:

  1. # -*- coding: utf-8 -*-
  2. from exts import db
  3. class User(db.Model):
  4. __tablename__ = "user"
  5. id = db.Column(db.Integer, primary_key=True, autoincrement=True)
  6. username = db.Column(db.String(50), nullable=False)

app.py文件:

  1. from flask import Flask
  2. import config
  3. from models import User
  4. from exts import db
  5. app = Flask(__name__)
  6. app.config.from_object(config)
  7. db.init_app(app) # db获取app中数据库的连接方式
  8. @app.route('/')
  9. def hello_world():
  10. return 'Hello World!'
  11. @app.route('/profile/')
  12. def profile():
  13. return "profile page"
  14. if __name__ == '__main__':
  15. app.run()

Flask-migrate详细介绍:

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

安装:pip install flask-migrate

实际使用:

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

  1. # -*- coding: utf-8 -*-
  2. from flask_script import Manager
  3. from app import app
  4. from flask_migrate import MigrateCommand, Migrate # 用来绑定app以及db
  5. from exts import db
  6. manager = Manager(app) # 绑定app,db到flask-migrate
  7. Migrate(app=app, db=db)
  8. # 添加falsk-migrate中子命令到db下:
  9. manager.add_command("db", MigrateCommand)
  10. if __name__ == "__main__":
  11. 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中即可

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/510850
推荐阅读
相关标签
  

闽ICP备14008679号