SQLAlchemy_定义(一对一/一对多/多对多)关系

本文详细介绍SQLAlchemy中一对一、一对多及多对多关系的定义与使用方法。包括如何在父表和子表间建立关系,以及如何利用backref和back_populates参数创建双向关系。

SQLAlchemy_定义(一对一/一对多/多对多)关系

Basic Relationship Patterns

基本关系模式 
The imports used for each of the following sections is as follows: 
下列的 import 语句,应用到接下来所有的代章节中:

?
1
2
3
4
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
One To Many

A one to many relationship places a foreign key on the child table referencing the parent. 
表示一对多的关系时,在子表类中通过 foreign key (外键)引用父表类。 
relationship() is then specified on the parent, as referencing a collection of items represented by the child: 
然后,在父表类中通过 relationship() 方法来引用子表的类:

?
1
2
3
4
5
6
7
8
9
10
11
class Parent(Base):
     __tablename__ = 'parent'
     id = Column(Integer, primary_key=True)
     children = relationship("Child")
    # 在父表类中通过 relationship() 方法来引用子表的类集合
 
class Child(Base):
     __tablename__ = 'child'
     id = Column(Integer, primary_key=True)
     parent_id = Column(Integer, ForeignKey('parent.id'))
     # 在子表类中通过 foreign key (外键)引用父表的参考字段

 To establish a bidirectional relationship in one-to-many, where the “reverse” side is a many to one, 
一对多的关系中建立双向的关系,这样的话在对方看来这就是一个多对一的关系, 
specify an additional relationship() and connect the two using the relationship.back_populates parameter: 
在子表类中附加一个 relationship() 方法,并且在双方的 relationship() 方法中使用 relationship.back_populates 方法参数:

?
1
2
3
4
5
6
7
8
9
10
11
12
class Parent(Base):
     __tablename__ = 'parent'
     id = Column(Integer, primary_key=True)
     children = relationship("Child", back_populates="parent")
 
class Child(Base):
     __tablename__ = 'child'
     id = Column(Integer, primary_key=True)
     parent_id = Column(Integer, ForeignKey('parent.id'))
     parent = relationship("Parent", back_populates="children")
     # 子表类中附加一个 relationship() 方法
     # 并且在(父)子表类的 relationship() 方法中使用 relationship.back_populates 参数

 Child will get a parent attribute with many-to-one semantics. 
这样的话子表将会在多对一的关系中获得父表的属性

Alternatively, the backref option may be used on a single relationship() instead of using back_populates: 
或者,可以在单一的 relationship() 方法中使用 backref 参数来代替 back_populates 参数:

?
1
2
3
4
5
6
7
8
9
class Parent(Base):
     __tablename__ = 'parent'
     id = Column(Integer, primary_key=True)
     children = relationship("Child", backref="parent")
 
class Child(Base):
     __tablename__ = 'child'
     id = Column(Integer, primary_key=True)
     parent_id = Column(Integer, ForeignKey('parent.id'))

 

One To One

One To One is essentially a bidirectional relationship with a scalar attribute on both sides. 
一对一是两张表之间本质上的双向关系。 
To achieve this, the uselist flag indicates the placement of a scalar attribute instead of a collection on the “many” side of the relationship. 
要做到这一点,只需要在一对多关系基础上的父表中使用 uselist 参数来表示。 
To convert one-to-many into one-to-one:

?
1
2
3
4
5
6
7
8
9
10
class Parent(Base):
     __tablename__ = 'parent'
     id = Column(Integer, primary_key=True)
     child = relationship("Child", uselist=False, back_populates="parent")
 
class Child(Base):
     __tablename__ = 'child'
     id = Column(Integer, primary_key=True)
     parent_id = Column(Integer, ForeignKey('parent.id'))
     parent = relationship("Parent", back_populates="child")

 To convert many-to-one into one-to-one:

?
1
2
3
4
5
6
7
8
9
10
class Parent(Base):
     __tablename__ = 'parent'
     id = Column(Integer, primary_key=True)
     child_id = Column(Integer, ForeignKey('child.id'))
     child = relationship("Child", back_populates="parent")
 
class Child(Base):
     __tablename__ = 'child'
     id = Column(Integer, primary_key=True)
     parent = relationship("Parent", back_populates="child", uselist=False)

 As always, the relationship.backref and backref() functions may be used in lieu of the relationship.back_populates approach; to specifyuselist on a backref, use the backref() function:

同样的,可以使用下面这种方式:

?
1
2
3
4
5
6
7
8
9
10
11
12
from sqlalchemy.orm import backref

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    child_id = Column(Integer, ForeignKey('child.id'))
    child = relationship("Child", backref=backref("parent", uselist=False))
Many To Many

Many to Many adds an association table between two classes. 
多对多关系会在两个类之间增加一个关联的表。 
The association table is indicated by the secondary argument to relationship()
这个关联的表在 relationship() 方法中通过 secondary 参数来表示。 
Usually, the Table uses the MetaData object associated with the declarative base class, 
通常的,这个表会通过 MetaData 对象来与声明基类关联, 
so that the ForeignKey directives can locate the remote tables with which to link: 
所以这个 ForeignKey 指令会使用链接来定位到远程的表:

定义中间表可以定义中间关系表相关的类,也可以直接通过Base.metdata生成对应关系表对象,不过基于code first准则,还是推荐将中间关系写成类。

构建第三张关系类实现多对多。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
Base = declarative_base() #生成sqlorm基础类
 
 
class HostUserToGroup(Base):
     __tablename__ = 'hostuser_to_group' # 表名hostuser_to_group
     nid = Column(Integer, primary_key=True,autoincrement=True)
     hostuser_id = Column(Integer,ForeignKey('host_user.id'),primary_key=True)# 外键关联host_user表的id字段
     group_id = Column(Integer,ForeignKey('group.id'),primary_key=True) # 外键关联group表的id字段
 
 
 
 
class UserProfileToGroup(Base):
     __tablename__ = 'userprofile_to_group'# 表名userprofile_to_group
     nid = Column(Integer, primary_key=True,autoincrement=True)
     userprofile_id = Column(Integer,ForeignKey('user_profile.id'),primary_key=True)# 外键关联user_profile表的id字段
     group_id = Column(Integer,ForeignKey('group.id'),primary_key=True)# 外键关联group表的id字段
 
 
class UserProfileToHostUser(Base):
     __tablename__ = 'userprofile_to_hostuser'# 表名userprofile_to_hostuser
     nid = Column(Integer, primary_key=True,autoincrement=True)
     userprofile_id = Column(Integer,ForeignKey('user_profile.id'),primary_key=True)# 外键关联user_profile表的id字段
     hostuser_id = Column(Integer,ForeignKey('host_user.id'),primary_key=True)# 外键关联host_user表的id字段
 
class Host(Base):
     __tablename__ = 'host' #表名host
     id = Column(Integer, primary_key= True, autoincrement= True)# id字段,主键,自动增长
     hostname = Column(String(64),unique= True,nullable= False)# hostname字段,唯一,不能为空
     ip_addr = Column(String(64),unique= True,nullable= False)#ip_addr字段,唯一,不能为空
     port = Column(Integer, default = 22) # port字段,整形,默认22
     def __repr__(self):
         return "< Hostobject: id=%s, hostname=%s, ip_addr=%s, port=%s>" %(self.id, self.hostname, self.ip_addr, self.port)
 
class Group(Base):
     __tablename__ = 'group' # 表名group
     id = Column(Integer, primary_key = True) # id字段,主键,自动增长
     name = Column(String(64), unique = True, nullable = False) # name字段,唯一,不为空
     def __repr__(self):
         return "< Group object: id=%s, name=%s>" %(self.id, self.name)
 
class UserProfile(Base):
     __tablename__ = 'user_profile' # 表名user_profile
     id = Column(Integer, primary_key = True) # id字段,主键,自动增长
     username = Column(String(64), unique = True, nullable = False) # username字段,唯一,不为空
     password = Column(String(255), nullable = False) # password字段,不为空
     hostusers = relationship('HostUser', secondary = UserProfileToHostUser.__tablename__, backref = 'user_profiles') # 多对多关联HostUser表类(注意不是表名),中间表类UserProfileToHostUser(注意不是表名),反向字段为user_profiles
     groups = relationship('Group', secondary = UserProfileToGroup.__tablename__, backref = 'user_profiles') # 多对多关联Group表类(注意不是表名),中间表类UserProfileToGroup(注意不是表名),反向字段为user_profiles
 
     def __repr__(self):
         return "< UserProfile object: id=%s, username=%s>" %(self.id, self.username)
 
class HostUser(Base):
     __tablename__ = 'host_user' # 表名host_user
     id = Column(Integer, primary_key = True) # id字段,主键,自动增长
     host_id = Column(Integer, ForeignKey('host.id')) # host_id,外键关联host表的id字段
     AuthTypes = [
         (u'ssh-password', u'SSH/Password'),
         (u'ssh-key', u'SSH/Key'),
     ] # 选项列表
     auth_type = Column(ChoiceType(AuthTypes)) # auth_type字段,只能是选项列表里规定的值
     username = Column(String(64), nullable = True) # username字段,不为空
     password = Column(String(255)) # password字段
     host = relationship('Host', backref = 'host_users')
     groups = relationship('Group', secondary = HostUserToGroup.__tablename__, backref = 'host_users') # 多对多关联Group表类(注意不是表名),中间表类HostUserToGroup(注意不是表名),反向字段为host_users
     __table_args = (UniqueConstraint('host_id', 'username', name = '_host_username_uc')) # host_id和username组成联合唯一约束
 
     def __repr__(self):
         return "< HostUser object: id=%s, host_id=%s, username=%s>" %(self.id, self.host_id, self.username)

 

构建关系表实现多对多实例如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
from sqlalchemy import create_engine,and_,or_,func,Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String,\
     ForeignKey, UniqueConstraint, DateTime
from sqlalchemy.orm import sessionmaker,relationship
from sqlalchemy_utils import ChoiceType,PasswordType
from datetime import datetime
 
Base = declarative_base() #生成sqlorm基础类
 
HostUserToGroup = Table('hostuser_to_group', Base.metadata,  # 表名hostuser_to_group
     Column('hostuser_id', ForeignKey('host_user.id'), primary_key = True), # 外键关联host_user表的id字段
     Column('group_id', ForeignKey('group.id'), primary_key = True), # 外键关联group表的id字段
)
 
UserProfileToGroup = Table('userprofile_to_group', Base.metadata, # 表名userprofile_to_group
     Column('userprofile_id', ForeignKey('user_profile.id'), primary_key = True), # 外键关联user_profile表的id字段
     Column('group_id', ForeignKey('group.id'), primary_key = True), # 外键关联group表的id字段
)
 
UserProfileToHostUser = Table('userprofile_to_hostuser', Base.metadata, # 表名userprofile_to_hostuser
     Column('userprofile_id', ForeignKey('user_profile.id'), primary_key = True), # 外键关联user_profile表的id字段
     Column('hostuser_id', ForeignKey('host_user.id'), primary_key = True), # 外键关联host_user表的id字段
)
 
class Host(Base):
     __tablename__ = 'host' #表名host
     id = Column(Integer, primary_key= True, autoincrement= True)# id字段,主键,自动增长
     hostname = Column(String(64),unique= True,nullable= False)# hostname字段,唯一,不能为空
     ip_addr = Column(String(64),unique= True,nullable= False)#ip_addr字段,唯一,不能为空
     port = Column(Integer, default = 22) # port字段,整形,默认22
     def __repr__(self):
         return "< Hostobject: id=%s, hostname=%s, ip_addr=%s, port=%s>" %(self.id, self.hostname, self.ip_addr, self.port)
 
class Group(Base):
     __tablename__ = 'group' # 表名group
     id = Column(Integer, primary_key = True) # id字段,主键,自动增长
     name = Column(String(64), unique = True, nullable = False) # name字段,唯一,不为空
     def __repr__(self):
         return "< Group object: id=%s, name=%s>" %(self.id, self.name)
 
class UserProfile(Base):
     __tablename__ = 'user_profile' # 表名user_profile
     id = Column(Integer, primary_key = True) # id字段,主键,自动增长
     username = Column(String(64), unique = True, nullable = False) # username字段,唯一,不为空
     password = Column(String(255), nullable = False) # password字段,不为空
     hostusers = relationship('HostUser', secondary = UserProfileToHostUser, backref = 'user_profiles') # 多对多关联HostUser表类(注意不是表名),中间表类UserProfileToHostUser(注意不是表名),反向字段为user_profiles
     groups = relationship('Group', secondary = UserProfileToGroup, backref = 'user_profiles') # 多对多关联Group表类(注意不是表名),中间表类UserProfileToGroup(注意不是表名),反向字段为user_profiles
 
     def __repr__(self):
         return "< UserProfile object: id=%s, username=%s>" %(self.id, self.username)
 
class HostUser(Base):
     __tablename__ = 'host_user' # 表名host_user
     id = Column(Integer, primary_key = True) # id字段,主键,自动增长
     host_id = Column(Integer, ForeignKey('host.id')) # host_id,外键关联host表的id字段
     AuthTypes = [
         (u'ssh-password', u'SSH/Password'),
         (u'ssh-key', u'SSH/Key'),
     ] # 选项列表
     auth_type = Column(ChoiceType(AuthTypes)) # auth_type字段,只能是选项列表里规定的值
     username = Column(String(64), nullable = True) # username字段,不为空
     password = Column(String(255)) # password字段
     host = relationship('Host', backref = 'host_users')
     groups = relationship('Group', secondary = HostUserToGroup, backref = 'host_users') # 多对多关联Group表类(注意不是表名),中间表类HostUserToGroup(注意不是表名),反向字段为host_users
     __table_args = (UniqueConstraint('host_id', 'username', name = '_host_username_uc')) # host_id和username组成联合唯一约束
 
     def __repr__(self):
         return "< HostUser object: id=%s, host_id=%s, username=%s>" %(self.id, self.host_id, self.username)

 

Linking Relationships with Backref 

简单来说, relationship函数是sqlalchemy对关系之间提供的一种便利的调用方式, backref参数则对关系提供反向引用的声明。

The backref keyword argument was first introduced in Object Relational Tutorial, and has been mentioned through- out many of the examples here. What does it actually do ? Let’s start with the canonical User and Address scenario: 

?
1
2
3
4
5
6
7
8
9
10
11
from sqlalchemy import Integer, ForeignKey, String, Column from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True) name = Column(String)
     addresses = relationship("Address", backref="user")
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
email = Column(String)
user_id = Column(Integer, ForeignKey('user.id'))

 

The above configuration establishes a collection of Address objects on User called User.addresses. It also establishes a .user attribute on Address which will refer to the parent User object.

In fact, the backref keyword is only a common shortcut for placing a second relationship() onto the Address mapping, including the establishment of an event listener on both sides which will mirror attribute op- erations in both directions. The above configuration is equivalent to: 

?
1
2
3
4
5
6
7
8
9
10
11
12
from sqlalchemy import Integer, ForeignKey, String, Column < br >from sqlalchemy.ext.declarative import declarative_base < br >from sqlalchemy.orm import relationship
Base = declarative_base()
class User(Base):
     __tablename__ = 'user'
     id = Column(Integer, primary_key=True) name = Column(String)
     addresses = relationship("Address", back_populates="user")
class Address(Base):
     __tablename__ = 'address'
     id = Column(Integer, primary_key=True)
     email = Column(String)
     user_id = Column(Integer, ForeignKey('user.id'))
     user = relationship("User", back_populates="addresses")

Above, we add a .user relationship to Address explicitly. On both relationships, the back_populates directive tells each relationship about the other one, indicating that they should establish “bidirectional” behavior between each other. The primary effect of this configuration is that the relationship adds event handlers to both attributes which have the behavior of “when an append or set event occurs here, set ourselves onto the incoming attribute using this particular attribute name”. The behavior is illustrated as follows. Start with a User and an Address instance. The .addresses collection is empty, and the .user attribute is None: 

下面来看下backref相关的源码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def backref(name, **kwargs):
     """Create a back reference with explicit keyword arguments, which are     the same arguments one can send to :func:`relationship`.
 
     Used with the ``backref`` keyword argument to :func:`relationship` in
     place of a string argument, e.g.::
 
         'items':relationship(
             SomeItem, backref=backref('parent', lazy='subquery'))
 
     .. seealso::
 
         :ref:`relationships_backref`
 
     """
 
     return (name, kwargs)

 

?
1
2
3
4
5
6
7
param backref:
           indicates the string name of a property to be placed on the related
           mapper's class that will handle this relationship in the other
           direction. The other property will be created automatically
           when the mappers are configured.  Can also be passed as a
           :func:`.backref` object to control the configuration of the
           new relationship.

 

 

 

0
0
«上一篇: 常用算法
»下一篇: 运维自动化之salt笔记

posted on 2016-10-20 11:36 Jason_wang_2016 阅读(442) 评论(0)编辑收藏

<think> 我们正在讨论SQLAlchemy ORM中的一对自关联关系,并使用back_populates参数来实现双向关系。 自关联指的是同一个表与自己关联,例如在员工表中,一个经理可以管理个员工,而每个员工只有一个经理(经理也是员工)。 我们将创建一个名为Employee的模型,它有两个关系- `manager`关系:表示员工的经理(对一,因为个员工可以有同一个经理) - `team`关系:表示经理管理的员工(一对,一个经理有个员工) 注意:由于是自关联,我们在同一个表上建立两个外键指向同一个表的主键。 步骤: 1. 定义模型,包含一个指向自身的外键(例如parent_id)。 2. 定义两个关系:一个用于从员工指向经理(对一),一个用于从经理指向个员工(一对)。 3. 使用back_populates参数来连接这两个关系,使其双向同步。 示例代码: 我们将创建一个Employee类,包含id、name和一个指向经理的外键(manager_id)。然后定义两个关系: manager = relationship("Employee", remote_side=[id], back_populates="team") team = relationship("Employee", back_populates="manager") 注意:外键manager_id将指向Employee表的id列。 详细说明: - 在manager关系中,我们指定remote_side=[id]来表示关系的远程端是id列(即本表的主键)。 - 在team关系中,我们不需要指定remote_side,因为它是从经理(即拥有该外键的记录的父记录)到个员工(子记录)的关系- 使用back_populates参数,我们将两个关系连接起来:当我们在一个员工上设置manager时,该经理的team集合中会自动添加该员工;反之亦然。 让我们编写具体的模型定义: </think> ### SQLAlchemy ORM 使用 `back_populates` 实现一对自关联详解 #### 核心概念 在自关联模型中,**单个数据库表**通过**外键关联自身**实现层级关系。典型场景包括: - 组织架构(员工-经理) - 评论系统(评论-回复) - 分类树(父分类-子分类) 一对自关联的核心要素: 1. **外键约束**:字段指向本表的主键 2. **双向关系**:使用 `back_populates` 同步父子关系 3. **关系方向**: - 父级 → 子级:一对 (`relationship`) - 子级 → 父级:对一 (`ForeignKey`) --- #### 实现方法与示例 以下以员工-经理关系为例: ```python from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class Employee(Base): __tablename__ = 'employees' id = Column(Integer, primary_key=True) name = Column(String(50)) # 指向本表主键的外键(可为空表示顶级节点) manager_id = Column(Integer, ForeignKey('employees.id')) # 子级关系一对):一个经理管理个员工 team = relationship( "Employee", # 关联自身 back_populates="manager", # 反向引用字段名 remote_side=[id] # 指定外键目标字段 ) # 父级关系对一):一个员工对应一个经理 manager = relationship( "Employee", back_populates="team", # 与子级关系对应 foreign_keys=[manager_id] # 明确外键字段 ) ``` --- #### 关键配置说明 | 参数 | 作用 | 必要性 | |------|------|--------| | `remote_side=[id]` | 声明关联的目标字段(通常是主键) | ✔️ 必须 | | `foreign_keys=[manager_id]` | 明确指定外键字段 | ✔️ 避免歧义 | | `back_populates="xxx"` | 同步双向关系的关键 | ✔️ 必须匹配 | | `cascade="all, delete-orphan"` | 级联删除子节点 | 按需添加 | --- #### 操作示例 ```python # 创建层级 ceo = Employee(name="CEO") manager = Employee(name="Manager", manager=ceo) # 设置父级 staff = Employee(name="Staff", manager=manager) # 双向自动同步 print(ceo.team) # 输出 [manager] print(manager.team) # 输出 [staff] print(staff.manager.name) # 输出 "Manager" # 删除经理(级联删除子员工) session.delete(manager) # staff记录将自动删除 ``` --- #### 常见问题解决 1. **歧义错误** → 明确指定 `foreign_keys` 2. **循环导入** → 使用字符串声明类名 `"Employee"` 3. **查询优化** → 启用 `lazy="dynamic"` 延迟加载 ```python team = relationship("Employee", lazy="dynamic", ...) ``` 通过 `back_populates` 实现的自关联模型,确保了数据层级完整性和操作双向同步[^1][^2]。实际应用中需根据业务需求添加索引、级联规则等优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值