sqlalchemy映射關係、外鍵和relationship查詢

SQLAlchemy中的映射關係有四種,分別是一對多,多對一,一對一,多對多
理解好映射關聯關係,對後續多表查詢,對象間關係非常重要。

下面可以先看看資料,如果不太理解可以看看本文,循序漸進的瞭解知識點。

一、映射關係理解

一對多(one to many):

因爲外鍵(ForeignKey)始終定義在多的一方.如果relationship定義在多的一方,那就是多對一,一對多與多對一的區別在於其關聯(relationship)的屬性在多的一方還是一的一方,如果relationship定義在一的一方那就是一對多.
這裏的例子中,一指的是Parent,一個parent有多個child:

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'))

多對一(many to one):

這個例子中many是指parent了,意思是一個child可能有多個parent(父親和母親),這裏的外鍵(child_id)和relationship(child)都定義在多(parent)的一方

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

爲了建立雙向關係,可以在relationship()中設置backref,Child對象就有parents屬性.設置 cascade= ‘all’,可以級聯刪除:

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer,primary_key = True)
    children = relationship("Child",cascade='all',backref='parent')
 
def delete_parent():
    session = Session()
    parent = session.query(Parent).get(2)
    session.delete(parent)
    session.commit()

不過不設置cascade,刪除parent時,其關聯的chilren不會刪除,只會把chilren關聯的parent.id置爲空,設置cascade後就可以級聯刪除children

一對一(one to one):

一對一就是多對一和一對多的一個特例,只需在relationship加上一個參數uselist=False替換多的一端就是一對一:
從一對多轉換到一對一:

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'))

多對多(many to many):

多對多關係需要一箇中間關聯表,通過參數secondary來指定,

from sqlalchemy import Table,Text
post_keywords = Table('post_keywords',Base.metadata,
        Column('post_id',Integer,ForeignKey('posts.id')),
        Column('keyword_id',Integer,ForeignKey('keywords.id'))
)
 
class BlogPost(Base):
    __tablename__ = 'posts'
    id = Column(Integer,primary_key=True)
    body = Column(Text)
    keywords = relationship('Keyword',secondary=post_keywords,backref='posts')
 
class Keyword(Base):
    __tablename__ = 'keywords'
    id = Column(Integer,primary_key = True)
    keyword = Column(String(50),nullable=False,unique=True)

二、外鍵 體現的外鍵特性

1.外鍵回顧

外鍵的出現是因爲兩張表之間需要有關聯,爲了保證數據的完整性和唯一性而產生的。有外鍵時會有兩張以上的表,分爲主表和附表。附表中數據往往是主表中數據的延伸,附表中有外鍵關聯到主表的主鍵上。

在sqlalchemy的ORM模型中,定義表時指定主鍵和外鍵。

  • 主鍵定義:

在字段信息後面加上primary_key=True

name = Column(String(20),primary_key=True)

  • 外鍵定義:

在字段後面加上Foreignkey(主表.主鍵)

company_name = Column(String(32),ForeignKey("company.name"))
company = relationship("Company",backref="phone_of_company") 

另外在定義主鍵時往往還會定義一個relationship,後續詳解

2.定義表

定義兩張表,company和phone,company中的name是主鍵,phone中的id是主鍵,並且phone中定義company_name外鍵。

sql_foreign_models.py

import sqlalchemy
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,Integer,String
#創建引擎
from sqlalchemy.orm import relationship

engine=create_engine("mysql+pymysql://test:[email protected]/pythonstudy",
                       encoding='utf-8', echo=True)
#生成ORM基類
Base=declarative_base()  #生成orm基類

class Company(Base):
    __tablename__ = "company"

    name = Column(String(20), primary_key=True)
    location = Column(String(20))

    def __repr__(self):
        return "name:{0} location:{1}".format(self.name, self.location)


class Phone(Base):
    __tablename__ = "phone"

    id = Column(Integer, primary_key=True)
    model = Column(String(32))
    price = Column(String(32))
    company_name = Column(String(32), ForeignKey("company.name"))
    company = relationship("Company", backref="phone_of_company")

    def __repr__(self):
        return "{0} model:{1},sales:{2} sales:{3} price:{4}".format(self.id, self.model, self.sales, self.price)
Base.metadata.create_all(engine)  # 創建表

3.插入數據

連接db同時填充調試數據 ,sql_insert.py,sql_foreign_insert.py

#sql_insert.py  
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.dialects.mysql import INTEGER, CHAR
from sqlalchemy import create_engine, Column
def insert(new_data):
    engine = create_engine("mysql+pymysql://test:[email protected]/pythonstudy",
                           encoding='utf-8', echo=True)
    print("創建數據庫引擎")
    DBSession = sessionmaker(bind=engine)
    session = DBSession()
    print("創建session對象")
    session.add(new_data)
    print("添加數據到session")
    session.commit()
    print("提交數據到數據庫")
    session.close()
    print("關閉數據庫連接")



#sql_foreign_insert.py

companys = {
    "Apple": "Amercian",
    "Xiaomi": "China",
    "Huawei": "China",
    "Sungsum": "Korea",
    "Nokia": "Finland"
}

phones = (
    [1, "iphoneX", "Apple", 8400],
    [2, "xiaomi2s", "Xiaomi", 3299],
    [3, "Huaweimate10", "Huawei", 3399],
    [4, "SungsumS8", "SungSum", 4099],
    [5, "NokiaLumia", "Nokia", 2399],
    [6, "iphone4s", "Apple", 3800]
)

for key in companys:
    new_company = Company(name=key, location=companys[key])
    insert(new_company)

for phone in phones:
    id = phone[0]
    model = phone[1]
    company_name = phone[2]
    price = phone[3]

    new_phone = Phone(id=id, model=model, company_name=company_name, price=price)
    insert(new_phone)

直接運行sql_foreign_insert.py 就可以完成表創建,數據庫數據表填充數據操作,一步到位。
數據庫表數據如下
在這裏插入圖片描述

在這裏插入圖片描述

4.sqlalchemy外鍵操作

總結外鍵的優點有兩個:保證數據的完整性和保證數據的一致性。那麼在sqlqlchemy如何體現完整性和一致性呢?通過數據的插入和刪除來體現。

  • 完整性:附表插入數據時會檢查外鍵所在字段在主表中是否存在

在phone表中插入數據:(7,Blackberry,“RIM”,3200)黑莓手機,所在公司是RIM。

#會直接報錯的:pymysql.err.IntegrityError: (1452, 'Cannot add or update a child row: a foreign key constraint fails

new_phone = Phone(id=7,model="BlackBerry",company_name="RIM",price=3200)
insert(new_phone)

報錯:不能添加或者更新一個子行,有一個外鍵關聯。

因爲主表company的主鍵,也就是phone外鍵關聯的字段沒有“RIM”,所以當phone寫入數據時會檢查company_name字段的值是否在company中存在。而company中不存在該值,所以不能寫入。這樣做就保證了數據的完整性。

  • 一致性:一致性的規則有多個,具體如下引用:
外鍵約束對父表的含義:
   在父表上進行update/delete以更新或刪除在子表中有一條或多條對應匹配行的候選鍵時,父表的行爲取決於:在定義子表的外鍵時指定的on update/on delete子句, InnoDB支持5種方式, 分列如下
   
   . cascade方式
在父表上update/delete記錄時,同步update/delete掉子表的匹配記錄
On delete cascade從mysql3.23.50開始可用; on update cascade從mysql4.0.8開始可用
 
   . set null方式 


在父表上update/delete記錄時,將子表上匹配記錄的列設爲null
要注意子表的外鍵列不能爲not null
On delete set null從mysql3.23.50開始可用; on update set null從mysql4.0.8開始可用
 
   . No action方式
如果子表中有匹配的記錄,則不允許對父表對應候選鍵進行update/delete操作
這個是ANSI SQL-92標準,從mysql4.0.8開始支持
 
   . Restrict方式
同no action, 都是立即檢查外鍵約束

舉個例子:刪除主表中的name=Sungsum的記錄。

from sqlalchemy.orm import relationship, sessionmaker

from com.wfc.python.day10sqlalchemy.外鍵和relationship查詢.sql_foreign_models import Company

engine=create_engine("mysql+pymysql://test:[email protected]/pythonstudy",
                       encoding='utf-8', echo=True)
#生成ORM基類
Base=declarative_base()  #生成orm基類
Session=sessionmaker(bind=engine)   #這裏的Session只是一個類, 而不是實例
session=Session()
company = session.query(Company).filter_by(name="Sungsum").first()
session.delete(company)
session.commit()

看看數據庫表數據
在這裏插入圖片描述
在這裏插入圖片描述
默認的外鍵關聯的動作是 “.set null”,即主表刪除數據,附表中關聯的字段設爲空。

除了默認的設置外,還可以選擇:

1、刪除主表數據,如果附表有記錄則不允許刪除

2、刪除主表數據,如果附表有記錄則一併刪除

三、外鍵和查詢

在數據結構上外鍵對連表查詢並沒有太多的幫助,但是在sqlalchemy的模型下外鍵對連表查詢有一定的優化,那就是relationship字段,其配合外鍵一起使用。

在沒有relationship字段時,如果想要查詢xiaomi2s手機的生產公司的地址如何查詢呢?分爲兩步走:

  • 查詢出phone表中xiaomi2s的company_name字段
  • 通過company_name字段查詢company表中的location字段。(phone.company_name==company.name)

有了relationship之後就不用分爲兩步走了,只需要一步就能搞定。在定義表的模型時,relationship將company和phone表關聯在一起。phone表中定義:

company_name = Column(String(32),ForeignKey("company.name"))
company = relationship("Company",backref="phone_of_company") 

表明將phone表和Company表聯繫在一起。backref是反向關聯,使用規則是:

  • company是主表,phone是從表。查詢phone表,返回phone_obj,可以通過phoen_obj.Company查詢到company中外鍵關聯的數據。查phone表返回company表裏的數據。這個稱之爲:正向查詢。
  • company是主表,phone是從表。查詢company表,返回company_obj,可以通過company_obj.phone_of_company查詢到phone表的外鍵關聯數據。查company表返回phone表裏的數據。這個稱之爲:反向查詢。

正向查詢

外鍵關聯_正向查詢.py

import sqlalchemy
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,Integer,String
#創建引擎
from sqlalchemy.orm import relationship, sessionmaker
from com.wfc.python.day10sqlalchemy.外鍵和relationship查詢.sql_foreign_models import Company, Phone
engine=create_engine("mysql+pymysql://test:[email protected]/pythonstudy",
                       encoding='utf-8', echo=True)
#生成ORM基類
Base=declarative_base()  #生成orm基類
Session=sessionmaker(bind=engine)   #這裏的Session只是一個類, 而不是實例
session=Session()
#查詢phone表
phone_obj = session.query(Phone).filter_by(id = 1).first()
#通過phone表關聯的relationship字段"Company"查詢出company表的數據
print(phone_obj.company.name)
print(phone_obj.company.location)

反向查詢

外鍵關聯_反向查詢.py

import sqlalchemy
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,Integer,String
#創建引擎
from sqlalchemy.orm import relationship, sessionmaker
from com.wfc.python.day10sqlalchemy.外鍵和relationship查詢.sql_foreign_models import Company, Phone
engine=create_engine("mysql+pymysql://test:[email protected]/pythonstudy",
                       encoding='utf-8', echo=True)
#生成ORM基類
Base=declarative_base()  #生成orm基類
Session=sessionmaker(bind=engine)   #這裏的Session只是一個類, 而不是實例
session=Session()
#查詢company表
company_obj = session.query(Company).filter_by(name = "Nokia").first()
   #通過phone表關聯的relationship的字段"backref="phone_of_company"",查詢phone表數據
print(type(company_obj))
print(company_obj.phone_of_company[0].id)
print(company_obj.phone_of_company[0].model)
print(company_obj.phone_of_company[0].price)
print(company_obj.phone_of_company[0].company_name)

本文所用到的源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章