最近想要学习SQLAlchemy, 发现网上的中文文档大多是机翻的, 读起来特别变扭, 因此对照着最新的英文文档梳理了一遍, 写下来记录一下

目前SQLAlchemy的版本为1.4.x, 风格处于1.x过渡到2.0的时代. 为了尽量让这篇文章的兼容之后的版本, 本文将讲述1.x和2.0两种风格的接口(主要是查询的接口)

其实在2.0风格中, 主要受到影响的是ORM的查询方式, 详情见文档: 2.0 Migration - ORM Usage

安装

  1. pip install sqlalchemy

检测sqlalchemy版本:

  1. >>>import sqlalchemy
  2. >>>sqlalchemy.__version__
  3. '1.4.27'

使用步骤

一般来说SQLAlchemy的使用方式有两种: CoreORM

两种有什么不同呢?

  1. ORM是构建在Core之上的
  2. Core更加底层, 可以执行直接执行SQL语句
  3. ORM类似于Django的ORM, 由于sqlalchemy提供了一套接口, 所以不需要我们直接写SQL语句 (1.x版本)
  4. 至于要用哪个, 等到你用到时, 你会知道的

组件依赖关系图:

Core

一般来说, 使用步骤如下:

  1. 配置数据库连接
  2. 建立连接
  3. 创建表
  4. 执行SQL语句, 按需开启事件是否自动提交
  5. 拿到返回数据, 执行其他代码

数据库的连接的格式

我们在创建引擎(连接)时, 需要指定数据库的URL, URL格式, 见: Engine Configuration, 总的来说, 格式就是: dialect[+driver]://user:password@host/dbname[?key=value..]

  • dialect 数据库名称(方言): 如mysql
  • driver 连接数据库的库: 如: pymysql
  • user 用户名
  • password 密码
  • host 地址
  • dbname 数据库名称
  • key=value 指的是给数据库的参数

如下面的URL:

  1. mysql+pymysql://root:passwd@127.0.0.1:3306/test_db?charset=utf8

建立连接

调用sqlalchemy.create_engine方法, 为了兼容2.0风格的接口, 可以加上future参数. 至于什么是2.0风格的接口, 可以看看官方文档: 2.0 style

create_engine有几个参数需要我们注意:

  • url 即数据库url, 其格式见上文: 数据库的连接的格式
  • echo参数为True时, 将会将engine的SQL记录到日志中 ( 默认输出到标准输出)
  • echo_poolTrue时,会将连接池的记录信息输出
  • future 使用2.0样式EngineConnection API

更多参数见官方文档: sqlalchemy.create_engine

例子

  1. from sqlalchemy import create_engine
  2. # 兼容2.0的写法
  3. # 返回对象不一样
  4. engine1 = create_engine("sqlite+pysqlite:///:memory:", echo=True, future=True)
  5. print(type(engine1))
  6. # <class 'sqlalchemy.future.engine.Engine'>
  7. engine2 = create_engine("sqlite+pysqlite:///:memory:", echo=True)
  8. print(type(engine2))
  9. # <class 'sqlalchemy.engine.base.Engine'>

注意, 由于sqlalchemy使用lazy initialization的策略连接数据库, 故此时还未真正地连接上数据库

创建表

我们想要让数据库创建一个表, 需要利用MetaData对象, 关于一些常用的MetaData方法, 见: MetaData

除了要MetaData对象外, 我们还需要Table对象, 用于定义一个表的结构

Table的一般使用

  1. mytable = Table("mytable", metadata,
  2. Column('mytable_id', Integer, primary_key=True),
  3. Column('value', String(50))
  4. )

Table的参数:

  • name 表名称
  • metadata 该表所属的MetaData对象
  • 其他参数: 通过Column指定一列数据, 格式见: Column定义

例子:

  1. from sqlalchemy import MetaData, Table, Column, Integer, String, ForeignKey
  2. from sqlalchemy import create_engine, text
  3. # 数据库配置
  4. DATABASE_CONFIG = {
  5. "username": "root",
  6. "password": "123456",
  7. "host": "localhost",
  8. "database": "test"
  9. }
  10. # 连接mysql
  11. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
  12. echo=True, future=True)
  13. metadata_obj = MetaData()
  14. user_table = Table(
  15. "user_account",
  16. metadata_obj,
  17. Column('id', Integer, primary_key=True),
  18. Column("username", String(30))) # String也可以不实例化
  19. # 第二个表
  20. address_table = Table(
  21. "address",
  22. metadata_obj,
  23. Column("id", Integer, primary_key=True),
  24. # 定义外键
  25. Column("uid", ForeignKey("user_account.id"), nullable=False),
  26. Column('email_address', String(32), nullable=False)
  27. )
  28. # 相当于执行 CREATE TABLE 语句
  29. metadata_obj.create_all(engine)
  30. """
  31. -- 相当于:
  32. CREATE TABLE user_account (
  33. id INTEGER NOT NULL AUTO_INCREMENT,
  34. username VARCHAR(30),
  35. PRIMARY KEY (id)
  36. );
  37. CREATE TABLE address (
  38. id INTEGER NOT NULL AUTO_INCREMENT,
  39. uid INTEGER NOT NULL,
  40. email_address VARCHAR(32) NOT NULL,
  41. PRIMARY KEY (id),
  42. FOREIGN KEY(uid) REFERENCES user_account (id)
  43. )
  44. """

create_all方法, 默认会在创建表之间检测一下表是否存在, 不存在时才创建.

Table的一些属性

  1. # ---------- 访问所有列
  2. # .c => Column
  3. print(user_table.c.keys())
  4. # ['id', 'username']
  5. # ---------- 访问某一列
  6. print(repr(user_table.c.username))
  7. # Column('username', String(length=30), table=<user>)
  8. # ---------- 返回主键
  9. print(user_table.primary_key)
  10. # 隐式生成
  11. # PrimaryKeyConstraint(Column('id', Integer(), table=<user>, primary_key=True, nullable=False))

在事务中执行SQL

通常, 我们通过调用engine.connectengine.begin方法开始一个事件

sqlalchemy使用事务有两种风格commit as you goBegin once, 前者需要我们手动提交, 后者会自动提交

手动提交

engine.connect方法符合python的上下文管理协议, 会返回一个Connection对象, 该方法会在不手动提交的情况下回滚.举个例子:

  1. from sqlalchemy import create_engine
  2. from sqlalchemy import text
  3. # 数据库配置
  4. DATABASE_CONFIG = {
  5. "username": "root",
  6. "password": "123456",
  7. "host": "localhost",
  8. "database": "test"
  9. }
  10. # 连接mysql
  11. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
  12. echo=True, future=True)
  13. with engine.connect() as conn:
  14. # 执行
  15. result = conn.execute(text("select 'hello world'")) # text 可以使用SQL语句
  16. print(result.all())
  17. # conn.commit()
  18. # [('hello world',)]
  19. # 最后会ROLLBACK

上面的代码中, 相当于开启了事务, 由于最后没有调用commit方法, 所以会回滚.

自动提交

engine.begin方法也符合python的上下文管理协议, 只要执行时不报错就会自动提交, 报错时会回滚.

  1. from sqlalchemy import create_engine
  2. from sqlalchemy import text
  3. # 数据库配置
  4. DATABASE_CONFIG = {
  5. "username": "root",
  6. "password": "123456",
  7. "host": "localhost",
  8. "database": "test"
  9. }
  10. # 连接mysql
  11. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
  12. echo=True, future=True)
  13. with engine.begin() as conn:
  14. result = conn.execute(text("select 'hello world'"))
  15. print(result.all())
  16. # [('hello world',)]
  17. # COMMIT

绑定参数

上面在事务中执行SQL语句时, 我们用到了sqlalchemy.text, 可以直接定义文本SQL字符串

为了避免被SQL注入, 故在需要传入参数的场景中需要根据sqlalchemy的方式传入, 而不是直接拼接成字符串.

使用:y的格式定义参数, 且将值以字典的形式传给execute

  1. from sqlalchemy import create_engine
  2. from sqlalchemy import text
  3. # 数据库配置
  4. DATABASE_CONFIG = {
  5. "username": "root",
  6. "password": "123456",
  7. "host": "localhost",
  8. "database": "test"
  9. }
  10. # 连接mysql
  11. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
  12. echo=True, future=True)
  13. with engine.begin() as conn:
  14. result = conn.execute(text("select name from userinfo where name like :y"), {"y": "lcz%"})
  15. print(result.all())
  16. # [('lczmx',)]
  17. # COMMIT

多个参数时, 可以这样

  1. with engine.connect() as conn:
  2. conn.execute(
  3. text("INSERT INTO userinfo (id, name) VALUES (:x, :y)"),
  4. [{"x": 1, "y": "lcmx"}, {"x": 2, "y": "xxx"}])
  5. conn.commit()

这种方式也可以

  1. stmt = text("SELECT x, y FROM some_table WHERE y > :y ORDER BY x, y").bindparams(y=6)
  2. with engine.connect() as conn:
  3. conn.execute(stmt)
  4. conn.commit()

增删改查

处理使用text直接执行SQL外, 你还可以使用其他语法增删改查数据

假如表结构如下:

  1. $show create table address;
  2. +---------+-----------------------------------------+
  3. | Table | Create Table |
  4. +---------+-----------------------------------------+
  5. | address | CREATE TABLE `address` (
  6. `id` int NOT NULL AUTO_INCREMENT,
  7. `uid` int NOT NULL,
  8. `email_address` varchar(32) NOT NULL,
  9. PRIMARY KEY (`id`),
  10. KEY `uid` (`uid`),
  11. CONSTRAINT `address_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user_account` (`id`)
  12. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=gbk |
  13. +---------+------------------------------------------+
  14. $show create table user_account;
  15. +--------------+------------------------------------+
  16. | Table | Create Table |
  17. +--------------+------------------------------------+
  18. | user_account | CREATE TABLE `user_account` (
  19. `id` int NOT NULL AUTO_INCREMENT,
  20. `username` varchar(30) DEFAULT NULL,
  21. PRIMARY KEY (`id`)
  22. ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=gbk |
  23. +--------------+-------------------------------------+
  24. 1 row in set (0.00 sec)

插入数据

使用insert(...).values(...)形式为数据库插入数据

  1. from sqlalchemy import MetaData, Table, Column, Integer, String, ForeignKey
  2. from sqlalchemy import create_engine, insert
  3. # 数据库配置
  4. DATABASE_CONFIG = {
  5. "username": "root",
  6. "password": "123456",
  7. "host": "localhost",
  8. "database": "test"
  9. }
  10. # 连接mysql
  11. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}?charset=utf8".format(**DATABASE_CONFIG),
  12. echo=True, future=True)
  13. metadata_obj = MetaData()
  14. user_table = Table(
  15. "user_account",
  16. metadata_obj,
  17. Column('id', Integer, primary_key=True),
  18. Column("username", String(30))) # String也可以不实例化
  19. # 第二个表
  20. address_table = Table(
  21. "address",
  22. metadata_obj,
  23. Column("id", Integer, primary_key=True),
  24. Column("uid", ForeignKey("user_account.id"), nullable=False),
  25. Column('email_address', String(32), nullable=False)
  26. )
  27. metadata_obj.create_all(bind=engine)
  28. with engine.connect() as conn:
  29. # 插入一条普通数据
  30. conn.execute(insert(user_table).values(id=1, username="lczmx"))
  31. # 插入外键等数据
  32. conn.execute(insert(address_table).values(uid=1, email_address="lczmx@foxmail.com"))
  33. # 自动生成value, 不需要我们手动指定
  34. conn.execute(insert(user_table),
  35. [{"username": "张三"},
  36. {"username": "李四"},
  37. {"username": "王五"},
  38. {"username": "赵六"},
  39. ])
  40. conn.commit()

SQLAlchemy还提供了更复杂的用法, 见: Inserting Rows with Core

注意: 插入数据没有返回值

删除数据

使用delete(...).where(...)的形式删除数据

目前的表数据:

  1. select u.id as uid, u.username, a.id as aid, a.email_address as email_address
  2. from user_account as u
  3. left join address as a on u.id=a.uid;
  1. +-----+----------+------+-------------------+
  2. | uid | username | aid | email_address |
  3. +-----+----------+------+-------------------+
  4. | 1 | lczmx | 1 | lczmx@foxmail.com |
  5. | 2 | 张三 | NULL | NULL |
  6. | 3 | 李四 | NULL | NULL |
  7. | 4 | 王五 | NULL | NULL |
  8. | 5 | 赵六 | NULL | NULL |
  9. +-----+----------+------+-------------------+

例子:

  1. from sqlalchemy import MetaData, Table, Column, Integer, String, ForeignKey
  2. from sqlalchemy import create_engine, delete
  3. # 数据库配置
  4. DATABASE_CONFIG = {
  5. "username": "root",
  6. "password": "123456",
  7. "host": "localhost",
  8. "database": "test"
  9. }
  10. # 连接mysql
  11. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}?charset=utf8".format(**DATABASE_CONFIG),
  12. echo=True, future=True)
  13. metadata_obj = MetaData()
  14. user_table = Table(
  15. "user_account",
  16. metadata_obj,
  17. Column('id', Integer, primary_key=True),
  18. Column("username", String(30))) # String也可以不实例化
  19. # 第二个表
  20. address_table = Table(
  21. "address",
  22. metadata_obj,
  23. Column("id", Integer, primary_key=True),
  24. Column("uid", ForeignKey("user_account.id"), nullable=False),
  25. Column('email_address', String(32), nullable=False)
  26. )
  27. metadata_obj.create_all(bind=engine)
  28. with engine.connect() as conn:
  29. # 一般删除
  30. # user_table.c 获取的是 列数据
  31. result1 = conn.execute(delete(user_table).where(user_table.c.id == 3))
  32. print(f"受影响行数: {result1.rowcount}") # 受影响行数: 1
  33. # and 删除
  34. result2 = conn.execute(delete(user_table).where(user_table.c.username == "张三", user_table.c.id == 2))
  35. print(f"受影响行数: {result2.rowcount}") # 受影响行数: 1
  36. conn.commit()

.rowcount属性获取受影响的行数

更多见: The delete() SQL Expression Construct

更新数据

使用update(...).where(...).values(...)的形式更新数据

  1. select u.id as uid, u.username, a.id as aid, a.email_address as email_address
  2. from user_account as u
  3. left join address as a on u.id=a.uid;
  1. +-----+----------+------+-------------------+
  2. | uid | username | aid | email_address |
  3. +-----+----------+------+-------------------+
  4. | 1 | lczmx | 1 | lczmx@foxmail.com |
  5. | 2 | 张三 | NULL | NULL |
  6. | 3 | 李四 | NULL | NULL |
  7. | 4 | 王五 | NULL | NULL |
  8. | 5 | 赵六 | NULL | NULL |
  9. +-----+----------+------+-------------------+

例子:

  1. from sqlalchemy import MetaData, Table, Column, Integer, String, ForeignKey
  2. from sqlalchemy import create_engine, update, bindparam, select
  3. # 数据库配置
  4. DATABASE_CONFIG = {
  5. "username": "root",
  6. "password": "123456",
  7. "host": "localhost",
  8. "database": "test"
  9. }
  10. # 连接mysql
  11. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}?charset=utf8".
  12. format(**DATABASE_CONFIG), echo=True, future=True)
  13. metadata_obj = MetaData()
  14. user_table = Table(
  15. "user_account",
  16. metadata_obj,
  17. Column('id', Integer, primary_key=True),
  18. Column("username", String(30))) # String也可以不实例化
  19. # 第二个表
  20. address_table = Table(
  21. "address",
  22. metadata_obj,
  23. Column("id", Integer, primary_key=True),
  24. Column("uid", ForeignKey("user_account.id"), nullable=False),
  25. Column('email_address', String(32), nullable=False)
  26. )
  27. metadata_obj.create_all(bind=engine)
  28. with engine.connect() as conn:
  29. # 一般更新
  30. result1 = conn.execute(update(user_table).where(
  31. user_table.c.username == "王五").values(username="王老五"))
  32. print(f"受影响行数: {result1.rowcount}") # 受影响行数: 1
  33. # 更新数据 加上 原来的数据
  34. result2 = conn.execute(
  35. update(user_table).where(user_table.c.username == "赵六").values(
  36. username=user_table.c.username + "一号"))
  37. print(f"受影响行数: {result2.rowcount}") # 受影响行数: 1
  38. # 以字典的形式, 替换更新多个值
  39. result3 = conn.execute(
  40. update(user_table).where(user_table.c.username == bindparam('old_name')).values(
  41. username=bindparam('new_name')),
  42. [
  43. {"old_name": "张三", "new_name": "新张三"},
  44. {"old_name": "李四", "new_name": "新李四"},
  45. ]
  46. )
  47. print(f"受影响行数: {result3.rowcount}") # 受影响行数: 2
  48. # 以 子查询 的方式 更新数据
  49. scalar_subq = (
  50. select(address_table.c.email_address).
  51. where(address_table.c.uid == user_table.c.id).
  52. order_by(address_table.c.id).
  53. limit(1).
  54. scalar_subquery()
  55. )
  56. # 将email_address的值 赋给 username
  57. update(user_table).values(username=scalar_subq)
  58. """
  59. -- 以上查询, 相当于:
  60. UPDATE user_account SET username=(SELECT address.email_address
  61. FROM address
  62. WHERE address.uid = user_account.id ORDER BY address.id
  63. LIMIT :param_1)
  64. """
  65. conn.commit()

修改后的结果:

  1. +-----+----------+------+-------------------+
  2. | uid | username | aid | email_address |
  3. +-----+----------+------+-------------------+
  4. | 1 | lczmx | 1 | lczmx@foxmail.com |
  5. | 2 | 新张三 | NULL | NULL |
  6. | 3 | 新李四 | NULL | NULL |
  7. | 4 | 王老五 | NULL | NULL |
  8. | 5 | 赵六一号 | NULL | NULL |
  9. +-----+----------+------+-------------------+

更多见: Updating and Deleting Rows with Core

查询数据

由于2.0的查询方式, Core和ORM都可以使用, 所以放在一起, 见下文: 查询数据详解

处理查询返回的数据

我们执行conn.execute方法的结果为: CursorResult对象

其本质上是继承与Result对象, 其使用方式见: Result

例子:

假如查询的表:

  1. mysql> select * from user_account;
  2. +----+----------+
  3. | id | username |
  4. +----+----------+
  5. | 9 | lczmx |
  6. | 10 | jack |
  7. | 11 | tom |
  8. | 12 | mike |
  9. +----+----------+
  10. 4 rows in set (0.00 sec)
  11. mysql>

利用SQLAlchemy获取数据:

  1. from sqlalchemy import MetaData, Table, Column, Integer, String, ForeignKey
  2. from sqlalchemy import create_engine, text
  3. # 数据库配置
  4. DATABASE_CONFIG = {
  5. "username": "root",
  6. "password": "123456",
  7. "host": "localhost",
  8. "database": "test"
  9. }
  10. # 连接mysql
  11. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
  12. echo=True, future=True)
  13. with engine.connect() as conn:
  14. # 执行
  15. result = conn.execute(text("select * from user_account;"))
  16. for row in result.all():
  17. # 使用f-strings 格式化字符串
  18. print(f"id: {row.id:3}, username: {row.username:20}")
  19. # 打印的结果:
  20. """
  21. id: 9, username: lczmx
  22. id: 10, username: jack
  23. id: 11, username: tom
  24. id: 12, username: mike
  25. """
  26. conn.commit()

ORM

和Core一样, ORM也有一定的使用步骤:

  1. 配置数据库连接, 见上文: 数据库的连接的格式
  2. 创建会话
  3. 创建表
  4. 使用接口, 增删改查数据
  5. 拿到返回数据, 执行其他代码

在学习SQLAlcehmy的ORM之前, 建议先了解一些概念, 以免后面会混淆

  1. 会话 Session

    会话是SQLAlchemy ORM与数据库的交互对象

    它可以管理建立连接engine, 并为通过会话加载或与会话关联的对象提供标识映射 (identity map)

    在使用时与Connection非常相似, 你可以对比着使用

  2. Base

    通过sqlalchemy.orm.declarative_base创建

    作为定义表的基类, 内部有包含MetaData对象

    可以类似于Django一样定义表

SQLAlchemy中, session是一个连接池, 的由其管理, 因此, 假如我们需要操作数据库的话, 需要在session中拿到Connection(连接)

创建会话

SQLAlchemy提供了两种创建会话的方法:

  1. sqlalchemy.orm.Session
    1. from sqlalchemy import create_engine
    2. from sqlalchemy.orm import Session
    3. # 创建引擎
    4. engine = create_engine('postgresql://scott:tiger@localhost/')
    5. # 创建会话
    6. # 以下with可以简写成 with Session(engine) as session, session.begin():
    7. with Session(engine) as session:
    8. # 开启自动提交
    9. with session.begin():
    10. # add方法 会将some_object 保存到数据库
    11. # session.add(some_object)
    12. # session.add(some_other_object)
    13. pass
  2. sqlalchemy.orm.sessionmaker
    1. from sqlalchemy import create_engine
    2. from sqlalchemy.orm import sessionmaker
    3. # 创建引擎
    4. engine = create_engine('postgresql://scott:tiger@localhost/')
    5. # 创建session
    6. Session = sessionmaker(engine)
    7. # 一般使用
    8. with Session() as session:
    9. # session.add(some_object)
    10. # session.add(some_other_object)
    11. # 提交
    12. session.commit()
    13. # 自动提交
    14. with Session.begin() as session:
    15. # session.add(some_object)
    16. # session.add(some_other_object)
    17. pass

虽然有两种方法创建会话, 但我们一般使用sessionmaker创建会话

另外补充一下session的其它使用方式:

  1. from sqlalchemy import create_engine
  2. from sqlalchemy.orm import sessionmaker
  3. engine = create_engine('postgresql://scott:tiger@localhost/')
  4. Session = sessionmaker(engine)
  5. # 从连接指定到session
  6. with engine.connect() as connection:
  7. with Session(bind=connection) as session:
  8. # 一些操作
  9. pass

下面列出session的一些常用方法, 增删改查数据时要用到

方法 参数 描述
add instance 下次刷新操作时, 将 instance 保留到数据库中
delete instance 下次刷新操作时, 将instance从数据库中删除
begin subtransactions nested _subtrans 开始事务
rollback 回滚当前事务
commit 提交当前事务
close 关闭此Session
execute statement params execution_option bind_arguments 执行SQL表达式构造
query *entities **kwargs 返回Query对象, 可用于查询数据
refresh instance attribute_names with_for_update instance执行刷新操作

例子:

  1. from sqlalchemy import create_engine, text
  2. from sqlalchemy.orm import Session
  3. # 数据库配置
  4. DATABASE_CONFIG = {
  5. "username": "root",
  6. "password": "123456",
  7. "host": "localhost",
  8. "database": "test"
  9. }
  10. # 连接mysql
  11. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
  12. echo=True, future=True)
  13. stmt = text("SELECT id, name FROM userinfo WHERE id > :y").bindparams(y=1)
  14. with Session(engine) as session:
  15. result = session.execute(stmt)
  16. print(result.all())
  17. # [(2, 'name2'), (3, 'name2')]
  18. # ROLLBACK

在ORM中创建表

使用ORM时, 我们也需要MetaData, 不同的是, 我们是通过sqlalchemy.orm.registry构造的. 而且, 我们不需要像Core那样直接声明Table, 而是继承某个公共基类 (Base), 添加属性即可. 有两种方式定义基类.

方式一:

  1. from sqlalchemy.orm import registry
  2. mapper_registry = registry()
  3. print(mapper_registry.metadata) # MetaData对象
  4. # 公共基类
  5. Base = mapper_registry.generate_base()

方法二:

  1. from sqlalchemy.orm import declarative_base
  2. # 内部 return registry(...).generate_base(...)
  3. Base = declarative_base()

现在你可以像在Django ORM中一样, 定义表并在数据库中创建表, 每一个Column表示一列数据, 关于Column的写法, 见: Column定义

  1. from sqlalchemy import Column, String, Integer, create_engine, SMALLINT, Boolean, ForeignKey
  2. from sqlalchemy.orm import relationship, declarative_base, sessionmaker
  3. # 导入公共基类
  4. Base = declarative_base()
  5. # 数据库配置
  6. DATABASE_CONFIG = {
  7. "username": "root",
  8. "password": "123456",
  9. "host": "localhost",
  10. "database": "test"
  11. }
  12. # 连接mysql
  13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
  14. echo=True, future=True)
  15. class Student(Base):
  16. __tablename__ = "student"
  17. sid = Column("sid", Integer, primary_key=True)
  18. name = Column("name", String(32), nullable=False, index=True, comment="姓名")
  19. age = Column("age", SMALLINT, nullable=False, comment="年龄")
  20. gender = Column("gender", Boolean, nullable=False, comment="性别, True: 男, False: 女")
  21. class Course(Base):
  22. __tablename__ = "course"
  23. cid = Column("cid", Integer, primary_key=True)
  24. name = Column("name", String(10), nullable=False, comment="科目名")
  25. tid = Column("tid", ForeignKey("teacher.tid"), comment="课程教师")
  26. class Teacher(Base):
  27. __tablename__ = "teacher"
  28. tid = Column("tid", Integer, primary_key=True)
  29. name = Column("name", String(10), nullable=False, comment="教师名")
  30. class Score(Base):
  31. __tablename__ = "score"
  32. sid = Column("sid", Integer, primary_key=True)
  33. score = Column("score", SMALLINT, nullable=False, comment="成绩")
  34. student_id = Column("student_id", ForeignKey("student.sid"), comment="成绩所属学生")
  35. course_id = Column("course_id", ForeignKey("course.cid"), comment="成绩所属科目")
  36. Base.metadata.create_all(bind=engine)
  37. """
  38. -- 对于sql
  39. CREATE TABLE student (
  40. sid INTEGER NOT NULL AUTO_INCREMENT,
  41. name VARCHAR(32) NOT NULL COMMENT '姓名',
  42. age SMALLINT NOT NULL COMMENT '年龄',
  43. gender BOOL NOT NULL COMMENT '性别, True: 男, False: 女',
  44. PRIMARY KEY (sid)
  45. )
  46. CREATE TABLE teacher (
  47. tid INTEGER NOT NULL AUTO_INCREMENT,
  48. name VARCHAR(10) NOT NULL COMMENT '教师名',
  49. PRIMARY KEY (tid)
  50. )
  51. CREATE TABLE course (
  52. cid INTEGER NOT NULL AUTO_INCREMENT,
  53. name VARCHAR(10) NOT NULL COMMENT '科目名',
  54. tid INTEGER COMMENT '课程教师',
  55. PRIMARY KEY (cid),
  56. FOREIGN KEY(tid) REFERENCES teacher (tid)
  57. )
  58. CREATE TABLE score (
  59. sid INTEGER NOT NULL AUTO_INCREMENT,
  60. score SMALLINT NOT NULL COMMENT '成绩',
  61. student_id INTEGER COMMENT '成绩所属学生',
  62. course_id INTEGER COMMENT '成绩所属科目',
  63. PRIMARY KEY (sid),
  64. FOREIGN KEY(student_id) REFERENCES student (sid),
  65. FOREIGN KEY(course_id) REFERENCES course (cid)
  66. )
  67. """

Base.metadataMetaData对象, 常用的MetaData方法见: MetaData

注: 你通过Student.__table__属性可以查看Table, 也可以通过Student.name访问某一列

你也可以通过__init__显示定义某些列

增删改查数据

插入数据

接上文 "在ORM中创建表" 中的表

1.x的接口与2.0的接口一样, 都是调用session.add(instance)方法添加到数据库 (add方法下次刷新操作时, 将instance保存到数据库)

注意: 自动生成的数据, 在未插入到数据库之前, 都为None, 如: 自动生成的主键

你也可以调用add_all(instance1, instance2, ...)方法, 区别只是插入一条和多条数据而已

  1. from sqlalchemy import Column, String, Integer, create_engine, SMALLINT, Boolean, ForeignKey
  2. from sqlalchemy.orm import relationship, declarative_base, sessionmaker
  3. from sqlalchemy.orm import Session
  4. from typing import Any
  5. # 导入公共基类
  6. Base = declarative_base()
  7. # 数据库配置
  8. DATABASE_CONFIG = {
  9. "username": "root",
  10. "password": "123456",
  11. "host": "localhost",
  12. "database": "test"
  13. }
  14. # 连接mysql
  15. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
  16. echo=True, future=True)
  17. class Student(Base):
  18. __tablename__ = "student"
  19. sid = Column("sid", Integer, primary_key=True)
  20. name = Column("name", String(32), nullable=False, index=True, comment="姓名")
  21. age = Column("age", SMALLINT, nullable=False, comment="年龄")
  22. gender = Column("gender", Boolean, nullable=False, comment="性别, True: 男, False: 女")
  23. class Course(Base):
  24. __tablename__ = "course"
  25. cid = Column("cid", Integer, primary_key=True)
  26. name = Column("name", String(10), nullable=False, comment="科目名")
  27. tid = Column("tid", ForeignKey("teacher.tid"), comment="课程教师")
  28. class Teacher(Base):
  29. __tablename__ = "teacher"
  30. tid = Column("tid", Integer, primary_key=True)
  31. name = Column("name", String(10), nullable=False, comment="教师名")
  32. class Score(Base):
  33. __tablename__ = "score"
  34. sid = Column("sid", Integer, primary_key=True)
  35. score = Column("score", SMALLINT, nullable=False, comment="成绩")
  36. student_id = Column("student_id", ForeignKey("student.sid"), comment="成绩所属学生")
  37. course_id = Column("course_id", ForeignKey("course.cid"), comment="成绩所属科目")
  38. Base.metadata.create_all(bind=engine)
  39. SessionLocal = sessionmaker(bind=engine)
  40. # 一般将 添加到数据库 封装成一个函数
  41. def create_data(db: Session, target_cls: Any, **kwargs):
  42. try:
  43. cls_obj = target_cls(**kwargs)
  44. # 添加一个
  45. db.add(cls_obj)
  46. # 添加多个:
  47. # db.add_all([obj1, obj2, ...])
  48. db.commit()
  49. # 手动将 数据 刷新到数据库
  50. db.refresh(cls_obj)
  51. return cls_obj
  52. except Exception as e:
  53. # 别忘记发生错误时回滚
  54. db.rollback()
  55. raise e
  56. session = SessionLocal()
  57. # -------------- 创建学生数据
  58. student = create_data(session, Student, sid=1, name="张三", age=22, gender=True)
  59. # -------------- 创建教师数据
  60. teacher = create_data(session, Teacher, tid=1, name="语文老师")
  61. # -------------- 创建课程数据
  62. course = create_data(session, Course, cid=1, name="语文", tid=teacher.tid)
  63. # -------------- 创建成绩数据
  64. score = create_data(session, Score, sid=1, score=89, student_id=student.sid, course_id=course.cid)

注意: 自动生成主键时, 只有在刷新到数据库中后, 才能获取主键

总的来说, 插入数据代码一般为:

  1. # 1. 实例化一个表类
  2. db_city = CityTable(....)
  3. # 2. 调用session的add方法
  4. session.add(db_city)
  5. # 3. 调用session的commit方法 提交事务
  6. session.commit()
  7. # 4. 手动调用session的refresh方法 将数据刷新到数据库
  8. session.refresh(db_city)

删除数据

1.x的方法

主要步骤是先查询再删除, 一般形式为: session.query(...).filter(...).delete()

  1. from sqlalchemy import Column, Integer, create_engine, SMALLINT, ForeignKey
  2. from sqlalchemy.orm import declarative_base, sessionmaker
  3. # 导入公共基类
  4. Base = declarative_base()
  5. # 数据库配置
  6. DATABASE_CONFIG = {
  7. "username": "root",
  8. "password": "123456",
  9. "host": "localhost",
  10. "database": "test"
  11. }
  12. # 连接mysql
  13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
  14. echo=True, future=True)
  15. class Score(Base):
  16. __tablename__ = "score"
  17. sid = Column("sid", Integer, primary_key=True)
  18. score = Column("score", SMALLINT, nullable=False, comment="成绩")
  19. student_id = Column("student_id", ForeignKey("student.sid"), comment="成绩所属学生")
  20. course_id = Column("course_id", ForeignKey("course.cid"), comment="成绩所属科目")
  21. Base.metadata.create_all(bind=engine)
  22. SessionLocal = sessionmaker(bind=engine)
  23. with SessionLocal() as session:
  24. # 方法一: 调用 session.delete 方法
  25. s = session.query(Score).filter(Score.score == 59).first()
  26. session.delete(s)
  27. # 方法二: 查询后直接删除
  28. session.query(Score).filter(Score.score == 59).delete()
  29. session.commit()

2.0的方法

像Core一样删除数据, 即delte(...).where(...)

  1. from sqlalchemy import Column, Integer, create_engine, SMALLINT, ForeignKey
  2. from sqlalchemy.orm import declarative_base, sessionmaker
  3. from sqlalchemy import select, delete
  4. # 导入公共基类
  5. Base = declarative_base()
  6. # 数据库配置
  7. DATABASE_CONFIG = {
  8. "username": "root",
  9. "password": "123456",
  10. "host": "localhost",
  11. "database": "test"
  12. }
  13. # 连接mysql
  14. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
  15. echo=True, future=True)
  16. class Score(Base):
  17. __tablename__ = "score"
  18. sid = Column("sid", Integer, primary_key=True)
  19. score = Column("score", SMALLINT, nullable=False, comment="成绩")
  20. student_id = Column("student_id", ForeignKey("student.sid"), comment="成绩所属学生")
  21. course_id = Column("course_id", ForeignKey("course.cid"), comment="成绩所属科目")
  22. Base.metadata.create_all(bind=engine)
  23. SessionLocal = sessionmaker(bind=engine)
  24. with SessionLocal() as session:
  25. session.execute(
  26. delete(Score).where(Score.sid == 1)
  27. )
  28. session.commit()

修改数据

1.x的方法

主要步骤是先查询再更新, 即: session.query(...).filter(...).update(...)

  1. from sqlalchemy import Column, Integer, create_engine, SMALLINT, ForeignKey
  2. from sqlalchemy.orm import declarative_base, sessionmaker
  3. # 导入公共基类
  4. Base = declarative_base()
  5. # 数据库配置
  6. DATABASE_CONFIG = {
  7. "username": "root",
  8. "password": "123456",
  9. "host": "localhost",
  10. "database": "test"
  11. }
  12. # 连接mysql
  13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
  14. echo=True, future=True)
  15. class Score(Base):
  16. __tablename__ = "score"
  17. sid = Column("sid", Integer, primary_key=True)
  18. score = Column("score", SMALLINT, nullable=False, comment="成绩")
  19. student_id = Column("student_id", ForeignKey("student.sid"), comment="成绩所属学生")
  20. course_id = Column("course_id", ForeignKey("course.cid"), comment="成绩所属科目")
  21. Base.metadata.create_all(bind=engine)
  22. SessionLocal = sessionmaker(bind=engine)
  23. with SessionLocal() as session:
  24. row = session.query(Score).filter(Score.score == 59).update({"score": 60})
  25. print(f"修改的行数: {row}")
  26. session.commit()

2.0的方法

同样和Core一样, 使用update(...).where(...).values(...)的形式更新数据

  1. from sqlalchemy import Column, Integer, create_engine, SMALLINT, ForeignKey
  2. from sqlalchemy.orm import declarative_base, sessionmaker
  3. from sqlalchemy import update, bindparam
  4. # 导入公共基类
  5. Base = declarative_base()
  6. # 数据库配置
  7. DATABASE_CONFIG = {
  8. "username": "root",
  9. "password": "123456",
  10. "host": "localhost",
  11. "database": "test"
  12. }
  13. # 连接mysql
  14. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
  15. echo=True, future=True)
  16. class Score(Base):
  17. __tablename__ = "score"
  18. sid = Column("sid", Integer, primary_key=True)
  19. score = Column("score", SMALLINT, nullable=False, comment="成绩")
  20. student_id = Column("student_id", ForeignKey("student.sid"), comment="成绩所属学生")
  21. course_id = Column("course_id", ForeignKey("course.cid"), comment="成绩所属科目")
  22. Base.metadata.create_all(bind=engine)
  23. SessionLocal = sessionmaker(bind=engine)
  24. with SessionLocal() as session:
  25. # 一般更新
  26. result1 = session.execute(update(Score).where(Score.score == 59).values(score=60))
  27. print(f"受影响行数: {result1.rowcount}")
  28. # # 更新数据 加上 原来的数据
  29. result2 = session.execute(
  30. update(Score).where(Score.score == 59).values(score=Score.score + 1))
  31. print(f"受影响行数: {result2.rowcount}") # 受影响行数: 1
  32. # 以字典的形式, 替换更新多个值
  33. result3 = session.execute(
  34. update(Score).where(Score.score == bindparam('old_score')).values(score=bindparam('new_score')),
  35. [
  36. {"old_score": 59, "new_score": 60},
  37. ]
  38. )
  39. print(f"受影响行数: {result3.rowcount}")
  40. session.commit()

同样.rowcount属性获取受影响行数

查询数据

1.x的方法

在SQLAlchemy1.x查询方式中, 使用Query对象进行查询, 类似于Django ORM的管理器, 可以较为简单地查询数据

假如要查询的表如下:

  1. class User(Base):
  2. __tablename__ = "User" # 设置表名
  3. uid = Column(Integer, primary_key=True)
  4. username = Column(String(80), unique=True)
  5. email = Column(String(120), unique=True)
  6. tags = Column(String(120))
  7. def __repr__(self):
  8. return '<User %r>' % self.username

表的数据:

  1. mysql> select * from User;
  2. +-----+----------+-------------------+------+
  3. | uid | username | email | tags |
  4. +-----+----------+-------------------+------+
  5. | 1 | 张三 | zhangesan@xx.com | 热情 |
  6. | 2 | 李四 | lisi@xx.com | 热情 |
  7. | 3 | 王五 | wangwu@xx.com | 开朗 |
  8. | 4 | lczmx | lczmx@foxmail.com | 热情 |
  9. +-----+----------+-------------------+------+
  10. 4 rows in set (0.10 sec)
  11. mysql>

使用例子:

  1. from sqlalchemy import Column, Integer, create_engine, String
  2. from sqlalchemy.orm import declarative_base, sessionmaker
  3. from sqlalchemy import not_, or_, desc
  4. # 导入公共基类
  5. Base = declarative_base()
  6. # 数据库配置
  7. DATABASE_CONFIG = {
  8. "username": "root",
  9. "password": "123456",
  10. "host": "localhost",
  11. "database": "test"
  12. }
  13. # 连接mysql
  14. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
  15. echo=True, future=True)
  16. class User(Base):
  17. __tablename__ = "User" # 设置表名
  18. uid = Column(Integer, primary_key=True)
  19. username = Column(String(80), unique=True)
  20. email = Column(String(120), unique=True)
  21. tags = Column(String(120))
  22. def __repr__(self):
  23. return '<User %r>' % self.username
  24. Base.metadata.create_all(bind=engine)
  25. SessionLocal = sessionmaker(bind=engine)
  26. with SessionLocal() as session:
  27. # ------------------ 查询所有User数据
  28. session.query(User).all()
  29. """
  30. 对应SQL
  31. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  32. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  33. """
  34. # ------------------ 查询有多少条数据
  35. session.query(User).count()
  36. """
  37. 对应SQL
  38. SELECT count(*) AS count_1 FROM (SELECT `User`.uid AS `User_uid`,
  39. `User`.username AS `User_username`, `User`.email AS `User_email`,
  40. `User`.tags AS `User_tags` FROM `User`) AS anon_1
  41. """
  42. # ------------------ 查询第1条数据
  43. session.query(User).first()
  44. """
  45. 对应SQL
  46. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  47. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  48. LIMIT 1
  49. """
  50. # ------------------ 根据主键查询
  51. session.query(User).get(1)
  52. """
  53. 对应SQL
  54. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  55. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  56. WHERE `User`.uid = 1
  57. """
  58. # ------------------ 简单查询, 使用 关键字实参 的形式来设置字段名
  59. session.query(User).filter_by(uid=1).all()
  60. """
  61. 对应SQL
  62. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  63. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  64. WHERE `User`.uid = 1
  65. """
  66. # ------------------ 复杂查询, 可以多个表一起,使用 恒等式'==' 等形式 来设置条件
  67. session.query(User).filter(User.uid == 1).all()
  68. """
  69. 对应SQL
  70. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  71. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  72. WHERE `User`.uid = 1
  73. """
  74. # ------------------ filter 查询开头
  75. session.query(User).filter(User.username.startswith("l")).all()
  76. """
  77. 对应SQL
  78. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  79. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  80. WHERE (`User`.username LIKE concat('l', '%%'))
  81. """
  82. # ------------------ filter 查询结尾
  83. session.query(User).filter(User.username.endswith("x")).all()
  84. """
  85. 对应SQL
  86. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  87. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  88. WHERE (`User`.username LIKE concat('%%', 'x'))
  89. """
  90. # ------------------ filter 查询是否包含
  91. session.query(User).filter(User.username.contains("lcz")).all()
  92. """
  93. 对应SQL
  94. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  95. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  96. WHERE (`User`.username LIKE concat(concat('%%', "lcz", '%%')))
  97. """
  98. # ------------------ filter 模糊查询
  99. session.query(User).filter(User.username.like("%cz%")).all()
  100. """
  101. 对应SQL
  102. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  103. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  104. WHERE `User`.username LIKE "%cz%"
  105. """
  106. # ------------------ filter 条件取反 (not)
  107. session.query(User).filter(not_(User.username == "lczmx")).all()
  108. """
  109. 对应SQL
  110. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  111. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  112. WHERE `User`.username != "lczmx"
  113. """
  114. # ------------------ filter条件 或 (or), 默认为and
  115. session.query(User).filter(
  116. or_(User.uid == 1, User.uid == 3), ).all()
  117. """
  118. 对应SQL
  119. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  120. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  121. WHERE `User`.uid = 1 OR `User`.uid = 3
  122. """
  123. # ------------------ filter条件 and or not 一起使用
  124. session.query(User).filter(or_(User.uid == 1, User.uid == 4), User.username == "lczmx",
  125. not_(User.email == "wangwu@xx.com")).all()
  126. """
  127. 对应SQL
  128. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  129. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  130. WHERE (`User`.uid = 1 OR `User`.uid = 4)
  131. AND `User`.username = "lczmx" AND `User`.email = "lczmx@foxmail.com"
  132. """
  133. # ------------------ filter 取反查询
  134. session.query(User).filter(User.username != "lczmx").all()
  135. """
  136. 对应SQL
  137. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  138. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  139. WHERE `User`.username != "lczmx";
  140. """
  141. # ------------------ 查询uid为[1, 3, 5, 7, 9]的用户
  142. session.query(User).filter(User.uid.in_([1, 3, 5, 7, 9])).all()
  143. """
  144. 对应SQL
  145. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  146. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  147. WHERE `User`.uid IN (1, 3, 5, 7, 9)
  148. """
  149. # ------------------ 分组查询
  150. # !! 注意不是query(User), 因为Query(User)对应的SQL为:
  151. # SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  152. # `User`.email AS `User_email`, `User`.tags AS `User_tags`
  153. session.query(User.tags).group_by(User.tags).all()
  154. """
  155. 对应SQL
  156. SELECT `User`.tags AS `User_tags` FROM `User` GROUP BY `User`.tags
  157. """
  158. # ------------------ 排序 顺序
  159. session.query(User).order_by(User.uid).all()
  160. """
  161. 对应SQL
  162. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  163. `User`.email AS `User_email`, `User`.tags AS `User_tags`
  164. FROM `User` ORDER BY `User`.uid
  165. """
  166. # ------------------ 排序 倒序
  167. session.query(User).order_by(desc(User.uid)).all()
  168. """
  169. 对应SQL
  170. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  171. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  172. ORDER BY `User`.uid DESC
  173. """
  174. # ------------------ 去重
  175. session.query(User.tags).distinct().all()
  176. """
  177. 对应SQL:
  178. SELECT DISTINCT `User`.tags AS `User_tags` FROM `User`;
  179. """
  180. # ------------------ 取几条数据
  181. session.query(User).limit(2).all()
  182. """
  183. 对应SQL:
  184. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  185. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  186. LIMIT 2;
  187. """
  188. # ------------------ 跳过几条个数据
  189. session.query(User).offset(1).limit(2).all()
  190. """
  191. 对应SQL
  192. SELECT `User`.uid AS `User_uid`, `User`.username AS `User_username`,
  193. `User`.email AS `User_email`, `User`.tags AS `User_tags` FROM `User`
  194. LIMIT 1, 2;
  195. """

关于query返回的对象

query的返回对象为sqlalchemy.orm.query.Query对象, 你可以与Result对象进行对比, 主要有以下的方法:

  • all()

    返回由表对象组成的列表
  • first()

    返回第一个结果 (表对象), 内部执行limit SQL
  • one()

    只返回一行数据或引发异常 (无数据时抛出: sqlalchemy.exc.NoResultFound, 多行数据时抛出: sqlalchemy.exc.MultipleResultsFound)
  • one_or_none()

    最多返回一行数据或引发异常 (无数据时返回None, 多行数据时抛出: sqlalchemy.exc.MultipleResultsFound)
  • scalar()

    获取第一行的第一列数据. 如果没有要获取的行, 则返回None, 多行数据时抛出: sqlalchemy.exc.MultipleResultsFound

以上查询不包含一些连表操作, 见: relationship连表操作

2.0的方法

2.0的返回结果也是Result对象, 关于Result对象, 见: Result

注意: 由于2.0的查询方式, Core和ORM都可以使用, 所以放在一起, 见下文: 查询数据详解

relationship连表操作

我们自定义外键时, 一般的步骤是:

  1. 子表使用字段名= Column(Integer, ForeignKey('主表名.主键'))的格式定义
  2. 除此外, 还需要在主表中定义relationship用于子表与主表之间的跨表查询

完整例子:

  1. from sqlalchemy import Column, Integer, create_engine, String, Text, ForeignKey
  2. from sqlalchemy.orm import declarative_base, sessionmaker, relationship
  3. # 导入公共基类
  4. Base = declarative_base()
  5. # 数据库配置
  6. DATABASE_CONFIG = {
  7. "username": "root",
  8. "password": "123456",
  9. "host": "localhost",
  10. "database": "test"
  11. }
  12. # 连接mysql
  13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
  14. echo=True, future=True)
  15. class User(Base):
  16. __tablename__ = 'user'
  17. uid = Column(Integer, primary_key=True, autoincrement=True)
  18. username = Column(String(16), nullable=False)
  19. password = Column(String(32), nullable=False)
  20. # Article是类名 user是反向访问的属性名称
  21. article = relationship("Article", backref="user")
  22. """
  23. article = relationship("Article", backref="user")
  24. 相当于:
  25. class User(Base):
  26. # Article是类名 user是反向访问的属性名称
  27. article = relationship("Article", back_populates="user")
  28. class Article(Base):
  29. # User是类名 addresses是反向访问的属性名称
  30. user = relationship("User", back_populates="addresses")
  31. """
  32. def __repr__(self):
  33. return "<User %s>" % self.username
  34. class Article(Base):
  35. __tablename__ = 'article'
  36. aid = Column(Integer, primary_key=True, autoincrement=True)
  37. title = Column(String(36), nullable=False)
  38. content = Column(Text, nullable=False)
  39. author_id = Column(Integer, ForeignKey("user.uid"))
  40. def __repr__(self):
  41. return "<User %s>" % self.title
  42. Base.metadata.create_all(bind=engine)

关于relationship

relationship在定义外键时, 有非常重要的作用, 如: 1. 跨表操作 2. 设置删除主表数据时子表的值

relationship的参数很多, 这里只列出常用的几个, 全部参数见文档: relationship

  • back_populates

    指定反向访问的属性名称

  • backref

    快捷设置两个relationship (设置back_populates的话, 要设置两个表)

  • cascade

    用于控制修改数据时的选项

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

    比如:

    1. articles = relationship("Article",cascade="save-update,delete")
  • order_by

    子表列表的排序方式

    1. # 倒序
    2. article = relationship("Article", backref="user", order_by="Article.aid.desc()")
    3. # 正序
    4. article = relationship("Article", backref="user", order_by="Article.aid")

本部分包括Core与ORM的跨表增删改查操作

  1. select `user`.uid as uid, `user`.username, `user`.password,
  2. (select GROUP_CONCAT(`article`.title) from article
  3. where `article`.author_id = `user`.uid) as article
  4. from user;
  5. +-----+----------+----------+---------------+
  6. | uid | username | password | article |
  7. +-----+----------+----------+---------------+
  8. | 1 | 张三 | 12345 | C++入门,C入门 |
  9. | 2 | 李四 | 12346 | python入门 |
  10. +-----+----------+----------+---------------+

GROUP_CONCAT可以让多行数据拼接成一行数据, 以,链接

通过relationship双向访问

即直接通过relationship访问主表或子表

  1. from sqlalchemy.orm import sessionmaker
  2. from sqlalchemy import select
  3. SessionLocal = sessionmaker(bind=engine)
  4. with SessionLocal() as session:
  5. # +++++++++++++++++++++++++ 子表访问主表, 返回主表对象
  6. """
  7. 实质上内部执行的SQL
  8. SELECT user.uid AS user_uid, user.username AS user_username, user.password AS user_password
  9. FROM user
  10. WHERE user.uid = %(pk_1)s
  11. """
  12. # ----------- 1.x方法
  13. article_1x = session.query(Article).first()
  14. print(article_1x.user) # <User 张三>
  15. # ----------- 2.0 方法
  16. article_20 = session.execute(select(Article)).first()
  17. print(article_20.Article.user) # <User 张三>
  18. # +++++++++++++++++++++++++ 主表访问子表, 返回子表列表
  19. # 实质是 sqlalchemy.orm.collections.InstrumentedList 对象
  20. # 是List的子类
  21. """
  22. 实质上内部执行的SQL
  23. SELECT article.aid AS article_aid, article.title AS article_title,
  24. article.content AS article_content, article.author_id AS article_author_id
  25. FROM article
  26. WHERE %(param_1)s = article.author_id ORDER BY article.aid
  27. """
  28. # ----------- 1.x方法
  29. user_1x = session.query(User).first()
  30. print(user_1x.article)
  31. # [<User C++入门>, <User C入门>]
  32. # ----------- 2.0方法
  33. user_20 = session.execute(select(User)).first()
  34. print(user_20.User.article)
  35. # [<User C++入门>, <User C入门>]

通过relationship修改关联关系

上面例子中说过, 主表.relationship字段InstrumentedList对象 (类似于List), 我们可以修改它, 然后调用commit方法即可. 子表.relationship字段是对应的主表, 可以修改为自己想要的, 同样调用commit方法即可

  1. from sqlalchemy.orm import sessionmaker
  2. from sqlalchemy import select
  3. SessionLocal = sessionmaker(bind=engine)
  4. with SessionLocal() as session:
  5. # +++++++++++++++++++++++++ 子表修改属于的主表
  6. """
  7. 对于SQL
  8. UPDATE article
  9. SET author_id=%(author_id)s
  10. WHERE article.aid = %(article_aid)s
  11. """
  12. # ----------- 1.x方法
  13. lisi = session.query(User).get(2)
  14. article_1x = session.query(Article).first()
  15. print(article_1x.user) # <User 张三>
  16. article_1x.user = lisi # 改为 <User 李四>
  17. session.commit() # 记得提交
  18. # ----------- 2.0 方法
  19. zhangsan = session.execute(select(User).where(User.uid == 1)).first()
  20. article_20 = session.execute(select(Article)).first()
  21. print(article_20.Article.user) # <User 李四>
  22. article_20.Article.user = zhangsan.User # 改为 <User 张三>
  23. session.commit() # 记得提交
  24. # +++++++++++++++++++++++++ 主表修改子表列表
  25. # ----------- 1.x方法 增加
  26. user_1x = session.query(User).first()
  27. # 添加一个新的子表数据
  28. # 会在Article中插入一条新的数据
  29. user_1x.article.append(Article(title="javascript 入门", content="console.log(hello world)"))
  30. session.commit() # 记得提交
  31. # ----------- 2.0 方法 移除
  32. user_20 = session.execute(select(User)).first()
  33. print(user_20.User.article) # [<User C++入门>, <User C入门>, <User javascript 入门>]
  34. article_js = session.execute(select(Article).where(Article.aid == 4)).scalar()
  35. # 从主表中移除与子表的关系
  36. user_20.User.article.remove(article_js)
  37. session.commit() # 记得提交

通过relationship修改子/主表数据

同样非常简单, 找到对应的类, 然后修改数据并commit即可

  1. from sqlalchemy.orm import sessionmaker
  2. from sqlalchemy import select
  3. SessionLocal = sessionmaker(bind=engine)
  4. with SessionLocal() as session:
  5. # +++++++++++++++++++++++++ 通过子表修改对应主表的数据
  6. # ----------- 1.x方法
  7. article_1x = session.query(Article).first()
  8. print(article_1x.user) # <User 张三>
  9. article_1x.user.username = "张三二号"
  10. session.commit() # 记得提交
  11. # ----------- 2.0 方法
  12. article_20 = session.execute(select(Article)).first()
  13. print(article_20.Article.user) # <User 张三二号>
  14. article_20.Article.user.username = "张三"
  15. session.commit() # 记得提交
  16. # +++++++++++++++++++++++++ 通过主表修改对应子表的数据
  17. # ----------- 1.x方法
  18. user_1x = session.query(User).first()
  19. print(user_1x.article) # [<User C++入门>, <User C入门>, <User javascript 入门>]
  20. user_1x.article[-1].title = "js入门"
  21. session.commit() # 记得提交
  22. # ----------- 2.0 方法
  23. user_20 = session.execute(select(User)).scalar()
  24. print(user_20.article) # [<User C++入门>, <User C入门>, <User js入门>]
  25. user_20.article[-1].title = "javascript 入门"
  26. session.commit() # 记得提交

通过relationship查询数据

正向查询, 子表利用主表的条件查询, 使用has, 条件和普通查询的条件一样

反向查询, 主表利用子表的条件查询, 使用any, 条件和普通查询的条件一样

  1. from sqlalchemy.orm import sessionmaker
  2. from sqlalchemy import select
  3. SessionLocal = sessionmaker(bind=engine)
  4. with SessionLocal() as session:
  5. # +++++++++++++++++++++++++ 反向查询
  6. """
  7. 对应的SQL
  8. SELECT user.uid AS user_uid, user.username AS user_username, user.password AS user_password
  9. FROM user
  10. WHERE EXISTS (SELECT 1
  11. FROM article
  12. WHERE user.uid = article.author_id AND article.title LIKE 'python%')
  13. """
  14. # ********* 查询有以python开头的Article的User
  15. # ----------- 1.x方法
  16. user_1x = session.query(User).filter(User.article.any(Article.title.like("python%")))
  17. print(user_1x.all()) # [<User 李四>]
  18. # ----------- 2.0方法
  19. user_20 = session.execute(
  20. select(User).where(User.article.any(Article.title.like("python%"))))
  21. print(user_20.all()) # [(<User 李四>,)]
  22. # +++++++++++++++++++++++++ 正向查询
  23. """
  24. 对应的SQL
  25. SELECT article.aid AS article_aid, article.title AS article_title,
  26. article.content AS article_content, article.author_id AS article_author_id
  27. FROM article
  28. WHERE EXISTS (SELECT 1
  29. FROM user
  30. WHERE user.uid = article.author_id AND (user.username LIKE concat(concat('%%', '四', '%%')))
  31. """
  32. # ********* 查询User表中username有 四 的Article
  33. # ----------- 1.x方法
  34. article_1x = session.query(Article).filter(Article.user.has(User.username.contains("四")))
  35. print(article_1x.all()) # [<User python入门>]
  36. # ----------- 2.0方法
  37. article_20 = session.execute(
  38. select(Article).where(Article.user.has(User.username.contains("四"))))
  39. print(article_20.all()) # [(<User python入门>,)]

建立多对多关系

可以通过relationship便捷使用多对多关系

  1. from sqlalchemy import Table, Text, Column, ForeignKey
  2. # 第三张表
  3. post_keywords = Table("post_keywords", Base.metadata,
  4. Column('post_id', ForeignKey('posts.id'), primary_key=True),
  5. Column('keyword_id', ForeignKey('keywords.id'), primary_key=True)
  6. )
  7. class BlogPost(Base):
  8. __tablename__ = 'posts'
  9. id = Column(Integer, primary_key=True)
  10. headline = Column(String(255), nullable=False)
  11. body = Column(Text)
  12. # 多对多关系 BlogPost<->Keyword
  13. keywords = relationship('Keyword',
  14. secondary=post_keywords,
  15. back_populates='posts')
  16. def __init__(self, headline, body):
  17. self.headline = headline
  18. self.body = body
  19. def __repr__(self):
  20. return "BlogPost(%r, %r)" % (self.headline, self.body)
  21. class Keyword(Base):
  22. __tablename__ = 'keywords'
  23. id = Column(Integer, primary_key=True)
  24. keyword = Column(String(50), nullable=False, unique=True)
  25. # 多对多关系 Keyword<->BlogPost
  26. posts = relationship('BlogPost',
  27. secondary=post_keywords,
  28. back_populates='keywords')
  29. def __init__(self, keyword):
  30. self.keyword = keyword
  31. Base.metadata.create_all(bind=engine)

添加操作例子:

  1. from sqlalchemy import select
  2. SessionLocal = sessionmaker(bind=engine)
  3. with SessionLocal() as session:
  4. # 比如添加一篇文章, 为文章添加多个keyword
  5. blog = BlogPost(headline="起飞!", body="我是文章的内容")
  6. # 获取子表列表 并 append
  7. blog.keywords.append(Keyword("新闻"))
  8. blog.keywords.append(Keyword("热门"))
  9. session.add(blog)
  10. session.commit()
  11. # ------- 添加第二篇文章
  12. keyword1 = session.execute(
  13. select(Keyword).filter_by(keyword="热门")
  14. ).scalar()
  15. new_blog = BlogPost(headline="震惊!", body="我是第二篇文章的内容")
  16. new_blog.keywords.append(keyword1)
  17. session.add(new_blog)
  18. session.commit()

查询操作例子:

  1. from sqlalchemy import select
  2. SessionLocal = sessionmaker(bind=engine)
  3. with SessionLocal() as session:
  4. # 查询所有 "热门" 文章
  5. blog = session.execute(
  6. select(BlogPost).where(BlogPost.keywords.any(Keyword.keyword == "热门"))
  7. ).all()
  8. print(blog)
  9. # [(BlogPost('起飞!', '我是文章的内容'),),
  10. # (BlogPost('震惊!', '我是第二篇文章的内容'),)]

其他操作也像一般的relationship一样操作

项目示范

一般来说, 我们使用SQLAlchemy的步骤大多相同, 下面

文件结构:

  1. +--- database.py # 用于 初始化session 和 公共基类
  2. +--- models.py # 定义表
  3. +--- crud.py # 封装增删改查的方法
  4. +--- schemas.py # 定义pydantic模型, 用于格式化已经取得的数据 [可选]
  5. +--- main.py # 执行主逻辑

database.py 初始化session和 公共基类:

  1. from sqlalchemy import create_engine
  2. from sqlalchemy.orm import declarative_base, sessionmaker
  3. # 数据库的URL
  4. # 替换成自己的
  5. SQLALCHEMY_DATABASE_URL = 'sqlite:///./coronavirus.sqlite3'
  6. # 创建引擎
  7. engine = create_engine(
  8. # echo=True表示引擎将用repr()函数记录所有语句及其参数列表到日志
  9. SQLALCHEMY_DATABASE_URL, encoding='utf-8', echo=True
  10. )
  11. # SessionLocal用于对数据的增删改查
  12. # flush()是指发送数据库语句到数据库,但数据库不一定执行写入磁盘;commit()是指提交事务,将变更保存到数据库文件
  13. SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, expire_on_commit=True)
  14. # 创建基本映射类, 用于创建表
  15. Base = declarative_base(bind=engine, name='Base')

models.py 定义表结构

  1. from sqlalchemy import Column, String, Integer, BigInteger, Date, DateTime, ForeignKey, func
  2. from sqlalchemy.orm import relationship
  3. # 导入公共基类
  4. from .database import Base
  5. class City(Base):
  6. __tablename__ = 'city'
  7. id = Column(Integer, primary_key=True, index=True, autoincrement=True)
  8. province = Column(String(100), unique=True, nullable=False, comment='省/直辖市')
  9. country = Column(String(100), nullable=False, comment='国家')
  10. country_code = Column(String(100), nullable=False, comment='国家代码')
  11. country_population = Column(BigInteger, nullable=False, comment='国家人口')
  12. data = relationship('Data', back_populates='city') # 'Data'是关联的类名;back_populates来指定反向访问的属性名称
  13. created_at = Column(DateTime, server_default=func.now(), comment='创建时间')
  14. updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment='更新时间')
  15. __mapper_args__ = {"order_by": country_code} # 默认是正序,倒序加上.desc()方法
  16. def __repr__(self):
  17. return f'{self.country}_{self.province}'
  18. class Data(Base):
  19. __tablename__ = 'data'
  20. id = Column(Integer, primary_key=True, index=True, autoincrement=True)
  21. city_id = Column(Integer, ForeignKey('city.id'), comment='所属省/直辖市') # ForeignKey里的字符串格式不是类名.属性名,而是表名.字段名
  22. date = Column(Date, nullable=False, comment='数据日期')
  23. confirmed = Column(BigInteger, default=0, nullable=False, comment='确诊数量')
  24. deaths = Column(BigInteger, default=0, nullable=False, comment='死亡数量')
  25. recovered = Column(BigInteger, default=0, nullable=False, comment='痊愈数量')
  26. city = relationship('City', back_populates='data') # 'City'是关联的类名;back_populates来指定反向访问的属性名称
  27. created_at = Column(DateTime, server_default=func.now(), comment='创建时间')
  28. updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment='更新时间')
  29. __mapper_args__ = {"order_by": date.desc()} # 按日期降序排列
  30. def __repr__(self):
  31. return f'{repr(self.date)}:确诊{self.confirmed}例'

crud.py 封装增删改查的方法:

  1. from sqlalchemy.orm import Session
  2. import models
  3. import schemas
  4. def get_city(db: Session, city_id: int):
  5. return db.query(models.City).filter(models.City.id == city_id).first()
  6. def get_city_by_name(db: Session, name: str):
  7. return db.query(models.City).filter(models.City.province == name).first()
  8. def get_cities(db: Session, skip: int = 0, limit: int = 10):
  9. return db.query(models.City).offset(skip).limit(limit).all()
  10. def create_city(db: Session, city: schemas.CreateCity):
  11. """
  12. 创建City数据
  13. """
  14. db_city = models.City(**city.dict())
  15. db.add(db_city)
  16. db.commit()
  17. db.refresh(db_city)
  18. return db_city
  19. def get_data(db: Session, city: str = None, skip: int = 0, limit: int = 10):
  20. if city:
  21. # 外键关联查询,这里不是像Django ORM那样Data.city.province
  22. return db.query(models.Data).filter(models.Data.city.has(province=city))
  23. return db.query(models.Data).offset(skip).limit(limit).all()
  24. def create_city_data(db: Session, data: schemas.CreateData, city_id: int):
  25. """
  26. 创建Data数据
  27. """
  28. db_data = models.Data(**data.dict(), city_id=city_id)
  29. db.add(db_data)
  30. db.commit()
  31. db.refresh(db_data)
  32. return db_data

schemas.py 定义pydantic模型, 用于格式化已经取得的数据:

  1. from datetime import date as date_
  2. from datetime import datetime
  3. from pydantic import BaseModel
  4. class CreateData(BaseModel):
  5. date: date_
  6. confirmed: int = 0
  7. deaths: int = 0
  8. recovered: int = 0
  9. class CreateCity(BaseModel):
  10. province: str
  11. country: str
  12. country_code: str
  13. country_population: int
  14. class ReadData(CreateData):
  15. id: int
  16. city_id: int
  17. updated_at: datetime
  18. created_at: datetime
  19. class Config:
  20. orm_mode = True
  21. class ReadCity(CreateCity):
  22. id: int
  23. updated_at: datetime
  24. created_at: datetime
  25. class Config:
  26. orm_mode = True

main.py 执行主逻辑:

  1. from sqlalchemy.orm import Session
  2. import crud
  3. import schemas
  4. from database import engine, Base, SessionLocal
  5. from models import City, Data
  6. # 创建表, 已经存在的将被忽略
  7. Base.metadata.create_all(bind=engine)
  8. db = SessionLocal()
  9. # 调用 crud
  10. db_city = crud.get_city_by_name(db, name="广东省")
  11. if db_city:
  12. raise Exception("City already registered")
  13. # 创建数据
  14. city = City(...)
  15. crud.create_city(db=db, city=city)

查询数据详解

只有涉及SQL 的查询数据, 必然绕不开FROM WHERE SELECT GROUP BY HAVING ORDER BY LIMIT INNER JOIN ... ON LEFT JOIN ... ON UNION 这些SQL查询语法, 那么它们在SQLAlchemy中是如何表示的呢? 实际上和SQL语句一样, SQLAlchemy的语法也有select where join order_by group_by having 等 ...

注意: 这些方法在Core与ORM中都适用

刚接触可能会觉得比较复杂, 但是假如有SQL基础的话, 用起来比较简单.

要查询的表结构为:

  1. # +++++++++++++++++++ 使用Core定义 +++++++++++++++++++
  2. from sqlalchemy import MetaData, create_engine
  3. from sqlalchemy import Table, Column, Integer, String, ForeignKey, Text
  4. # 数据库配置
  5. DATABASE_CONFIG = {
  6. "username": "root",
  7. "password": "123456",
  8. "host": "localhost",
  9. "database": "test"
  10. }
  11. # 连接mysql
  12. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}?charset=utf8".
  13. format(**DATABASE_CONFIG), echo=True, future=True)
  14. metadata_obj = MetaData()
  15. user_table = Table(
  16. "user",
  17. metadata_obj,
  18. Column("uid", Integer, primary_key=True, autoincrement=True),
  19. Column("username", String(16), nullable=False),
  20. Column("password", String(32), nullable=False),
  21. )
  22. article_table = Table(
  23. 'article',
  24. metadata_obj,
  25. Column("aid", Integer, primary_key=True, autoincrement=True),
  26. Column("title", String(36), nullable=False),
  27. Column("content", Text, nullable=False),
  28. Column("author_id", Integer, ForeignKey("user.uid"))
  29. )
  30. metadata_obj.create_all(bind=engine)
  31. # +++++++++++++++++++ 使用ORM定义 +++++++++++++++++++
  32. from sqlalchemy import Column, Integer, create_engine, String, Text, ForeignKey
  33. from sqlalchemy.orm import declarative_base, relationship
  34. # 导入公共基类
  35. Base = declarative_base()
  36. # 数据库配置
  37. DATABASE_CONFIG = {
  38. "username": "root",
  39. "password": "123456",
  40. "host": "localhost",
  41. "database": "test"
  42. }
  43. # 连接mysql
  44. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".
  45. format(**DATABASE_CONFIG), echo=True, future=True)
  46. class User(Base):
  47. __tablename__ = 'user'
  48. uid = Column(Integer, primary_key=True, autoincrement=True)
  49. username = Column(String(16), nullable=False)
  50. password = Column(String(32), nullable=False)
  51. # Article是类名 user是反向访问的属性名称
  52. article = relationship("Article", backref="user")
  53. def __repr__(self):
  54. return "<User %s>" % self.username
  55. class Article(Base):
  56. __tablename__ = 'article'
  57. aid = Column(Integer, primary_key=True, autoincrement=True)
  58. title = Column(String(36), nullable=False)
  59. content = Column(Text, nullable=False)
  60. author_id = Column(Integer, ForeignKey("user.uid"))
  61. def __repr__(self):
  62. return "<User %s>" % self.title
  63. Base.metadata.create_all(bind=engine)

表数据为:

  1. mysql> select * from article, user where user.uid=article.author_id;
  2. +-----+------------+--------------------+-----------+-----+----------+----------+
  3. | aid | title | content | author_id | uid | username | password |
  4. +-----+------------+--------------------+-----------+-----+----------+----------+
  5. | 1 | C++入门 | c++ hello world | 1 | 1 | 张三 | 12345 |
  6. | 2 | C入门 | c hello world | 1 | 1 | 张三 | 12345 |
  7. | 3 | python入门 | print(hello world) | 2 | 2 | 李四 | 12346 |
  8. +-----+------------+--------------------+-----------+-----+----------+----------+
  9. 3 rows in set (0.12 sec)

select

该函数可以指定要查询的表、列及添加别名

  1. from sqlalchemy.orm import sessionmaker
  2. from sqlalchemy import select
  3. conn = engine.connect() # engine 在上面定义
  4. SessionLocal = sessionmaker(bind=engine)
  5. session = SessionLocal()
  6. # 1 ++++++++++++++++++ 一般查询 ++++++++++++++++++
  7. """
  8. 对应SQL
  9. SELECT user.uid, user.username, user.password FROM user
  10. WHERE user.uid = 2
  11. """
  12. # -------------- Core
  13. result_core_1 = conn.execute(select(user_table).where(user_table.c.uid == 2))
  14. print(result_core_1.all()) # [(2, '李四', '12346')]
  15. # -------------- ORM
  16. result_orm_1 = session.execute(select(User).where(User.uid == 2))
  17. print(result_orm_1.all()) # [(<User 李四>,)]
  18. # 2 ++++++++++++++++++ 查询全部列 ++++++++++++++++++
  19. """
  20. 对应SQL
  21. SELECT user.uid, user.username, user.password
  22. FROM user
  23. """
  24. # -------------- Core
  25. result_core_2 = conn.execute(select(user_table))
  26. print(result_core_2.all()) # [(1, '张三', '12345'), (2, '李四', '12346')]
  27. # -------------- ORM
  28. result_orm_2 = session.execute(select(User))
  29. print(result_orm_2.all()) # [(<User 张三>,), (<User 李四>,)]
  30. # 3 ++++++++++++++++++ 查询指定的列 ++++++++++++++++++
  31. """
  32. 对应SQL
  33. SELECT user.username
  34. FROM user
  35. """
  36. # -------------- Core
  37. result_core_3 = conn.execute(select(user_table.c.username))
  38. print(result_core_3.all()) # [('张三',), ('李四',)]
  39. # -------------- ORM
  40. result_orm_3 = session.execute(select(User.username))
  41. print(result_orm_3.all()) # [('张三',), ('李四',)]
  42. # 4 ++++++++++++++++++ 两个表 联合查询 ++++++++++++++++++
  43. """
  44. 对应SQL
  45. SELECT user.username, article.title
  46. FROM user, article
  47. WHERE user.uid = article.author_id
  48. """
  49. # -------------- Core
  50. result_core_4 = conn.execute(
  51. select(user_table.c.username, article_table.c.title).where(
  52. user_table.c.uid == article_table.c.author_id))
  53. # [('张三', 'C++入门'), ('张三', 'C入门'), ('李四', 'python入门')]
  54. print(result_core_4.all())
  55. # -------------- ORM
  56. result_orm_4 = session.execute(select(User.username, Article.title).where(
  57. User.uid == Article.author_id))
  58. # [('张三', 'C++入门'), ('张三', 'C入门'), ('李四', 'python入门')]
  59. print(result_orm_4.all())
  60. # 5 ++++++++++++++++++ 为列添加别名 ++++++++++++++++++
  61. """
  62. 对应SQL
  63. SELECT user.username AS name
  64. FROM user
  65. WHERE user.uid = 1
  66. """
  67. # -------------- Core
  68. result_core_5 = conn.execute(
  69. select((user_table.c.username).label("name")).where(
  70. user_table.c.uid == 1))
  71. # print(result_core_5.all()) # [('张三',)]
  72. # -------------- ORM
  73. result_orm_5 = session.execute(
  74. select((User.username).label("name")).where(
  75. User.uid == 1))
  76. # print(result_orm_5.all()) # [('张三',)]
  77. # 利用别名 取值
  78. print(result_core_5.first().name) # 张三
  79. print(result_orm_5.first().name) # 张三
  80. # 6 ++++++++++++++++++ 为表添加别名 ++++++++++++++++++
  81. """
  82. 对应SQL
  83. SELECT user_1.username
  84. FROM user AS user_1
  85. WHERE user_1.uid = 1
  86. """
  87. # -------------- Core
  88. user_table_core_alias = user_table.alias()
  89. result_core_6 = conn.execute(
  90. select(user_table_core_alias.c.username).where(
  91. user_table_core_alias.c.uid == 1))
  92. print(result_core_6.all()) # [('张三',)]
  93. # -------------- ORM
  94. from sqlalchemy.orm import aliased
  95. user_table_orm_alias = aliased(User)
  96. result_orm_6 = session.execute(
  97. select(user_table_orm_alias.username).where(
  98. user_table_orm_alias.uid == 1))
  99. print(result_orm_6.all()) # [('张三',)]
  100. # 7 ++++++++++++++++++ 与text 结合, 添加额外列 ++++++++++++++++++
  101. """
  102. 对应SQL
  103. SELECT now() AS now, user.username, '自定义字符'
  104. FROM user
  105. """
  106. from sqlalchemy import literal_column, text
  107. # literal_column表示一列数据
  108. # text可以转化成SQL
  109. # -------------- Core
  110. result_core_7 = conn.execute(
  111. select(literal_column("now()").label("now"), user_table.c.username, text("'自定义字符'")))
  112. print(result_core_7.all())
  113. """
  114. [(datetime.datetime(2022, 1, 8, 18, 54, 44), '张三', '自定义字符'),
  115. (datetime.datetime(2022, 1, 8, 18, 54, 44), '李四', '自定义字符')]
  116. """
  117. # -------------- ORM
  118. result_orm_7 = session.execute(
  119. select(literal_column("now()").label("now"), User.username, text("'自定义字符'")))
  120. print(result_orm_7.all())
  121. """
  122. [(datetime.datetime(2022, 1, 8, 18, 59, 42), '张三', '自定义字符'),
  123. (datetime.datetime(2022, 1, 8, 18, 59, 42), '李四', '自定义字符')]
  124. """

where

过滤数据, Coretable.c.xxx获取行, 假如是ORM的话为:类名.属性名

  1. from sqlalchemy.orm import sessionmaker
  2. from sqlalchemy import select
  3. conn = engine.connect() # engine 在上面定义
  4. SessionLocal = sessionmaker(bind=engine)
  5. session = SessionLocal()
  6. # 1 ++++++++++++++++++ 条件默认为and ++++++++++++++++++
  7. """
  8. 对应SQL
  9. SELECT user.uid, user.username, user.password
  10. FROM user
  11. WHERE user.uid < 3 AND user.uid > 1
  12. """
  13. # -------------- Core
  14. result_core_1 = conn.execute(
  15. select(user_table).where(user_table.c.uid < 3, user_table.c.uid > 1))
  16. print(result_core_1.all()) # [(2, '李四', '12346')]
  17. # -------------- ORM
  18. result_orm_1 = session.execute(select(User).where(User.uid < 3, User.uid > 1))
  19. print(result_orm_1.all()) # [(<User 李四>,)]
  20. # 2 ++++++++++++++++++ 修改条件为not or ++++++++++++++++++
  21. """
  22. 对应SQL
  23. SELECT user.uid, user.username, user.password
  24. FROM user
  25. WHERE (user.uid = 1 OR user.uid = 2) AND user.username != "李四"
  26. """
  27. from sqlalchemy import or_, not_
  28. # -------------- Core
  29. result_core_2 = conn.execute(
  30. select(user_table).where(
  31. or_(user_table.c.uid == 1, user_table.c.uid == 2),
  32. not_(user_table.c.username == "李四"),
  33. ))
  34. print(result_core_2.all()) # [(1, '张三', '12345')]
  35. # -------------- ORM
  36. result_orm_2 = session.execute(select(User).where(
  37. or_(User.uid == 1, User.uid == 2),
  38. not_(User.username == "李四")))
  39. print(result_orm_2.all()) # [(<User 张三>,)]
  40. # 3 ++++++++++++++++++ startswith ++++++++++++++++++
  41. """
  42. 对应SQL
  43. SELECT user.uid, user.username, user.password
  44. FROM user
  45. WHERE (user.username LIKE concat('张', '%%'))
  46. """
  47. # -------------- Core
  48. result_core_3 = conn.execute(
  49. select(user_table).where(
  50. user_table.c.username.startswith("张")))
  51. print(result_core_3.all()) # [(1, '张三', '12345')]
  52. # -------------- ORM
  53. result_orm_3 = session.execute(select(User).where(
  54. User.username.startswith("张")))
  55. print(result_orm_3.all()) # [(<User 张三>,)]
  56. # 4 ++++++++++++++++++ endswith ++++++++++++++++++
  57. """
  58. 对应SQL
  59. SELECT user.uid, user.username, user.password
  60. FROM user
  61. WHERE (user.username LIKE concat('%%', '三'))
  62. """
  63. # -------------- Core
  64. result_core_4 = conn.execute(
  65. select(user_table).where(
  66. user_table.c.username.endswith("三")))
  67. print(result_core_4.all()) # [(1, '张三', '12345')]
  68. # -------------- ORM
  69. result_orm_4 = session.execute(select(User).where(
  70. User.username.endswith("三")))
  71. print(result_orm_4.all()) # [(<User 张三>,)]
  72. # 5 ++++++++++++++++++ endswith ++++++++++++++++++
  73. """
  74. 对应SQL
  75. SELECT user.uid, user.username, user.password
  76. FROM user
  77. WHERE (user.username LIKE concat(concat('%%', '三', '%%'))
  78. """
  79. # -------------- Core
  80. result_core_5 = conn.execute(
  81. select(user_table).where(
  82. user_table.c.username.contains("三")))
  83. print(result_core_5.all()) # [(1, '张三', '12345')]
  84. # -------------- ORM
  85. result_orm_5 = session.execute(select(User).where(
  86. User.username.contains("三")))
  87. print(result_orm_5.all()) # [(<User 张三>,)]
  88. # 6 ++++++++++++++++++ like ++++++++++++++++++
  89. """
  90. 对应SQL
  91. SELECT user.uid, user.username, user.password
  92. FROM user
  93. WHERE user.username LIKE '%三'
  94. """
  95. # -------------- Core
  96. result_core_6 = conn.execute(
  97. select(user_table).where(
  98. user_table.c.username.like("%三")))
  99. print(result_core_6.all()) # [(1, '张三', '12345')]
  100. # -------------- ORM
  101. result_orm_6 = session.execute(select(User).where(
  102. User.username.like("%三")))
  103. print(result_orm_6.all()) # [(<User 张三>,)]
  104. # 7 ++++++++++++++++++ in ++++++++++++++++++
  105. """
  106. 对应SQL
  107. SELECT user.uid, user.username, user.password
  108. FROM user
  109. WHERE user.uid IN (1, 2)
  110. """
  111. # -------------- Core
  112. result_core_7 = conn.execute(
  113. select(user_table).where(
  114. user_table.c.uid.in_((1, 2))
  115. ))
  116. print(result_core_7.all()) # [(1, '张三', '12345'), (2, '李四', '12346')]
  117. # -------------- ORM
  118. result_orm_7 = session.execute(
  119. select(User).where(
  120. User.uid.in_((1, 2))
  121. ))
  122. print(result_orm_7.all()) # [(<User 张三>,), (<User 李四>,)]
  123. # 8 ++++++++++++++++++ between ... and ... ++++++++++++++++++
  124. """
  125. 对应SQL
  126. SELECT user.uid, user.username, user.password
  127. FROM user
  128. WHERE user.uid BETWEEN 1 AND 2
  129. """
  130. # -------------- Core
  131. result_core_8 = conn.execute(
  132. select(user_table).where(
  133. user_table.c.uid.between(1, 2)
  134. ))
  135. print(result_core_8.all()) # [(1, '张三', '12345'), (2, '李四', '12346')]
  136. # -------------- ORM
  137. result_orm_8 = session.execute(
  138. select(User).where(
  139. User.uid.between(1, 2)
  140. ))
  141. print(result_orm_8.all()) # [(<User 张三>,), (<User 李四>,)]
  142. # 9 ++++++++++++++++++ is null 或 is not null ++++++++++++++++++
  143. """
  144. 对应SQL
  145. SELECT user.uid, user.username, user.password
  146. FROM user
  147. WHERE user.uid IS NOT NULL
  148. """
  149. from sqlalchemy import not_
  150. # -------------- Core
  151. result_core_9 = conn.execute(
  152. select(user_table).where(
  153. # user_table.c.uid.is_(None), # IS NULL
  154. not_(user_table.c.uid.is_(None)), # IS NOT NULL
  155. ))
  156. print(result_core_9.all()) # [(1, '张三', '12345'), (2, '李四', '12346')]
  157. # -------------- ORM
  158. result_orm_9 = session.execute(
  159. select(User).where(
  160. # User.uid.is_(None), # IS NULL
  161. not_(User.uid.is_(None)), # IS NOT NULL
  162. ))
  163. print(result_orm_9.all()) # [(<User 张三>,), (<User 李四>,)]

除此之外, 你还可以使用一些其他运算符, 详情见: Operator Reference

order_by

order_by用于排序

  1. from sqlalchemy.orm import sessionmaker
  2. from sqlalchemy import select
  3. conn = engine.connect() # engine 在上面定义
  4. SessionLocal = sessionmaker(bind=engine)
  5. session = SessionLocal()
  6. # 1 ++++++++++++++++++ 根据某一列排序 (默认升序) ++++++++++++++++++
  7. """
  8. 对应SQL
  9. SELECT user.uid, user.username, user.password
  10. FROM user ORDER BY user.uid
  11. """
  12. # -------------- Core
  13. result_core_1 = conn.execute(
  14. select(user_table).order_by(user_table.c.uid))
  15. print(result_core_1.all()) # [(1, '张三', '12345'), (2, '李四', '12346')]
  16. # -------------- ORM
  17. result_orm_1 = session.execute(
  18. select(User).order_by(User.uid))
  19. print(result_orm_1.all()) # [(<User 张三>,), (<User 李四>,)]
  20. # 2 ++++++++++++++++++ 手动指定升序/降序 ++++++++++++++++++
  21. """
  22. 对应SQL
  23. SELECT user.uid, user.username, user.password
  24. FROM user ORDER BY user.uid ASC/DESC
  25. """
  26. # -------------- Core
  27. result_core_2 = conn.execute(
  28. # select(user_table).order_by(user_table.c.uid.asc()) # 升序
  29. select(user_table).order_by(user_table.c.uid.desc()) # 降序
  30. )
  31. print(result_core_2.all())
  32. # [(1, '张三', '12345'), (2, '李四', '12346')] / [(2, '李四', '12346'), (1, '张三', '12345')]
  33. # -------------- ORM
  34. result_orm_2 = session.execute(
  35. # select(User).order_by(User.uid.asc()) # 升序
  36. select(User).order_by(User.uid.desc()) # 降序
  37. )
  38. print(result_orm_2.all())
  39. # [(<User 张三>,), (<User 李四>,)] / [(<User 李四>,), (<User 张三>,)]
  40. # 3 ++++++++++++++++++ 根据别名排序 ++++++++++++++++++
  41. """
  42. 对应SQL
  43. SELECT user.username AS name
  44. FROM user ORDER BY name DESC
  45. """
  46. from sqlalchemy import desc, asc
  47. # -------------- Core
  48. result_core_3 = conn.execute(
  49. select((user_table.c.username).label("name")).order_by(desc("name"))
  50. )
  51. print(result_core_3.all()) # [('张三',), ('李四',)]
  52. # -------------- ORM
  53. result_orm_3 = session.execute(
  54. select((User.username).label("name")).order_by(desc("name"))
  55. )
  56. print(result_orm_3.all()) # [('张三',), ('李四',)]

group_by和having

group_by用于分组, having类似于where, 但可以对已分组数据使用聚合函数

  1. from sqlalchemy.orm import sessionmaker
  2. from sqlalchemy import select, func
  3. conn = engine.connect() # engine 在上面定义
  4. SessionLocal = sessionmaker(bind=engine)
  5. session = SessionLocal()
  6. # 1 ++++++++++++++++++ group_by一般使用 ++++++++++++++++++
  7. """
  8. 对应SQL
  9. SELECT count(article.author_id) AS count
  10. FROM article GROUP BY article.author_id
  11. """
  12. # func 内有很多内置函数
  13. # -------------- Core
  14. result_core_1 = conn.execute(
  15. select(func.count(article_table.c.author_id).label("count")).group_by(article_table.c.author_id)
  16. )
  17. print(result_core_1.all()) # [(2,), (1,)]
  18. # -------------- ORM
  19. result_orm_1 = session.execute(
  20. select(func.count(Article.author_id).label("count")).group_by(Article.author_id)
  21. )
  22. print(result_orm_1.all()) # [(2,), (1,)]
  23. # 2 ++++++++++++++++++ group by + having ++++++++++++++++++
  24. """
  25. 对应SQL
  26. SELECT count(article.author_id) AS count
  27. FROM article GROUP BY article.author_id
  28. HAVING count(article.author_id) > 1
  29. """
  30. # func 内有很多内置函数
  31. # -------------- Core
  32. result_core_2 = conn.execute(
  33. select(func.count(article_table.c.author_id).label("count")).group_by(
  34. article_table.c.author_id).having(func.count(article_table.c.author_id) > 1)
  35. )
  36. print(result_core_2.all()) # [(2,)]
  37. # -------------- ORM
  38. result_orm_2 = session.execute(
  39. select(func.count(Article.author_id).label("count")).group_by(
  40. Article.author_id).having(func.count(Article.author_id) > 1)
  41. )
  42. print(result_orm_2.all()) # [(2,)]

除了count外, 还有其他的方法, 详情见: 内置函数

limit和offset

limit: 表示取几条数据, offset: 表示要跳过多少条数据

  1. from sqlalchemy.orm import sessionmaker
  2. from sqlalchemy import select
  3. conn = engine.connect() # engine 在上面定义
  4. SessionLocal = sessionmaker(bind=engine)
  5. session = SessionLocal()
  6. # 1 ++++++++++++++++++ 仅使用LIMIT ++++++++++++++++++
  7. """
  8. 对应SQL
  9. SELECT user.uid, user.username, user.password
  10. FROM user
  11. LIMIT 1
  12. """
  13. # -------------- Core
  14. result_core_1 = conn.execute(select(user_table).limit(1))
  15. print(result_core_1.all()) # [(1, '张三', '12345')]
  16. # -------------- ORM
  17. result_orm_1 = session.execute(select(User).limit(1))
  18. print(result_orm_1.all()) # [(<User 张三>,)]
  19. # 2 ++++++++++++++++++ 使用LIMIT和OFFSET ++++++++++++++++++
  20. """
  21. 对应SQL
  22. SELECT user.uid, user.username, user.password
  23. FROM user
  24. LIMIT 1, 1
  25. """
  26. # -------------- Core
  27. result_core_2 = conn.execute(select(user_table).limit(1).offset(1))
  28. print(result_core_2.all()) # [(2, '李四', '12346')]
  29. # -------------- ORM
  30. result_orm_2 = session.execute(select(User).limit(1).offset(1))
  31. print(result_orm_2.all()) # [(<User 李四>,)]

去重

你可以在查询的时候使用SQL进行去重, 亦可以在取到数据后进行去重

  1. from sqlalchemy.orm import sessionmaker
  2. from sqlalchemy import select
  3. conn = engine.connect() # engine 在上面定义
  4. SessionLocal = sessionmaker(bind=engine)
  5. session = SessionLocal()
  6. # 1 ++++++++++++++++++ 在SQL中去重 ++++++++++++++++++
  7. """
  8. 对应SQL
  9. SELECT DISTINCT article.author_id
  10. FROM article
  11. """
  12. # -------------- Core
  13. result_core_1 = conn.execute(
  14. select(article_table.c.author_id).distinct()
  15. )
  16. print(result_core_1.all()) # [(1,), (2,)]
  17. # -------------- ORM
  18. result_orm_1 = session.execute(
  19. select(Article.author_id).distinct())
  20. print(result_orm_1.all()) # [(1,), (2,)]
  21. # 2 ++++++++++++++++++ 在结果中去重 ++++++++++++++++++
  22. """
  23. 对应SQL
  24. SELECT article.author_id
  25. FROM article
  26. """
  27. # -------------- Core
  28. result_core_2 = conn.execute(select(article_table.c.author_id))
  29. print(result_core_2.unique().all()) # [(1,), (2,)]
  30. # -------------- ORM
  31. result_orm_2 = session.execute(select(Article.author_id))
  32. print(result_orm_2.unique().all()) # [(1,), (2,)]

连接查询

内连接查询 外连接查询 完全连接查询三种连接查询方式, 在SQLAlchemy中分别对应着join(...) join(..., isouter=True) join(..., full=True)

  1. from sqlalchemy.orm import sessionmaker
  2. from sqlalchemy import select
  3. conn = engine.connect() # engine 在上面定义
  4. SessionLocal = sessionmaker(bind=engine)
  5. session = SessionLocal()
  6. # 1 ++++++++++++++++++ 内连接查询 (自动推断join的表) 方式一 ++++++++++++++++++
  7. """
  8. 对应SQL
  9. SELECT user.username, article.title
  10. FROM user INNER JOIN article ON user.uid = article.author_id
  11. """
  12. # -------------- Core
  13. result_core_1 = conn.execute(
  14. select(user_table.c.username, article_table.c.title).join_from(user_table, article_table))
  15. print(result_core_1.all()) # [('张三', 'C++入门'), ('张三', 'C入门'), ('李四', 'python入门')]
  16. # -------------- ORM
  17. result_orm_1 = session.execute(
  18. select(User.username, Article.title).join_from(User, Article))
  19. print(result_orm_1.all()) # [('张三', 'C++入门'), ('张三', 'C入门'), ('李四', 'python入门')]
  20. # 2 ++++++++++++++++++ 内连接查询 (自动推断join的表) 方式二 ++++++++++++++++++
  21. """
  22. 对应SQL
  23. SELECT user.username, article.title
  24. FROM user INNER JOIN article ON user.uid = article.author_id
  25. """
  26. # -------------- Core
  27. result_core_2 = conn.execute(
  28. select(user_table.c.username, article_table.c.title).join(article_table))
  29. print(result_core_2.all()) # [('张三', 'C++入门'), ('张三', 'C入门'), ('李四', 'python入门')]
  30. # -------------- ORM
  31. result_orm_2 = session.execute(select(User.username, Article.title).join(Article))
  32. print(result_orm_2.all()) # [('张三', 'C++入门'), ('张三', 'C入门'), ('李四', 'python入门')]
  33. # 3 ++++++++++++++++++ 内连接查询 (手动指定join的表) ++++++++++++++++++
  34. """
  35. 对应SQL
  36. SELECT user.username, article.title
  37. FROM user INNER JOIN article ON user.uid = article.author_id
  38. """
  39. # -------------- Core
  40. result_core_3 = conn.execute(
  41. select(user_table.c.username, article_table.c.title).select_from(user_table).join(article_table))
  42. print(result_core_3.all()) # [('张三', 'C++入门'), ('张三', 'C入门'), ('李四', 'python入门')]
  43. # -------------- ORM
  44. result_orm_3 = session.execute(
  45. select(User.username, Article.title).select_from(User).join(Article))
  46. print(result_orm_3.all()) # [('张三', 'C++入门'), ('张三', 'C入门'), ('李四', 'python入门')]
  47. # 4 ++++++++++++++++++ 内连接查询 (手动指定on的条件) ++++++++++++++++++
  48. """
  49. 对应SQL
  50. SELECT user.username, article.title
  51. FROM user INNER JOIN article ON user.uid = article.author_id
  52. """
  53. # -------------- Core
  54. result_core_4 = conn.execute(
  55. select(user_table.c.username, article_table.c.title).select_from(
  56. user_table).join(article_table, user_table.c.uid == article_table.c.author_id))
  57. print(result_core_4.all()) # [('张三', 'C++入门'), ('张三', 'C入门'), ('李四', 'python入门')]
  58. # -------------- ORM
  59. result_orm_4 = session.execute(
  60. select(User.username, Article.title).select_from(
  61. User).join(Article, User.uid == Article.author_id))
  62. print(result_orm_4.all()) # [('张三', 'C++入门'), ('张三', 'C入门'), ('李四', 'python入门')]
  63. # 5 ++++++++++++++++++ 外连接查询 ++++++++++++++++++
  64. """
  65. 对应SQL
  66. SELECT user.uid, user.username, user.password, article.title
  67. FROM user LEFT OUTER JOIN article ON user.uid = article.author_id
  68. """
  69. # -------------- Core
  70. result_core_5 = conn.execute(
  71. select(user_table, article_table.c.title).join(article_table, isouter=True))
  72. print(result_core_5.all())
  73. # [(1, '张三', '12345', 'C入门'), (1, '张三', '12345', 'C++入门'), (2, '李四', '12346', 'python入门')]
  74. # -------------- ORM
  75. result_orm_5 = session.execute(
  76. select(User, Article.title).join(Article, isouter=True))
  77. print(result_orm_5.all()) # [(<User 张三>, 'C入门'), (<User 张三>, 'C++入门'), (<User 李四>, 'python入门')]
  78. # 6 ++++++++++++++++++ 完全连接查询 ++++++++++++++++++
  79. # # !!! 注意: MYSQL 中没有 FULL OUTER JOIN, 执行时会报错
  80. """
  81. 对应SQL
  82. SELECT user.uid, user.username, user.password, article.title
  83. FROM user LEFT OUTER JOIN article ON user.uid = article.author_id
  84. """
  85. # -------------- Core
  86. result_core_6 = conn.execute(
  87. select(user_table, article_table.c.title).join(article_table, full=True))
  88. print(result_core_6.all())
  89. # -------------- ORM
  90. result_orm_6 = session.execute(
  91. select(User, Article.title).join(Article, full=True))
  92. print(result_orm_6.all())

注意: 使用ORM的方式进行连表操作时, 可以通过relationship, 即不需要直接指定第二张表, 指定relationship

比如:

  1. from sqlalchemy.orm import sessionmaker
  2. from sqlalchemy import select
  3. conn = engine.connect() # engine 在上面定义
  4. SessionLocal = sessionmaker(bind=engine)
  5. session = SessionLocal()
  6. """
  7. 对应SQL
  8. SELECT user.username, article.title
  9. FROM user INNER JOIN article ON user.uid = article.author_id
  10. """
  11. result_orm = session.execute(
  12. select(User.username, Article.title).select_from(User).join(User.article))
  13. print(result_orm.all()) # [('张三', 'C++入门'), ('张三', 'C入门'), ('李四', 'python入门')]

UNION和UNION ALL

UNION ALL: 合并两个或多个SELECT语句的结果, 结果不会去重

UNION: 合并两个或多个SELECT语句的结果, 结果会去重

  1. from sqlalchemy.orm import sessionmaker
  2. from sqlalchemy import select, union_all, union
  3. conn = engine.connect() # engine 在上面定义
  4. SessionLocal = sessionmaker(bind=engine)
  5. session = SessionLocal()
  6. # 1 ++++++++++++++++++ UNION ALL ++++++++++++++++++
  7. """
  8. 对应SQL
  9. SELECT user.uid, user.username, user.password
  10. FROM user
  11. WHERE user.uid = 1
  12. UNION ALL
  13. SELECT user.uid, user.username, user.password
  14. FROM user
  15. WHERE user.uid = 2
  16. """
  17. # -------------- Core
  18. result_core_1_stmt1 = select(user_table).where(user_table.c.uid == 1)
  19. result_core_1_stmt2 = select(user_table).where(user_table.c.uid == 2)
  20. result_core_1_u = union_all(result_core_1_stmt1, result_core_1_stmt2)
  21. result_core_1 = conn.execute(result_core_1_u)
  22. print(result_core_1.all()) # [(1, '张三', '12345'), (2, '李四', '12346')]
  23. # -------------- ORM
  24. result_orm_1_stmt1 = select(user_table).where(user_table.c.uid == 1)
  25. result_orm_1_stmt2 = select(user_table).where(user_table.c.uid == 2)
  26. result_orm_1_u = union_all(result_orm_1_stmt1, result_orm_1_stmt2)
  27. result_orm_1 = session.execute(result_orm_1_u)
  28. print(result_orm_1.all()) # [(1, '张三', '12345'), (2, '李四', '12346')]
  29. # 2 ++++++++++++++++++ UNION ++++++++++++++++++
  30. """
  31. 对应SQL
  32. SELECT user.uid, user.username, user.password
  33. FROM user
  34. WHERE user.uid = 1
  35. UNION
  36. SELECT user.uid, user.username, user.password
  37. FROM user
  38. WHERE user.uid = 1
  39. """
  40. # -------------- Core
  41. result_core_2_stmt1 = select(user_table).where(user_table.c.uid == 1)
  42. result_core_2_stmt2 = select(user_table).where(user_table.c.uid == 1)
  43. result_core_2_u = union(result_core_2_stmt1, result_core_2_stmt2)
  44. result_core_2 = conn.execute(result_core_2_u)
  45. print(result_core_2.all()) # [(1, '张三', '12345')]
  46. # -------------- ORM
  47. result_orm_2_stmt1 = select(user_table).where(user_table.c.uid == 1)
  48. result_orm_2_stmt2 = select(user_table).where(user_table.c.uid == 1)
  49. result_orm_2_u = union(result_orm_2_stmt1, result_orm_2_stmt2)
  50. result_orm_2 = session.execute(result_orm_2_u)
  51. print(result_orm_2.all()) # [(1, '张三', '12345')]

子查询

即形如: SELECT * FROM data WHERE name IN (SELECT name FROM user);

SELECT * FROM data WHERE EXISTS (SELECT name FROM user);的查询

或使用WITH temp AS (...) 作为临时表

注意: 在使用子查询后,SQL语句的查询性能变得非常糟糕, 至于如何取舍看个人权衡了

关于子查询的优化, 见: 深入理解MySql子查询IN的执行和优化

  1. from sqlalchemy.orm import sessionmaker
  2. from sqlalchemy import select, func
  3. conn = engine.connect() # engine 在上面定义
  4. SessionLocal = sessionmaker(bind=engine)
  5. session = SessionLocal()
  6. # 1 ++++++++++++++++++ EXISTS 子查询 ++++++++++++++++++
  7. """
  8. 对应SQL
  9. SELECT user.username
  10. FROM user
  11. WHERE EXISTS (SELECT count(article.author_id) AS count_1
  12. FROM article GROUP BY article.author_id
  13. HAVING count(article.author_id) >= 2)
  14. """
  15. # -------------- Core
  16. subquery_core_1 = (select(func.count(article_table.c.author_id)).group_by(
  17. article_table.c.author_id).having(
  18. func.count(article_table.c.author_id) >= 2)).exists()
  19. result_core_1 = conn.execute(select(user_table.c.username).where(subquery_core_1))
  20. print(result_core_1.all()) # [('张三',), ('李四',)]
  21. # -------------- ORM
  22. subquery_orm_1 = (select(func.count(Article.author_id)).group_by(
  23. Article.author_id).having(
  24. func.count(Article.author_id) >= 2)).exists()
  25. result_orm_1 = session.execute(select(User.username).where(subquery_orm_1))
  26. print(result_orm_1.all()) # [('张三',), ('李四',)]
  27. # 2 ++++++++++++++++++ 其它 子查询 ++++++++++++++++++
  28. """
  29. 对应SQL
  30. SELECT article.title, anon_1.username
  31. FROM article, (SELECT user.uid AS uid, user.username AS username
  32. FROM user
  33. WHERE user.uid = 1) AS anon_1
  34. WHERE article.author_id = anon_1.uid
  35. """
  36. # -------------- Core
  37. subquery_core_2 = select(user_table.c.uid, user_table.c.username).where(
  38. user_table.c.uid == 1).subquery()
  39. result_core_2 = conn.execute(
  40. select(article_table.c.title, subquery_core_2.c.username).where(
  41. article_table.c.author_id == subquery_core_2.c.uid))
  42. print(result_core_2.all()) # [('C++入门', '张三'), ('C入门', '张三')]
  43. # # -------------- ORM
  44. subquery_orm_2 = select(User.uid, User.username).where(User.uid == 1).subquery()
  45. result_orm_2 = session.execute(
  46. select(Article.title, subquery_orm_2.c.username).where(
  47. Article.author_id == subquery_orm_2.c.uid))
  48. print(result_orm_2.all()) # [('C++入门', '张三'), ('C入门', '张三')]
  49. # 3 ++++++++++++++++++ with 添加临时表 ++++++++++++++++++
  50. """
  51. 对应SQL
  52. WITH anon_1 AS
  53. (SELECT user.uid AS uid, user.username AS username
  54. FROM user
  55. WHERE user.uid = 1)
  56. SELECT article.title, anon_1.username
  57. FROM article, anon_1
  58. WHERE article.author_id = anon_1.uid
  59. """
  60. # -------------- Core
  61. subquery_core_3 = select(user_table.c.uid, user_table.c.username).where(
  62. user_table.c.uid == 1).cte()
  63. result_core_3 = conn.execute(
  64. select(article_table.c.title, subquery_core_3.c.username).where(
  65. article_table.c.author_id == subquery_core_3.c.uid))
  66. print(result_core_3.all()) # [('C++入门', '张三'), ('C入门', '张三')]
  67. # -------------- ORM
  68. subquery_orm_3 = select(User.uid, User.username).where(User.uid == 1).cte()
  69. result_orm_3 = session.execute(
  70. select(Article.title, subquery_orm_3.c.username).where(
  71. Article.author_id == subquery_orm_3.c.uid))
  72. print(result_orm_3.all()) # [('C++入门', '张三'), ('C入门', '张三')]

从1.x迁移到2.0的接口

一些Query的接口用起来还是特别好用的, 因此在2.0风格的接口中, 一些接口仍然可以使用

get根据主键查询

注意: 这是session的方法

  1. # ---------------- 1.x
  2. session.query(User).get(42)
  3. # ---------------- 2.0
  4. session.get(User, 42)

filter_by简单查询

注意: 这是select的方法

  1. result = session.execute(
  2. select(User).filter_by(username="张三")
  3. )
  4. print(result.all()) # [(<User 张三>,)]

filter复杂查询

注意: 这是select的方法

  1. result = session.execute(
  2. select(User).filter(User.username == "张三")
  3. )
  4. print(result.all()) # [(<User 张三>,)]

1.x与2.0的ORM接口对比

以下表格来自于: 2.0 Migration - ORM Usage



一些类的介绍

Result

表示从数据库中返回的结果, 一行数据使用Row对象表示, 关于Row, 见: Row

从SQLAlchemy1.4开始, Core和ORM的结果(Result), 使用的接口相同

  1. from sqlalchemy import create_engine, text
  2. from sqlalchemy.orm import declarative_base, sessionmaker
  3. # 导入公共基类
  4. Base = declarative_base()
  5. # 数据库配置
  6. DATABASE_CONFIG = {
  7. "username": "root",
  8. "password": "123456",
  9. "host": "localhost",
  10. "database": "test"
  11. }
  12. # 连接mysql
  13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
  14. echo=True, future=True)
  15. Session = sessionmaker(bind=engine)
  16. with Session() as session:
  17. session_res = session.execute(
  18. text("select tid, name from teacher;")
  19. )
  20. # <class 'sqlalchemy.engine.cursor.CursorResult'>
  21. print(type(session_res))
  22. with engine.connect() as conn:
  23. conn_res = conn.execute(
  24. text("select tid, name from teacher;")
  25. )
  26. # <class 'sqlalchemy.engine.cursor.CursorResult'>
  27. print(type(conn_res))

注意: 例子中的teacher表的数据为:

  1. +-----+----------+
  2. | tid | name |
  3. +-----+----------+
  4. | 1 | 语文老师 |
  5. | 2 | 英语老师 |
  6. | 3 | 数学老师 |
  7. +-----+----------+

Result的全部方法

  • unique(strategy=None)

    去重, 但需要注意何时调用, 应该在调用如.all()这种生成Row的方法之前调用, 否则返回的对象都没有unique这个方法

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res1 = session.execute(
    18. text("select tid, name from teacher;")
    19. )
    20. session_res2 = session.execute(
    21. text("select tid, name from teacher;")
    22. )
    23. session_res = session_res1.merge(session_res2)
    24. # [1, 2, 3, 1, 2, 3] ==> [1, 2, 3]
    25. print(session_res.scalars().unique().all())
  • all

    返回所有Row数据的列表, 之后的调用将返回一个空列表

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res = session.execute(
    18. text("select tid, name from teacher;")
    19. )
    20. # ---------------------------------- 第一次调用all
    21. first_all = session_res.all()
    22. print(type(session_res.all())) # <class 'list'>
    23. for row in first_all:
    24. print(type(row)) # <class 'sqlalchemy.engine.row.Row'>
    25. print(f"{row.tid}-{row.name}")
    26. # ---------------------------------- 第二次调用all
    27. print(session_res.all()) # []
  • fetchall()

    all方法一样

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res = session.execute(
    18. text("select tid, name from teacher;")
    19. )
    20. first_fetchall = session_res.fetchall()
    21. second_fetchall = session_res.fetchall()
    22. # [(1, '语文老师'), (2, '英语老师'), (3, '数学老师')]
    23. print(first_fetchall)
    24. # []
    25. print(second_fetchall)
  • fetchmany(size=None)

    取多行数据, size表示取多少行数据 , 当所有行都用完时, 返回一个空列表

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res = session.execute(
    18. text("select tid, name from teacher;")
    19. )
    20. # 一共3行数据
    21. # 取两行数据: [(1, '语文老师'), (2, '英语老师')]
    22. print(session_res.fetchmany(2))
    23. # 取一行数据: [(3, '数学老师')]
    24. print(session_res.fetchmany(1))
    25. # 没有数据了, 返回空列表: []
    26. print(session_res.fetchmany(1))
  • fetchone()

    取一行数据, 当所有行都用完时,返回None

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res = session.execute(
    18. text("select tid, name from teacher;")
    19. )
    20. while True:
    21. row = session_res.fetchone()
    22. if not row:
    23. break
    24. print(row)
    25. """
    26. (1, '语文老师')
    27. (2, '英语老师')
    28. (3, '数学老师')
    29. """
  • first()

    获取第一行数据,关闭Result并丢弃其余行, 如果没有行,则不获取 (即返回值为None)

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res = session.execute(
    18. text("select tid, name from teacher;")
    19. )
    20. # (1, '语文老师')
    21. print(session_res.first())
    22. # !!! 由于Result已经关闭, 继续操作会报错:
    23. # sqlalchemy.exc.ResourceClosedError: This result object is closed.
    24. print(session_res.first())
  • one()

    只返回一行数据或引发异常, 并关闭Result (无数据时抛出: sqlalchemy.exc.NoResultFound, 多行数据时抛出: sqlalchemy.exc.MultipleResultsFound)

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res = session.execute(
    18. text("select tid, name from teacher where tid=1;")
    19. )
    20. # (1, '语文老师')
    21. print(session_res.one())
  • one_or_none()

    最多返回一行数据或引发异常, 并关闭Result (无数据时返回None, 多行数据时抛出: sqlalchemy.exc.MultipleResultsFound)

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res1 = session.execute(
    18. text("select tid, name from teacher where tid < 1;")
    19. )
    20. # None
    21. print(session_res1.one_or_none())
    22. session_res2 = session.execute(
    23. text("select tid, name from teacher where tid = 1;")
    24. )
    25. # (1, '语文老师')
    26. print(session_res2.one_or_none())
  • columns(*col_expressions)

    限制返回列, 也可以对列进行重新排序

    即假如结果的列为(a, b, c, d), 但我只需要a和b, 那么只需要result.columns("a", "b"), 你还可以调整它们的顺序, 以方便解包

    注意: 这会修改Result的列, 且该方法的返回值就是修改后的Result对象

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res1 = session.execute(
    18. text("select tid, name from teacher;")
    19. )
    20. session_res2 = session.execute(
    21. text("select tid, name from teacher;")
    22. )
    23. for row in session_res1.columns("tid"):
    24. # 返回值时修改后的Result
    25. # 获取字段名元组
    26. print(row._fields) # ('tid',)
    27. # 会修改原Result
    28. session_res2.columns("name")
    29. for row in session_res2:
    30. # 获取字段名元组
    31. print(row._fields) # ('name',)
  • scalar()

    获取第一行的第一列数据, 并关闭Result. 如果没有要获取的行, 则返回None

    如: [(1, "lczmx"), (2, "jack")], 返回 1

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res = session.execute(
    18. text("select tid, name from teacher;")
    19. )
    20. print(session_res.scalar()) # 1
  • scalar_one()

    只返回一行数据的第一列或引发异常, 并关闭Result (无数据时抛出: sqlalchemy.exc.NoResultFound, 多行数据时抛出: sqlalchemy.exc.MultipleResultsFound)

    Result.one() + Result.scalar()

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res = session.execute(
    18. text("select tid, name from teacher where tid=1;")
    19. )
    20. print(session_res.scalar_one()) # 1
  • scalar_one_or_none()

    最多返回一行数据的第一列或引发异常, 并关闭Result (无数据时返回None, 多行数据时抛出: sqlalchemy.exc.MultipleResultsFound)

    Result.one_or_none() + Result.scalar()

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res1 = session.execute(
    18. text("select tid, name from teacher where tid<1;")
    19. )
    20. print(session_res1.scalar_one_or_none()) # None
    21. session_res2 = session.execute(
    22. text("select tid, name from teacher where tid=1;")
    23. )
    24. print(session_res2.scalar_one_or_none()) # 1
  • scalars(index=0)

    返回一个ScalarResult对象, 该对象以每行数据的 第index列 元素作为数据 (而不是ResultRow)

    该对象的方法有: unique partitions fetchall fetchmany all first one_or_none one

    具体使用与Result类似

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res = session.execute(
    18. text("select tid, name from teacher;")
    19. )
    20. # [1, 2, 3]
    21. print(session_res.scalars().all())
  • mappings()

    返回一个MappingResult对象, MappingResult对象与Result对象类似, 但是一行数据使用RowMapping对象表示, RowMapping对象类似于字典对象, 简而言之: 调用该方法, 你可以将一行数据由类元组变为类字典

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res = session.execute(
    18. text("select tid, name from teacher;")
    19. )
    20. for d in session_res.mappings():
    21. # 像操作字典一样操作 d 即可
    22. print(d.get("tid"), d.get("name"))
  • keys()

    从SQLAlchemy1.4起 (之前的版本返回一个列表), 该方法将返回一个RMKeyView对象, 该对象可迭代, 其_keys属性存放列的名称, 由于实现的__contains__方法, 因此也可以使用in运算符作判断.

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. from collections import Iterable
    4. # 导入公共基类
    5. Base = declarative_base()
    6. # 数据库配置
    7. DATABASE_CONFIG = {
    8. "username": "root",
    9. "password": "123456",
    10. "host": "localhost",
    11. "database": "test"
    12. }
    13. # 连接mysql
    14. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    15. echo=True, future=True)
    16. Session = sessionmaker(bind=engine)
    17. with Session() as session:
    18. session_res = session.execute(
    19. text("select tid, name from teacher;")
    20. )
    21. keys = session_res.keys()
    22. print(keys) # RMKeyView(['tid', 'name'])
    23. # <class 'sqlalchemy.engine.result.RMKeyView'>
    24. print(type(keys))
    25. # 可迭代的
    26. print(isinstance(keys, Iterable)) # True
    27. if "name" in keys:
    28. print("name in keys")
  • freeze()

    可以对Result进行缓存, 见官方文档: Re-Executing Statements

  • merge(*others)

    该方法合并其他Result, 返回一个MergedResult对象, 你可以像一般的Result一样操作它, 但是取值的时候注意游标的位置(MergedResult关闭, Result也关闭; MergedResult取完了值, Result的值也被取完)

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res1 = session.execute(
    18. text("select tid, name from teacher where tid=1;")
    19. )
    20. session_res2 = session.execute(
    21. text("select tid, name from teacher where tid=2;")
    22. )
    23. session_res = session_res2.merge(session_res1)
    24. # 注意 先session_res2再session_res1
    25. # (2, '英语老师')
    26. print(session_res.fetchone())
    27. # [(1, '语文老师')]
    28. print(session_res1.all())
    29. # session_res已经取过一次
    30. # 所以返回: []
    31. print(session_res2.all())
  • partitions(size=None)

    迭代生成size大小的行的子列表, sizeNone时调用Result.yield_per(), 否则调用Result.fetchmany(size)

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res1 = session.execute(
    18. text("select tid, name from teacher;")
    19. )
    20. # 每次迭代 取 1行 数据
    21. for i in session_res1.partitions():
    22. print(i)
    23. """
    24. [(1, '语文老师')]
    25. [(2, '英语老师')]
    26. [(3, '数学老师')]
    27. """
    28. session_res2 = session.execute(
    29. text("select tid, name from teacher;")
    30. )
    31. # 每次迭代 取 2行 数据
    32. for i in session_res2.partitions(2):
    33. print(i)
    34. """
    35. [(1, '语文老师'), (2, '英语老师')]
    36. [(3, '数学老师')]
    37. """
    38. # 已经迭代完了, 就没有值了
    39. print(list(session_res2.partitions())) # []
  • yield_per(num)

    迭代num行数据, 返回的是Result对象

    1. from sqlalchemy import create_engine, text
    2. from sqlalchemy.orm import declarative_base, sessionmaker
    3. # 导入公共基类
    4. Base = declarative_base()
    5. # 数据库配置
    6. DATABASE_CONFIG = {
    7. "username": "root",
    8. "password": "123456",
    9. "host": "localhost",
    10. "database": "test"
    11. }
    12. # 连接mysql
    13. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
    14. echo=True, future=True)
    15. Session = sessionmaker(bind=engine)
    16. with Session() as session:
    17. session_res = session.execute(
    18. text("select tid, name from teacher;")
    19. )
    20. # [(1, '语文老师'), (2, '英语老师'), (3, '数学老师')]
    21. print(session_res.yield_per(1).all())
  • close

    关闭此Result, 再操作的话会抛出异常: sqlalchemy.exc.ResourceClosedError: This result object is closed.

Row

一般来说, 一行数据是一个Row对象

常用的属性或方法:

属性/方法 属性或方法 描述
_asdict 方法 返回字段名与值的字典数据 并添加到 _mapping属性
_fields 属性 返回字符名的元组
_mapping 属性 返回字段名与值的字典数据

一般使用:

注意: 我们每一次迭代Result对象, 得到的是Row对象

通过属性取值

  1. result = conn.execute(text("select x, y from some_table"))
  2. for row in result:
  3. y = row.y
  4. # illustrate use with Python f-strings
  5. print(f"Row: {row.x} {row.y}")

在ORM中, select(Article)的属性是Article, select(Article.title)的属性是title

通过元组解包

  1. result = conn.execute(text("select x, y from some_table"))
  2. for x, y in result:
  3. # ...

通过索引取值

  1. result = conn.execute(text("select x, y from some_table"))
  2. for row in result:
  3. x = row[0]

MetaData

MetaData是包含TableEngine的对象, 也就是说它主要是用来管理Table (表)的

下面列出MetaData对象的一些方法

方法 参数 描述
clear 清除此元数据中的所有表对象
create_all(bind=None, tables=None, checkfirst=True) bind: 数据库Engine tables: Table对象列表 checkfirst: 是否 仅不存在表时 创建 在数据库中创建 元数据中的所有表
drop_all(bind=None, tables=None, checkfirst=True) bind: 数据库Engine tables: Table对象列表 checkfirst: 是否 仅存在表时 删除 在数据库中删除 元数据中存储的所有表
remove(table) table: 表对象 从此元数据中删除给定的表对象
tables tables是属性, 无参数 返回Table的字段对象

内置函数

常用的SQL函数:

函数名 对应的SQL函数 描述
max MAX 返回一组值中的最大值
min MIN 返回一组值中的最小值
count COUNT 返回匹配指定条件的行数, 没有参数时为: COUNT(*)
sum SUM 计算一组值的总和
rank RAND 产生 0 至 1 之间的随机数
concat CONCAT 多个字符串连接为一个字符串
char_length CHAR_LENGTH 计算字符串字符数
coalesce COALESCE 接受一系列的表达式或列, 返回第一个非空的值
session_user SESSION_USER 返回当前连接的当前用户名和主机名, 形如: root@localhost
user USER 返回连接的当前用户名和主机名, 形如: root@localhost
current_user CURRENT_USER 返回用户名和主机名, 形如: root@localhost
current_date CURRENT_DATE 函数返回当前日期, 格式: YYYY-MM-DD
current_time CURRENT_TIME 返回当前时间, 格式: HH-MM-SS
current_timestamp CURRENT_TIMESTAMP 返回当前日期和时间, 格式: YYYY-MM-DD HH-MM-SS
localtime LOCALTIME 返回当前日期和时间, 格式: YYYY-MM-DD HH-MM-SS
localtimestamp LOCALTIMESTAMP 返回当前日期和时间, 格式: YYYY-MM-DD HH-MM-SS
now NOW 返回当前日期和时间, 格式: YYYY-MM-DD HH-MM-SS
sysdate SYSDATE 返回当前日期和时间, 格式: YYYY-MM-DD HH:MM:SS
array_agg ARRAY_AGG PostgreSql可用, 把表达式变成一个数组
dense_rank DENSE_RANK 用于排名, 见: MySQL DENSE_RANK() 函数
percent_rank PERCENT_RANK 计算分区或结果集中行的百分位数排名
Function 描述一个SQL函数, 见: Function
GenericFunction 见: GenericFunction
grouping_sets GROUPING SETS 定义分组集, 见: SQL Grouping Sets运算符
rollup ROLLUP 生成小计和总计, 见: MySQL ROLLUP
cube 见: cube
cume_dist 见: cume_dist

全部函数见: SQL and Generic Functions

使用例子:

  1. from sqlalchemy import create_engine
  2. from sqlalchemy.orm import declarative_base, sessionmaker
  3. from sqlalchemy import Column, Integer, String
  4. from sqlalchemy import select, func
  5. # 导入公共基类
  6. Base = declarative_base()
  7. # 数据库配置
  8. DATABASE_CONFIG = {
  9. "username": "root",
  10. "password": "123456",
  11. "host": "localhost",
  12. "database": "test"
  13. }
  14. # 连接mysql
  15. engine = create_engine("mysql+pymysql://{username}:{password}@{host}/{database}".format(**DATABASE_CONFIG),
  16. echo=True, future=True)
  17. Session = sessionmaker(bind=engine)
  18. class Teacher(Base):
  19. __tablename__ = "teacher"
  20. tid = Column("tid", Integer, primary_key=True, autoincrement=True)
  21. name = Column("name", String(10), nullable=False, comment="教师名")
  22. with Session() as session:
  23. session_res = session.execute(
  24. select(func.count()).select_from(Teacher)
  25. )
  26. # (3,)
  27. print(session_res.one())

Column定义

一个Column即表的一列数据, 和我们用SQL语句定义一列数据一样, 参数主要包括: 字段名、字段类型、约束条件, 比如:

  1. from sqlalchemy import Column, String
  2. Column("name", String(30), unique=True, nullable=False, comment='姓名')

字段类型, 一般你可以用直接指定数据库的字段类型, 也可以让SQLAlchemy的DDL自动选择字段类型

直接使用数据库的字段类型

字段类型 描述
ARRAY(item_type, ...) 数组类型, 目前只支持PostgreSQL, 因此建议用: sqlalchemy.dialects.postgresql.ARRAY
BIGINT SQL BIGINT类型
BINARY(length) SQL BINARY类型
BLOB SQL BLOB类型
BOOLEAN SQL布尔类型
CHAR SQLCHAR类型
CLOB SQL CLOB型
DATE SQL DATE期类型
DATETIME SQL DATETIME类型
DECIMAL SQL DECIMAL类型
FLOAT SQL FLOAT类型
INT sqlalchemy.sql.sqltypes.INTEGER的别名
INTEGER SQL INT或INTEGER类型
JSON SQL JSON类型
NCHAR SQL NChar类型
NUMERIC SQL NUMERIC类型
NVARCHAR SQL NVARCHAR类型
REAL SQL REAL类型
SMALLINT SQL SMALLINT类型
TEXT SQL TEXT类型
TIME SQL TIME类型
TIMESTAMP SQL TIMESTAMP类型
VARBINARY SQLVARBINARY类型
VARCHAR SQL VARCHAR类型

关于SQL的数据类型, 见: SQL 数据类型

自动转化的字段类型

字段类型 描述 通常对应的字段类型
Boolean 布尔数据类型 Boolean或SMALLINT
String 所有字符串和字符类型的基 VARCHAR
Text 大小可变的字符串类型 CLOB或TEXT
LargeBinary 大的二进制字节数据类型 BLOB或BYTEA
Unicode 长度可变的Unicode字符串类型
UnicodeText 无限长的Unicode字符串类型
SmallInteger 较小的一种 int 整数 SMALLINT
Integer int类型
BigInteger BIGINT数据类型 BIGINT
Numeric 用于固定精度数字的类型 NUMERIC或DECIMAL
Float 浮点类型 FLOAT 或 REAL .
Date datetime.date类型
Time datetime.time类型
DateTime datetime.datetime类型
Interval datetime.timedelta类型
Enum 枚举类型 ENUM或VARCHAR
PickleType 保存使用pickle序列化的python对象 二进制类型
SchemaType 将类型标记为可能需要架构级DDL才能使用
MatchType 引用match运算符的返回类型 MySQL的是浮点型

约束条件

约束条件和其他参数 描述
autoincrement 是否自增
default 设置默认参数, 可以是可调用对象
index 是否创建索引
info SchemaItem.info的属性
nullable 是否非空, Falsenot null
primary_key 是否为主键
unique 是否唯一
comment 注释字段, 会写入SQL 中的COMMENT
onupdate 调用更新数据时传入的值, 如: updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
server_default 为SQLAlchemy的DDL设置默认值, 可以是str unicode text(), 如: sqlalchemy.func.now()

注 : sqlalchemy.func, 用于生成SQL函数表达式, 详情见: 内置函数

Column的例子:

  1. from sqlalchemy import DateTime, func
  2. class Data(Base):
  3. __tablename__ = 'data'
  4. id = Column(Integer, primary_key=True, index=True, autoincrement=True)
  5. created_at = Column(DateTime, server_default=func.now(), comment='创建时间')
  6. updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment='更新时间')

SQLAlchemy完全入门的更多相关文章

  1. SQLAlchemy 快速入门、基础知识

    SQLAlchemy 是Python 编程语言下的一款开源软件.提供了SQL工具包及对象关系映射(ORM)工具. ORM, 全称Object Relational Mapping, 中文叫做对象关系映 ...

  2. SQLAlchemy文档翻译

    想记录一下SQLAlchemy的入门学习,然后突发奇想觉得:为什么不直接翻译一下文档呢?于是顺手查了查怎么使用Gitbook,2333 于是就在Github开了项目,然后导入了Gitbook,开始写. ...

  3. SQLAlchemy 教程 —— 基础入门篇

    SQLAlchemy 教程 -- 基础入门篇 一.课程简介 1.1 实验内容 本课程带领大家使用 SQLAlchemy 连接 MySQL 数据库,创建一个博客应用所需要的数据表,并介绍了使用 SQLA ...

  4. (转)SQLAlchemy入门和进阶

    URL:https://zhuanlan.zhihu.com/p/27400862 https://www.cnblogs.com/mrchige/p/6389588.html---SQLAlchem ...

  5. sqlalchemy入门记录

    前言 发现翻译全文时间比较久,所以先整个简单的使用说明吧 安装SQLAlchemy pip install sqlalchemy 查看SQLAlchemy版本 In [1]: import sqlal ...

  6. SQLAlchemy入门

    什么是SQLAlchemy? 是Python连接SQL数据库的一种方式,需要通过驱动来连接. 是Python中使用最广泛的ORM(对象关系映射)工具之一,即把数据库中的二维表结构映射成一个list对象 ...

  7. Flask入门之SQLAlchemy数据库连接操作(第15讲)

    一.库安装 Flask-SQLAlchemy 2 SQLAlchemy 1.0.8 二.进入venv 三.切换到项目Sample\ 文件夹,进入manager.py 的shell python man ...

  8. Flask入门之SQLAlchemy配置与数据库连接

    1. 安装SQLAlchemy pip install flask-sqlalchemy 2. 导入和配置 from flask_sqlalchemy import SQLAlchemy basedi ...

  9. 新手入门Sqlalchemy

    此文已由作者尤炳棋授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 入职考拉半年多,一直在从事KLQA平台的开发,KLQA平台后端是用基于python的flask框架搭建的.F ...

随机推荐

  1. socket通道

    一.socket 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket. 就是两个进程,跨计算机,他俩需要通讯的话,需要通过网络对接起来.这就是 socket 的作 ...

  2. MySQL数据库如何查看数据文件的存放位置

    SHOW GLOBAL VARIABLES;

  3. Decorator 模式转载

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://tianli.blog.51cto.com/190322/35287 摘要:本文深 ...

  4. ciscn_2019_s_6

    例行检查 没有开启nx保护,考虑用shellcode来做这道题 程序放入ida查看 我们可以输入48个字符覆盖0使printf打印出bp的值 继续看这里,buf的大小实际上只有0x38的大小,但是re ...

  5. CF628B New Skateboard 题解

    Content 有一个长度为 \(n\) 的数字串 \(s\),求出有多少个子串能够被 \(4\) 整除. 数据范围:\(1\leqslant n\leqslant 3\times 10^5\). S ...

  6. wget 用法http://www.cnblogs.com/cy-8593/p/9523035.html

    http://www.cnblogs.com/cy-8593/p/9523035.html

  7. python2升级到python3 yum不可用解决方案

    /usr/libexec/urlgrabber-ext-down /usr/bin/yum 这两个文件解释器 写 /usr/bin/python2

  8. Sort 多列正排序,倒排序

    linux sort 多列正排序,倒排序 转自https://segmentfault.com/a/1190000005713784  发布于 2016-06-14  sort是在Linux里非常常用 ...

  9. tomcat Address already in use: JVM_Bind

    运行多个tomcat时,出现tomcat Address already in use: JVM_Bind这个错误,可以按照如下方式解决: 修改F:\tomcat20111101\apache-tom ...

  10. 【LeetCode】359. Logger Rate Limiter 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 字典 日期 题目地址:https://leetcode ...