SQLAlchemy示例:AdjacencyList,单向链接列表

本文介绍如何使用Python的SQLAlchemy ORM库实现自关联模型,即一个表与自身建立关联,用于表示树形结构的数据。文章详细解释了如何定义自关联的模型、处理关系方向、查询多级子节点及懒加载等问题。

这里说的 AdjacencyList , 就是最常用来在关系数据库中表示树结构的,parent方式:

idnameparent
1null
21
32

上面的数据, 表示的结构就是:

|- 二
|- 三
模型定义很好做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, ForeignKey
from sqlalchemy.types import Integer, Unicode
from sqlalchemy.orm import relationship, sessionmaker, joinedload

BaseModel = declarative_base()
Engine = create_engine('sqlite://', echo=True)
Session = sessionmaker(Engine)

class Node(BaseModel):
__tablename__ = 'node'

id = Column(Integer, autoincrement=True, primary_key=True)
name = Column(Unicode(32), nullable=False, server_default='')
parent = Column(Integer, ForeignKey('node.id'), index=True,
nullable=False, server_default='0')

这里不让parent字段有null, 而使用0代替.
这个例子在关系上, 有一个纠结的地方, 因为 node 这个表, 它是自关联的, 所以如果想要childrenparent_obj 这两个关系时:

1
2
children = relationship('Node')
parent_obj = relationship('Node')

呃, 尴尬了.
如果是两个表, 那么 SQLAlchemy 可以通过外键在哪张表这个信息, 来确定关系的方向:

1
2
3
4
5
6
7
8
class Blog(BaseModel):
...
user = Column(Integer, ForeignKey('user.id'))
user_obj = relationship('User')

class User(BaseModel):
...
blog_list = relationship('Blog')

因为外键在 Blog 中, 所以 Blog -> Useruser_obj 是一个 N -> 1关系.
反之, User -> Blogblog_list 则是一个 1 -> N 的关系.
而自相关的 Node 无法直接判断方向, 所以 SQLAlchemy 会按 1 -> N 处理, 那么:

1
2
children = relationship('Node')
parent_obj = relationship('Node')

这两条之中, children 是正确的, 是我们想要的. 要定义 parent_obj 则需要在 relationship 中通过参数明确表示方向:

1
parent_obj = relationship('Node', remote_side=[id])

这种方式就定义了一个, “到 id” 的 N -> 1 关系.
现在完整的模型定义是:

1
2
3
4
5
6
7
8
9
10
class Node(BaseModel):
__tablename__ = 'node'

id = Column(Integer, autoincrement=True, primary_key=True)
name = Column(Unicode(32), nullable=False, server_default='')
parent = Column(Integer, ForeignKey('node.id'), index=True,
nullable=False, server_default='0')

children = relationship('Node') # 1 -> N
parent_obj = relationship('Node', remote_side=[id])

查询方面没什么特殊的了, 不过我发现在自相关的模型关系, lazy 选项不起作用:

1
2
children = relationship('Node', lazy="joined")
parent_obj = relationship('Node', remote_side=[id], lazy="joined")

都是无效的, 只有在查询时, 手动使用 options() 定义:

1
2
n = session.query(Node).filter(Node.name==u'一')\
.options(joinedload('parent_obj')).first()

如果要一次查出多级的子节点:

1
2
3
n = session.query(Node).filter(Node.name==u'一')\
.options(joinedload('children').joinedload('children')).first()
print n.name, n.children, n.children[0].children

多个 joinedload() 串连的话, 可以使用 joinedload_all() 来整合:

1
2
3
4
from sqlalchemy.orm import joinedload_all

n = session.query(Node).filter(Node.name==u'一')\
.options(joinedload_all('children', 'children')).first()

在修改方面, 删除的话, 配置了 cascade , 删除父节点, 则子节点也会自动删除:

1
2
3
4
children = relationship('Node', lazy='joined', cascade='all') # 1 -> N
node = session.query(Node).filter(Node.name == u'一').first()
session.delete(node)
session.commit()

如果只删除子节点, 那么 delete-orphan 选项就很好用了:

1
2
3
4
children = relationship('Node', lazy='joined', cascade='all, delete-orphan') # 1 -> N
node = session.query(Node).filter(Node.name == u'一').first()
node.children = []
session.commit()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ox180x

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值