建立关系
relationship函数是sqlalchemy对关系之间提供一种便利调用方式,back_populates参数则对关系提供反向引用的声明。
1 | from sqlalchemy import create_engine, Column, Integer, String, ForeignKey |
2 | from sqlalchemy.orm import sessionmaker, relationship |
3 | from sqlalchemy.ext.declarative import declarative_base |
4 | |
5 | Model = declarative_base() |
6 | |
7 | class User(Model): |
8 | __tablename__ = 'users' |
9 | id = Column(Integer, primary_key=True, autoincrement=True) |
10 | name = Column(String(64)) |
11 | fullname = Column(String(128)) |
12 | password = Column(String(128)) |
13 | addresses = relationship("Address", order_by=Address.id, back_populates='user') |
14 | |
15 | def __repr__(self): |
16 | return "<User(name={}, fullname={})".format(self.name, self.fullname) |
17 | |
18 | class Address(Model): |
19 | __tablename__ = 'addresses' |
20 | id = Column(Integer, primary_key = True, autoincrement=True) |
21 | email_address = Column(String(64), nullable=False) |
22 | user_id = Column(Integer, ForeignKey('users.id')) |
23 | user = relationship('User', back_populates='addresses') |
24 | |
25 | def __repr__(self): |
26 | return "<Address(email_address={})>".format(self.email_address) |
27 | |
28 | |
29 | engine = create_engine("mysql+pymysql://root:123456@localhost:3306/mytest?charset=utf8mb4", echo=True) |
30 | Model.metadata.create_all(engine) |
31 | |
32 | Session = sessionmaker(bind=engine) |
33 | session = Session() |
34 | |
35 | jusene = User(name='jusene', fullname='Jusene Ben', password='1q2w3ee') |
36 | jusene.addresses |
37 | [] |
38 | |
39 | jusene.addresses = [ |
40 | Address(email_address='jusene@123.com'), |
41 | Address(email_address='jusene@163.com') |
42 | ] |
43 | |
44 | jusene.addresses[1].user |
45 | <User(name='jusene'), fullname='Jusene Ben'> |
46 | |
47 | session.add(jusene) |
48 | session.commit() |
连表查询
1 | # SQL: SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password, addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id FROM users, addresses WHERE users.id = addresses.user_id AND addresses.email_address = 'jusene@123.com' |
2 | for u, a in session.query(User, Address).filter(User.id == Address.user_id).filter(Address.email_address == 'jusene@123.com').all(): |
3 | print(u) |
4 | print(a) |
5 | |
6 | # 内连接 |
7 | # SQL: SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password FROM users INNER JOIN addresses ON users.id = addresses.user_id WHERE addresses.email_address = 'jusene@123.com' |
8 | session.query(User).join(Address).filter(Address.email_address == 'jusene@123.com').all() |
9 | |
10 | # 左外连接 |
11 | # SQL: SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password FROM users LEFT OUTER JOIN addresses ON users.id = addresses.user_id WHERE addresses.email_address = 'jusene@123.com' |
12 | session.query(User).outerjoin(Address).filter(Address.email_address == 'jusene@123.com').all() |
使用别名
1 | # SQL: SELECT users.name AS users_name, addresses_1.email_address AS addresses_1_email_address, addresses_2.email_address AS addresses_2_email_address FROM users INNER JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id INNER JOIN addresses AS addresses_2 ON users.id = addresses_2.user_id WHERE addresses_1.email_address = 'jusene@163.com' AND addresses_2.email_address = 'jusene@123.com' |
2 | |
3 | from sqlalchemy.orm import aliased |
4 | |
5 | ad1 = aliased(Address) |
6 | ad2 = aliased(Address) |
7 | |
8 | for username, email1, email2 in \ |
9 | session.query(User.name, ad1.email_address, ad2.email_address). \ |
10 | join(ad1, User.addresses). \ |
11 | join(ad2, User.addresses). \ |
12 | filter(ad1.email_address == 'jusene@123.com'). \ |
13 | filter(ad2.email_address == 'jusene@163.com'): |
14 | |
15 | print(username, email1, email2) |
使用子查询
1 | from sqlalchemy.sql import func |
2 | |
3 | # SQL: SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password, anon_1.address_count AS anon_1_address_count FROM users LEFT OUTER JOIN (SELECT addresses.user_id AS user_id, count('*') AS address_count FROM addresses GROUP BY addresses.user_id) AS anon_1 ON users.id = anon_1.user_id ORDER BY users.id |
4 | sub = session.query(Address.user_id, func.count('*').label('address_count')).group_by(Address.user_id).subquery() |
5 | for u, count in session.query(User, sub.c.address_count).outerjoin(sub, User.id == sub.c.user_id).order_by(User.id): |
6 | print(u, count) |
将子查询作为入口
1 | # SQL: SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password, anon_1.id AS anon_1_id, anon_1.email_address AS anon_1_email_address, anon_1.user_id AS anon_1_user_id FROM users INNER JOIN (SELECT addresses.id AS id, addresses.email_address AS email_address, addresses.user_id AS user_id FROM addresses WHERE addresses.email_address = 'jusene@163.com' AS anon_1 ON users.id = anon_1.user_id |
2 | stmt = session.query(Address).filter(Address.email_address == 'jusene@163.com').subquery() |
3 | ad = aliased(Address, stmt) |
4 | for user, address in session.query(User, ad).join(ad, User.addresses): |
5 | print(user) |
6 | print(address) |
使用exists
exists
1
from sqlalchemy.sql import exists
2
3
# SQL: SELECT users.name AS users_name FROM users WHERE EXISTS (SELECT * FROM addresses WHERE addresses.user_id = users.id)
4
stmt = exists().where(Address.user_id == User.id)
5
for name, in session.query(User.name).filter(stmt):
6
print(name)
any(relationship)
1
# SQL: SELECT users.name AS users_name FROM users WHERE EXISTS (SELECT 1 FROM addresses WHERE users.id = addresses.user_id)
2
for name, in session.query(User.name).filter(User.addresses.any()):
3
print(name)
1 | # SQL: SELECT users.name AS users_name FROM users WHERE EXISTS (SELECT 1 FROM addresses WHERE users.id = addresses.user_id AND addresses.email_address LIKE "%163%") |
2 | for name, in session.query(User.name).filter(User.addresses.any(Address.email_address.like('%163%'))): |
3 | print(name) |
- has
1
# SQL: SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id FROM addresses WHERE NOT (EXISTS (SELECT 1 FROM users WHERE users.id = addresses.user_id AND users.name = 'jusene')
2
session.query(Address).filter(~Address.user.has(User.name == 'jusene')).all()
表关系
一对多
表A(一)与表B(多),表A中的一条记录对应表B中的多条记录之间要有关系,就必须要有外键,把表A的主键添加到表B里面,充当表B的外键,一对多的实现,在多的一方表里面添加外键。
1 | class Parent(Model): |
2 | __tablename__ = 'parent' |
3 | id = Column(Integer, primary_key=True) |
4 | name = Column(String(64), nullable=False) |
5 | children = relationship('Child') |
6 | |
7 | def __repr__(self): |
8 | return "<Parent(name={})>".format(self.name) |
9 | |
10 | class Child(Model): |
11 | __tablename__ = 'child' |
12 | id = Column(Integer, primary_key=True) |
13 | email = Column(String(128), nullable=False) |
14 | parent_id = Column(Integer, ForeignKey('parent.id')) |
15 | |
16 | def __repr__(self): |
17 | return "<Child(email={})>".format(self.email) |
18 | |
19 | jusene = Parent(name='jusene') |
20 | jusene.children |
21 | [] |
22 | jusene.children = [Child(email='jusene@163.com'), Child(email='jusene@qq.com')] |
23 | session.add(jusene) |
24 | session.commit() |
25 | |
26 | for row in session.query(Parent): |
27 | print(row.children[1].email) |
多对一
在一对多的关系中建立双向关系,这样的话在对方看来就是多对一的关系,在子类表附加一个relationship方法,并且在双方的relationship方法中使用back_populates方法。
1 | class Parent(Model): |
2 | __tablename__ = 'parent' |
3 | id = Column(Integer, primary_key=True) |
4 | name = Column(String(64), nullable=False) |
5 | children = relationship('Child',back_populates='parent') |
6 | |
7 | def __repr__(self): |
8 | return "<Parent(name={})>".format(self.name) |
9 | |
10 | class Child(Model): |
11 | __tablename__ = 'child' |
12 | id = Column(Integer, primary_key=True) |
13 | email = Column(String(128), nullable=False) |
14 | parent_id = Column(Integer, ForeignKey('parent.id')) |
15 | parent = relationship('Parent', back_populates='children') |
16 | |
17 | def __repr__(self): |
18 | return "<Child(email={})>".format(self.email) |
19 | |
20 | for row in session.query(Child).filter(Child.email == 'jusene@qq.com'): |
21 | print(row.parent.name) |
这样的话子表将在多对一的关系中获得父表的属性
或者在单一的relationship方法中使用backref参数代替back_populates参数:
1 | class Parent(Model): |
2 | __tablename__ = 'parent' |
3 | id = Column(Integer, primary_key=True) |
4 | name = Column(String(64), nullable=False) |
5 | children = relationship('Child',backref='parent') |
6 | |
7 | def __repr__(self): |
8 | return "<Parent(name={})>".format(self.name) |
9 | |
10 | class Child(Model): |
11 | __tablename__ = 'child' |
12 | id = Column(Integer, primary_key=True) |
13 | email = Column(String(128), nullable=False) |
14 | parent_id = Column(Integer, ForeignKey('parent.id')) |
15 | |
16 | def __repr__(self): |
17 | return "<Child(email={})>".format(self.email) |
一对一
在一对一关系中,A 表中的一行最多只能匹配于 B 表中的一行,反之亦然。如果相关列都是主键或都具有唯一约束,则可以创建一对一关系。
一对一是两张表之间本质的双向关系,要做到这一点,需要在一对多的关系基础上的父表中使用uselist参数来表示。
一对多改写成一对一
1
class Parent(Model):
2
__tablename__ = 'parent'
3
id = Column(Integer, primary_key=True)
4
name = Column(String(64), nullable=False)
5
children = relationship('Child',uselist=False, back_populates='parent')
6
7
def __repr__(self):
8
return "<Parent(name={})>".format(self.name)
9
10
class Child(Model):
11
__tablename__ = 'child'
12
id = Column(Integer, primary_key=True)
13
email = Column(String(128), nullable=False)
14
parent_id = Column(Integer, ForeignKey('parent.id'))
15
parent = relationship('Parent', back_populates='children')
16
17
def __repr__(self):
18
return "<Child(email={})>".format(self.email)
多对一改写一对一
1
class Parent(Model):
2
__tablename__ = 'parent'
3
id = Column(Integer, primary_key=True)
4
name = Column(String(64), nullable=False)
5
children = relationship('Child', back_populates='parent')
6
7
def __repr__(self):
8
return "<Parent(name={})>".format(self.name)
9
10
class Child(Model):
11
__tablename__ = 'child'
12
id = Column(Integer, primary_key=True)
13
email = Column(String(128), nullable=False)
14
parent_id = Column(Integer, ForeignKey('parent.id'))
15
parent = relationship('Parent', back_populates='children', uselist=False)
16
17
def __repr__(self):
18
return "<Child(email={})>".format(self.email)
使用backref
1
from sqlalchemy.orm import backref
2
3
class Parent(Model):
4
__tablename__ = 'parent'
5
id = Column(Integer, primary_key=True)
6
name = Column(String(64), nullable=False)
7
children = relationship('Child', backref=backref('parent', uselist=False))
8
9
def __repr__(self):
10
return "<Parent(name={})>".format(self.name)
11
12
13
class Child(Model):
14
__tablename__ = 'child'
15
id = Column(Integer, primary_key=True)
16
email = Column(String(128), nullable=False)
17
parent_id = Column(Integer, ForeignKey('parent.id'))
18
19
def __repr__(self):
20
return "<Child(email={})>".format(self.email)
多对多
在多对多关系中,A 表中的一行可以匹配 B 表中的多行,反之亦然。要创建这种关系,需要定义第三个表,称为结合表,它的主键由 A 表和 B 表的外部键组成。
多对多关系会在两个类之间增加一个关联的表。
这个关联的表在 relationship() 方法中通过 secondary 参数来表示。
通常的,这个表会通过 MetaData 对象来与声明基类关联。
所以这个 ForeignKey 指令会使用链接来定位到远程的表。
1 | ## 多对多关系的两个表之间的一个关联表 |
2 | from sqlalchemy import Table, Text |
3 | association_table = Table('association', Model.metadata, |
4 | Column('left_id', Integer, ForeignKey('left.id')), |
5 | Column('right_id', Integer, ForeignKey('right.id')) |
6 | ) |
7 | class Parent(Model): |
8 | __tablename__ = 'left' |
9 | id = Column(Integer, primary_key=True) |
10 | children = relationship('Child', secondary=association_table) |
11 | |
12 | class Child(Model): |
13 | __tablename__ 'right' |
14 | id = Column(Integer, primary_key=True) |
subquery load
1 | from sqlalchemy.orm import subqueryload |
2 | |
3 | # SQL: SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password FROM users WHERE users.name = 'jusene' |
4 | # SQL: SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id, anon_1.users_id AS anon_1_users_id FROM (SELECT users.id AS users_id FROM users WHERE users.name = 'jusene') AS anon_1 INNER JOIN addresses ON anon_1.users_id = addresses.user_id ORDER BY anon_1.users_id, addresses.id |
5 | jusene = session.query(User).options(subqueryload(User.addresses)).filter(User.name=='jusene').one() |
6 | jusene |
7 | <User(name=jusene, fullname=Jusene Ben) |
8 | jusene.addresses |
9 | [<Address(email_address=jusene@123.com)>, |
10 | <Address(email_address=jusene@163.com)>] |
在本例中,我们希望指明该用户。地址应尽快加载。加载一组对象及其相关集合的一个很好的选择是orm.subqueryload()选项,它会发出第二个SELECT语句,该语句会完全加载与刚刚加载的结果相关联的集合。
joined load
1 | from sqlalchemy.orm import joinedload |
2 | |
3 | # SQL: SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password, addresses_1.id AS addresses_1_id, addresses_1.email_address AS addresses_1_email_address, addresses_1.user_id AS addresses_1_user_id FROM users LEFT OUTER JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id WHERE users.name = 'jusene' ORDER BY addresses_1.id |
4 | jusene = session.query(User).options(joinedload(User.addresses)).filter(User.name == 'jusene').one() |
5 | jusene |
6 | <User(name=jusene, fullname=Jusene Ben) |
7 | jusene.addresses |
8 | [<Address(email_address=jusene@123.com)>, |
9 | <Address(email_address=jusene@163.com)>] |