• MySQL学习笔记——〇六SQLAlchemy框架


    我们在前面所用的方法都是在可视化的视图软件或者terminal里直接写SQL语句来对数据库进行访问,这里我们大概讲一下一个新的框架——SQLAlchemy。

    OEM框架
    OEM的概念
    对象-关系映射(Object/Relation Mapping,简称ORM),是随着面向对象的软件开发方法发展而产生的。面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系。因此,对象-关系映射(ORM)系统一般以中间件的形式存在,主要实现程序对象到关系数据库数据的映射。
    我们今天要用的SQLAlchemy,就是一种Python中最长用的OEM架构。
    Python中面向对象的几种特殊的行为
    有几种方法我们平时不太常用,但是在SQLAlchemy里是要常用到的,这里再重新复习一下。
    class Foo():
        def __call__(self):
            print('in __call__ func')
    
        def __getitem__(self,key):
            print('in __getitem__',key)
    
        def __setitem__(self,key,value):
            print('in __setitem__,key=%s,values=%s'%(key,value))
    
        def __delitem__(self,key):
            print('in __delitem__',key)
    
    f=Foo()
    # f()
    # in __call__ func
    f[1,2,3,4]
    # in __getitem__ (1, 2, 3, 4)
    f[123]='112233'
    # in __setitem__,key=123,values=112233
    del f[123]
    # in __delitem__ 123

    上面是Python中的几种特殊的行为,今天会用到,一定要注意括号的种类。

    MySQL的面向对象

     在SQLAlchemy里,我们是用面向对象的思路对MySQL进行操作的,其中,table对应的就是类,而对象,就是table里的每一行的数据。我们要建立这样一个数据库文件,里面有两个表

    这就需要用两个类来描述

    from sqlalchemy.ext.declarative import declarative_base
    Base = declarative_base()
    class Department(Base):
        __tablename__='department'                          #指定表名
        id=Column(Integer,primary_key=True,autoincrement=True)
        title=Column(CHAR(10))
    
    class User(Base):
        __table__name='user'                                #指定表名
        id=Column(Integer,primary_key=True,autoincrement=True)
        name=Column(CHAR(15))
        dep_id= Column(Integer,ForeignKey(UserType.id))     #指定外键

    而数据就是依据类进行实例化

    dep1 = Department(title='销售') 
    dep2=Department(title='人事')
    user1=User(name='a',dep_id=1)

    至于如何利用SQLAlchemy实现MySQL面向对象的编程方法 ,我们在下面简单说一下。

    code first和db first

    OEM框架有两种模式,code first和db first

    db first 是手动创建数据库,再写代码。根据数据库的表生成类。

    code first 是先写代码,后创建数据库。根据类创建数据库表。

    SQLAlchemy的使用

    我们在这里还是按照增删改查来说明其用法。

    表的增、删

    在用SQLAlchemy进行数据库读写的时候,首先要保证数据库内有要使用的database的存在,假设我们直接创建一个空的database,名字就叫test:

    create database test default charset='utf8';

    我们今天所有的操作都是基于这个数据库的。

    进行表的操作需要先对类进行声明(前面的例子)

    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index,CHAR,VARCHAR
    from sqlalchemy.orm import sessionmaker, relationship
    from sqlalchemy import create_engine
    
    
    Base = declarative_base()
    
    class Department(Base):
        __tablename__='department'                          #指定表名
        id=Column(Integer,primary_key=True,autoincrement=True)
        title=Column(CHAR(10))
    
    class User(Base):
        __tablename__='user'                                #指定表名
        id=Column(Integer,primary_key=True,autoincrement=True)
        name=Column(CHAR(15))
        dep_id= Column(Integer,ForeignKey(Department.id))     #指定外键
        
    
    def create_db():
        engine = create_engine("mysql+pymysql://root:@127.0.0.1:3306/test?charset=utf8", max_overflow=5)
        Base.metadata.create_all(engine)
    
    
    def drop_db():
        engine = create_engine("mysql+pymysql://root:@127.0.0.1:3306/test?charset=utf8", max_overflow=5)
        Base.metadata.drop_all(engine)
    
    
    create_db() #创建表
    drop_db()   #删除表

    上面的代码就说明了如何通过SQLAlchemy来实现数据库中表的创建和删除。这里的create_db()和drop_db()两个方法在执行的时候会先把数据库中的表和代码中的类进行比对,如果类中在,就进行相关的操作,比方我们把User那个类注释掉,执行drop的方法只会删除department这个表(例子中的并不会,因为我们在类的声明中指定了外键,department这个表是不能被先删除的。)

    连接初始化

    我们看一看上面表操作的时候有一段共同的代码

    engine = create_engine("mysql+pymysql://root:@127.0.0.1:3306/test?charset=utf8", max_overflow=5)

    因为SQLAlchemy不能直接进行数据库的操作,必须通过第三方的工具来实现(如下图)

    因为我们用的数据库是MySQL,在Python中对应的工具是pymysql

    mysql+pymsql表明白我们用的数据库种类和所使用的API

    root:是登录名后面可以加密码:root:123456(由于本机安装MySQL的时候是没有定义root用户密码,所以:后为空)

    127.0.0.1:3306是对应的MySQL服务器的IP和端口,由于是本机,所以用的127.0.0.1,默认端口3306

    /test?charset=utf8 连接的数据库的库名和对应的字符集。(为什么用?来分割我也不知道)
    max_overflow=5定义的是连接池的连接数。
    上面这行代码不仅在创建表或删除表的时候要用到,在进行数据库查询或读写的时候也要通过这段代码,我们还可以在调试代码的时候给echo赋值为True
    engine = create_engine("mysql+pymysql://root:@127.0.0.1:3306/test?charset=utf8", max_overflow=5,echo=True)

    就可以返回执行的SQL语句。

    然后就是创建节点session并绑定engine

    engine = create_engine("mysql+pymysql://root:@127.0.0.1:3306/test?charset=utf8", max_overflow=5,echo=True)  
    Session=sessionmaker(bind=engine)
    
    session=Session()

    下面我们就可以对数据库进行增删改查的操作了。

    添加数据

    被添加的数据要按照类进行初始化,然后添加

    添加添加单条数据

    department1=Department(title='销售')
    department2=Department(title='人事')
    department3=Department(title='财务')
    session.add(department1)
    session.add(department2)
    session.add(department3)
    
    session.commit()

    还可以进行批量操作,关键字为add_all()

    users=[User(name='a',dep_id=1),
            User(name='b',dep_id=2),
            User(name='c',dep_id=3),
            User(name='d',dep_id=2),
            User(name='e',dep_id=1),
    ]
    session.add_all(users)

    要注意一点:进行添加操作以后要执行下面的代码:一定不能忘记,删和改操作也一样。

    session.commit()

    查询操作

    除了添加操作意外,删除和修改数据都是基于查询的操作基础上的,所以查询操作就比较重要。

    users=session.query(User)
    print(users)

    看一看打印出的结论是什么:

    users: SELECT user.id AS user_id, user.name AS user_name, user.dep_id AS user_dep_id 
    FROM use

    也就是说,query方法返回的值是一段SQL语句。我们需要的是查询的返回对象,那么就要加一个all()方法,然后在打印一下看看结论是什么(user表数据有点多,这里改成department表)

    deps=session.query(Department).all()
    print(deps)
    
    
    ##########输出##########
    [<__main__.Department object at 0x0000020C2A0C3A88>, <__main__.Department object at 0x0000020C2A0C3708>, <__main__.Department object at 0x0000020C2A0C3188>]

    可以看出来,是个列表,用切片截取一个,用type查一下类型,可以发现是个对象

    <class '__main__.Department'>

     看看那个对象的名称,Department,我们试试按照当时构造类的情形取一下里面的值

    deps=session.query(Department).all()
    print(deps[0].id,deps[0].title)
    
    ##########输出##########
    1 销售

    这样就取出了table里的内容。我们可以用for循环获取所有的select结论

    deps=session.query(Department).all()
    for i in deps:
        print(i.id,i.title)
    
    ##########输出##########
    1 销售
    2 人事
    3 财务

    如果我们需要在实现select后面加上where实现筛选的效果。

    deps=session.query(Department).filter(Department.id>2).all()

    我们在select的时大部分操作都是指定了字段,那么就要这么做

    deps=session.query(Department.id,Department.title)

    在query后加上参数就是对指定的字段直接进行索引。

    查询的方法还有很多可以讲的,我们后面慢慢再讲。

    删除操作

    删除操作就是把筛选出来的数据加上delete()方法就可以了。

    session.query(Department).filter(Department.id>3).delete()
    session.commit()        #切记要加上这一段

    修改操作

    修改操作也是基于查询操作上的

    session.query(Department).filter(Department.id==3).update({'title':'财务'})   #用字典的方式来改
    session.query(Department).filter(Department.id>0).update({Department.title:Department.title+''},synchronize_session=False) 
    #利用原有基础上修改,一定要加后面的参数synchronize_session,字符串给定参数值为False
    session.query(Department).filter(Department.id>0).update({Department.title:Department.title+3},synchronize_session='evaluate')
    #如果要改变的参数为数字类型,进行修改时synchronize_session,字符串给定参数值为'evaluate'
    详细的查询操作

     下面我们来仔细的看看查询操作:分组、连表、通配符、子查询、limit、还有原生SQL。

    条件查询filter和filter_by

    常用的条件查询是filter和filter_by,首先,要知道下面的语句都是成立的

    result = session.query(Department).filter_by(title='销售').all()
    result = session.query(Department).filter_by(title='销售',id=1).all()
    result = session.query(Department).filter(Department.title=='销售').all()

    特别注意的是filter和filter_by两个方法的使用区别,

    模块语法><(大于和小于)查询and_和or_查询
    filter_by() 直接用属性名,比较用= 不支持 不支持
    filter() 用类名.属性名,比较用== 支持 支持


    filter_by()可以实现下面的查询要求(值截取表达式后面的部分,前面的result都省略掉)
    :

    user表里name为a的

    session.query(User).filter_by(name='a').all()

    user里name为a并且部门id为1

    session.query(User).filter_by(name='a',dep_id=1).all()

    但是如果想查询名字为a或者部门id为1的就不能满足了.

    filter()能够实现的要求

    user表内名字为a的

    session.query(User).filter(User.name=='a').all()

    名字为a并且部门id为1

    session.query(User).filter(User.name=='a',User.dep_id==1).all()

    总之,filter_by()只接受键值对参数,所以不支持所有比较性质(包括大小和与或)的查询

    in和not in

    还有一个常用的方法就是where ..in

    result = session.query(User).filter(User.id.in_([1,3]))
    result = session.query(User).filter(~User.id.in_([1,3]))

    注意in的用法,in后面是跟了个下划线的,并且后面给定的参数一用()括起来的列表。

    第二行的~表示not,相当于not in的效果。

    与或筛选

    我们在上面提到了与或的筛选,要实现与或的要先导入一个新的库

    from sqlalchemy import and_,or_

    我们继续看看,如果想要筛选出user表内名字为a并且部门id为1的数据

    session.query(User).filter(and_(User.name=='a',User.dep_id==1)).all()

    可以看出来和前面那个方法结果是一样的,所以,filter里默认的关系是and。

    筛选名字为a或者部门id为1的数据

    session.query(User).filter(or_(User.name=='a',User.dep_id==1)).all()

    通配符

    通配符的用法和SQL差不多,就是在筛选的时候加上反复like(),匹配的关键字和SQL语句是一样的,下划线匹配单个字符,百分号匹配多个字符

    result = session.query(User).filter(User.name.like('_a%')).all()

    排序

    result = session.query(User).order_by(User.id.desc()).all()     
    result = session.query(User).order_by(User.id.asc()).all()
    result = session.query(User).order_by(User.id.desc(),User.name.desc()).all()

    第三行代码是先按id的降序,在按照name的升序。 

    limit分页

    因为我们用query返回的对象是个列表,可以直接用切片的方法来实现limit的效果。

    result = session.query(User)[2:4]

    注意这里,切片的时候一定不要用all()。

    join连表

    SQLAlchemy连表时必须在类中定义好外键关系,否则无法连表。

    这里先讲一下比较笨的join方法,后面我们还可以利用SQLAlchemy提供的比较便利的方法实现连表的功能。

    result = session.query(User.id,User.name,Department.title).join(Department).all()for i in result:
        print(i.id,i.name,i.title)

    我们可以通过打印不带all函数的result,查看SQL语句

    result = session.query(User.id,User.name,Department.title).join(Department)
    print(result)
    
    ##########输出##########
    SELECT user.id AS user_id, user.name AS user_name, department.title AS department_title 
    FROM user INNER JOIN department ON department.id = user.dep_id

    可以发现,默认的join的形式是inner join,但是如果我们需要用left join,就需要一个参数

    result = session.query(User.id,User.name,Department.title).join(Department,isouter=True)
    print(result)
    
    ##########输出##########
    SELECT user.id AS user_id, user.name AS user_name, department.title AS department_title 
    FROM user LEFT OUTER JOIN department ON department.id = user.dep_id

    SQLAlchemy是不支持right join的,但是我们可以通过改变两个join对象的位置实现right join的效果。

    字段的获取

    我们上面的例子都是指获取了User里的两个字段和department里的一个字段,所以都写在query里比较方便,但是如果需要多个字段或者要join多个表的话,这样写是不合适的,这样就有下面的方法

    result = session.query(User,Department).join(Department)
    for i in result:
        print(i)

    我们先看一看打印出来的结果

    (<__main__.User object at 0x0000022694D9C048>, <__main__.Department object at 0x0000022694D9C408>)
    (<__main__.User object at 0x0000022694D9C548>, <__main__.Department object at 0x0000022694D9C5C8>)
    (<__main__.User object at 0x0000022694D9C648>, <__main__.Department object at 0x0000022694D9C6C8>)
    (<__main__.User object at 0x0000022694D9C748>, <__main__.Department object at 0x0000022694D9C5C8>)
    (<__main__.User object at 0x0000022694D9C808>, <__main__.Department object at 0x0000022694D9C408>)

    在对result进行遍历的时候,每次打印的i都是一个元组,这么我们就可以对这个元组进行切片然后取到所需的数据

    result = session.query(User,Department).join(Department).all()
    for i in result:
        print(i[0].id,i[0].name,i[1].id,i[1].title)

    这样也是可以的。

    笛卡尔积

    前面的join连表还是可以利用笛卡尔及的方法,但是这种方法是不推荐的,我们只需要知道有这么个方法就可以了。

    result = session.query(User,Department).filter(User.dep_id==Department.id).all()
    
    print(result)
    for i in result:
        print(i.User.id,i.User.name,i.Department.title)

    union连表

    union连表需要把两个query对象直接连起来就可以了

    q1 = session.query(User.id,User.name)
    q2 = session.query(Department.id,Department.title)
    result = q1.union(q2)
    print(result)
    for i in result:
        print(i.id,i.name)

    注意后面取值的方法,字段的标题是要用union前面的那个表的字段。同样连表的时候要求字段数量一致。

    分组group_by()

    分组功能常用的聚合函数也需要在使用前导入,

    from sqlalchemy.sql import func  #func为聚合函数
    result = session.query(User.dep_id,func.count(User.id)).group_by(User.dep_id).all()
    print(result)
    
    ##########输出##########
    [(1, 2), (2, 2), (3, 1)]

    分组以后的输出直接就是一个列表,列表里的数据是依据query函数生成的元组。

    带筛选的分组

    筛选的关键字和SQL语句的一样,都是having

    result = session.query(User.dep_id,func.count(User.id)).group_by(User.dep_id).having(User.dep_id>2).all()
    print(result)

    虚拟表

    我们又是后会用到select...from (select ...from) as t1;这样的虚拟表,在SQLAlchemy里是这用的,比方我们需要获取department为销售的员工信息

    t1 = session.query(Department.id).filter_by(title='销售').subquery()       #此处不可加all()
    result = session.query(User).filter(User.dep_id.in_(t1)).all()
    for i in result:
        print(i.id,i.name)

    上面的t1,就类似一个虚拟表。

    子查询

     我们在前面将使用案例的时候(第16题)使用过一个方法,代码是这样的

    SELECT
        student_id,
        ( SELECT num FROM score AS s2 WHERE s2.student_id = s1.student_id AND course_id = 1 ) AS 'course1_num',
        ( SELECT num FROM score AS s2 WHERE s2.student_id = s1.student_id AND course_id = 2 ) AS 'course2_num',
        ( SELECT num FROM score AS s2 WHERE s2.student_id = s1.student_id AND course_id = 3 ) AS 'course3_num' 
    FROM
        score AS s1 
    GROUP BY
        student_id

    就是把一个搜索的结论作为一个映射放在select里。这样的方式在SQLAlchemy里也是可以实现的 。

    首先看一看怎么实现把结论作为映射

    result = session.query(User.name,session.query(User.dep_id).filter(User.id==1).as_scalar())

    这么就起到了下面语句的效果

    select name,(SELECT dep_id from user where id=1) from user;

    上面最后的as_scalar()就是把索引的结果作为一项拿出来.。

     那如何把外面的循环的项放在里面循环使用呢?

    result = session.query(User.name,session.query(User).filter(User.id==User.dep_id).as_scalar())

    这个方法应该可以,但是我试了没成功,不知道是不是表的结构有问题。

    SQLAlchemy的便利用法

    正向关系操作

    我们在上面讲连表的时候讲过那个join是一个比较笨的方法,因为SQLAlchemy里提供了一些比较便利的用法:这里先讲一下连表的用法。

    我们在定义外键的时候,可以定义一下外键的关系

    class User(Base):
        __tablename__='user'                                #指定表名
        id=Column(Integer,primary_key=True,autoincrement=True)
        name=Column(CHAR(15))
        dep_id= Column(Integer,ForeignKey(Department.id))     #指定外键
        dep=relationship(Department)

    在声明类的时候,我们最后指定了User的外键是那个表,那么我们在查询user表的时候,可以看一下dep

    result = session.query(User)
    print(result)
    for i in result:
        print(i.dep)
    
    ##########输出##########
    <__main__.Department object at 0x000002C3DA3AE588>
    <__main__.Department object at 0x000002C3DA3AE948>
    <__main__.Department object at 0x000002C3DA3BB308>
    <__main__.Department object at 0x000002C3DA3AE948>
    <__main__.Department object at 0x000002C3DA3AE588>

    dep对应的是department对象。,就可以直接获取通过外键连表对应的字段

    result = session.query(User)
    print(result)
    for i in result:
        print(i.id,i.name,i.dep.title)
    
    ##########输出##########
    1 aaa 销售
    2 babb 人事
    3 ccc 财务
    4 ddd 人事
    5 eee 销售

    反向关系操作

     这时候如果我们需要查询每个部门的员工,要怎么做呢?先看看比较复杂的方法

    result = session.query(Department)
    print(result)
    for i in result:
        print(i.id,i.title,session.query(User.name).filter(User.dep_id==i.id).all())
    
    ##########输出##########
    1 销售 [('aaa',), ('eee',)]
    2 人事 [('babb',), ('ddd',)]
    3 财务 [('ccc',)]

    用关系操作的反向操作

    class User(Base):
        __tablename__='user'                                #指定表名
        id=Column(Integer,primary_key=True,autoincrement=True)
        name=Column(CHAR(15))
        dep_id= Column(Integer,ForeignKey(Department.id))     #指定外键
        dep=relationship(Department,backref='aabbcc')

    我们把把关系做了个类似回调的效果,这个新添加的aabbcc就可以作为Department的项目被调用,我们可以试一试

    result = session.query(Department)
    print(result)
    for i in result:
        print(i.id,i.title,i.aabbcc)
    
    ##########输出##########
    1 销售 [<__main__.User object at 0x0000015B58E14948>, <__main__.User object at 0x0000015B58E14AC8>]
    2 人事 [<__main__.User object at 0x0000015B58DED488>, <__main__.User object at 0x0000015B58DED288>]
    3 财务 [<__main__.User object at 0x0000015B58DED248>]

    可以发现,aabbcc就成了一个列表(我们的外键是一对多的)。也就是我们的aabbcc执行了一段这样的代码

    session.query(User).filter(User.dep_id=i.id).all()

    想实现前面的效果就要这么做

    result = session.query(Department)
    print(result)
    for i in result:
        print(i.id,i.title)
        for employee in i.aabbcc:
            print(employee.name)
    
    ##########输出##########
    1 销售
    aaa
    eee
    2 人事
    babb
    ddd
    3 财务
    ccc
  • 相关阅读:
    BZOJ 4260 Codechef REBXOR
    [SHOI2008]小约翰的游戏John
    [POI2016]Nim z utrudnieniem
    [CQOI2013]棋盘游戏
    [SDOI2016]硬币游戏
    [BZOJ3083]遥远的国度
    [Luogu3727]曼哈顿计划E
    [HihoCoder1413]Rikka with String
    [CF666E]Forensic Examination
    [BZOJ4004][JLOI2015]装备购买
  • 原文地址:https://www.cnblogs.com/yinsedeyinse/p/12309779.html
Copyright © 2020-2023  润新知