Python中利用SQLAlchemy模块操作MySQL数据库

Contents

  • 1. 关于"主键", "外键", "索引", "事务", "存储过程"和"触发器"
    • 1.1. 主键
    • 1.2. 外键
    • 1.3. 索引
    • 1.4. 事务
    • 1.5. 存储过程
    • 1.6. 触发器
  • 2. 使用SQLAlchemy创建一个表
    • 2.1. 要求
    • 2.2. 分析
    • 2.3. 代码实现
      • 2.3.1. SQLAlchemy Core模式创建数据表
      • 2.3.2. SQLAlchemy ORM模式创建数据表
  • 3. 使用SQLAlchemy写出上述表的CRUD示例
    • 3.1. 使用SQLAlchemy Core模式实现CRUD
    • 3.2. 使用SQLAlchemy ORM模式实现CRUD
  • 4. 实现查询分页
  • 5. References

1. 关于"主键", “外键”, “索引”, “事务”, “存储过程"和"触发器”

1.1. 主键

主键(Primary Key),即逐渐约束,就是表中的任意两条数据的主键约束字段的值不能重复,即通过某个或某几个唯一的字段即可区分开两条数据。选取主键的一个基本原则就是最好不使用任何业务相关的字段作为主键。

另外,在关系型数据库中,还允许通过多个字段联合标定为主键,此时就称这几个字段为联合主键。对于联合主键,允许其中的某个字段值相同,只要多个字段组合在一起能够区分两条数据即可。

1.2. 外键

外键(Foreign Key),即外键约束,是指一个表中的字段被另外一个表中的字段关联引用,从而达到保持两张表中外键约束的字段确定的该条数据与其引用的另一张表中的相关字段对应的数据保持逻辑关联。其可以对外键所在表中的数据作出限制,使外键以及其所引用字段所在表的数据能够保持参照完整性。

外键会对数据库的性能带来一些负向影响,所以对于一些要求性能的数据库,通常是在软件的逻辑层面保证数据的逻辑关联以及参照完整性。

1.3. 索引

索引(Index),是数据库表中的一个字段或者多个字段组合而成,用于提高表中数据的查询速度,对合适的字段构建合适的索引,是提高数据库性能的重要方式。在MySQL中,所有数据类型都能被索引,比如普通索引、唯一性索引、全文索引等等。

通过索引查询数据的时候,可以不必读完记录(插入到表中的一条数据)的所有信息,而只是查询索引列。如果没有索引,在查询数据的时候,将会读取每条记录的所有字段信息进行匹配。

不同的存储引擎定义了表所支持的最大索引数和最大索引长度。索引通常有两种存储类型:B型树索引和哈希索引(Hash)。InnoDB和MyISAM存储引擎支持B型树索引;Memory存储引擎支持Hash索引和B型树索引,默认为前者。

MySQL中支持的索引类型大致如下:

  • 普通索引: 在创建索引时,不指定任何限制条件,这种索引可以创建在任何数据类型的字段上,该字段的值是否唯一和非空由字段本身的完整性约束条件决定。
  • 唯一性索引: 在创建的时候,指定UNIQUE约束条件,表示创建一个唯一性索引。在字段上创建该类索引的时候,要求该字段的值需要是唯一的。主键是一种特殊的唯一性索引。
  • 全文索引: 在创建索引的时候,指定FULLTEXT作为约束条件,表示创建一个唯一性索引。全文索引只能创建在数据类型为CHAR, VARCHAR, TEXT的字段上面。查询数据量较大的字段时,使用全文索引可以提高查询速度。默认情况下,全文索引在搜索的时候,不区分大小写。但如果对于使用的列使用二进制排序后,全文索引搜索的时候可以区分大小写。
  • 单列索引: 在表的单个字段上创建的索引,单列索引只在创建了索引的字段上进行索引。这种索引并不是特制某种类型的索引,所以可以是普通索引、唯一性索引以及全文索引。
  • 多列索引: 在表的多个字段上创建的索引,即为多列索引。在检索的时候可以通过定义了索引的多个字段进行查询。不过,只有当查询条件中使用了这些字段的第一个字段的时候,这种类型的索引才会被使用。比如在表的id, name, sex这三个字段上创建了一个多列索引,那么只有当查询条件中使用了id字段的时候,才会使用多列索引进行搜索。
  • 空间索引: 在创建索引的时候,指定了SPECIAL参数,表示创建空间索引。空间索引只能建立在空间数据类型上。这类索引很少被使用到。

为了使索引的使用效率更高,所以在创建索引的时候,需要考虑在哪些字段上创建索引,以及创建什么类型的索引。

索引的设计原则如下:

  • 使用唯一性索引:通过该类索引可以快速的确定某条记录。
  • 为经常需要排序、分组和联合操作的字段建立索引:如果某个字段经常需要执行ORDER BY, GROUP BY, DISTINCT, UNION等操作,则需要在该字段上创建索引,以便提升操作效率,可以有效避免因为排序带来的性能损失。
  • 经常需要作为查询条件的字段,也需要为其创建索引:如果在查询数据的时候,经常需要对某个字段作为查询条件,,此时为这样的字段创建索引,就可以提高整个表的查询速度。
  • 限制索引数量:索引的数量并非越多越好,每个索引都会占用一定量的磁盘空间。所以,索引越多,需要的磁盘空间也就越大。而且,更新表中数据的时候,索引越多,更新操作也就越慢。同时修改表的时候,对索引的更新和重构也会很麻烦。
  • 为了提高索引的效率,尽量使用数量少的索引:比如对一个CHAR(100)类型的字段进行全文索引的时候没,就没有对CHAR(10)类型的字段进行全文索引的时候效率高。
  • 尽量使用前缀来索引: 如果索引字段的值很长,最好使用值的前缀来索引。由于在搜索的时候,只需要检索字段的前面几个字符,所以检索效率就会很高。
  • 对于不再使用或者很少使用的索引,应该删除: 不再使用的索引,在更新表中数据的时候,会增加更新操作的耗时,降低更新效率。

在实际使用上,根据实际需要创建索引,并对索引结构进行优化。

1.4. 事务

MySQL事务是一个包含一条或者多条SQL语句,执行特定操作的逻辑工作单元,其作为一个基础单元,要么被整体提交,要么被整体回滚。当事务中的SQL语句对数据库进行修改的时候,只有当所有的修改操作都成功之后,才会将该事务提交;当事务中的部分语句执行失败的时候,则将事务整体回滚。

MySQL数据库中,InnoDB存储引擎支持事务功能,通过事务功能,可以用来维护数据库的完整性。

事务用来管理增删改操作(insert, delete, update),事务需要满足4个条件(ACID):

  • 原子性(Atomicity,也被称为不可分割性):一个事务中的所有操作要么全部完成,要么全部撤销,不会结束在中间某个环节。事务在执行过程中发生错误,该事务会被回滚到事务开始执行之前的状态。
  • 一致性(Consistency):在事务开始执行以及结束之后,数据库的完整性没有被破坏。即写入的所有记录都符合表中预设的约束条件。
  • 隔离性(Isolation,又称为独立性):数据库支持多个并发的事务同时对其数据进行读写和修改的能力,隔离性可以防止事务并发执行的时候导致的数据不一致的情况。事务隔离分为多个级别:
    • 读未提交(Read Uncommited)
    • 读已提交(Read Commited)
    • 可重复读(Repeatable Read)
    • 串行化(Serializable)
  • 持久性(Durability):事务执行完成之后,对数据库的修改操作就是持久的,并且写入到了数据库中的。

在 MySQL 命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。因此要显式地开启一个事务务须使用命令 BEGINSTART TRANSACTION,或者执行命令 SET AUTOCOMMIT=0,用来禁止使用当前会话的自动提交。

事务控制语句:

  • BEGINSTART TRANSACTION显式地开启一个事务;
  • COMMIT 也可以使用 COMMIT WORK,不过二者是等价的。COMMIT会提交事务,并使已对数据库进行的所有修改成为永久性的;
  • ROLLBACK也可以使用ROLLBACK WORK,不过二者是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改;
  • SAVEPOINT identifierSAVEPOINT允许在事务中创建一个保存点,一个事务中可以有多个SAVEPOINT
  • RELEASE SAVEPOINT identifier 删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
  • ROLLBACK TO identifier把事务回滚到标记点;
  • SET TRANSACTION用来设置事务的隔离级别。InnoDB 存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE

1.5. 存储过程

存储过程(Stored Procedure)是在数据库中定义的一系列SQL语句的集合,其中包含了存储过程的名字、参数列表以及SQL语句。所有的关系型数据库系统都支持存储过程。通过将数据库密集型操作定义为存储过程,可以给外部应用程序提供一个API接口。而且这个存储过程可以在多个程序以及多种变成语言中被重复使用。从而可以避免重复的数据库操作代码。另外,对于一些敏感数据,使用存储过程可以隐藏数据库的细节信息,比如字段名称等等,这样就可以提供一个一致性的、安全的操作环境。而且可以将操作记录在数据库中,此时用户和应用程序就无需直接访问数据库表,而只能调用特定的存储过程即可。

存储过程实际上是存储在数据库服务器上面的,就像在数据库上预先准备好的一系列SQL语句,所以执行速度很快。因为这个过程中,应用程序只需要调用存储过程即可,无需有重复的网络请求,只需要调用存储在数据库服务器上的存储过程即可。但是在应用程序中调用存储过程的API也有很明显的缺点,那就是缺少灵活性,无法在应用程序中灵活安排或者修改、更新存储过程中的操作。

在MySQL数据库中,可以通过CREATE PROCEDURE语句创建存储过程。

1.6. 触发器

触发器就是当指定的表上发生指定的修改操作(增删改,INSERT, UPDATE, DELETE)的时候,自动被调用执行的一组操作集合。触发器可用于验证输入数据、保持审计追踪等。

通过CREATE TRIGGER语句可以创建一个新的触发器。

需要注意的是,MySQL的触发器只有通过SQL语句修改数据库表的时候,才会被自动激活;如果是通过API连接到数据库服务器并传送SQL语句到服务器,则无法自动触发存储过程,也就意味着触发器无法通过使用NDB API执行的更新操作被激活。

2. 使用SQLAlchemy创建一个表

2.1. 要求

包含username, age, sex字段

2.2. 分析

SQLAlchemy是一个Python操作SQL关系型数据库的软件包,其支持ORM(Object Relational Mapping,即对象关系映射)。SQLAlchemy提供了两种使用模式:SQLAlchemy Core模式以及SQLAlchemy ORM模式。

  • SQLAlchemy Core模式:对于Core模式,是其作为数据库工具包的基础架构而存在的,其中提供了管理到数据库连接、与数据库查询以及结果进行交互、可编程的结构化SQL语句的功能。
  • SQLAlchemy ORM模式:对于ORM模式,是构建在Core模式之上的,使用面向对象的方式操作数据库。ORM模式中提供了额外的配置层允许用于自定义的Python类映射到数据库的表以及其他结构中,其中也包含了对象持久化机制:Session。

下面的示例代码中,会分别使用这两种机制创建数据表。

2.3. 代码实现

先使用SQLAlchemy Core模式创建数据表。在开始之前,先准备数据库,并完成对普通用户的授权操作。除此之外,还需要安装pymysql后端驱动(DBAPI),用于连接到数据库。

SQLAlchemy官方支持的DBAPI如下所示:

  • mysqlclient (maintained fork of MySQL-Python)
  • PyMySQL
  • MySQL Connector/Python
  • asyncmy
  • aiomysql
  • CyMySQL
  • OurSQL
  • PyODBC

此处使用pymysql,所以需要使用pip install pymysql完成该软件包的安装。除此之外,也可以使用上面列出来的其他软件包。

使用root用户身份登录数据库服务器,创建一个名为mytest的数据库,并将其授权给[email protected].%这个用户。具体如下所是:

mysql> select user();
+----------------+
| user()         |
+----------------+
| root@localhost |
+----------------+
1 row in set (0.00 sec)

mysql> create database mytest;
Query OK, 1 row affected (0.00 sec)

mysql> grant all privileges on mytest.* to 'albert'@'192.168.122.%' identified by 'password';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.01 sec)

接下来使用albert用户连接到数据库,查看数据库以及表是否授权完成。具体如下所示:

mysql> select user();
+----------------------+
| user()               |
+----------------------+
| [email protected] |
+----------------------+
1 row in set (0.00 sec)

mysql> show databases;
+-----------------------+
| Database              |
+-----------------------+
| information_schema    |
| Chinook               |
| mytest                |
| test                  |
| test1                 |
| test_sqlalchemy_orm   |
| test_sqlalchemy_orm_1 |
+-----------------------+
7 rows in set (0.00 sec)

mysql> use mytest;
Database changed
mysql> show tables;
Empty set (0.01 sec)

接下来就可以通过SQLAlchemy Core模式创建所需要的表了。

2.3.1. SQLAlchemy Core模式创建数据表

具体代码如下所示:

from sqlalchemy import (
    create_engine,
    Table,
    MetaData,
    Column,
    Integer,
    String,
    Boolean
)
# from datetime import datetime


"""
创建一个数据库表:其中包含username, age, sex字段
"""

engine = create_engine('mysql+pymysql://albert:[email protected]/mytest')
conn = engine.connect()

metadata = MetaData(bind=conn)

persons = Table(
    'persons', metadata,
    Column('id', Integer(), primary_key=True, autoincrement=True),
    Column('username', String(55), nullable=False),
    Column('age', Integer(), nullable=False),
    Column('sex', Boolean(), default=1, nullable=False)
)

# 创建上面的数据库表
try:
    metadata.create_all(engine)
except Exception as e:
    print(e)

# 向表中插入几条数据
persons_insert_meth = persons.insert()

persons_insert_values = [
    {
        'username': 'zhangsan',
        'age': 22,
        'sex': 1
    },
    {
        'username': 'lisi',
        'age': 20,
        'sex': 0
    },
    {
        'username': 'wangwu',
        'age': 21,
        'sex': 1
    },
    {
        'username': 'zhaoliu',
        'age': 20,
        'sex': 0
    }
]

insert_ret_val = conn.execute(persons_insert_meth, persons_insert_values)
print(insert_ret_val)

# 关闭到数据库的连接
conn.close()
engine.dispose()

上述就完成了SQLAlchemy Core模式的数据库表创建以及数据插入,但是并不完美,因为重复执行的时候,会将上述的数据重复插入。执行结果如下所示:

/usr/bin/python3.8 ./sqlalchemy_core.py
<sqlalchemy.engine.cursor.LegacyCursorResult object at 0x7f72ff7a35b0>

Process finished with exit code 0

通过albet用户连接到数据库,并查看表是否被成功创建以及表中是否插入了数据,具体如下所示:

mysql> select user();
+----------------------+
| user()               |
+----------------------+
| [email protected] |
+----------------------+
1 row in set (0.01 sec)

mysql> show tables;
+------------------+
| Tables_in_mytest |
+------------------+
| persons          |
+------------------+
1 row in set (0.00 sec)

mysql> rehash;
mysql> select * from persons;
+----+----------+-----+-----+
| id | username | age | sex |
+----+----------+-----+-----+
|  1 | zhangsan |  22 |   1 |
|  2 | lisi     |  20 |   0 |
|  3 | wangwu   |  21 |   1 |
|  4 | zhaoliu  |  20 |   0 |
+----+----------+-----+-----+
4 rows in set (0.00 sec)

从上述结果中可以看出,persons表已经被成功创建出来了,并且已经插入了数据。

2.3.2. SQLAlchemy ORM模式创建数据表

使用SQLAlchemy ORM模式创建数据表,并向其中插入数据的代码实现如下所示:

from sqlalchemy import (
    create_engine,
    Column,
    Integer,
    String,
    Boolean
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker


Base = declarative_base()


class Persons1(Base):
    __tablename__ = 'persons1'

    id = Column(Integer(), primary_key=True, autoincrement=True)
    username = Column(String(55), nullable=False)
    age = Column(Integer(), nullable=False)
    sex = Column(Boolean(), nullable=False)

    def __repr__(self):
        return "Persons1(username='{self.username}', " \
                "age={self.age}, " \
                "sex={self.sex})".format(self=self)


# 构建数据库连接
engine = create_engine('mysql+pymysql://albert:[email protected]/mytest')   # 需要安装pymysql包作为后端驱动

# 创建数据库表
Base.metadata.create_all(engine)

# 构建会话连接
Session = sessionmaker(engine)
session = Session()

# 向表中插入数据
p1 = Persons1(
    username='zhangsan',
    age=22,
    sex=1
)

p2 = Persons1(
    username='lisi',
    age=20,
    sex=0
)

p3 = Persons1(
    username='wangwu',
    age=21,
    sex=1
)

p4 = Persons1(
    username='zhaoliu',
    age=20,
    sex=0
)

var_lst = ['p' + str(i) for i in range(1, 5)]
lc_lst = locals()
try:
    for var in var_lst:
        print(lc_lst[var]
        session.add(lc_lst[var])
        session.commit()
except Exception as e:
    session.rollback()          # 执行失败则回滚插入操作
    print(e)


# 关闭数据库连接
session.close()
engine.dispose()

上述代码的执行结果如下所示:

/usr/bin/python3.8 ./sqlalchemy_orm.py
Persons1(username='zhangsan', age=22, sex=1)
Persons1(username='lisi', age=20, sex=0)
Persons1(username='wangwu', age=21, sex=1)
Persons1(username='zhaoliu', age=20, sex=0)

Process finished with exit code 0

上述代码并不完美,因为重复执行的时候,会将上述的数据重复插入。

以albert用户连接到数据库,查询数据表,具体如下所示:

mysql> select user();
+----------------------+
| user()               |
+----------------------+
| [email protected] |
+----------------------+
1 row in set (0.00 sec)

mysql> show tables;                              
+------------------+                             
| Tables_in_mytest |                             
+------------------+                             
| persons          |                             
| persons1         |                             
+------------------+                             
2 rows in set (0.00 sec) 

mysql> rehash;                                   
mysql> select * from persons1;
+----+----------+-----+-----+
| id | username | age | sex |
+----+----------+-----+-----+
|  1 | zhangsan |  22 |   1 |
|  2 | lisi     |  20 |   0 |
|  3 | wangwu   |  21 |   1 |
|  4 | zhaoliu  |  20 |   0 |
+----+----------+-----+-----+
4 rows in set (0.00 sec) 

上述就是用SQLAlchemy ORM的方式创建的数据表,并向表中插入了相关的数据。

3. 使用SQLAlchemy写出上述表的CRUD示例

因为上述代码中已经插入了相关数据,所以在上述代码的基础上,再增加查询、修改和删除的相关操作即可。由于需要操作已经存在的表,所以需要用到反射技术(reflect)来获取数据库中已经存在的表。

此处依然分别使用SQLAlchemy Core以及ORM两种方式操作。

3.1. 使用SQLAlchemy Core模式实现CRUD

该模式下的查询、修改和删除相关的代码实现,如下所示:

from sqlalchemy import (
    create_engine,
    select,
    update,
    delete,
    MetaData
)


engine = create_engine('mysql+pymysql://albert:[email protected]/mytest')
conn = engine.connect()

metadata = MetaData(bind=conn)

# reflect all tables in mytest database
metadata.reflect(bind=engine)
all_tables_dct = metadata.tables

persons = all_tables_dct['persons']

# query table
print('\n', '=.*' * 15, 'query all columns', '*.=' * 15)
query_stmt = select([persons])      # 查询所有字段
query_rp = conn.execute(query_stmt).fetchall()
column_lst = persons.columns.keys()
print(
    '{:2s} {:10s} {:3s} {:2s}'.format(*column_lst),
    '-' * 21,
    sep='\n',
    end='\n'
)
for row in query_rp:
    print('{:2d} {:10s} {:3d} {:2d}'.format(*row))

print('\n', '=.*' * 15, 'query specified columns', '*.=' * 15)
query_stmt_1 = select([persons.c.username, persons.c.age, persons.c.sex]).limit(2)
query_rp_1 = conn.execute(query_stmt_1)
print(
    '{:10s} {:3s} {:2s}'.format(*column_lst[1:]),
    '-' * 18,
    sep='\n',
    end='\n'
)
for row in query_rp_1:
    print('{:10s} {:3d} {:2d}'.format(*row))

print('\n', '=.*' * 15, 'query and filter', '*.=' * 15)
query_stmt_2 = select([persons.c.username, persons.c.age, persons.c.sex]).where(
    persons.c.age > 21
)
query_rp_2 = conn.execute(query_stmt_2).fetchall()
print(
    '{:10s} {:3s} {:2s}'.format(*column_lst[1:]),
    '-' * 18,
    sep='\n',
    end='\n'
)
for row in query_rp_2:
    print('{:10s} {:3d} {:2d}'.format(*row))

# update table
print('\n', '=.*' * 15, 'update table', '*.=' * 15)
update_stmt = update(persons).where(
    persons.c.username == 'lisi'
).values(age = persons.c.age + 4)       # 将用户名为lisi的人,年龄增加4岁,重复执行,会一直持续增加
update_rp = conn.execute(update_stmt)
print('{} rows changed'.format(update_rp.rowcount), end='\n\n')       # 打印修改的行数
s_in_update_stmt = select([persons])
s_in_update_rp = conn.execute(s_in_update_stmt)
column_lst = persons.columns.keys()
print(
    '{:2s} {:10s} {:3s} {:2s}'.format(*column_lst),
    '-' * 21,
    sep='\n',
    end='\n'
)
for row in s_in_update_rp:
    print('{:2d} {:10s} {:3d} {:2d}'.format(*row))


# delete record from table
print('\n', '=.*' * 15, 'delete record', '*.=' * 15)
del_rec_stmt = delete(persons).where(
    persons.c.age > 22
)
del_rec_rp = conn.execute(del_rec_stmt)
print('{} rows changed'.format(update_rp.rowcount), end='\n\n')       # 打印修改的行数
s_in_del_stmt = select([persons])
s_in_del_rp = conn.execute(s_in_del_stmt)
column_lst = persons.columns.keys()
print(
    '{:2s} {:10s} {:3s} {:2s}'.format(*column_lst),
    '-' * 21,
    sep='\n',
    end='\n'
)
for row in s_in_del_rp:
    print('{:2d} {:10s} {:3d} {:2d}'.format(*row))

上述代码就是对于已经存在的数据表进行的查询、修改和删除操作。上述代码的执行结果如下所示:

/usr/bin/python3.8 /home/albertqee/PyCharm-Project/Python-16th-week/homework/sqlalchemy_core_CRUD.py

 =.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.* query all columns *.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=
id username   age sex
---------------------
 1 zhangsan    22  1
 2 lisi        32  0
 3 wangwu      21  1
 4 zhaoliu     20  0

 =.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.* query specified columns *.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=
username   age sex
------------------
zhangsan    22  1
lisi        32  0

 =.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.* query and filter *.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=
username   age sex
------------------
zhangsan    22  1
lisi        32  0

 =.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.* update table *.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=
1 rows changed

id username   age sex
---------------------
 1 zhangsan    22  1
 2 lisi        36  0
 3 wangwu      21  1
 4 zhaoliu     20  0

 =.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.*=.* delete record *.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=*.=
1 rows changed

id username   age sex
---------------------
 1 zhangsan    22  1
 3 wangwu      21  1
 4 zhaoliu     20  0

Process finished with exit code 0

3.2. 使用SQLAlchemy ORM模式实现CRUD

这种模式下的查询、修改和删除的代码实现如下所示:

from sqlalchemy import (
    create_engine
)
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.automap import automap_base


engine = create_engine('mysql+pymysql://albert:[email protected]/mytest')
Session = sessionmaker(bind=engine)
session = Session()

AutoMapBase = automap_base()
AutoMapBase.prepare(engine, reflect=True)

# 提取已经存在的表的实力对象映射
persons1 = AutoMapBase.classes.persons1

# query table
print('\n', '=.*' * 5, 'query all columns', '*.=' * 5)
query_res_rp = session.query(persons1)
# print(persons1.__table__.columns.keys())        # 获取字段名称
columns = persons1.__table__.columns.keys()
print(
    '{:2s} {:10s} {:3s} {:3s}'.format(*columns),
    '-' * 21,
    sep='\n'
)
for row in query_res_rp:        # type(row) -> 
    print(
        '{:2d} {:10s} {:3d} {:3d}'.format(
            row.id,
            row.username,
            row.age,
            row.sex
        )
    )

#
print('\n', '=.*' * 5, 'query with condition', '*.=' * 5)
query_res_rp_1 = session.query(
    persons1.username,
    persons1.age,
    persons1.sex
).where(persons1.age == 20)  # filter, filter_by这两个方法也可以

columns = persons1.__table__.columns.keys()
print(
    '{:10s} {:3s} {:3s}'.format(*columns),
    '-' * 18,
    sep='\n'
)
for row in query_res_rp_1:
    print(
        '{:10s} {:3d} {:3d}'.format(*row)
    )

# update table
print('\n', '=.*' * 5, 'update table', '*.=' * 5)
try:
    update_query = session.query(persons1)
    lisi = update_query.filter(persons1.username == 'lisi').one()
    lisi.age += 4
    '''
    或者通过这种方式更新:
    lisi.update({persons1.age: persons1.age + 4})
    '''
    session.commit()
    print(
        '{:2s} {:10s} {:3s} {:3s}'.format(*columns),
        '-' * 21,
        sep='\n'
    )
    for row in query_res_rp:        # type(row) -> 
        print(
            '{:2d} {:10s} {:3d} {:3d}'.format(
                row.id,
                row.username,
                row.age,
                row.sex
            )
        )
except Exception as e:
    session.rollback()
    print(e)

# delete record
print('\n', '=.*' * 5, 'delete record', '*.=' * 5)
del_query = session.query(persons1)
try:
    del_rec = del_query.filter(persons1.username == 'lisi').one()
    session.delete(del_rec)
    session.commit()
except Exception as e:
    session.rollback()
    print(e)

print(
    '{:2s} {:10s} {:3s} {:3s}'.format(*columns),
    '-' * 21,
    sep='\n'
)
for row in query_res_rp:        # type(row) -> 
    print(
        '{:2d} {:10s} {:3d} {:3d}'.format(
            row.id,
            row.username,
            row.age,
            row.sex
        )
    )

上述代码也是通过反射技术获取到数据库中的所有表,如果要获取某一张表,则可以使用Talbe('table_name', metadata, autoload=True, autoload_with=engine)的方式来装载已经存在的表。

上述代码的执行结果如下所示:

/usr/bin/python3.8 ./sqlalchemy_orm_CRUD.py

 =.*=.*=.*=.*=.* query all columns *.=*.=*.=*.=*.=
id username   age sex
---------------------
 1 zhangsan    22   1
 2 lisi        20   0
 3 wangwu      21   1
 4 zhaoliu     20   0

 =.*=.*=.*=.*=.* query with condition *.=*.=*.=*.=*.=
id         username age
------------------
lisi        20   0
zhaoliu     20   0

 =.*=.*=.*=.*=.* update table *.=*.=*.=*.=*.=
id username   age sex
---------------------
 1 zhangsan    22   1
 2 lisi        24   0
 3 wangwu      21   1
 4 zhaoliu     20   0

 =.*=.*=.*=.*=.* delete record *.=*.=*.=*.=*.=
id username   age sex
---------------------
 1 zhangsan    22   1
 3 wangwu      21   1
 4 zhaoliu     20   0

Process finished with exit code 0

4. 实现查询分页

如果只想要查询结果中指定数据量的行被显示出来,可以在查询语句中使用LIMIT子句进行分页操作,此时就无需提取整个查询结果集了。

LIMIT子句实现分页查询的语法结构如下所是:

SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset

上述可以拆分成两种形式:

  • SELECT * FROM table LIMIT [offset,] rows
    

    这种形式里面,将偏移量指定在行数的前面,并将两者用逗号分隔。具体示例如下所示:

    mysql> select * from Album limit 2, 10;
    +---------+--------------------------------+----------+
    | AlbumId | Title                          | ArtistId |
    +---------+--------------------------------+----------+
    |       3 | Restless and Wild              |        2 |
    |       4 | Let There Be Rock              |        1 |
    |       5 | Big Ones                       |        3 |
    |       6 | Jagged Little Pill             |        4 |
    |       7 | Facelift                       |        5 |
    |       8 | Warner 25 Anos                 |        6 |
    |       9 | Plays Metallica By Four Cellos |        7 |
    |      10 | Audioslave                     |        8 |
    |      11 | Out Of Exile                   |        8 |
    |      12 | BackBeat Soundtrack            |        9 |
    +---------+--------------------------------+----------+
    10 rows in set (0.00 sec)
    
  • SELECT * FROM table LIMIT rows OFFSET offset
    

    这种方式将偏移量放在了行数的后面。具体示例如下所是:

    mysql> select * from Album limit 10 offset 2;
    +---------+--------------------------------+----------+
    | AlbumId | Title                          | ArtistId |
    +---------+--------------------------------+----------+
    |       3 | Restless and Wild              |        2 |
    |       4 | Let There Be Rock              |        1 |
    |       5 | Big Ones                       |        3 |
    |       6 | Jagged Little Pill             |        4 |
    |       7 | Facelift                       |        5 |
    |       8 | Warner 25 Anos                 |        6 |
    |       9 | Plays Metallica By Four Cellos |        7 |
    |      10 | Audioslave                     |        8 |
    |      11 | Out Of Exile                   |        8 |
    |      12 | BackBeat Soundtrack            |        9 |
    +---------+--------------------------------+----------+
    10 rows in set (0.01 sec)
    

上面的两种使用形式都可以实现分页查询的效果。

5. References

[1]. MySQL 事务
[2]. MySQL Transaction
[3]. MySQL Procedure
[4]. 存储过程(procedure)和函数(function)
[5]. 5.5 Creating and Calling Stored Procedures
[6]. 23.3 Using Triggers
[7]. MySQL分页查询方法及优化
[8]. 8.2.1.17 LIMIT Query Optimization
[9]. MySQL and MariaDB

你可能感兴趣的:(Python,数据库,mysql,python)