使用数据库元数据
SQLAlchemy 1.4 / 2.0 Tutorial
此页是 SQLAlchemy 1.4/2.0教程 .
上一页: 处理事务和DBAPI |下一步: |next|
使用数据库元数据
随着引擎和SQL执行的停止,我们准备开始一些炼金术。SQLAlchemy Core和ORM的核心元素是SQL表达式语言,它允许流畅、可组合地构造SQL查询。这些查询的基础是表示数据库概念(如表和列)的Python对象。这些对象统称为 database metadata .
SQLAlchemy中数据库元数据最常见的基本对象是 MetaData
, Table
和 Column
. 下面的部分将说明如何在面向核心的样式和面向ORM的样式中使用这些对象。
ORM读者们,跟我们呆在一起!
与其他部分一样,核心用户可以跳过ORM部分,但是ORM用户最好从两个角度熟悉这些对象。
使用表对象设置元数据
当我们使用关系数据库时,我们创建和查询的基本结构称为 桌子 . 在SQLAlchemy中,“table”由一个类似于 Table
.
要开始使用SQLAlchemy表达式语言,我们需要 Table
对象,这些对象表示我们感兴趣的所有数据库表。每个 Table
可能是 宣布 ,这意味着我们在源代码中明确说明了表的外观或可能是什么 反射 ,这意味着我们根据特定数据库中已经存在的内容生成对象。这两种方法也可以在许多方面混合使用。
无论我们将声明还是反映我们的表,我们从一个集合开始,这个集合将是我们放置表的地方,称为 MetaData
对象。这个对象本质上是一个 facade 在Python字典中存储了一系列 Table
对象键控到其字符串名称。构造此对象的方式如下:
>>> from sqlalchemy import MetaData >>> metadata_obj = MetaData()
有一间单人房 MetaData
对象用于整个应用程序是最常见的情况,表示为应用程序中单个位置的模块级变量,通常是在“模型”或“dbschema”类型的包中。可以有多个 MetaData
集合也是如此,但是,如果一系列 Table
相互关联的对象属于单个 MetaData
收藏。
一旦我们拥有了 MetaData
对象,我们可以声明一些 Table
物体。本教程将从经典的SQLAlchemy教程模型开始,即表的模型 user
,例如,它将表示网站的用户和表 address
,表示与中的行关联的电子邮件地址列表 user
表。我们通常分配每个 Table
对象,该变量将是我们在应用程序代码中引用表的方式:
>>> from sqlalchemy import Table, Column, Integer, String >>> user_table = Table( ... "user_account", ... metadata_obj, ... Column('id', Integer, primary_key=True), ... Column('name', String(30)), ... Column('fullname', String) ... )
我们可以观察到 Table
construct看起来很像SQL CREATE TABLE语句;从表名开始,然后列出每列,其中每列都有一个名称和一个数据类型。我们上面使用的对象是:
Column
-表示数据库表中的列,并将其自身分配给Table
对象。这个Column
通常包括一个字符串名和一个类型对象。收藏Column
对象的父对象Table
通常通过位于Table.c
::>>> user_table.c.name Column('name', String(length=30), table=<user_account>) >>> user_table.c.keys() ['id', 'name', 'fullname']
Integer
,String
-这些类表示SQL数据类型,可以传递给Column
无论是否需要实例化。上面,我们想给“name”列指定一个“30”的长度,因此我们实例化了String(30)
. 但是对于“id”和“fullname”,我们没有指定这些,所以我们可以发送类本身。
参见
参考和API文档 MetaData
, Table
和 Column
位于 用元数据描述数据库 . 数据类型的参考文档位于 列和数据类型 .
在下一节中,我们将说明 Table
这是为了产生 DDL 在特定的数据库连接上。但首先我们要宣布第二个 Table
.
声明简单约束
第一 Column
在上面 user_table
包括 Column.primary_key
参数,它是指示 Column
应该是此表的主键的一部分。主键本身通常隐式声明,并由 PrimaryKeyConstraint
我们可以在 Table.primary_key
属性 Table
对象:
>>> user_table.primary_key PrimaryKeyConstraint(Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False))
最典型的显式声明的约束是 ForeignKeyConstraint
对象与数据库相对应 foreign key constraint . 当我们声明相互关联的表时,SQLAlchemy使用这些外键约束声明的存在,不仅使它们在CREATE语句中发送到数据库,而且还帮助构造SQL表达式。
A ForeignKeyConstraint
它只涉及目标表上的一个列,通常通过 ForeignKey
对象。下面我们宣布第二张桌子 address
将有一个外键约束引用 user
表:
>>> from sqlalchemy import ForeignKey >>> address_table = Table( ... "address", ... metadata_obj, ... Column('id', Integer, primary_key=True), ... Column('user_id', ForeignKey('user_account.id'), nullable=False), ... Column('email_address', String, nullable=False) ... )
上面的第三个约束条件中也使用了“非空”约束 Column.nullable
参数。
小技巧
当使用 ForeignKey
对象 Column
定义,我们可以省略该数据类型 Column
;它是从相关列的值自动推断出来的,在上面的示例中 Integer
的数据类型 user_account.id
列。
在下一节中,我们将为 user
和 address
表以查看完成的结果。
向数据库发送DDL
我们构建了一个相当复杂的对象层次结构来表示两个数据库表,从根开始 MetaData
对象,然后分成两部分 Table
对象,每个对象都包含 Column
和 Constraint
物体。随着Core和ORM的进一步发展,这个对象结构将成为我们执行的大多数操作的中心。
我们可以用这个结构做的第一件有用的事情是发出createtable语句,或者 DDL ,以插入和查询来自它们的数据。通过调用 MetaData.create_all()
我们的方法 MetaData
,发送 Engine
引用目标数据库:
>>> metadata_obj.create_all(engine) BEGIN (implicit) PRAGMA main.table_...info("user_account") ... PRAGMA main.table_...info("address") ... CREATE TABLE user_account ( id INTEGER NOT NULL, name VARCHAR(30), fullname VARCHAR, PRIMARY KEY (id) ) ... CREATE TABLE address ( id INTEGER NOT NULL, user_id INTEGER NOT NULL, email_address VARCHAR NOT NULL, PRIMARY KEY (id), FOREIGN KEY(user_id) REFERENCES user_account (id) ) ... COMMIT
默认情况下,DDL create进程包含一些SQLite特定的PRAGMA语句,这些语句在发出create之前测试每个表是否存在。在BEGIN/COMMIT对中还包含了完整的一系列步骤,以适应事务性DDL(SQLite实际上支持事务性DDL,但是 sqlite3
数据库驱动程序在“自动提交”模式下运行DDL)。
create进程还负责按照正确的顺序发出create语句;上面,外键约束依赖于 user
表已存在,因此 address
然后创建表。在更复杂的依赖场景中,外键约束也可以在使用ALTER之后应用于表。
这个 MetaData
对象还具有 MetaData.drop_all()
方法,该方法将以与为删除架构元素而发出CREATE相反的顺序发出DROP语句。
迁移工具通常是合适的
总体而言,的创建/删除功能 MetaData
对于测试套件、小型和/或新应用程序以及使用短期数据库的应用程序非常有用。然而,对于长期管理应用程序数据库模式,模式管理工具,如 Alembic ,它构建在SQLAlchemy基础上,可能是一个更好的选择,因为它可以随着应用程序设计的变化,管理和协调随着时间的推移逐渐改变固定数据库模式的过程。
用ORM定义表元数据
仅限ORM的这一节将提供一个示例,使用更加以ORM为中心的配置范例声明与上一节中说明的相同的数据库结构。使用ORM时,我们声明的过程 Table
元数据通常与声明过程相结合 mapped 上课。映射的类是我们想要创建的任何Python类,然后它将具有链接到数据库表中的列的属性。虽然有几种不同的实现方式,但最常见的方式是 declarative ,并允许我们声明用户定义的类和 Table
一次使用元数据。
设置注册表
使用ORM时 MetaData
集合仍然存在,但是它本身包含在名为 registry
. 我们创建一个 registry
通过构造它:
>>> from sqlalchemy.orm import registry >>> mapper_registry = registry()
以上 registry
,在构造时,自动包含 MetaData
对象,该对象将存储 Table
物体::
>>> mapper_registry.metadata MetaData()
而不是声明 Table
对象,现在我们将通过应用于映射类的指令间接声明它们。在最常见的方法中,每个映射的类都从一个称为 陈述性基础 . 我们从 registry
使用 registry.generate_base()
方法:
>>> Base = mapper_registry.generate_base()
小技巧
创建 registry
“声明性基”类可以使用历史上熟悉的 declarative_base()
功能:
from sqlalchemy.orm import declarative_base Base = declarative_base()
声明映射类
这个 Base
上面的对象是一个Python类,它将作为我们声明的ORM映射类的基类。我们现在可以为 user
和 address
新课程表 User
和 Address
::
>>> from sqlalchemy.orm import relationship >>> class User(Base): ... __tablename__ = 'user_account' ... ... id = Column(Integer, primary_key=True) ... name = Column(String(30)) ... fullname = Column(String) ... ... addresses = relationship("Address", back_populates="user") ... ... def __repr__(self): ... return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})" >>> class Address(Base): ... __tablename__ = 'address' ... ... id = Column(Integer, primary_key=True) ... email_address = Column(String, nullable=False) ... user_id = Column(Integer, ForeignKey('user_account.id')) ... ... user = relationship("User", back_populates="addresses") ... ... def __repr__(self): ... return f"Address(id={self.id!r}, email_address={self.email_address!r})"
以上两个类现在是我们的映射类,可以在ORM持久性和查询操作中使用,这将在后面介绍。但它们也包括 Table
对象是作为声明性映射过程的一部分生成的,并且与我们在前面的核心部分中直接声明的对象等效。我们可以看到这些 Table
使用 .__table__
属性:
>>> User.__table__ Table('user_account', MetaData(), Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False), Column('name', String(length=30), table=<user_account>), Column('fullname', String(), table=<user_account>), schema=None)
这个 Table
对象是从基于 .__tablename__
属性,以及通过使用 Column
为类中的类级别属性指定的对象。这些 Column
对象通常可以在构造函数中没有显式的“name”字段来声明,因为声明性进程将根据使用的属性名自动命名它们。
参见
声明性映射 -声明性类映射概述
其他映射类详细信息
要快速解释上述类,请注意以下属性:
the classes have an automatically generated __init__() method -默认情况下,两个类都接收
__init__()
方法,该方法允许对象的参数化构造。我们可以自由提供我们自己的__init__()
方法也是。这个__init__()
允许我们创建User
和Address
传递属性名,上面大部分属性名直接链接到Column
对象,作为参数名:>>> sandy = User(name="sandy", fullname="Sandy Cheeks")
有关此方法的详细信息,请访问 缺省构造 .
we provided a __repr__() method -这是 完全可选 ,并且是严格的,以便我们的自定义类具有描述性字符串表示形式,并且在其他情况下不需要:
>>> sandy User(id=None, name='sandy', fullname='Sandy Cheeks')
上面有一件有趣的事需要注意的是
id
属性自动返回None
而不是升高AttributeError
对于缺少属性的Python通常的行为也是如此。我们还包括一个双向关系 -这是另一个 完全可选 在这里我们使用了一个名为
relationship()
在两个类上,这向ORM指示User
和Address
类在 one to many / many to one 关系。使用relationship()
我们可以在后面的教程中演示它的行为 不需要的 为了定义Table
结构。
向数据库发送DDL
此节的名称与节的名称相同 向数据库发送DDL 就核心问题进行了讨论。这是因为用我们的ORM映射类发出DDL没有什么不同。如果我们想为 Table
作为声明性映射类的一部分创建的对象,我们仍然可以使用 MetaData.create_all()
像以前一样。
在我们的例子中,我们已经生成了 user
和 address
SQLite数据库中的表。如果我们还没有这样做,我们就可以自由地利用 MetaData
与我们的 registry
和ORM声明性基类,使用 MetaData.create_all()
::
# emit CREATE statements given ORM registry mapper_registry.metadata.create_all(engine) # the identical MetaData object is also present on the # declarative base Base.metadata.create_all(engine)
组合核心表声明和ORM声明性声明
作为之前在 声明映射类 ,我们也可以利用 Table
我们直接在节中创建的对象 使用表对象设置元数据 与来自 declarative_base()
生成的基类。
这种形式称为 hybrid table ,它包括分配给 .__table__
属性,而不是让声明性进程生成它:
class User(Base): __table__ = user_table addresses = relationship("Address", back_populates="user") def __repr__(self): return f"User({self.name!r}, {self.fullname!r})" class Address(Base): __table__ = address_table user = relationship("User", back_populates="addresses") def __repr__(self): return f"Address({self.email_address!r})"
上面两个类与我们在前面的映射示例中声明的类是等价的。
传统的“声明性基础”方法使用 __tablename__
自动生成 Table
对象仍然是声明表元数据的最常用方法。然而,不管它实现的ORM映射功能,就表声明而言,它只是在 Table
建造师。
接下来,当我们在本节中讨论ORM的数据操作时,我们将参考上面的ORM映射类 使用ORM插入行 .
表反射
为了完成关于使用表元数据的部分,我们将演示在本节开头提到的另一个操作,即 桌子反射 . 表反射是指生成 Table
通过读取数据库的当前状态。而在前面的章节中我们已经声明 Table
对象,然后将DDL发送到数据库,反射过程则相反。
作为反射的示例,我们将创建一个新的 Table
对象,该对象表示 some_table
我们在本文档前面几节中手动创建的对象。执行此操作的方式也有一些变化,但最基本的是构造 Table
对象,给定表的名称和一个 MetaData
集合,而不是指示单个 Column
和 Constraint
对象,则将其传递给目标 Engine
使用 Table.autoload_with
参数:
>>> some_table = Table("some_table", metadata_obj, autoload_with=engine) BEGIN (implicit) PRAGMA main.table_...info("some_table") [raw sql] () SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE name = ? AND type = 'table' [raw sql] ('some_table',) PRAGMA main.foreign_key_list("some_table") ... PRAGMA main.index_list("some_table") ... ROLLBACK
在过程结束时, some_table
对象现在包含有关 Column
对象,并且该对象的使用方式与 Table
我们明确声明::
>>> some_table Table('some_table', MetaData(), Column('x', INTEGER(), table=<some_table>), Column('y', INTEGER(), table=<some_table>), schema=None)
参见
阅读有关表和架构反射的更多信息,请访问 反映数据库对象 .
对于与ORM相关的表反射变体,部分 用反射表声明性地映射 包括可用选项的概述。
SQLAlchemy 1.4 / 2.0 Tutorial
下一个教程部分: 使用数据