geekdoc-python-zh/docs/pythoncentral/sqlalchemy-association-tabl...

10 KiB
Raw Blame History

SQLAlchemy 关联表

原文:https://www.pythoncentral.io/sqlalchemy-association-tables/

关联表

在我们之前的文章中,我们使用关联表来建模表之间的many-to-many关系,比如DepartmentEmployee之间的关系。在本文中,我们将更深入地研究关联表的概念,看看我们如何使用它来进一步解决更复杂的问题。

部门员工链接和额外数据

在上一篇文章中,我们创建了以下 SQLAlchemy 模型:


import os
from sqlalchemy 导入列DateTimeStringIntegerForeignKeyfunc 
 from sqlalchemy.orm 导入关系back ref
from sqlalchemy . ext . declarative import declarative _ base
Base = declarative_base()
class Department(Base):
_ _ tablename _ _ = ' Department '
id = Column(Integerprimary _ key = True)
name = Column(String)
employees = relationship(
' Employee '
secondary = ' Department _ Employee _ link '
)
class Employee(Base):
_ _ tablename _ _ = ' Employee '
id = Column(Integerprimary _ key = True)
name = Column(String)
hired _ on = Column(DateTimedefault = func . now())
departments = relationship(
Department
secondary = ' Department _ Employee _ link '
)
class DepartmentEmployeeLink(Base):
_ _ tablename _ _ = ' department _ employee _ link '
department _ id = Column(IntegerForeignKey('department.id ')primary _ key = True)
employee _ id = Column(IntegerForeignKey('employee.id ')primary_key=True) 

请注意,DepartmentEmployeeLink类包含两个外键列,足以模拟DepartmentEmployee之间的多对多关系。现在我们再添加一列extra_data和两个关系departmentemployee


import os
from sqlalchemy 导入列DateTimeStringIntegerForeignKeyfunc 
 from sqlalchemy.orm 导入关系back ref
from sqlalchemy . ext . declarative import declarative _ base
Base = declarative_base()
class Department(Base):
_ _ tablename _ _ = ' Department '
id = Column(Integerprimary _ key = True)
name = Column(String)
employees = relationship(
' Employee '
secondary = ' Department _ Employee _ link '
)
class Employee(Base):
_ _ tablename _ _ = ' Employee '
id = Column(Integerprimary _ key = True)
name = Column(String)
hired _ on = Column(DateTimedefault = func . now())
departments = relationship(
Department
secondary = ' Department _ Employee _ link '
)
class DepartmentEmployeeLink(Base):
_ _ tablename _ _ = ' Department _ Employee _ link '
Department _ id = Column(IntegerForeignKey('department.id ')primary _ key = True)
Employee _ id = Column(IntegerForeignKey('employee.id ')primary _ key = True)
extra _ data = Column(String(256))
Department = relationship(Departmentbackref = backref(" Employee _ assoc "))
Employee = relationship(Employee

通过在DepartmentEmployeeLink关联模型上增加一个额外的列和两个额外的关系,我们可以存储更多的信息,并且可以更加自由地使用这些信息。例如,假设我们有一个在 IT 部门兼职的员工约翰,我们可以将字符串“兼职”插入到列extra_data中,并创建一个DepartmentEmployeeLink对象来表示这种关系。


>>> fp = 'orm_in_detail.sqlite'

>>> if os.path.exists(fp):

...     os.remove(fp)

...

>>> from sqlalchemy import create_engine

>>> engine = create_engine('sqlite:///association_tables.sqlite')

>>>

>>> from sqlalchemy.orm import sessionmaker

>>> session = sessionmaker()

>>> session.configure(bind=engine)

>>> Base.metadata.create_all(engine)

>>>

>>>

>>> IT = Department(name="IT")

>>> John = Employee(name="John")

>>> John_working_part_time_at_IT = DepartmentEmployeeLink(department=IT, employee=John, extra_data='part-time')

>>> s = session()

>>> s.add(John_working_part_time_at_IT)

>>> s.commit()

然后,我们可以通过查询 IT 部门或DepartmentEmployeeLink模型来找到 John。


>>> IT.employees[0].name

u'John'

>>> de_link = s.query(DepartmentEmployeeLink).join(Department).filter(Department.name == 'IT').one()

>>> de_link.employee.name

u'John'

>>> de_link = s.query(DepartmentEmployeeLink).filter(DepartmentEmployeeLink.extra_data == 'part-time').one()

>>> de_link.employee.name

u'John'

最后,使用关系Department.employees添加 IT 员工仍然有效,如前一篇文章所示:


>>> Bill = Employee(name="Bill")

>>> IT.employees.append(Bill)

>>> s.add(Bill)

>>> s.commit()

链接与 Backref 的关系

到目前为止,我们在relationship定义中使用的一个常见关键字参数是backref。一个backref是将第二个relationship()放置到目标表上的常见快捷方式。例如,下面的代码通过在Post.owner上指定一个backref将第二个relationship()“帖子”放到user表格上:


class User(Base):

    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)

    name = Column(String(256))
 Post(Base):
_ _ tablename _ _ = ' Post '
id = Column(Integerprimary _ key = True)
owner _ id = Column(Integerforeign key(' User . id ')
owner = relationship(Userbackref = backref(' Post 'uselist=True)) 

这相当于以下定义:


class User(Base):

    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)

    name = Column(String(256))

    posts = relationship("Post", back_populates="owner")
 Post(Base):
_ _ tablename _ _ = ' Post '
id = Column(Integerprimary _ key = True)
owner _ id = Column(Integerforeign key(' User . id ')
owner = relationship(Userback _ populated = " posts ")

现在我们在UserPost之间有了一个one-to-many关系。我们可以通过以下方式与这两个模型进行交互:


>>> s = session()

>>> john = User(name="John")

>>> post1 = Post(owner=john)

>>> post2 = Post(owner=john)

>>> s.add(post1)

>>> s.add(post2)

>>> s.commit()

>>> s.refresh(john)

>>> john.posts

[, ]

>>> john.posts[0].owner
> > > John . posts[0]. owner . name
u ' John '

一对一

在模型之间创建one-to-one关系与创建many-to-one关系非常相似。通过在backref()中将uselist参数的值修改为False,我们强制数据库模型以one-to-one关系相互映射。


class User(Base):

    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)

    name = Column(String(256))
类地址(Base):
_ _ tablename _ _ = ' Address '
id = Column(Integerprimary _ key = True)
Address = Column(String(256))
User _ id = Column(Integerforeign key(' User . id '))
User = relationship(' User 'backref=backref('address 'uselist=False)) 

然后,我们可以按以下方式使用模型:


>>> s = session()

>>> john = User(name="John")

>>> home_of_john = Address(address="1234 Park Ave", user=john)

>>> s.add(home_of_john)

>>> s.commit()

>>> s.refresh(john)

>>> john.address.address

u'1234 Park Ave'

>>> john.address.user.name

u'John'

>>> s.close()

关系更新级联

在关系数据库中,参照完整性保证当one-to-manymany-to-many关系中被引用对象的主键改变时,引用主键的引用对象的外键也将改变。但是,对于不支持参照完整性的数据库,如关闭了参照完整性选项的 SQLite 或 MySQL更改被引用对象的主键值不会触发引用对象的更新。在这种情况下我们可以使用relationshipbackref中的passive_updates标志来通知数据库执行额外的 SELECT 和 UPDATE 语句,这些语句将更新引用对象的外键的值。

在下面的例子中,我们在UserAddress之间构造了一个one-to-many关系,并且没有在关系中指定passive_updates标志。数据库后端是 SQLite。


class User(Base):

    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)

    name = Column(String(256))
类地址(Base):
_ _ tablename _ _ = ' Address '
id = Column(Integerprimary _ key = True)
Address = Column(String(256))
User _ id = Column(Integerforeign key(' User . id '))
User = relationship(
' User 'backref=backref('addresses 'uselist=True) 
 ) 

然后,当我们改变一个User对象的主键值时,它的Address对象的user_id外键值将不会改变。因此,当你想再次访问一个addressuser对象时,你会得到一个AttributeError


>>> s = session()

>>> john = User(name='john')

>>> home_of_john = Address(address='home', user=john)

>>> office_of_john = Address(address='office', user=john)

>>> s.add(home_of_john)

>>> s.add(office_of_john)

>>> s.commit()

>>> s.refresh(john)

>>> john.id

1

>>> john.id = john.id + 1

>>> s.commit()

>>> s.refresh(home_of_john)

>>> s.refresh(office_of_john)

>>> home_of_john.user.name

Traceback (most recent call last):

  File "", line 1, in

AttributeError: 'NoneType' object has no attribute 'name'

>>> s.close()

如果我们在Address模型中指定了passive_updates标志,那么我们可以更改john的主键,并期望 SQLAlchemy 发出额外的 SELECT 和 UPDATE 语句来保持home_of_john.useroffice_of_john.user是最新的。


class User(Base):

    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)

    name = Column(String(256))
类地址(Base):
_ _ tablename _ _ = ' Address '
id = Column(Integerprimary _ key = True)
Address = Column(String(256))
User _ id = Column(Integerforeign key(' User . id '))
User = relationship(
' User 'backref=backref('addresses 'uselist=Truepassive_updates=False) 
 ) 


>>> s = session()

>>> john = User(name='john')

>>> home_of_john = Address(address='home', user=john)

>>> office_of_john = Address(address='office', user=john)

>>> s.add(home_of_john)

>>> s.add(office_of_john)

>>> s.commit()

>>> s.refresh(john)

>>> john.id

1

>>> john.id = john.id + 1

>>> s.commit()

>>> s.refresh(home_of_john)

>>> s.refresh(office_of_john)

>>> home_of_john.user.name

u'john'

>>> s.close()

摘要

在本文中,我们将深入探讨 SQLAlchemy 的关联表和关键字参数backref。理解这两个概念背后的机制对于完全掌握复杂的连接查询通常是至关重要的,这将在以后的文章中展示。