SqlAlchemy 2.0 中文文档(十一)(4)

简介: SqlAlchemy 2.0 中文文档(十一)

SqlAlchemy 2.0 中文文档(十一)(3)https://developer.aliyun.com/article/1562989


多对多

多对多在两个类之间添加了一个关联表。这个关联表几乎总是以一个核心Table对象或其他核心可选项(如Join对象)的形式给出,并通过relationship()函数的relationship.secondary参数指示。通常,Table使用与声明基类关联的MetaData对象,以便ForeignKey指令可以定位要链接的远程表:

from __future__ import annotations
from sqlalchemy import Column
from sqlalchemy import Table
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
    pass
# note for a Core table, we use the sqlalchemy.Column construct,
# not sqlalchemy.orm.mapped_column
association_table = Table(
    "association_table",
    Base.metadata,
    Column("left_id", ForeignKey("left_table.id")),
    Column("right_id", ForeignKey("right_table.id")),
)
class Parent(Base):
    __tablename__ = "left_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    children: Mapped[List[Child]] = relationship(secondary=association_table)
class Child(Base):
    __tablename__ = "right_table"
    id: Mapped[int] = mapped_column(primary_key=True)

提示

上面的“关联表”已建立了引用关系的外键约束,这些约束指向关系两侧的两个实体表。association.left_idassociation.right_id的数据类型通常是从引用表的数据类型推断出来的,可以省略。虽然 SQLAlchemy 没有要求,但建议将指向两个实体表的列建立在唯一约束或更常见的主键约束中;这样可以确保无论应用程序端是否存在问题,表中都不会持续存在重复行:

association_table = Table(
    "association_table",
    Base.metadata,
    Column("left_id", ForeignKey("left_table.id"), primary_key=True),
    Column("right_id", ForeignKey("right_table.id"), primary_key=True),
)

设置双向多对多

对于双向关系,关系的两侧都包含一个集合。使用relationship.back_populates进行指定,并且对于每个relationship()指定共同的关联表:

from __future__ import annotations
from sqlalchemy import Column
from sqlalchemy import Table
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
    pass
association_table = Table(
    "association_table",
    Base.metadata,
    Column("left_id", ForeignKey("left_table.id"), primary_key=True),
    Column("right_id", ForeignKey("right_table.id"), primary_key=True),
)
class Parent(Base):
    __tablename__ = "left_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    children: Mapped[List[Child]] = relationship(
        secondary=association_table, back_populates="parents"
    )
class Child(Base):
    __tablename__ = "right_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    parents: Mapped[List[Parent]] = relationship(
        secondary=association_table, back_populates="children"
    )

使用“secondary”参数的后期评估形式

relationship()relationship.secondary参数还接受两种不同的“后期评估”形式,包括字符串表名称以及 lambda 可调用。有关背景和示例,请参见使用“secondary”参数的后期评估形式进行多对多关系部分。

使用集合、列表或其他集合类型进行多对多

配置多对多关系的集合与一对多的配置相同,如在使用集合、列表或其他集合类型进行一对多关系中所述。对于使用Mapped进行注释的映射,集合可以由Mapped泛型类内部使用的集合类型指示,例如set

class Parent(Base):
    __tablename__ = "left_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    children: Mapped[Set["Child"]] = relationship(secondary=association_table)

当使用命令式映射(即一对多情况)的非注释形式时,可以通过relationship.collection_class参数传递用作集合的 Python 类。

另请参阅

自定义集合访问 - 包含有关集合配置的进一步详细信息,包括一些将relationship()映射到字典的技术。

从多对多表中删除行

对于relationship()relationship.secondary参数是唯一的行为,这里指定的Table将自动受到 INSERT 和 DELETE 语句的影响,当对象被添加或从集合中删除时。没有必要手动从此表中删除。从集合中删除记录的行为将导致刷新时删除该行的效果:

# row will be deleted from the "secondary" table
# automatically
myparent.children.remove(somechild)

当子对象直接传递给Session.delete()时,“次要”表中的行如何删除经常会引起一个问题:

session.delete(somechild)

这里有几种可能性:

  • 如果从ParentChild有一个relationship(),但是没有将特定的Child链接到每个Parent的反向关系,SQLAlchemy 不会意识到删除此特定Child对象时需要维护链接到Parent的“次要”表。不会删除“次要”表的删除。
  • 如果存在将特定的Child链接到每个Parent的关系,假设它被称为Child.parents,SQLAlchemy 默认会加载Child.parents集合以定位所有Parent对象,并从建立此链接的“次要”表中删除每行。请注意,此关系不需要是双向的;SQLAlchemy 严格地查看与被删除的Child对象相关联的每个relationship()
  • 在这里的一个性能较高的选项是使用数据库中使用的外键的 ON DELETE CASCADE 指令。假设数据库支持这个特性,数据库本身可以被设置为在“子”中的引用行被删除时自动删除“次要”表中的行。在这种情况下,SQLAlchemy 可以被指示放弃主动加载Child.parents集合,使用relationship()上的relationship.passive_deletes指令;参见使用 ORM 关系的外键 ON DELETE 级联以获取更多关于此的详细信息。

再次注意,这些行为与与relationship()一起使用的relationship.secondary选项相关。如果处理的是显式映射的关联表,并且这些表出现在相关relationship()relationship.secondary选项中,则可以改用级联规则来自动删除实体,以响应相关实体的删除 - 有关此功能的信息,请参阅级联。

另请参阅

在多对多关系中使用级联删除

在多对多关系中使用外键 ON DELETE

设置双向多对多

对于双向关系,关系的两端都包含一个集合。使用relationship.back_populates来指定,并且对于每个relationship()都要指定共同的关联表:

from __future__ import annotations
from sqlalchemy import Column
from sqlalchemy import Table
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
    pass
association_table = Table(
    "association_table",
    Base.metadata,
    Column("left_id", ForeignKey("left_table.id"), primary_key=True),
    Column("right_id", ForeignKey("right_table.id"), primary_key=True),
)
class Parent(Base):
    __tablename__ = "left_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    children: Mapped[List[Child]] = relationship(
        secondary=association_table, back_populates="parents"
    )
class Child(Base):
    __tablename__ = "right_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    parents: Mapped[List[Parent]] = relationship(
        secondary=association_table, back_populates="children"
    )

使用延迟评估形式的“secondary”参数

relationship()relationship.secondary参数还接受两种不同的“延迟评估”形式,包括字符串表名以及 lambda 可调用。有关背景和示例,请参阅使用“secondary”参数的延迟评估形式进行多对多关系部分。

使用集合、列表或其他集合类型进行多对多关系

对于多对多关系的集合配置与一对多完全相同,如使用集合、列表或其他集合类型进行一对多关系中所述。对于使用Mapped进行注释的映射,可以通过Mapped泛型类中使用的集合类型来指示集合,例如set

class Parent(Base):
    __tablename__ = "left_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    children: Mapped[Set["Child"]] = relationship(secondary=association_table)

当使用非注释形式,包括命令式映射时,就像一对多一样,可以使用relationship.collection_class参数传递要用作集合的 Python 类。

另请参阅

自定义集合访问 - 包含有关集合配置的进一步详细信息,包括一些将relationship()映射到字典的技术。

从多对多表中删除行

relationship()参数中唯一的行为是,指定的Table在对象被添加或从集合中删除时会自动受到 INSERT 和 DELETE 语句的影响。无需手动从此表中删除。从集合中删除记录的行为将导致在 flush 时删除该行:

# row will be deleted from the "secondary" table
# automatically
myparent.children.remove(somechild)

经常出现的一个问题是当直接将子对象传递给Session.delete()时如何删除“secondary”表中的行:

session.delete(somechild)

这里有几种可能性:

  • 如果从ParentChild有一个relationship(),但没有一个反向关系将特定的Child与每个Parent关联起来,SQLAlchemy 将不会意识到当删除这个特定的Child对象时,它需要维护将其与Parent链接起来的“secondary”表。不会删除“secondary”表。
  • 如果有一个将特定的Child与每个Parent关联起来的关系,假设它被称为Child.parents,SQLAlchemy 默认会加载Child.parents集合以定位所有Parent对象,并从建立此链接的“secondary”表中删除每一行。请注意,此关系不需要是双向的;SQLAlchemy 严格查看与正在删除的Child对象相关联的每一个relationship()
  • 这里的一个性能更高的选项是与数据库一起使用 ON DELETE CASCADE 指令。假设数据库支持这个功能,数据库本身可以被设置为在“子”中的引用行被删除时自动删除“辅助”表中的行。在这种情况下,SQLAlchemy 可以被指示不要主动加载 Child.parents 集合,使用 relationship.passive_deletes 指令在 relationship() 上;有关此更多详细信息,请参阅 使用外键 ON DELETE cascade 处理 ORM 关系。

再次注意,这些行为relationship()relationship.secondary 选项相关。如果处理显式映射的关联表,而不是存在于相关 relationship()relationship.secondary 选项中的关联表,那么级联规则可以被用来在相关实体被删除时自动删除实体 - 有关此功能的信息,请参阅 级联。

另请参阅

使用多对多关系的级联删除

使用外键 ON DELETE 处理多对多关系

协会对象

协会对象模式是多对多关系的一种变体:当一个关联表包含除了那些与父表和子表(或左表和右表)的外键不同的额外列时,通常最理想的是将这些列映射到自己的 ORM 映射类。这个映射类被映射到了Table,在使用多对多模式时,它本来会被指定为 relationship.secondary

在关联对象模式中,不使用relationship.secondary参数;相反,将类直接映射到关联表。然后,两个独立的relationship()构造首先通过一对多将父侧链接到映射的关联类,然后通过多对一将映射的关联类链接到子侧,以形成从父对象到关联对象到子对象的单向关联对象关系。对于双向关系,使用四个relationship()构造将映射的关联类与父对象和子对象在两个方向上进行链接。

下面的示例说明了一个新的类Association,它映射到名为associationTable;此表现在包括一个额外的列称为extra_data,它是一个字符串值,与ParentChild之间的每个关联一起存储。通过将表映射到显式类,从ParentChild的基本访问明确使用了Association

from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
    pass
class Association(Base):
    __tablename__ = "association_table"
    left_id: Mapped[int] = mapped_column(ForeignKey("left_table.id"), primary_key=True)
    right_id: Mapped[int] = mapped_column(
        ForeignKey("right_table.id"), primary_key=True
    )
    extra_data: Mapped[Optional[str]]
    child: Mapped["Child"] = relationship()
class Parent(Base):
    __tablename__ = "left_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    children: Mapped[List["Association"]] = relationship()
class Child(Base):
    __tablename__ = "right_table"
    id: Mapped[int] = mapped_column(primary_key=True)

为了说明双向版本,我们添加了两个更多的relationship()构造,使用relationship.back_populates连接到现有的构造:

from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
    pass
class Association(Base):
    __tablename__ = "association_table"
    left_id: Mapped[int] = mapped_column(ForeignKey("left_table.id"), primary_key=True)
    right_id: Mapped[int] = mapped_column(
        ForeignKey("right_table.id"), primary_key=True
    )
    extra_data: Mapped[Optional[str]]
    child: Mapped["Child"] = relationship(back_populates="parents")
    parent: Mapped["Parent"] = relationship(back_populates="children")
class Parent(Base):
    __tablename__ = "left_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    children: Mapped[List["Association"]] = relationship(back_populates="parent")
class Child(Base):
    __tablename__ = "right_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    parents: Mapped[List["Association"]] = relationship(back_populates="child")

使用关联对象模式的直接形式需要在将子对象附加到父对象之前将其与关联实例关联;同样,从父对象到子对象的访问需要通过关联对象进行:

# create parent, append a child via association
p = Parent()
a = Association(extra_data="some data")
a.child = Child()
p.children.append(a)
# iterate through child objects via association, including association
# attributes
for assoc in p.children:
    print(assoc.extra_data)
    print(assoc.child)

为了增强关联对象模式,使得对Association对象的直接访问是可选的,SQLAlchemy 提供了关联代理扩展。该扩展允许配置属性,这些属性将通过单个访问实现两次“跳跃”,一次是到关联对象,另一次是到目标属性。

另请参阅

关联代理 - 允许在三类关联对象映射中在父对象和子对象之间直接进行“多对多”样式的访问。

警告

避免直接混合使用关联对象模式和多对多模式,因为这会导致数据可能以不一致的方式读取和写入,除非采取特殊步骤;关联代理通常用于提供更简洁的访问。有关此组合引入的注意事项的更详细背景,请参阅下一节将关联对象与多对多访问模式组合使用。

将关联对象与多对多访问模式结合使用

如前一节所述,关联对象模式不会自动与相同表/列的多对多模式集成。由此可知,读操作可能返回冲突数据,写操作也可能尝试刷新冲突更改,导致完整性错误或意外插入或删除。

为了说明,下面的示例配置了ParentChild之间的双向多对多关系,通过Parent.childrenChild.parents。同时,还配置了一个关联对象关系,即Parent.child_associations -> Association.childChild.parent_associations -> Association.parent之间的关系:

from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
    pass
class Association(Base):
    __tablename__ = "association_table"
    left_id: Mapped[int] = mapped_column(ForeignKey("left_table.id"), primary_key=True)
    right_id: Mapped[int] = mapped_column(
        ForeignKey("right_table.id"), primary_key=True
    )
    extra_data: Mapped[Optional[str]]
    # association between Assocation -> Child
    child: Mapped["Child"] = relationship(back_populates="parent_associations")
    # association between Assocation -> Parent
    parent: Mapped["Parent"] = relationship(back_populates="child_associations")
class Parent(Base):
    __tablename__ = "left_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    # many-to-many relationship to Child, bypassing the `Association` class
    children: Mapped[List["Child"]] = relationship(
        secondary="association_table", back_populates="parents"
    )
    # association between Parent -> Association -> Child
    child_associations: Mapped[List["Association"]] = relationship(
        back_populates="parent"
    )
class Child(Base):
    __tablename__ = "right_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    # many-to-many relationship to Parent, bypassing the `Association` class
    parents: Mapped[List["Parent"]] = relationship(
        secondary="association_table", back_populates="children"
    )
    # association between Child -> Association -> Parent
    parent_associations: Mapped[List["Association"]] = relationship(
        back_populates="child"
    )

使用此 ORM 模型进行更改时,对Parent.children进行的更改不会与在 Python 中对Parent.child_associationsChild.parent_associations进行的更改协调;虽然所有这些关系将继续正常运作,但一个上的更改不会显示在另一个上,直到Session过期,通常在Session.commit()之后会自动发生。

此外,如果发生冲突更改,例如同时添加新的Association对象并将相同相关的Child附加到Parent.children,则在工作单元刷新过程中会引发完整性错误,如下例所示:

p1 = Parent()
c1 = Child()
p1.children.append(c1)
# redundant, will cause a duplicate INSERT on Association
p1.child_associations.append(Association(child=c1))

直接将Parent.children附加Child也意味着在association表中建行,而不指定association.extra_data列的任何值,该列将接收NULL作为其值。

如果知道自己在做什么,使用上述映射是可以的;在很少使用“关联对象”模式的情况下使用多对多关系可能有充分的理由,因为在单个多对多关系中加载关系更容易,这也可以稍微优化“secondary”表在  SQL 语句中的使用方式,与两个分开的关系到显式关联类的使用方式相比。至少最好将relationship.viewonly参数应用于“secondary”关系,以避免发生冲突更改的问题,并防止将NULL写入附加的关联列,如下所示:

class Parent(Base):
    __tablename__ = "left_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    # many-to-many relationship to Child, bypassing the `Association` class
    children: Mapped[List["Child"]] = relationship(
        secondary="association_table", back_populates="parents", viewonly=True
    )
    # association between Parent -> Association -> Child
    child_associations: Mapped[List["Association"]] = relationship(
        back_populates="parent"
    )
class Child(Base):
    __tablename__ = "right_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    # many-to-many relationship to Parent, bypassing the `Association` class
    parents: Mapped[List["Parent"]] = relationship(
        secondary="association_table", back_populates="children", viewonly=True
    )
    # association between Child -> Association -> Parent
    parent_associations: Mapped[List["Association"]] = relationship(
        back_populates="child"
    )

上述映射不会将任何更改写入到数据库的Parent.childrenChild.parents,从而防止冲突的写入。然而,如果在相同事务或Session中对这些集合进行更改,那么对Parent.childrenChild.parents的读取将不一定匹配从Parent.child_associationsChild.parent_associations读取的数据。如果关联对象关系的使用不频繁,并且针对访问多对多集合的代码进行了精心组织以避免过时的读取(在极端情况下,直接使用Session.expire()来使集合在当前事务中刷新),那么这种模式可能是可行的。

上述模式的一种流行替代方案是,直接的多对多Parent.childrenChild.parents关系被一个扩展所取代,该扩展将通过Association类透明地代理,同时从 ORM 的角度保持一切一致。这个扩展被称为关联代理。

另请参阅

关联代理 - 允许在三类关联对象映射之间直接进行“多对多”样式的父子访问。### 将关联对象与多对多访问模式结合使用

如前所述,在上一节中,关联对象模式不会自动与同时针对相同表/列使用的多对多模式集成。由此可见,读取操作可能会返回冲突的数据,并且写入操作也可能尝试刷新冲突的更改,导致完整性错误或意外的插入或删除。

为了说明,下面的示例配置了ParentChild之间的双向多对多关系,通过Parent.childrenChild.parents。同时,还配置了一个关联对象关系,Parent.child_associations -> Association.childChild.parent_associations -> Association.parent

from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
    pass
class Association(Base):
    __tablename__ = "association_table"
    left_id: Mapped[int] = mapped_column(ForeignKey("left_table.id"), primary_key=True)
    right_id: Mapped[int] = mapped_column(
        ForeignKey("right_table.id"), primary_key=True
    )
    extra_data: Mapped[Optional[str]]
    # association between Assocation -> Child
    child: Mapped["Child"] = relationship(back_populates="parent_associations")
    # association between Assocation -> Parent
    parent: Mapped["Parent"] = relationship(back_populates="child_associations")
class Parent(Base):
    __tablename__ = "left_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    # many-to-many relationship to Child, bypassing the `Association` class
    children: Mapped[List["Child"]] = relationship(
        secondary="association_table", back_populates="parents"
    )
    # association between Parent -> Association -> Child
    child_associations: Mapped[List["Association"]] = relationship(
        back_populates="parent"
    )
class Child(Base):
    __tablename__ = "right_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    # many-to-many relationship to Parent, bypassing the `Association` class
    parents: Mapped[List["Parent"]] = relationship(
        secondary="association_table", back_populates="children"
    )
    # association between Child -> Association -> Parent
    parent_associations: Mapped[List["Association"]] = relationship(
        back_populates="child"
    )

当使用此 ORM 模型进行更改时,在 Python 中对Parent.children进行的更改不会与对Parent.child_associationsChild.parent_associations进行的更改协调;虽然所有这些关系都将继续正常运行,但在Session过期之前,一个的更改不会显示在另一个上,Session.commit()通常会在自动发生后使之过期。

另外,如果发生冲突的更改,例如同时添加一个新的Association对象,同时将相同的相关Child附加到Parent.children,则在工作单元刷新过程进行时,会引发完整性错误,如下例所示:

p1 = Parent()
c1 = Child()
p1.children.append(c1)
# redundant, will cause a duplicate INSERT on Association
p1.child_associations.append(Association(child=c1))

直接将Child附加到Parent.children也意味着在association表中创建行,而不指定association.extra_data列的任何值,该列的值将为NULL

如果你知道自己在做什么,像上面的映射那样使用映射是可以的;在很少使用“关联对象”模式的情况下使用多对多关系可能是有充分理由的,这是因为沿着单一的多对多关系加载关系是更容易的,这也可以略微优化“辅助”表在  SQL 语句中的使用方式,与如何使用两个到显式关联类的分离关系相比。至少应该将relationship.viewonly参数应用于“辅助”关系,以避免出现冲突更改的问题,并防止将NULL写入附加的关联列,如下所示:

class Parent(Base):
    __tablename__ = "left_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    # many-to-many relationship to Child, bypassing the `Association` class
    children: Mapped[List["Child"]] = relationship(
        secondary="association_table", back_populates="parents", viewonly=True
    )
    # association between Parent -> Association -> Child
    child_associations: Mapped[List["Association"]] = relationship(
        back_populates="parent"
    )
class Child(Base):
    __tablename__ = "right_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    # many-to-many relationship to Parent, bypassing the `Association` class
    parents: Mapped[List["Parent"]] = relationship(
        secondary="association_table", back_populates="children", viewonly=True
    )
    # association between Child -> Association -> Parent
    parent_associations: Mapped[List["Association"]] = relationship(
        back_populates="child"
    )

上面的映射不会将对Parent.childrenChild.parents的任何更改写入数据库,从而防止冲突写入。但是,如果在相同的事务或Session中对这些集合进行更改的地方读取Parent.childrenChild.parents将不一定与从Parent.child_associationsChild.parent_associations中读取的数据匹配。如果对关联对象关系的使用不频繁,并且针对访问多对多集合的代码进行了精心组织以避免过时读取(在极端情况下,直接使用Session.expire()来导致集合在当前事务中被刷新),那么这种模式可能是可行的。

一个流行的替代模式是,直接的多对多Parent.childrenChild.parents关系被一个扩展所取代,该扩展将通过Association类透明地代理,同时从 ORM 的角度保持一切一致。这个扩展被称为关联代理。

另请参阅

关联代理 - 允许在三类关联对象映射中直接实现“多对多”样式的父子访问。


SqlAlchemy 2.0 中文文档(十一)(5)https://developer.aliyun.com/article/1562991

相关文章
|
2月前
|
SQL 数据库 Python
SqlAlchemy 2.0 中文文档(十一)(3)
SqlAlchemy 2.0 中文文档(十一)
30 11
|
2月前
|
存储 SQL Python
SqlAlchemy 2.0 中文文档(十一)(5)
SqlAlchemy 2.0 中文文档(十一)
34 10
|
2月前
|
SQL API 数据库
SqlAlchemy 2.0 中文文档(十一)(1)
SqlAlchemy 2.0 中文文档(十一)
28 2
|
2月前
|
SQL 关系型数据库 API
SqlAlchemy 2.0 中文文档(十七)(4)
SqlAlchemy 2.0 中文文档(十七)
42 4
|
2月前
|
存储 SQL 数据库
SqlAlchemy 2.0 中文文档(十一)(2)
SqlAlchemy 2.0 中文文档(十一)
21 2
|
2月前
|
SQL 测试技术 数据库
SqlAlchemy 2.0 中文文档(十二)(5)
SqlAlchemy 2.0 中文文档(十二)
17 2
|
2月前
|
测试技术 Python 容器
SqlAlchemy 2.0 中文文档(十二)(4)
SqlAlchemy 2.0 中文文档(十二)
19 1
|
2月前
|
SQL 存储 关系型数据库
SqlAlchemy 2.0 中文文档(十二)(1)
SqlAlchemy 2.0 中文文档(十二)
15 1
|
2月前
|
测试技术 Python 容器
SqlAlchemy 2.0 中文文档(十二)(2)
SqlAlchemy 2.0 中文文档(十二)
19 1
|
2月前
|
API 数据库 C++
SqlAlchemy 2.0 中文文档(十四)(1)
SqlAlchemy 2.0 中文文档(十四)
14 1