深究 sqlalchemy 中表关系table relationships
深究 sqlalchemy 中表关系table relationships
为什么需要定义Relationships
在相关联的表中,我们可以不创建表关联的定义,而只是单纯互相引用id即可。但是,查询和使用起来就要麻烦很多:
1 |
|
可以看到,这样的效率非常低。
好在原生的SQL就有relationship设置,SQLAlchemy将其引入到了ORM模型中。
它可以让我们只在表中声明表之间的关系,之后每次使用就完全无需手动交叉搜索
,而是像对待一个表中的数据一样直接使用。
为什么不需要定义relationships?
经过实践返回来加的这一节:实践中的SQLAlchemy的”relationship”在一定程度上反而导致了整体表关联关系的极大复杂化,还有效率的极其低下。
如果你的数据库只有两个表的话,那么relationship随便定义随便用。如果只有几百条数据的话,那么也请随便玩。
但是,当数据库中有数十个表以上,单个关联层级就多过三个表以上层层关联,而且各个数据量以万为单位。那么,”relationship”会把整个人都搞垮,简直还不如手写SQL语句清晰好理解,并且效率也差在了秒级与毫秒级的区别上。
SQLAlchemy只能很轻松handle
Many to Many
,但是如果是常见的Many to Many to Many
,或者是Many to Many to Many to Many
,那简直就是噩梦。
但是,我们都知道,项目做到一定程度,都会摆脱不了ORM。无论是自己造轮子还是用别人的,无论起点是不是纯SQL,终点都是ORM。
那么该怎么办呢?
网友的建议是:
用SQLAlchemy建立各种ORM类对象,不要用内置的关联,直接在查询的时候手动SQL语句!
经过实践,我的建议是:
- 容易SQL-Injection注入的地方,用SQLAlchemy的
query
- 创建ORM对象时候,用SQLAlchemy
- 多层关联的时候,不要用SQLAlchemy
- 查询的时候,用SQL
- 插入数据的时候,不要用SQLAlchemy。(官方都说明了插入百万级的时候,和SQL插件是秒级的)
relationship() 函数
参考官方文档:Linking Relationships with Backref
SQLAlchemy创建表关联时,使用的是relationshi()
这个函数。
它返回的是一个类的属性,比如father类的children
属性。但是,它实际上并没有在father表中创建任何叫children的列,而是自动帮你到相关联的children表中去找数据,让你用起来感觉没有差别而已。
这是非常方便的!
relationship()
这个函数的参数非常多,每一个参数都有很多内容需要理解。因为所有的表关联的形态,都是在这个函数里面定义的。
以下分别讲解。
Reference 正向引用
传统的方法,是在父类中定义一个关系 relationship
或叫正向引用 Reference
,子类只需定义一个外键。比如:
1 |
|
这样当每次我们使用father.children
的时候,就会自动返回与这个father相关联的所有children了。
Back Reference 反向引用
单纯定义的relationship('子类名')
只是一个正向引用,也就是只能让父类调用子对象。反过来,如果要问children他们的父亲是谁,就不行了。
所以,我们还需要一个反向引用 (Back Reference)
的声明,让子对象能够知道父对象是谁。
定义方式是在父类的relationship(..)中加一个参数backref
:
1 |
|
注意:
- backref参数里面使用的随便写,主要用于之后子类的引用。
- backref参数是
双向性
的,意思是,只需要在父类中声明一次,那么父⇄子
的双向关系就确立了,不用再去子类中写一遍。
这时候,我们在添加就可以这样互相调用了:
1 |
|
Bidirectional & Unidirectional Back Reference 双向和单向的反向引用
后来,SQLAlchemy发现这种只在一边定义双向性backref
的方法有点不太直观,所以又添加了另一个参数back_populates
参数,而这个back_populates参数是单向性的,也就是说:
你要确立双方向关系就必须在两边的类中都声明一遍。这样比较直观。
可以把
backref
和back_populates
都读为”as”,这样就好记忆了。
比如:
1 |
|
注意:back_populates
要求父类子类的关系名称必须严格“对称”:
- 父类的relationship属性名
children
,必须对应子类的关系中的back_populates
中的值 - 子类的relationship属性名
parent
,必须对应父类的关系中的back_populates
中的值
这样一来利用反向引用
参数创建的关系就确立了。但是注意,
无论用backref
还是back_populates
创建的关联,如果我们必须要为父子对象添加对象间的关联才能引用,否则谁也不知道谁是谁的父亲、儿子:
1 |
|
另外:上面添加父子关系的时候,不光可以用daddy.children.append
,
还可以在声明子对象的时候确定:son = Child( parent=daddy )
反向引用
参数对比:
backref
参数:双方向。在父类中定义即可。只能通过daddy.children.append()
方式添加子对象关联。back_populates
参数:单方向。必须在父子类中都定义,且属性名称必须严格对称。还可以通过Child(parent=daddy)
的方式添加父对象关联。
SQL中的表关系
对应关系:
- One to Many 一对多:
- Many to One 多对一:
- Many to Many 多对多:
One to Many 一对多
建立一个One-to-Many
的多表关联:
1 |
|
创建好关联的表以后,我们就可以直接插入数据了。注意,插入带关联的数据也和SQL插入有些不同:
1 |
|
Many to One 多对一
比如职工和公司的关系就是多对一。这和公司与职工对一对多有什么区别?
区别其实是在SQL语句中的:多对一的关联关系,是在多的一方的表中定义,一的一方表中没有任何关系定义:
1 |
|
Many to Many 多对多
多对多的关系也很常见,比如User和Radio的关系:
一个Radio可以有多个用户可以订阅,一个用户可以订阅多个Radio。
SQL中处理多对多的关系时,是把多对多分拆成两个一对多关系。做法是:新创建一个表,专门存储映射关系。原本的两个表无需设置任何外键。
SQLAlchemy的实践中,也和SQL中的做法一样。
注意:既然有了专门的Mapping映射表,那么两个表各自就不需要注册任何ForeignKey外键了。
示例:
1 |
|
其中,secondary
是专门用来指明映射表的。
注意:多对多的时候我们也可以用
backref
参数来添加互相引用。但是这种方法太不直观了,容易产生混乱。所以这里建议用back_populates
参数,在两方都添加引用,表现一种平行地位,方便理解。
然后插入数据时候是这么用:
1 |
|
Many to Many to Many 多对多对多 (深层关联)
深层关联,为了避免理解困难,最笨的方法就是简单的使用外键ID,然后手动搜索另一个表的对应ID。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!