Python SQLALchemy框架

雲崖先生發表於2020-12-17

SQLALchemy

   SQLALchemyPython中的一款優秀的ORM框架,它可以作用於任何第三方Web框架,如flasktornado等框架。

   SQLALchemy相較於DjangoORM來說更加的貼近原生SQL語句,因此學習難度較低。

   img

  

組成部分描述
Engine 框架引擎
Connection Pooling 資料庫連結池
Dialect 資料庫DB API種類
Schema/Types 架構&型別
SQL Exprression Language SQL表示式語言

   下載SQLALchemy模組:

pip install sqlalchemy

   值得注意的是SQLALchemy必須依賴其他操縱資料的模組,Dialect用於和資料API進行交流,根據配置檔案的不同呼叫不同的資料庫API,從而實現對資料庫的操作,如:

MySQL-Python
    mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
    
pymysql
    mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
    
MySQL-Connector
    mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
    
cx_Oracle
    oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]
    
更多:http://docs.sqlalchemy.org/en/latest/dialects/index.html

表操作

   SQLALchemy中不允許修改表結構,如果修改表結構則需要刪除舊錶,再建立新表:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import datetime

from sqlalchemy import Column, Integer, String, DateTime, UniqueConstraint, Index
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base

# 基礎類
Base = declarative_base()

# 建立引擎
engine = create_engine(
    "mysql+pymysql://root@127.0.0.1:3306/db1?charset=utf8",
    # "mysql+pymysql://root:123@127.0.0.1:3306/db1?charset=utf8",  # 有密碼時
    max_overflow=0,  # 超過連線池大小外最多建立的連線
    pool_size=5,  # 連線池大小
    pool_timeout=30,  # 池中沒有執行緒最多等待的時間,否則報錯
    pool_recycle=-1  # 多久之後對執行緒池中的執行緒進行一次連線的回收(重置)
)


class Users(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String(32), index=True, nullable=False)
    age = Column(Integer,nullable=False)
    phone = Column(String(11))
    addr = Column(String(64), nullable=True)
    create_time = Column(DateTime, default=datetime.datetime.now)  # 一定不要加括號

    __table_args__ = (
        UniqueConstraint("id", "name"),  # 建立聯合唯一 可指定name給個別名
        Index("phone", "addr", unique=True),  # 建立聯合唯一索引  可指定name給個別名
    )

    def __str__(self):
        return "object:<id:%s name:%s>" % (self.id, self.name)


def create_tb():
    """
    建立表
    :return:
    """
    Base.metadata.create_all(engine)


def drop_tb():
    """
    刪除表
    :return:
    """
    Base.metadata.drop_all(engine)


if __name__ == '__main__':
    drop_tb()
    create_tb()

連結庫

   表建立好之後,開始連結庫。

from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import scoped_session

# 匯入引擎,模型表等
from models import *

# 通過Session繫結引擎和資料庫建立關係
Session = sessionmaker(bind=engine)
# 建立連結池,使用session即可為當前執行緒拿出一個連結物件。內部採用threading.local進行隔離
session = scoped_session(Session)

單表記錄

新增記錄

   新增單條記錄:

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

# 匯入引擎,模型表等
from models import *

# 通過Session繫結引擎和資料庫建立關係
Session = sessionmaker(bind=engine)
# 建立連結池,使用session即可為當前執行緒拿出一個連結物件。內部採用threading.local進行隔離
session = scoped_session(Session)


user_obj = Users(name="user001", phone="15125352333",age=23, addr="China")
session.add(user_obj)

# 提交
session.commit()

# 關閉連結(可使用session.remove())
session.close()

修改記錄

   修改記錄:

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

# 匯入引擎,模型表等
from models import *

# 通過Session繫結引擎和資料庫建立關係
Session = sessionmaker(bind=engine)
# 建立連結池,使用session即可為當前執行緒拿出一個連結物件。內部採用threading.local進行隔離
session = scoped_session(Session)

# 修改名字
session.query(Users).filter_by(id=1).update({"name": "USER001"})
# 修改年齡,使用+號,預設為"fetch",代表只允許int型別使用+號
session.query(Users).filter_by(id=1).update({"age": Users.age + 1},synchronize_session="fetch")
# 修改地址,使用+號,由於是字元型別,所以要修改synchronize_session=False
session.query(Users).filter_by(id=1).update({"addr":Users.addr + "BeiJing"},synchronize_session=False)

# 提交
session.commit()

# 關閉連結
session.close()

刪除記錄

   刪除案例:

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

# 匯入引擎,模型表等
from models import *

# 通過Session繫結引擎和資料庫建立關係
Session = sessionmaker(bind=engine)
# 建立連結池,使用session即可為當前執行緒拿出一個連結物件。內部採用threading.local進行隔離
session = scoped_session(Session)

session.query(Users).filter_by(id=2).delete()

# 提交
session.commit()

# 關閉連結
session.close()

批量增加

   批量增加:

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

# 匯入引擎,模型表等
from models import *

# 通過Session繫結引擎和資料庫建立關係
Session = sessionmaker(bind=engine)
# 建立連結池,使用session即可為當前執行緒拿出一個連結物件。內部採用threading.local進行隔離
session = scoped_session(Session)

# 批量增加
session.add_all([
    Users(name="user002",age=21,phone="13269867233",addr="ShangHai"),
    Users(name="user003",age=18,phone="13269867234",addr="GuangZhou"),
    Users(name="user003",age=24,phone="13269867235",addr="ChongQing"),
])

# 提交
session.commit()

# 關閉連結
session.close()

單表查詢

基本查詢

   基本查詢:

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

# 匯入引擎,模型表等
from models import *

# 通過Session繫結引擎和資料庫建立關係
Session = sessionmaker(bind=engine)
# 建立連結池,使用session即可為當前執行緒拿出一個連結物件。內部採用threading.local進行隔離
session = scoped_session(Session)

# 查詢
# -- 查所有 --
result_01 = session.query(Users).all()
# -- 過濾 --
result_02 = session.query(Users).filter(Users.name == "USER001").all()  # Python表示式的形式過濾
result_03 = session.query(Users).filter_by(name="user002").all()  # ORM形式過濾
result_04 = session.query(Users).filter_by(name="user003").first()  # ORM形式過濾 取第一個

print(result_01) # [<models.Users>,<models.Users>,<models.Users>]
print(result_02)
print(result_03)
print(result_04) # object:<id:3 name:user003>  通過__str__拿到結果

# 提交
session.commit()

# 關閉連結
session.close()

其他過濾

   條件查詢:

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

# 匯入引擎,模型表等
from models import *

# 通過Session繫結引擎和資料庫建立關係
Session = sessionmaker(bind=engine)
# 建立連結池,使用session即可為當前執行緒拿出一個連結物件。內部採用threading.local進行隔離
session = scoped_session(Session)

# 只拿某欄位
result_00 = session.query(Users.name,Users.age).first()
print(result_00)

# and(用逗號或者用and_)
result_01 = session.query(Users).filter( Users.id > 1,Users.age < 23).all()
print(result_01)

from sqlalchemy import and_
result_02 = session.query(Users).filter(and_( Users.id > 1,Users.age < 23)).all()
print(result_02)

# or
from sqlalchemy import  or_
result_03 = session.query(Users).filter(or_(Users.id > 3,Users.age < 23)).all()
print(result_03)

# and與or的組合使用
result_04 = session.query(Users).filter(or_(
    Users.id > 1,
    and_(Users.id > 2, Users.age < 24)
)).all()
print(result_04)

# 範圍
result_05 = session.query(Users).filter(Users.age.between(18,24)).all()
print(result_05)

# 包含
result_06 = session.query(Users).filter(Users.age.in_([18,21,24])).all()
print(result_06)

# 取反 ~
result_07 = session.query(Users).filter(~Users.age.in_([18,21,24])).all()
print(result_07)

# 萬用字元
result_08 = session.query(Users).filter(Users.name.like("us%")).all()
print(result_08)

# 分頁
result_09 = session.query(Users).all()[0:1]
print(result_09)

# 排序
result_10 = session.query(Users).order_by(Users.id.desc()).all()  # 倒序
print(result_10)

result_11 = session.query(Users).order_by(Users.id.asc()).all()  # 正序
print(result_11)

# 分組
result_12 = session.query(Users).group_by(Users.id).all()
print(result_12)


# 聚合函式
from sqlalchemy.sql import func
result_13 = session.query(
    func.max(Users.age),
    func.sum(Users.age),
    func.min(Users.age),
).group_by(Users.name).having(func.max(Users.age > 12)).all()
print(result_13)

# 提交
session.commit()

# 關閉連結
session.close()

多表相關

一對多

   首先是建立一對多的關係,使用relationship做邏輯一對多,不會在物理表中建立關係,但是可以通過該欄位進行增刪改查:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base

# 基礎類
Base = declarative_base()

# 建立引擎
engine = create_engine(
    "mysql+pymysql://root@127.0.0.1:3306/db1?charset=utf8",
    # "mysql+pymysql://root:123@127.0.0.1:3306/db1?charset=utf8",  # 有密碼時
    max_overflow=0,  # 超過連線池大小外最多建立的連線
    pool_size=5,  # 連線池大小
    pool_timeout=30,  # 池中沒有執行緒最多等待的時間,否則報錯
    pool_recycle=-1  # 多久之後對執行緒池中的執行緒進行一次連線的回收(重置)
)


class Classes(Base):
    __tablename__ = "classes"

    id = Column(Integer, primary_key=True)
    name = Column(String(32), nullable=False)


class Students(Base):
    __tablename__ = "students"

    id = Column(Integer, primary_key=True)
    name = Column(String(32), nullable=False)
    # 真實約束欄位:避免髒資料寫入,在物理表中會建立真實欄位關係
    # 可選級聯操作:CASCADE,DELETE、RESTRICT
    fk_class = Column(Integer, ForeignKey("classes.id",ondelete="CASCADE",onupdate="CASCADE"))
    # 邏輯關係欄位:不會在真實物理表中建立欄位,但是可以通過該邏輯欄位進行增刪改查
    # backref:反向查詢的名字
    re_class = relationship("Classes",backref="students")

def create_tb():
    """
    建立表
    :return:
    """
    Base.metadata.create_all(engine)


def drop_tb():
    """
    刪除表
    :return:
    """
    Base.metadata.drop_all(engine)


if __name__ == '__main__':
    drop_tb()
    create_tb()

   通過邏輯欄位進行增加:

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

# 匯入引擎,模型表等
from models import *

# 通過Session繫結引擎和資料庫建立關係
Session = sessionmaker(bind=engine)
# 建立連結池,使用session即可為當前執行緒拿出一個連結物件。內部採用threading.local進行隔離
session = scoped_session(Session)


session.add_all(
    [
        Students(name="學生01", re_class=Classes(name="一年級一班")),  # 自動填入fk_class
        Students(name="學生02", re_class=Classes(name="一年級二班")),
    ]
)

# 提交
session.commit()

# 關閉連結
session.close()

多對多

   多對多也使用relationship做邏輯多對多,不會在物理表中建立關係,但是可以通過該欄位進行增刪改查。

   使用relationship時,傳入指定手動生成的第三張表,代表這是多對多關係:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship

# 基礎類
Base = declarative_base()

# 建立引擎
engine = create_engine(
    "mysql+pymysql://root@127.0.0.1:3306/db1?charset=utf8",
    # "mysql+pymysql://root:123@127.0.0.1:3306/db1?charset=utf8",  # 有密碼時
    max_overflow=0,  # 超過連線池大小外最多建立的連線
    pool_size=5,  # 連線池大小
    pool_timeout=30,  # 池中沒有執行緒最多等待的時間,否則報錯
    pool_recycle=-1  # 多久之後對執行緒池中的執行緒進行一次連線的回收(重置)
)


class Classes(Base):
    __tablename__ = "classes"

    id = Column(Integer, primary_key=True)
    name = Column(String(32), nullable=False)


class Students(Base):
    __tablename__ = "students"

    id = Column(Integer, primary_key=True)
    name = Column(String(32), nullable=False)
    # 可選級聯操作:CASCADE,DELETE、RESTRICT
    fk_class = Column(Integer, ForeignKey("classes.id", ondelete="CASCADE", onupdate="CASCADE"))
    # 邏輯關係欄位:不會在真實物理表中建立欄位,但是可以通過該邏輯欄位進行增刪改查
    # backref:反向查詢的名字
    re_class = relationship("Classes", backref="students")


class Teachers(Base):
    __tablename__ = "teachers"

    id = Column(Integer, primary_key=True)
    name = Column(String(32), nullable=False)
    # 邏輯欄位M2M:指定第三張表,secondary引數為__tablename__,反向查詢為teachers
    re_class = relationship("Classes", secondary="teachersm2mclasses", backref="teachers")


class TeachersM2mClasses(Base):
    __tablename__ = "teachersm2mclasses"

    id = Column(Integer, primary_key=True)
    teacher_id = Column(Integer, ForeignKey("teachers.id"))
    class_id = Column(Integer, ForeignKey("classes.id"))

    __table_args__ = (
        UniqueConstraint("teacher_id", "class_id"),  # 建立聯合唯一 可指定name給個別名
    )


def create_tb():
    """
    建立表
    :return:
    """
    Base.metadata.create_all(engine)


def drop_tb():
    """
    刪除表
    :return:
    """
    Base.metadata.drop_all(engine)


if __name__ == '__main__':
    drop_tb()
    create_tb()

   用一個列表,將班級的記錄物件放進去,你可以用多種增加方式,使用邏輯欄位新增或自己操縱第三張表:

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

# 匯入引擎,模型表等
from models import *

# 通過Session繫結引擎和資料庫建立關係
Session = sessionmaker(bind=engine)
# 建立連結池,使用session即可為當前執行緒拿出一個連結物件。內部採用threading.local進行隔離
session = scoped_session(Session)

session.add_all(
    [
        Teachers(name="老師01",re_class=[ 
            session.query(Classes).filter_by(id=1).first()
        ]),
        Teachers(name="老師02",re_class=[
            session.query(Classes).filter_by(id=1).first()
        ]),
        Teachers(name="老師03",re_class=[
            session.query(Classes).filter_by(id=2).first()
        ]),
    ]
)

# 提交
session.commit()

# 關閉連結
session.close()

組合查詢

   組合查詢將兩張表用笛卡爾積的效果顯現出來:

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

# 匯入引擎,模型表等
from models import *

# 通過Session繫結引擎和資料庫建立關係
Session = sessionmaker(bind=engine)
# 建立連結池,使用session即可為當前執行緒拿出一個連結物件。內部採用threading.local進行隔離
session = scoped_session(Session)

# 必須用filter,獲取全部也是,不可以使用all因為他會返回一個list,list不具備union_all
# 使用filter返回的物件是:<class 'sqlalchemy.orm.query.Query'>
# 並且query中必須單拿某一個欄位,如果不指定欄位就直接返回物件

s = session.query(Students.name).filter()
t = session.query(Teachers.name).filter()
c = session.query(Classes.name).filter()
ret = s.union_all(t).union_all(c).all()  # 用列表顯示
print(ret)
# [('學生01',), ('學生02',), ('老師01',), ('老師02',), ('老師03',), ('一年級一班',), ('一年級二班',)]

# 提交
session.commit()

# 關閉連結
session.close()

連表查詢

   使用join進行連表查詢:

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

# 匯入引擎,模型表等
from models import *

# 通過Session繫結引擎和資料庫建立關係
Session = sessionmaker(bind=engine)
# 建立連結池,使用session即可為當前執行緒拿出一個連結物件。內部採用threading.local進行隔離
session = scoped_session(Session)

# 手動指定條件查詢
result = session.query(Students.name, Classes.name).filter(Students.id == Classes.id).all()
for i in result:
    print(i)

# 連線查詢,同上,內部自動指定 Students.fk_class == Classes.id 的條件
result = session.query(Students.name, Classes.name).join(Classes).all()
# 相當於:result = session.query(Students.name,Classes.name).join(Classes, Students.fk_class == Classes.id).all()
for i in result:
    print(i)

# 左連結查詢,即使有同學沒有班級也拿出來
result = session.query(Students.name, Classes.name).join(Classes, isouter=True).all()
for i in result:
    print(i)

# 如果想檢視有哪些班級沒有同學,就換一個位置
result = session.query(Students.name, Classes.name).join(Students, isouter=True).all()
for i in result:
    print(i)

# 三表查詢,需要自己指定條件
result = session.query(Teachers.name, Classes.name, TeachersM2mClasses.id) \
    .join(Teachers, TeachersM2mClasses.teacher_id == Teachers.id) \
    .join(Classes, TeachersM2mClasses.class_id == Classes.id) \
    .filter()  # 檢視原生語句
print(result)
for i in result:
    print(i)

# 提交
session.commit()

# 關閉連結
session.close()

正反查詢

   上面是使用join進行的連表查詢,其實也可以使用邏輯欄位relationship查詢。

  

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

# 匯入引擎,模型表等
from models import *

# 通過Session繫結引擎和資料庫建立關係
Session = sessionmaker(bind=engine)
# 建立連結池,使用session即可為當前執行緒拿出一個連結物件。內部採用threading.local進行隔離
session = scoped_session(Session)

# 正向:檢視第一個老師都在哪些班級(通過邏輯欄位的名字)
result = session.query(Teachers).first()
# result.re_class是一個列表,存了有關該老師所在的班級 <class 'sqlalchemy.orm.collections.InstrumentedList'>

for class_obj in result.re_class:  # 檢視其所有的班級
    print(class_obj.name)

# 反向:檢視第一個班級下都有哪些老師,都有哪些學生(通過邏輯欄位中的backref引數進行反向查詢)
result = session.query(Classes).first()

# 看老師
for teacher_obj in result.teachers:
    print(teacher_obj.name)

# 看學生
for student_obj in result.students:
    print(student_obj.name)

# 提交
session.commit()

# 關閉連結
session.close()

正反方法

   使用邏輯欄位relationship可擁有一些方法執行增刪改。

   由於邏輯欄位是一個類似列表的存在,所以列表的方法都能用。

print(type(老師物件.班級列表))

# <class 'sqlalchemy.orm.collections.InstrumentedList'>

   比如使用extend方法增加老師的班級:

# 給老師增加班級

result = session.query(Teachers).first()


# extend方法:
result.re_class.extend([
    Classes(name="三年級一班",),
    Classes(name="三年級二班",),
])

   使用remove方法刪除老師的班級:

# 減少老師所在的班級
result = session.query(Teachers).first()

# 待刪除的班級物件,集合查詢比較快
delete_class_set = {
    session.query(Classes).filter_by(id=7).first(),
    session.query(Classes).filter_by(id=8).first(),
}

# 循換老師所在的班級
# remove方法:
for class_obj in result.re_class:
    if class_obj in delete_class_set:
        result.re_class.remove(class_obj)

   使用clear清空老師所對應的班級:

# 拿出一個老師
result = session.query(Teachers).first()

result.re_class.clear()

原生SQL

檢視SQL命令

   如果一條查詢語句是以filter結尾,則返回結果物件的__str__方法中都是SQL語句:

result = session.query(Teachers).filter()
print(result)

# SELECT teachers.id AS teachers_id, teachers.name AS teachers_name 
# FROM teachers

   如果是all結尾,返回的就是一個列表,first結尾也是一個列表:

result = session.query(Teachers).all()
print(result)

# [<models.Teachers object at 0x00000178EB0B5550>, <models.Teachers object at 0x00000178EB0B5518>, <models.Teachers object at 0x00000178EB0B5048>]

執行SQL語句

   執行原生SQL

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

# 匯入引擎,模型表等
from models import *

# 通過Session繫結引擎和資料庫建立關係
Session = sessionmaker(bind=engine)
# 建立連結池,使用session即可為當前執行緒拿出一個連結物件。內部採用threading.local進行隔離
session = scoped_session(Session)

cursor = session.execute(r"select * from students where id <= (:num)",params={"num":2})
print(cursor.fetchall())

# 提交
session.commit()

# 關閉連結
session.close()

相關文章