SQLALchemy中關於復雜關系表模型的映射處理


映射在第五步,我們還是一步一步來哈

一. 關系介紹

  舉一個比較經典的關系,部門與員工(以下是我的需求情況,算是把該有的關系都涉及到了)

  1.每個部門會有很多成員(這里排除一個成員屬於多個部門的情況)    --->  一對多

  2.每個部門都有一個負責人   --->  多對一

  3.每個部門可能有一個上級部門 ---> 自關聯多對一

  4.每個員工都有一個主屬部門  ---> 多對一

  5.每個員工可能有很多附屬部門 ---> 多對多

  6.每個員工可能有很多上級員工 ---> 自關聯多對多

 

二. 設計部門與成員表模型

  直接附上模型表代碼,但是不包含關系映射(單獨寫一步)

from sqlalchemy.ext.declarative import declarative_base BaseModel = declarative_base()    # 創建模型對象的基類

# 部門
class Department(BaseModel):   __tablename__ = 'dep'   id = Column(Integer(), primary_key=True, autoincrement=True)   name = Column(String(30),nullable=False,unique=True)    # 部門名稱
  staff_id = Column(Integer(),ForeignKey('staff.id'))    # 負責人
  up_dep_id = Column(Integer(),ForeignKey('dep.id'))   # 上級部門----自關聯
  def __repr__(self):  # 方便打印查看
    return '<Department %s>' % self.name # 員工
class Staff(BaseModel):   __tablename__ = 'staff'     id = Column(Integer(), primary_key=True, autoincrement=True)   name = Column(String(30),nullable=False,unique=True)    # 員工名稱
  main_dep_id = Column(Integer(),ForeignKey('dep.id'))   # 主要部門
  def __repr__(self):     return '<Staff %s>' % self.name

 

三. 設計第三方表模型--附屬部門與上級員工

   建立多對多關系映射時secondary參數需要指定一個第三方表模型對象,但不是自己寫的Class哦,而是一個Table對象

from sqlalchemy import Table  # 使用Table專門生成第三方表模型

# 第三方表--附屬部門
aux_dep_table = Table('staff_aux_dep',BaseModel.metadata,     Column('id',Integer(),primary_key=True, autoincrement=True),     Column('dep_id', Integer(), ForeignKey('dep.id',ondelete='CASCADE')),     Column('staff_id', Integer(), ForeignKey('staff.id',ondelete='CASCADE')) ) # 第三方表--上級員工
up_obj_table = Table('staff_up_obj',BaseModel.metadata,     Column('id',Integer(),primary_key=True, autoincrement=True),     Column('up_staff_id', Integer(), ForeignKey('staff.id',ondelete='CASCADE')),     Column('down_staff_id', Integer(), ForeignKey('staff.id',ondelete='CASCADE')) )

 

四. 生成表

from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker engine = create_engine('mysql+pymysql://root:123456@localhost:3306/test?charset=utf8')  # 關聯數據庫
DBSession = sessionmaker(engine)          # 創建DBSession類
session = DBSession()                 # 創建session對象

BaseModel.metadata.create_all(engine)      # 數據庫生成表

 

 

五. relationship--關系映射

  簡單來說, relationship函數是sqlalchemy對關系之間提供的一種便利的調用方式, relationship的返回值賦給的變量名為正向調用的屬性名,綁定給當前表模型類中,而backref參數則是指定反向調用的屬性名,綁定給關聯的表模型類中,如下部門表中Department.staffs就為正向,Staff.main_dep就為反向。

  先導入relationship

from sqlalchemy.orm import relationship

  

  先映射部門表,需要注意的是:

    1. 是由於部門與員工之間有多重外鍵約束,在多對一或一對多關系相互映射時需要用foreign_keys指定映射的具體字段

    2. 自關聯多對一或一對多時候,需要用remote_side參數指定‘一’的一方,值為一個Colmun對象(必須唯一)

# 部門
class Department(BaseModel): __tablename__ = 'dep'   id = Column(Integer(), primary_key=True, autoincrement=True)   name = Column(String(30), nullable=False, unique=True)                  # 部門名稱
  staff_id = Column(Integer(), ForeignKey('staff.id'))                  # 負責人
  up_dep_id = Column(Integer(), ForeignKey('dep.id'))                  # 上級部門----自關聯
 
  # 主屬部門為此部門的所有員工,反向Staff實例.main_dep獲取員工的主屬部門
  main_staffs = relationship('Staff', backref='main_dep', foreign_keys='Staff.main_dep_id')   # 部門的直屬上級部門,反向Department實例.down_deps,獲取部門的所有直屬下級部門(自關聯多對一需用remote_side=id指定‘一’的一方)
  up_dep = relationship('Department', backref='down_deps', remote_side=id)

  重新生成數據(當調用屬性映射的為‘多’的一方,則代表的是一個InstrumentedList 類型的結果集,是List的子類,對這個集合的修改,就是修改外鍵關系)

staff_names = ['','','','',''] dep_names = ['1部','2部','21部','22部'] staff_list = [Staff(name=name) for name in staff_names] dep_list = [Department(name=name) for name in dep_names] # 為所有員工初始分配一個主屬部門(按列表順序對應)
[dep.main_staffs.append(staff) for staff,dep in zip(staff_list,dep_list)] # 關聯上下級部門(2部的上級為1部,2部的下級為21、22部)
dep_list[1].up_dep = dep_list[0] dep_list[1].down_deps.extend(dep_list[2:]) session.add_all(staff_list + dep_list) session.commit()      # 數據持久化到數據庫

 

 

 

   部門表關系映射 查詢測試

dep_2 = session.query(Department).filter_by(name='2部').one()  # 獲取 2部 部門
staff_me = session.query(Staff).filter(Staff.name=='').one() # 獲取 ‘我’ 員工
print(dep_2.main_staffs)          # 主屬部門為2部的所有員工 print(dep_2.up_dep)             # 2部的上級部門 print(dep_2.down_deps)           # 2部的所有直屬下級部門 print(staff_me.main_dep)         # 員工‘我’ 的主屬部門 # [<Staff 你>] # <Department 1部> # [<Department 21部>, <Department 22部>] # <Department 1部>

 

 

  映射員工表,需要注意的是:

    1. 多對多關系中需要用secondary參數指定第三方表模型對象

    2. 自關聯多對多需要用primaryjoin和secondaryjoin指定主副連接關系,查詢邏輯是根據主連接關系對應的第三方表的字段查詢(例,查詢id為10的員工的所有上級對象,就會在第三方表里查詢down_staff_id=10對應的所有數據,而把每條數據的up_staff_id值對應員工表的id查詢出來的對象集合,就為id為10的員工的所有上級對象了)

# 員工
class Staff(BaseModel):   __tablename__ = 'staff'     id = Column(Integer(), primary_key=True, autoincrement=True)   name = Column(String(30), nullable=False, unique=True)              # 員工名稱
  main_dep_id = Column(Integer(), ForeignKey('dep.id'))            # 主要部門

  # 員工的所有附屬部門,反向為所有附屬部門為此部門的員工
  aux_deps = relationship(Department, secondary=aux_dep_table,backref = 'aux_staffs')   # 員工的所有直屬上級員工,反向為員工的所有直屬下級員工(自關聯多對多需要指定第三方表主連接關系與副連接關系,查詢邏輯是根據主連接關系對應的第三方表的字段查詢)
  up_staffs = relationship('Staff', secondary=up_obj_table,                 primaryjoin = id == up_obj_table.c.down_staff_id,        # 主連接關系 為 本表id字段對應第三方表的down_staff_id
                    secondaryjoin = id == up_obj_table.c.up_staff_id,        # 副連接關系 為 本表id對應第三表的up_staff_id
                    backref = 'down_staffs')                     

  建立數據映射關系

staff_all_list = session.query(Staff).all()                    # 獲取所有員工對象列表
dep_1 = session.query(Department).filter_by(name='1部').one()  # 獲取 部門 1部
dep_2 = session.query(Department).filter_by(name='2部').one()  # 獲取 部門 2部
staff_me = session.query(Staff).filter_by(name='').one()     # 獲取員工 ‘我’
 dep_1.aux_staffs.extend(staff_all_list) # 分配所有員工的附屬部門都為1部
staff_me.aux_deps.append(dep_2)                    # 給員工‘我’額外分配附屬部門2部
staff_all_list.remove(staff_me) staff_me.down_staffs.extend(staff_all_list) # 將所有除員工‘我’以外的員工 都成為 員工‘我’的下級
session.commit()

 

  員工表關系映射 查詢測試

staff_you = session.query(Staff).filter_by(name='').one()   # 獲取員工 你

print(dep_1.aux_staffs.all())                 # 所有附屬部門為1部的員工
print(staff_me.aux_deps.all())                # 員工‘我’ 的所有附屬部門
print(staff_me.down_staffs.all())             # 員工‘我’ 的所有直屬下級員工
print(staff_you.up_staffs.all())              # 員工‘你’ 的所有直屬上級員工
 # [<Staff 我>, <Staff 你>, <Staff 他>, <Staff 她>, <Staff 它>] # [<Department 1部>] # [<Staff 你>, <Staff 他>, <Staff 她>, <Staff 它>] # [<Staff 我>]

  

  如果我們想根據調用屬性獲得的集合,再進行篩選,可以嗎?

  模擬個場景,我已經知道了員工‘我’有一個附屬部門叫‘1部’了,我想知道除了‘1部’的其他附屬部門,如下:

from sqlalchemy import not_ staff_me.aux_deps.filter(not_(Department.name=='1部')) # AttributeError: 'InstrumentedList' object has no attribute 'filter' 

  報錯了,也是,之前都說了,調用屬性映射的為‘多’的一方代表的是一個InstrumentedList 對象,只有為Query對象才能有filter、all、one等的方法。

  那么該怎么轉換成Query對象呢,通過查看relationship的參數,發現了lazy,通過改變它的值可以在查詢時可以得到不同的結果,他有很多值可以選擇:

    1. 默認值為select, 他直接會導出所有的結果對象合成一個列表

aux_deps = relationship(Department, secondary=aux_dep_table, backref='aux_staffs', lazy='select') print(type(staff_me.aux_deps)) print(staff_me.aux_deps) # <class 'sqlalchemy.orm.collections.InstrumentedList'> # [<Department 1部>, <Department 2部>]

    2. dynamic,他會生成一個繼承與QueryAppenderQuery對象,可以用於繼續做過濾操作,這就是我們模擬的場景想要的,需要注意的是,如果調用屬性映射的不是‘多’而是‘一’的一方,那么就會報錯(雖然為默認值select沒有影響,但本來就一個結果,就不要加lazy參數了)

aux_deps = relationship(Department, secondary=aux_dep_table, backref='aux_staffs', lazy='dynamic') print(type(staff_me.aux_deps)) print(staff_me.aux_deps.all())        # 獲取員工‘我’的所有附屬部門
print(staff_me.aux_deps.filter(not_(Department.name == '1部')).all())  # 獲取員工‘我’除1部以外的所有附屬部門

# <class 'sqlalchemy.orm.dynamic.AppenderQuery'> # [<Department 1部>, <Department 2部>] # [ <Department 2部>]

    3. 其他的還有很多參數,例如joined,連接查詢,使用不當會有性能問題,以下為谷歌翻譯的,大家有興趣可以去看看原文https://docs.sqlalchemy.org/en/latest/orm/relationship_api.html,ctrl+f搜lazy

  我們現在知道當需要對映射的結果集繼續篩選的時候,可以在relationship指定lazy參數為'dynamic',但是在這里加好像只是正向調用的時候有效,反向還是為InstrumentedList 對象

dep_1 = session.query(Department).filter_by(name='1部').one()  # 獲取 部門 1部
print(dep_1.aux_staffs.all())      # 獲取所有附屬部門為1部的員工

# AttributeError: 'InstrumentedList' object has no attribute 'all'

 

  如果反向的時候我們該加在哪里呢?其實backref參數也可以接受一個元祖,里面只能是兩個參數,一個跟之前一樣是個字符串,為反向調用的屬性名,另一個就是一個加關於反向調用時的參數(dict對象)

aux_deps = relationship(Department, secondary=aux_dep_table, backref=('aux_staffs',{'lazy':'dynamic'}), lazy='dynamic') print(type(dep_1.aux_staffs.all())) print(dep_1.aux_staffs.all())    # 獲取所有附屬部門為1部的員工

# <class 'sqlalchemy.orm.dynamic.AppenderQuery'> # [<Staff 我>, <Staff 你>, <Staff 他>, <Staff 她>, <Staff 它>]

 

掌握了如上,之后使用SQLALchemy在工作中遇到的奇葩關系應該都能處理了吧,我語言組織能力比較弱,有什么錯誤還請指出----1738268742@qq.com

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM