Python---SQLAlchemy

本文介绍了SQLAlchemy的安装,它需依赖pymysql等第三方插件实现数据库操作。还阐述了其使用方法,包括执行原生sql语句、用orm创建数据库表、对数据表增删改查,介绍了两种连接模式,解读了scoped_session源码流程,对比了filter和filter_by的区别,以及一对多和多对多查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、安装SQLAlchemy

pip install sqlalchemy

SQLAlchemy本身无法操作数据库,其必须以来pymsql等第三方插件,从而实现对数据库的操作,如:

pymysql
    mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]

MySQL-Python
    mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>

目标:通过类或对象进行操作----->sql语句-(pymysql/mysqldb)---->在数据库执行  所以要有这两个模块。

二、使用SQLAlchemy

执行原生的sql语句

# 基本写法不常见

from sqlalchemy import create_engine
from sqlalchemy.engine.base import Engine

engine = create_engine("mysql+pymysql://root:@127.0.0.1:3306/lesson??charset=utf-8",
                      max_overflow=0, # 超过连接池外最大连接的个数
                      pool_size=5, #连接池大小
                      pool_timeout=10, # 池中没有线程最多等待的时间
                      pool_recycle=-1) # 多久时间对连接池的线程进行回收(重置)


def task():
    conn = engine.raw_connection()  # 创建一个连接
    cursor = conn.cursor()   # 创建游标
    cursor.execute("select * from lab")   # 执行sql语句
    result = cursor.fetchall()   # 查询所有语句
    cursor.close()   # 关闭游标
    conn.close()   # 关闭连接
    return result

print(task())

orm进行数据库表的创建

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

Base = declarative_base()

# 创建单表
class User(Base):  # 需要继承Base
    __tablename__ = "lab233"  # 定义数据库的表名
    id = Column(Integer,primary_key=True, autoincrement=True)  #  主键自增
    name = Column(String(32), nullable=True, index=True) # name列 非空 索引
    ctime = Column(DateTime, default=datetime.datetime.now)   # 这里不加括号,加上括号就相当于写死了,当不加括号每次执行都会自动加上括号执行
    __table_args__ = (
        UniqueConstraint("id","name",name="uix_id_name"),  # 联合唯一索引  id和name创建联合唯一索引
        Index("ix_id_name", "name","email"),  # 联合索引 name和Email
    )  # 给这个表创建一些参数
--------------------------------------------------------------------------
# 创建一对多表
class Hobby(Base):
    __tablename__ = 'hobby'
    id = Column(Integer, primary_key=True)
    caption = Column(String(50), default='篮球')


class Person(Base):
    __tablename__ = 'person'
    nid = Column(Integer, primary_key=True)
    name = Column(String(32), index=True, nullable=True)
    hobby_id = Column(Integer, ForeignKey("hobby.id"))  # 创建外建  bobby是表名而不是类名

    # 与生成表结构无关,仅用于查询方便
    hobby = relationship("Hobby", backref='pers')
---------------------------------------------------------------------------
# 创建多对多的表 
class Server2Group(Base):  # 第三张表包含两个外键
    __tablename__ = 'server2group'
    id = Column(Integer, primary_key=True, autoincrement=True)
    server_id = Column(Integer, ForeignKey('server.id'))
    group_id = Column(Integer, ForeignKey('group.id'))


class Group(Base):
    __tablename__ = 'group'
    id = Column(Integer, primary_key=True)
    name = Column(String(64), unique=True, nullable=False)

    # 与生成表结构无关,仅用于查询方便
    servers = relationship('Server', secondary='server2group', backref='groups')


class Server(Base):
    __tablename__ = 'server'
    id = Column(Integer, primary_key=True, autoincrement=True)
    hostname = Column(String(64), unique=True, nullable=False)

if __name__ =="__main__":  # 这里只需要执行一次
    engine = create_engine("mysql+pymysql://root:@127.0.0.1:3306/lesson??charset=utf-8", max_overflow=0, pool_size=5, pool_timeout=10, pool_recycle=-1) 
    Base.metadata.create_all(engine)  # 帮助你创建表,不加这句话不会自动创建表
    # Base.metadata.drop_all(engine)  # 帮助你删除表

操作数据表进行增删改查

第一种连接模式

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from tpp import User  # 导入表


# 创建连接池
engine = create_engine("mysql+pymysql://root:@127.0.0.1:3306/lesson??charset=utf-8", max_overflow=0,pool_size=5, pool_timeout=10,pool_recycle=-1) 

Session = sessionmaker(bind=engine)
#从连接池中获取连接
session = Session() # 每次执行操作都要创建Session  第一种获取连接的方式

# 进行orm的操作  像极了java的orm操作
obj1 = User(name="pp")  # 向User表中添加一行数据
session.add(obj1)   # 添加到会话中

session.commit() # 提交数据
session.close() # 关闭session  将连接返回连接池

第二种连接模式, 推荐这种模式

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import scoped_session
from tpp import User   # 导入表


# 创建连接池
engine = create_engine("mysql+pymysql://root:@127.0.0.1:3306/lesson??charset=utf-8", max_overflow=0,pool_size=5,pool_timeout=10,pool_recycle=-1) 
Session = sessionmaker(bind=engine)

#从连接池中获取连接
session = scoped_session(Session)  # 第二种获取连接的方式为每个线程单独创建session

# 进行orm的操作  像极了java的orm操作
obj1 = User(name="saosan")
session.add(obj1)

session.commit() # 提交数据
session.close() # 关闭session  将连接返回连接池

scoped_session虽然没有add、commit这些方法但是也可以运行这些方法,但是在scoped_session中并能没有看到这些方法的影子,所以猜想这两个类有继承的关系,但是事实并不是这样.....

源码流程解读

__all__ = ['scoped_session']   #  当一个py文件写了一个__all__方法就只能导出__all__后面的东西

class scoped_session(object):
    session_factory = None

    def __init__(self, session_factory, scopefunc=None):  # session_factory是我们传进来的Session类
        self.session_factory = session_factory
        if scopefunc:   # scopefunc=None执行else
            self.registry = ScopedRegistry(session_factory, scopefunc)
        else:
            self.registry = ThreadLocalRegistry(session_factory)
-----------------------------------------------------------------------------
ThreadLocalRegistry的__init__方法

class ThreadLocalRegistry(ScopedRegistry):

    def __init__(self, createfunc):  # createfunc就是Session类
        self.createfunc = createfunc
        self.registry = threading.local()  # 创建threading.local
-----------------------------------------------------------------------------
此时self.registry封装了两个值self.createfunc、self.registry 

我们回到scoped_session继续往下看

ScopedSession = scoped_session  # 其实ScopedSession和scoped_session两个是等价的

for meth in Session.public_methods:  # 通过 遍历+反射 的方式为scoped_session增加Session类的方法
    setattr(scoped_session, meth, instrument(meth))  # 设置属性
----------------------------------------------------------------------
Session.public_methods
public_methods = (
        '__contains__', '__iter__', 'add', 'add_all', 'begin', 'begin_nested',
        'close', 'commit', 'connection', 'delete', 'execute', 'expire',
        'expire_all', 'expunge', 'expunge_all', 'flush', 'get_bind',
        'is_modified', 'bulk_save_objects', 'bulk_insert_mappings',
        'bulk_update_mappings',
        'merge', 'query', 'refresh', 'rollback',
        'scalar')  # Session类所有的方法都在这
----------------------------------------------------------------------
def instrument(name):
    def do(self, *args, **kwargs):
        return getattr(self.registry(), name)(*args, **kwargs)  # 通过反射获取函数的属性
    return do
----------------------------------------------------------------------
执行self.registry的__call__方法

def __call__(self):
    try:
        return self.registry.value  # 第一次进来没有这个属性就报错
    except AttributeError:
        val = self.registry.value = self.createfunc()  # 在这里实例化了Session类
        return val  # 返回Session类的对象

这就很好的解释了为什么在scoped_session中仍然能用Session中的方法。

如何增、删、改、查?

删除:

session.query(User).filter(User.id>3).delete() # 删除User表id>3的数据

增加:

obj1 = User(name="saosan")  # 创建一个User对象
session.add(obj1)  # 添加一行记录到User表

obj1 = User(name="qq")
obj2 = User(name="uu")
obj3 = User(name="ii")
session.add_all([obj1,obj2,obj3]) # 批量增加,列表里放一堆对象

修改:

session.query(User).filter(User.id==3).update({"name":"lee"})  # 修改时后面是字典

session.query(User).filter(User.id==3).update({User.name:User.name+"sin"},synchronize_session=False)  # 也可以完成F操作,需要加上synchronize_session=False这个参数

session.query(User).filter(User.id==3).update({User.name:User.name+999},synchronize_session="evaluate")  # 数字的话,需要加上synchronize_session这个参数

查询:

session.query(User).all()  # 查询User表所有的数据, query()放类名, 可以通过遍历获取每个元素

for i in session.query(User).all():  # 获得具体值
    print(i.name)

session.query(User.name).all()  # 表示只取User.name一列数据

session.query(User.id.label("nid"),User.name).all()  # 查询id和name两列数据,并为id列起了别名 

for i in session.query(User.id.label("nid"),User.name):  # label相当于取别名
    print(i.nid)   # 可以通过别名获取属性

session.query(User.name).filter_by(name="ttp").all()  # 获取name="ttp"的全部数据

session.query(User.name).filter_by(name="ttp").first()  # 获取name="ttp"的第一条数据

session.query(User).filter(User.id>1, User.name=="saosan").all()   #  查询id>1并且name为saosan的数据, ','代表且的意思

session.query(User).filter(User.id.between(1,2)).all()  # 取大于等于1小于等于2的数据,两边都取得到

session.query(User).filter(User.id.in_([1,2])).all()  # 查询id在[1,2]的数据  in后面要下划线

session.query(User).filter(~User.id.in_([1,2])).all()  # ~代表非的意思,取反

session.query(User).filter(User.id.in_(session.query(User.id).filter(User.name=="ttp"))).all()  # 进行嵌套查询,和sql嵌套查询类似


from sqlalchemy.sql import text
session.query(User).filter(text("id=:id and name=:name")).params(id=1,name="ttp").order_by(User.id).all()  # :id只是占位符,通过后面的params传参数

session.query(User).from_statement(text("SELECT * FROM user where name=:name")).params(name='ttp').all()  # 直接写原生的sql语句



from sqlalchemy import and_, or_
session.query(User).filter(and_(User.name=="ttp", User.id == 1))  # 且条件

session.query(User).filter(or_(User.name=="ttp", User.id == 1))   # 或条件

※: filter和filter_by的区别 : filter里面传的是表达式 而 filter_by里面传的是参数,只能等于, 不能大于小于。

其他常见用法:

通配符

session.query(User).filter(User.name.like("t%")).all()  # 查询t开头的数据

session.query(User).filter(~User.name.like("t%")).all()   # ~代表非的意思,取反

limit限制数据

session.query(User).all()[1:2] # 直接用切片

排序

session.query(User).order_by(User.name.desc(), User.id.asc()).all()  # desc降序,asc升序

分组

session.query(User).group_by(User.id).all()   # 通过id进行分组

session.query(func.max(User.id)).group_by(User.id).all() # 通过id分组后,取最大的id

session.query(func.min(User.id)).group_by(User.id).all() # 通过id分组后,取最小的id

session.query(func.sum(User.id)).group_by(User.id).all()  # 通过id分组后,取和

session.query(User).group_by(User.id).having(User.id>2).all()   # having 再加条件

连表
session.query(User,User).filter(User.id == User.id).all()  # filter 里面是连接条件

session.query(User).join(User).all()   #  第二种  默认是inner join

session.query(User).join(User,isouter=True).all()  # 加上isouter=True就是外连接

session.query(User).join(User,User.id == User.id).all()   # 连表的条件自己可以定义

# 也可以在on后面增加多个连表条件
session.query(User).join(User,and_(User.id == User.id,User.name == User.name)).all()

组合
from sqlalchemy import union,union_all

q1 = session.query(Users.name).filter(Users.id > 2)
q2 = session.query(Favor.caption).filter(Favor.nid < 2)
ret = q1.union(q2).all()

写原生sql
cursor = session.execute("select * from user")  # 通过session执行sql语句
result = cursor.fetchall()  # 查询全部数据

cursor = session.execute("insert into lab233(name) values(:name)",params={'name':"lol"})
print(cursor.lastrowid)   # 打印最后插入数据的id

一对多查询

表结构:

class Hobby(Base):
    __tablename__ = 'hobby'
    id = Column(Integer, primary_key=True)
    caption = Column(String(50), default='篮球')


class Person(Base):
    __tablename__ = 'person'
    nid = Column(Integer, primary_key=True)
    name = Column(String(32), index=True, nullable=True)
    hobby_id = Column(Integer, ForeignKey("hobby.id"))  # 创建外建  bobby是表名而不是类名

    # 与生成表结构无关,仅用于查询方便,backref 是反向查找使用到的
    hobby = relationship("Hobby", backref='pers')

查询:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy import func
from sqlalchemy.orm import scoped_session,relationship
from tpp import Server2Group,Group,Server

# 创建连接池
engine = create_engine("mysql+pymysql://root:zyz521@127.0.0.1:3306/lesson??charset=utf-8", max_overflow=0,pool_size=5,pool_timeout=10,pool_recycle=-1)
Session = sessionmaker(bind=engine)
#从连接池中获取连接
session = Session() 
# orm操作

# 一对多查询
# 法一
result = session.query(Person,Hobby).all()
for row in result:
    print(row[0].name, row[1].caption)  # row[0]是Person, row[1]是Hobby

# 法二
result = session.query(Person.name,Hobby.caption).join(Hobby,isouter=True).all()
for i in result:
    print(i.name,i.caption)

# 反向查找  通过backref参数
result = session.query(Hobby).filter(Hobby.id == 1).first()  # 只有单个对象才有反向查找
for i in result.pers:
    print(i.name)

# 反向增加  通过backref参数
obj = Hobby(caption="sing")
obj.pers = [Person(name="lff"),Person(name="dzw")]
session.add(obj)

session.commit() 
session.close() 

多对多查询

表结构:

class Server2Group(Base):
    __tablename__ = 'server2group'
    id = Column(Integer, primary_key=True, autoincrement=True)
    server_id = Column(Integer, ForeignKey('server.id'))
    group_id = Column(Integer, ForeignKey('group.id'))

class Group(Base):
    __tablename__ = 'group'
    id = Column(Integer, primary_key=True)
    name = Column(String(64), unique=True, nullable=False)

    # 与生成表结构无关,仅用于查询方便   这个外键写到那张表里都没关系, secondary 是第三张表
    servers = relationship('Server', secondary='server2group', backref='groups')

class Server(Base):
    __tablename__ = 'server'
    id = Column(Integer, primary_key=True, autoincrement=True)
    hostname = Column(String(64), unique=True, nullable=False)

查询:

# 多表查询

# 通过外键添加数据不仅会在两张表中添加数据,而且还会在第三张表内添加数据
# 正向添加
obj = Group(id=1,name="fangshan")
obj.servers = [Server(hostname="qqqqq"), Server(hostname="wwwwwww")]
session.add(obj)

# 反向添加
obj = Server(hostname="rrrrrr")
obj.groups = [Group(id=2,name="fengtai"),Group(id=3,name="haidian")]
session.add(obj)

# 反向查询
obj = session.query(Server).first()
print(obj.groups)

#关联子查询 select id,name,(select count(id) from teacher where teacher.id = user.id) as nid from user  括号里的select必须保证这里只能有一个结果  这种就是再加一列的情况
# 先写子查询,filter是条件过滤, correlate 放的是要关联的表
subquery = session.query(func.count(Server.id)).filter(Server.id == Group.id).correlate(Group).as_scalar()
# 主查询中放子查询和关联的了类
print(session.query(Group,subquery).all())

session.commit() 
session.close() 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值