使用数据库元数据

优质
小牛编辑
177浏览
2023-12-01

SQLAlchemy 1.4 / 2.0 Tutorial

此页是 SQLAlchemy 1.4/2.0教程 .

上一页: 处理事务和DBAPI |下一步: |next|

使用数据库元数据

随着引擎和SQL执行的停止,我们准备开始一些炼金术。SQLAlchemy Core和ORM的核心元素是SQL表达式语言,它允许流畅、可组合地构造SQL查询。这些查询的基础是表示数据库概念(如表和列)的Python对象。这些对象统称为 database metadata .

SQLAlchemy中数据库元数据最常见的基本对象是 MetaDataTableColumn . 下面的部分将说明如何在面向核心的样式和面向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语句;从表名开始,然后列出每列,其中每列都有一个名称和一个数据类型。我们上面使用的对象是:

  • Table -表示数据库表并将其自身分配给 MetaData 收集。

  • 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']
  • IntegerString -这些类表示SQL数据类型,可以传递给 Column 无论是否需要实例化。上面,我们想给“name”列指定一个“30”的长度,因此我们实例化了 String(30) . 但是对于“id”和“fullname”,我们没有指定这些,所以我们可以发送类本身。

参见

参考和API文档 MetaDataTableColumn 位于 用元数据描述数据库 . 数据类型的参考文档位于 列和数据类型 .

在下一节中,我们将说明 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 列。

在下一节中,我们将为 useraddress 表以查看完成的结果。

向数据库发送DDL

我们构建了一个相当复杂的对象层次结构来表示两个数据库表,从根开始 MetaData 对象,然后分成两部分 Table 对象,每个对象都包含 ColumnConstraint 物体。随着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映射类的基类。我们现在可以为 useraddress 新课程表 UserAddress ::

>>> 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__() 允许我们创建 UserAddress 传递属性名,上面大部分属性名直接链接到 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指示 UserAddress 类在 one to many / many to one 关系。使用 relationship() 我们可以在后面的教程中演示它的行为 不需要的 为了定义 Table 结构。

向数据库发送DDL

此节的名称与节的名称相同 向数据库发送DDL 就核心问题进行了讨论。这是因为用我们的ORM映射类发出DDL没有什么不同。如果我们想为 Table 作为声明性映射类的一部分创建的对象,我们仍然可以使用 MetaData.create_all() 像以前一样。

在我们的例子中,我们已经生成了 useraddress 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 集合,而不是指示单个 ColumnConstraint 对象,则将其传递给目标 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

下一个教程部分: 使用数据