SQLALchemy中關於複雜關係表模型的映射處理

映射在第五步,咱們仍是一步一步來哈html

一. 關係介紹mysql

  舉一個比較經典的關係,部門與員工(如下是個人需求狀況,算是把該有的關係都涉及到了)sql

  1.每一個部門會有不少成員(這裏排除一個成員屬於多個部門的狀況)    --->  一對多數據庫

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

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

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

  5.每一個員工可能有不少附屬部門 ---> 多對多ide

  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部的下級爲2一、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

相關文章
相關標籤/搜索