参考

建表

Many to Many

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id'))
)

class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship(
"Child",
secondary=association_table,
back_populates="parents")

class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
parents = relationship(
"Parent",
secondary=association_table,
back_populates="children")
  1. 多对多需要一个联系表association_table , 这张联系表其实是一张虚拟表 .

    secondary: 多对多指定中间表关键字

    backref: 在一对多或多对一之间建立双向关系

  2. children = relationship("Child",secondary=association_table,back_populates="parents") : Parent表的children字段通过association_table联系表 , 链接到Child表的parents字段

    这样,Parent表就可以通过Parent.children.id 获取到孩子的id了

  3. 同理parents = relationship("Parent",secondary=association_table,back_populates="children") : Child表的parents字段通过association_table联系表 , 链接到Parent表的children字段. 这样,Child表就可以通过Child.parents.id获取到父母的id了

结论 : children = relationship("Child",secondary=association_table,back_populates="parents")的直接作用就是说 : 使得Child表成为Parent表的children字段 , 也就是说,通过Parent表的children字段就可以获取到Child表

当使用backref参数代替时 relationship.back_populates,backref将自动secondary为反向关系使用相同的参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id'))
)

class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child",
secondary=association_table,
backref="parents")

class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)

One to Many

表示一对多的关系时,在子表类中通过 foreign key引用父表类。然后,在父表类中通过 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 (外键)引用父表的参考字段
  • Parent表通过Parent.children获取Child表
  • Child表通过Child.parent_id获取Parent表的children字段

relationship foreign key总结:

  1. 父表类中通过 relationship() 方法来引用子表的类集合
  2. 子表类中通过 foreign key引用父表的参考字段

也就是说 , 外键就只是约束而已,relationship可以获取子表的集合

Many to One

在一对多的关系中建立双向的关系,这样的话在对方看来这就是一个多对一的关系, 在子表类中附加一个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 参数

这样的话子表将会在多对一的关系中获得父表的属性

或者,可以在单一的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'))

使用backref , 对方就不必再写一个relationship了

One to One

一对一就是多对一和一对多的一个特例,只需在relationship加上一个参数uselist=False替换多的一端就是一对一

从一对多转换到一对一 :

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

class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))

从多对一转换到一对一 :

1
2
3
4
5
6
7
8
9
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))

class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)

对象在session中的四种状态

  • Transient:实例还不在session中,还没有保存到数据库中去,没有数据库身份,想刚创建出来的对象比如User(),仅仅只有mapper()与之关联
  • Pending:用add()一个transient对象后,就变成了一个pending对象,这时候仍然没有flushed到数据库中去,直到flush发生。
  • Persistent:实例出现在session中而且在数据库中也有记录了,通常是通过flush一个pending实例变成Persistent或者从数据库中querying一个已经存在的实例。
  • Detached:一个对象它有记录在数据库中,但是不在任何session中,

关联查询

外键查询

1
session.query(User).join(Address).filter(Address.email=='lzjun@qq.com').all()

指定ON字段:

1
q = session.query(User).join(Address, User.id==Address.user_id)

多个join :

1
2
q = session.query(User).join("orders", "items", "keywords")
q = session.query(User).join(User.orders).join(Order.items).join(Item.keywords)

子查询JOIN:

1
2
address_subq = session.query(Address).filter(Address.email_address == 'ed@foo.com').subquery()
q = session.query(User).join(address_subq, User.addresses)

join from:

1
q = session.query(Address).select_from(User).join(User.addresses).filter(User.name == 'ed')

和下面的SQL等价:

1
2
3
SELECT address.* FROM 
user JOIN address ON user.id=address.user_id
WHERE user.name = :name_1

左外连接,指定isouter=True,等价于Query.outerjoin()

1
q = session.query(Node).join("children", "children", aliased=True, isouter=True).filter(Node.name == 'grandchild 1')

back_populates参数的作用

back_populates , 顾名思义, 就是向后填充数据的意思

简单来说 : 就是提供双向绑定 , 当一方修改的时候通知另一方进行修改

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

class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship("Parent")

如果没有提供back_populates参数 , 在修改Child实例的parent属性后 , 并不会修改Parent实例的child属性

1
2
3
4
5
>>> parent = Parent()
>>> child = Child()
>>> child.parent = parent
>>> print parent.children
[]

现在看看添加了back_populates参数 :

1
2
3
4
5
6
7
8
9
10
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")
1
2
3
4
5
>>> parent = Parent()
>>> child = Child()
>>> child.parent = parent
>>> print parent.children
[Child(...)]

也就是说 , back_populates的值一定是另一个Class的属性值

1
2
3
4
5
class Class1:
attr1 = relationship("class2", back_populates="attr2")

class Class2:
attr2 = relationship("class1", back_populates="attr1")

relationship的参数backref和back_populates的区别

  1. 有关backref和back_populates的作用,都是在一对多或多对多查询的时候。设置一个值,这个值用来从一个表格对象指向到另一个对象,只是用法稍微有点差别。
  2. backref是新式的方法,back_populates是老式的方法,但是目前都可以使用
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
from flask_sqlalchemy import SQLAlchemy
from APP.app import app

db = SQLAlchemy(app)

association_table = db.Table('association',
db.Column('id', db.Integer, primary_key=True, autoincrement=True),
db.Column('customer_id', db.Integer, db.ForeignKey('customer.id')),
db.Column('product_id', db.Integer, db.ForeignKey('product.id'))
)


class Customer(db.Model):
__tablename__ = 'customer'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(10))
work = db.Column(db.String(20))

customer_to_product = db.relationship('Product', secondary=association_table,
back_populates='product_to_customer')

def __repr__(self):
return 'name:{name} work:{work}'.format(name=self.name, work=self.work)


class Product(db.Model):
__tablename__ = 'product'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(10))
price = db.Column(db.Float)

product_to_customer = db.relationship('Customer', secondary=association_table,
back_populates='customer_to_product')

def __repr__(self):
return 'name:{name} price:{price}'.format(name=self.name, price=self.price)



key = '司马老板'
ct = Customer.query.filter_by(name=key).first()
print(ct, type(ct))

p = ct.customer_to_product
print(p, type(p))

for i in p:
print(i.name, i.price)
  • 在多对多的关系中,需要在2个表格里面,如果使用back_populates,需要2个类里面同时使用关系指向。
  • 2个类里面的关系指向,字段名称一致,例如:
1
2
3
4
5
6
7
# 例如,在Customer类里面
customer_to_product = db.relationship('Product', secondary=association_table,
back_populates='product_to_customer')

# 例如,在Product类里面
product_to_customer = db.relationship('Customer', secondary=association_table,
back_populates='customer_to_product')

remote_side : 外键引用的是自身

remote_side : 表中的外键引用的是自身时,如Node类,如果想表示多对一的关系,那么就可以使用remote_side

1
2
3
4
5
6
class Node(Base):
__tablename__ = 'node'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('node.id'))
data = Column(String(50))
parent = relationship("Node", remote_side=[id])

如果是想建立一种双向的关系,那么还是结合backref:

1
2
3
4
5
6
7
8
9
class Node(Base):
__tablename__ = 'node'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('node.id'))
data = Column(String(50))
children = relationship(
"Node",
backref=backref('parent', remote_side=[id])
)