Flask-SQLAlchemy installation and setup
- SQLALchemy is actually an abstraction of the database, so that developers do not need to deal directly with SQL statements, but operate the database through Python objects. While giving up some performance overhead, in exchange for a greater improvement in development efficiency
- SQLAlchemy is a relational database framework that provides high-level ORM and underlying native database operations. flask-sqlalchemy is a flask extension that simplifies SQLAlchemy operations
Document address: https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/
SQLAlchemy documentation: https://docs.sqlalchemy.org/en/20/
Install
- Install flask-sqlalchemy
pip install flask-sqlalchemy
- If you are connecting to a mysql database, you need to install
pymysql
pip install pymysql
Database connection settings
- In Flask-SQLAlchemy, the database is specified using a URL, and the database used by the program must be stored in the SQLALCHEMY_DATABASE_URI key of the Flask configuration object
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:[email protected]:3306/test'
- other settings:
# 动态追踪修改设置,如未设置只会提示警告
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
#查询时会显示原始SQL语句
app.config['SQLALCHEMY_ECHO'] = True
- After the configuration is complete, you need to create the database used by the project in MySQL
$ mysql -uroot -pmysql
$ create database test charset utf8;
- other configuration
SQLALCHEMY_DATABASE_URI 用于连接的数据库 URI 。例如:sqlite:tmp/test.dbmysql://username:password@server/db
SQLALCHEMY_BINDS 一个映射 binds 到连接 URI 的字典。更多 binds 的信息见用 Binds 操作多个数据库。
SQLALCHEMY_ECHO 如果设置为Ture, SQLAlchemy 会记录所有 发给 stderr 的语句,这对调试有用。(打印sql语句)
SQLALCHEMY_RECORD_QUERIES 可以用于显式地禁用或启用查询记录。查询记录 在调试或测试模式自动启用。更多信息见get_debug_queries()。
SQLALCHEMY_NATIVE_UNICODE 可以用于显式禁用原生 unicode 支持。当使用 不合适的指定无编码的数据库默认值时,这对于 一些数据库适配器是必须的(比如 Ubuntu 上 某些版本的 PostgreSQL )。
SQLALCHEMY_POOL_SIZE 数据库连接池的大小。默认是引擎默认值(通常 是 5 )
SQLALCHEMY_POOL_TIMEOUT 设定连接池的连接超时时间。默认是 10 。
SQLALCHEMY_POOL_RECYCLE 多少秒后自动回收连接。这对 MySQL 是必要的, 它默认移除闲置多于 8 小时的连接。注意如果 使用了 MySQL , Flask-SQLALchemy 自动设定 这个值为 2 小时。
Connect to other databases
For a complete list of connection URIs, please jump to the SQLAlchemy documentation ( supported database ) below . Some common connection strings are given here.
- Postgres:
postgresql://scott:tiger@localhost/mydatabase
- MySQL:
mysql://scott:tiger@localhost/mydatabase
or
mysql+pymysql://scott:tiger@localhost/mydatabase
- Oracle:
oracle://scott:[email protected]:1521/sidname
- SQLite (note the four slashes at the beginning):
sqlite:absolute/path/to/foo.db
Commonly used SQLAlchemy field types
Commonly used SQLAl1chemy column options
Common SQLAlchemy relational options
Basic operation of database
- In Flask-SQLAlchemy, insert, modify, and delete operations are managed by database sessions.
- A session is represented by db.session. Before preparing to write data to the database, add data to the session and then call the commit() method to commit the session.
- In Flask-SQLAlchemy, the query operation is to manipulate data through the query object.
- The most basic query is to return all the data in the table, and more precise database queries can be performed through filters.
Define the model class in the view function
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
#设置连接数据库的URL
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:123456@localhost/test'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
#查询时会显示原始SQL语句
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
class Role(db.Model):
# 定义表名,数据库中的真实表名
__tablename__ = 'roles'
# 定义列对象,变量名是数据库字段名
id = db.Column(db.Integer, primary_key=True) # 整型主键会默认设置自增主键
name = db.Column(db.String(64), unique=True)
users = db.relationship('User', backref='role')
#repr()方法显示一个可读字符串,使用print打印对象时,会输出这里定义的内容
def __repr__(self):
return 'Role:%s'% self.name
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True, index=True)
email = db.Column(db.String(64),unique=True)
password = db.Column(db.String(64))
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
def __repr__(self):
return 'User:%s'%self.name
@app.route("/")
def index():
return "index page"
if __name__ == '__main__':
app.run(debug=True)
Create table:
db.create_all()
delete table
db.drop_all()
Insert a piece of data
ro1 = Role(name='admin')
db.session.add(ro1)
db.session.commit()
Insert a piece of data again
ro2 = Role(name='user')
db.session.add(ro2)
db.session.commit()
Insert multiple data at once
us1 = User(name='wang',email='[email protected]',password='123456',role_id=ro1.id)
us2 = User(name='zhang',email='[email protected]',password='201512',role_id=ro2.id)
us3 = User(name='chen',email='[email protected]',password='987654',role_id=ro2.id)
us4 = User(name='zhou',email='[email protected]',password='456789',role_id=ro1.id)
us5 = User(name='tang',email='[email protected]',password='158104',role_id=ro2.id)
us6 = User(name='wu',email='[email protected]',password='5623514',role_id=ro2.id)
us7 = User(name='qian',email='[email protected]',password='1543567',role_id=ro1.id)
us8 = User(name='liu',email='[email protected]',password='867322',role_id=ro1.id)
us9 = User(name='li',email='[email protected]',password='4526342',role_id=ro2.id)
us10 = User(name='sun',email='[email protected]',password='235523',role_id=ro2.id)
db.session.add_all([us1,us2,us3,us4,us5,us6,us7,us8,us9,us10])
db.session.commit()
Inquire
Commonly used SQLAlchemy query filters (for filtering)
filter() # 把过滤器添加到原查询上,返回一个新查询
filter_by() # 把等值过滤器添加到原查询上,返回一个新查询
limit # 使用指定的值限定原查询返回的结果
offset() # 偏移原查询返回的结果,返回一个新查询
order_by() # 根据指定条件对原查询结果进行排序,返回一个新查询
group_by() # 根据指定条件对原查询结果进行分组,返回一个新查询
The above filter conditions can be used in conjunction, such asUser.query.filter().offset().order_by().limit()
Commonly used SQLAlchemy query executors (for fetching results)
all() # 以列表形式返回查询的所有结果
first() # 返回查询的第一个结果,如果未查到,返回None
first_or_404() # 返回查询的第一个结果,如果未查到,返回404
get() # 返回指定主键对应的行,如不存在,返回None
get_or_404() # 返回指定主键对应的行,如不存在,返回404
count() # 返回查询结果的数量
paginate() # 返回一个Paginate对象,它包含指定范围内的结果
Query: filter_by precise query
Returns all people whose first name is equal to wang
User.query.filter_by(name='wang').all() # flask-sqlalchemy提供的写法,query无参数
user = db.session.query(User).filter_by(name='wang').all() # sqlalchemy提供的写法,query有参数
Executing sql is the filter condition in where
SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.password AS users_password, users.role_id AS users_role_id
FROM users
WHERE users.name = %(name_1)s
first() returns the first object found in the query
User.query.first()
sql, you can see that it is limit
SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.password AS users_password, users.role_id AS users_role_id
FROM users
LIMIT %(param_1)s
all() returns all objects found in the query
User.query.all()
SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.password AS users_password, users.role_id AS users_role_id
FROM users
filter fuzzy query, returns all the data whose name ends with g
User.query.filter(User.name.endswith('g')).all()
SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.password AS users_password, users.role_id AS users_role_id
FROM users
WHERE (users.name LIKE concat('%%', %(name_1)s))
Note that the '%%' in concat is the formatting of python strings, such as:
a= "%%%s%%"%("abc")
实际表示的是
'%abc%'
It %(name_1)s
is also formatted, such as:
print(“I’m %(name)s. I’m %(age)d” % {‘name’:‘Pythontab’, ‘age’:99})
That is, the final sql will be
SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.password AS users_password, users.role_id AS users_role_id
FROM users
WHERE (users.name LIKE concat('%', 'g')) - 也就是以g结尾
get(): The parameter is the primary key, if the primary key does not exist, no content is returned
User.query.get(1)
Logical NOT, return all data whose name is not equal to wang
User.query.filter(User.name!='wang').all()
SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.password AS users_password, users.role_id AS users_role_id
FROM users
WHERE users.name != %(name_1)s
not_ is equivalent to negate
from sqlalchemy import not_
User.query.filter(not_(User.name=='chen')).all()
SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.password AS users_password, users.role_id AS users_role_id
FROM users
WHERE users.name != %(name_1)s
Essentially indistinguishable from logical NOT
Logical and, need to import and, return all data that meets the condition of and()
from sqlalchemy import and_
User.query.filter(and_(User.name!='wang',User.email.endswith('163.com'))).all()
SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.password AS users_password, users.role_id AS users_role_id
FROM users
WHERE users.name != %(name_1)s AND (users.email LIKE concat('%%', %(email_1)s))
Logical or, need to import or_
from sqlalchemy import or_
User.query.filter(or_(User.name!='wang',User.email.endswith('163.com'))).all()
SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.password AS users_password, users.role_id AS users_role_id
FROM users
WHERE users.name != %(name_1)s OR (users.email LIKE concat('%%', %(email_1)s))
Group statistics group_by
flask-sqlalchemy can't achieve
from sqlalchemy import func
# 统计每个角色的用户数,即用户表以role_id分组统计count即可
ss = db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_id).all()
If you need to filter the grouped data use having
ss = db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_id).having(func.count(User.role_id)>1).all()
Execute sql as follows:
SELECT users.role_id AS users_role_id, count(users.role_id) AS count_1 FROM users GROUP BY users.role_id HAVING count(users.role_id) > 1
Delete after querying data
user = User.query.first()
db.session.delete(user)
db.session.commit()
User.query.all()
update data
user = User.query.first()
user.name = 'dong'
db.session.commit()
User.query.first()
Links Between Models
one-to-many
In the Role and User defined above, the core code of the one-to-many relationship is as follows
class Role(db.Model):
...
#关键代码
users = db.relationship('User', backref='role')
...
class User(db.Model):
...
# 外键,数据库中真实存在的字段
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
It realtionship
describes the relationship between Role
and User
, in the one-to-many relationship, it is defined on 一
one side (in this example, one role corresponds to multiple users, so it is defined in the Role class).
The first parameter is the class "User" corresponding to the reference, input as a string. The
second parameter backref
is a new attribute created for the class User
, so that when there is a user object, the object user.role
corresponding to the user can be obtained Role
; in addition, if there is a role Object, you can use it directly when you want to get all the corresponding users role.users
.
There is also a third parameter lazy
that determines when SQLALchemy loads data from the database (not set in the case)
- If it is set to subquery mode (subquery), the object associated with it will be loaded immediately after the Role object is loaded, which will reduce the total number of queries, but if the number of returned items is large, it will be slower. Set to For subquery, role.users returns a list of all data
- It can also be set to dynamic, so that the associated objects will be loaded when they are used, and filtered before returning. If the number of returned objects is large, or there will be many in the future, it is best to use this method Way. If it is set to dynamic, role.users returns the query object, and does not do real query, you can use the query object to do other logic, such as: first sort and then return the result
Cannot be omitted in the User class role_id
. This field is set as a foreign key, which is associated with the id of the roles table. When used, user.role
this field will be used to get all the attributes of the role object. Otherwise, only user.role_id
the id can be obtained, but not other attributes. .
many to many
Requirement example: Students choose courses online, each student can choose multiple courses, and each course can also be selected by multiple students, forming a many-to-many relationship. The only point in the description of the many-to-many relationship is that a separate table needs to be added to record the correspondence between the two tables . This table is a table that actually exists in the database.
# 中间表,注意中间表使用db.Table,而非继承自db.Model,该表未设置主键
tb_student_course = db.Table('tb_student_course',
db.Column('student_id', db.Integer, db.ForeignKey('students.id')),
db.Column('course_id', db.Integer, db.ForeignKey('courses.id'))
)
# 学生表
class Student(db.Model):
__tablename__ = "students"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
# 关系,secondary指定关联中间表。通过backref使Course类中有student属性
courses = db.relationship('Course', secondary=tb_student_course,
backref='student',
lazy='dynamic')
# 课程表
class Course(db.Model):
__tablename__ = "courses"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
Example relational query (one-to-many):
The relationship between roles and users is a one-to-many relationship. A role can have multiple users, and a user can only belong to one role.
Query all users with known roles
#查询roles表id为1的角色
ro1 = Role.query.get(1)
#查询该角色的所有用户
ro1.users.all()
Query the roles of known users
#查询users表id为3的用户
us1 = User.query.get(3)
#查询用户属于什么角色
us1.role
SELECT roles.id AS roles_id, roles.name AS roles_name
FROM roles
WHERE roles.id = %(pk_1)s
The above two are known role_id
or user_id
, so the join query is not involved
Example of relational query (many-to-many)
Query which courses a student has taken
stu1 = Student.query.get(1)
cos = stu1.courses # Student中有属性courses定义了关系
for co in cos:
print(co.name)
execute sql
SELECT courses.id AS courses_id, courses.name AS courses_name FROM courses, tb_student_course WHERE 1 = tb_student_course.student_id AND courses.id = tb_student_course.course_id
Query which students are enrolled in a course
co1 = Course.query.get(1)
stu = co1.student # [<Student 1>, <Student 2>, <Student 3>] 学生列表
for s in stu:
print(s.name)
execute sql
SELECT students.id AS students_id, students.name AS students_name FROM students, tb_student_course WHERE 1 = tb_student_course.course_id AND students.id = tb_student_course.student_id
join query
- Inner join inner join
# 以下查询结果中只包含user表的字段,不含角色表字段,实践中用途不大。得到的是User对象列表
# 其中User.role_id==Role.id在设置的有外键时,可不指定
d = User.query.join(Role, User.role_id==Role.id).all()
# sql
SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.password AS users_password, users.role_id AS users_role_id
FROM users INNER JOIN roles ON users.role_id = roles.id
To query specific fields below, use with_entities
the field information to be queried
# 查询用户角色不为空时,用户的角色名(本质是inner join)
s = User.query.join(Role, User.role_id==Role.id).with_entities(User.id, User.name, User.role_id, Role.name).all()
# 原生sqlalchemy的写法
db.session.query(User.id, User.name, User.role_id, Role.name).join(User, User.role_id==Role.id, isouter=True).all()
# 结果样例,列表嵌套元组
[(6, 'wu', 2, 'user'), (7, 'qian', 1, 'admin'), (8, 'liu', 1, 'admin'), (9, 'li', 2, 'user'), (10, 'sun', 2, 'user')]
# 执行sql如下
SELECT users.id AS users_id, users.name AS users_name, users.role_id AS users_role_id, roles.name AS roles_name
FROM users INNER JOIN roles ON users.role_id = roles.id
- left join
d = User.query.join(Role, User.role_id==Role.id, isouter=True).with_entities(User.id, User.name, User.role_id, Role.name).all()
# sql
SELECT users.id AS users_id, users.name AS users_name, users.role_id AS users_role_id, roles.name AS roles_name
FROM users LEFT OUTER JOIN roles ON users.role_id = roles.id
- Right join
is the same as left join, the two tables can be reversed
In addition, there is User.query.outerjoin
an interface that represents external connection, but its essence is still join. The source code is as follows:
Association query is not mandatory to set foreign keys in the model class, as long as it can be associated through a certain field, you can use join query, you only need to specify the association field in the join.
subquery
# 下边代码是在Role和User表各添加了deleted字段,表示是否删除,0未删除,1删除。
# 通过subquery设定子查询
abc = db.session.query(User.id.label('id'), User.name.label('username'), Role.name.label('rolename'),
Role.deleted.label('deleted'), func.IF(Role.deleted==0,1,0).label('snapshot')).\
join(Role, Role.user_id == User.id, isouter=True).subquery()
xx = db.session.query(abc.c.id, abc.c.username, func.sum(abc.c.snapshot)).group_by(abc.c.id).all()
execute sql
select userid, username, sum(snp) from (
SELECT users.id userid, users.name username, roles.name rolename,roles.deleted, if(roles.deleted=0,1,0) as snp
FROM users LEFT OUTER JOIN roles ON roles.user_id = users.id) s GROUP BY s.userid;
Common functions
Aggregation functions usually used in the database such as sum, count, if function, etc. are all sqlalchemy.func
in
from sqlalchemy import func
# 聚合函数
db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_id).all()
# if 函数
db.session.query(User.id, func.IF(User.id > 4, 1, 0).label('xx')) # user id大于4,返回1,否则返回0
Where label is used to set the alias