美文网首页
SQLAlchemy笔记

SQLAlchemy笔记

作者: 宽哥好 | 来源:发表于2019-11-26 11:19 被阅读0次

SQLAlchemy介绍和基本使用

数据库时一个网站的基础,在 flask 中可以自由的使用MySQLPostgreSQLSQLliteRedisMongoDB来写原生的语句实现功能,也可以使用更高级别的数据库抽象方式,如SQLAlchemyMongoEngine这样的 OR(D)M.

开始之前,需要准备的软件有:

  1. mysql
  2. MySQLdb(python2)或pymysql(python3),通过pip可以安装
  3. SQLAlchemy: SQLAlchemy 是一个数据库的 ORM框架,安装命令为:
    pip install SQLAlchemy

通过SQLAlchemy来连接数据库,示例如下:

from sqlalchemy import create_engine

# 数据库连接变量
HOSTNAME = '127.0.0.1'
PORT = 3306
DATABASE = 'sqlalchemy_demo'
USERNAME = 'root'
PASSWORD = 'chenkuan1110'
DB_URI = 'mysql+mysqldb://{}:{}@{}:{}/{}'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)

# 创建数据库引擎
engine = create_engine(DB_URI)

# 创建连接
with engine.connect() as conn:
    ret = conn.execute('SELECT 1')
    print(ret.fetchone())

首先从sqlalchemy中导入create_engine,这个函数用来创建引擎
然后通过 engine.connect()来连接数据库。
其中 create_engine()需要传递一个字符串,这个字符串是对连接什么数据库的描述,需要符合下面这个规则:

dialect+driver://username:password@host:port/database
  • dialect: 数据库的实现,比如 MySQLPostgreSQLSQLlite,并且转换为小写。

  • driver: python 和数据库之前对应的驱动,如果不指定会选择默认的驱动。'MySQL'的默认驱动是MySQLdb

  • username: 数据库连接的用户名

  • password: 数据库连接的密码

  • host: 数据库的域名

  • port:数据库服务器的监听端口,默认为 3306,默认可省略该端口号

  • database: 连接数据库的名称

如果以上输出了 (1,),那么数据库就是连接成功的

通过SQLAlchemy执行原生SQL

在上面的例子中,通过conn.execute(原生SQL语句)就可以执行原生SQL的操作


ORM的介绍

ORM: Object Relationship Mapping 对象关系映射
对象模型与数据库表的映射,通过ORM,我们可以通过类的方式去操作数据库,而不用再写原生的SQL。
表 <--> 类
记录 <--> 对象示例
字段 <--> 属性

使用ORM的优点:

  1. 易用性 直观
  2. 性能损耗小
  3. 设计灵活 可以轻松写出复杂的查询
  4. 可移植性 封装了底层的数据库实现,可以轻松切换数据库

定义 ORM模型,并映射到数据库中

  1. declarative_base 根据engine创建一个ORM基类。

  2. Base 这个基类来创建自己的ORM类,在定义自己的模型时,要定义类属性__tablename__,指定这个模型映射到数据库中的表名。

  3. 创建属性来映射到表中的字段,所有映射到表的属性都是Cloum的示例对象

  4. 使用Base.metadata.create_all()来将模型映射到表。
    示例如下:

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
# 数据库连接变量
HOSTNAME = '127.0.0.1'
PORT = 3306
DATABASE = 'sqlalchemy_demo'
USERNAME = 'root'
PASSWORD = 'xxx'
DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
# 创建数据库引擎
engine = create_engine(DB_URI)
# 需要映射的表结构
# create table student(
#     id int primary key autoincrement,
#     name varchar(50),
#     age int
# )
Base = declarative_base(engine)  # 利用 declarative_base() 创建一个基类
# 1.类
class Student(Base):
   __tablename__ = 'person'  # 表名
   # 字段
   id = Column(Integer, primary_key=True, autoincrement=True)
   name = Column(String(50))
   age = Column(Integer)

# 3.映射
Base.metadata.create_all()

注意: 一旦将模型通过Base.metadata.create_all()映射到数据库中后,即使表结构发生了变化,数据库中表也不会改变

SQLAlchemy常用数据类型

  • Integer :整形
  • Float :浮点型
  • Boolean :传递 True/False 进去
  • DECIMAL :定点类型 (总位数,小数点后的位数)
  • Enum :枚举类型
  • Date: 传递datetime.date()进去
  • DateTime: 传递 datetime.datetime() 进去
  • Time:传递 datetime.time() 进去
  • String : 字符类型,使用时需要指定长度,区别于 Text 类型
  • Text : 文本类型
  • LONGTEXT : 长文本类型 ,需要导入from sqlalchemy.dialects.mysql import LONGTEXT

Column常用参数

  • default :默认值 在发表时间这种字段中很有用
  • nullable: 是否可空 默认可以为空
  • primary_key :是否为主键
  • unique :是否唯一
  • autoincrement : 是否自动增长
  • onupdate: 更新时执行的函数
  • name: 该属性在数据库中的字段映射

query可用参数:

  1. 模型对象。指定查找这个模型的所有对象。
  2. 模型中的属性。 可以指定只查询某个模型中的几个属性。
  3. 聚合函数。
    from sqlalchemy import func # 导入聚合函数
    • func.count:统计行的数量
    • func.avg: 平均值
    • func.max: 最大值
    • func.min : 最小值
    • func.sum : 求和
      func上,其实没有任何聚合函数,但是因为底层做了一些魔术,只要mysql中有的聚合函数,都可以通过func调用

过滤条件:

过滤时数据提取的重要功能之一,下面有一些常见的过滤条件,这些过滤条件都只能通过filter方法实现

  1. equals:

    query.fileter(User.name == 'ed')
    
  2. not equals:

    query.fileter(User.name != 'ed')
    
  3. like:

    query.filter(User.name.like('%ed%'))
    

    ilike: 不区分大小写

  4. in:

    query.filter(User.name.in_(['en', 'wendy', 'jack']))
    # 同时, in 也可以用做一个Query
    query.filter(User.name.in_(query.filter(User.name.like('%ed%'))))
    
  5. not in:

    query.filter(User.name.in_(['en','wendy','jack']))
    
  6. is null:

    query.filter(User.name == None)
    # 或者是
    query.fileter(User.name.is_(None))
    
  7. is not null

    query.filter(User.name != None)
    # 或者是
    query.fileter(User.name.isnot(None))
    
  8. and:

    from sqlalchemy import and_
    query.filter(and_(User.name == 'ed',User.fullname == 'Ed Jones'))
    # 或者是传递多个参数
    query.fileter(User.name == 'ed',User.fullname='Ed Jones')
    # 或者通过多次 filter 操作
    query.filter(User.name == 'ed').filter(User.fillname == 'Ed Jones')
    
  9. or

    form sqlalchemy import or_
    query.filter(or_(User.name == 'ed',User.fillname == 'wendy'))
    

外键及其约束

外键

SQLAlchemy 通过 ForeignKey类来实现,并且可以指定表的外键约束。示例代码如下:

class author(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), nullable=False)


class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(30), nullable=False)
    content = Column(Text, nullable=False)

    # 外键
    uid = Column(Integer, ForeignKey('user.id'))

外键约束有以下,通过 ForeignKey 的ondelete参数设置:

  1. RESTRIC : 父表数据被删除,会阻止删除 (默认)
  2. NO ACTION : 在 MySQL中 同 RESTRIC
  3. CASCADE : 级联删除
  4. SET NULL: 父表数据被删除,字表数据会设置为NULL。

ORM层外键 外键关系 一对多

mysql级别的外键,还不够ORM,必须拿到一个表的外键,然后再通过这个外键去另一张表中查询所需数据,这样太麻烦了。SQLAlchemy 提供了 一个relationship,这个类可以定义属性,以后在访问相关联的表的时候就可以通过 对象.属性来访问。
示例如下:

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), nullable=False)

    # articles = relationship('Article')

    def __repr__(self):
        return '<User> username:{}'.format(self.username)

class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(30), nullable=False)
    content = Column(Text, nullable=False)

    # 外键
    uid = Column(Integer, ForeignKey('user.id'))

    # 外键引用
    author = relationship('User', backref='articles')  # backref 表示在user表中反向引用这个,可以通过 User.articles获取

    def __repr__(self):
        return '<Article> id:{}, title:{},content:{}'.format(self.id, self.title, self.content)

另外可以通过backref来指定 反向访问的属性名。

一对一

在sqlalchemy中,如果需要将两个模型映射为 一对一 的关系,那么就应该在父模型中,指定引用的时候,传递一个uselist=False这个参数进去,就是告诉父模型,以后引用这个模型的时候,不再是一个列表,而是一个属性了,示例代码如下:

# 一
class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), nullable=False)

    # 一对一
    extend = relationship('UserExtend', uselist=False)

    def __repr__(self):
        return '<User> username:{}'.format(self.username)


# 一
class UserExtend(Base):
    __tablename__ = 'extend'
    id = Column(Integer, primary_key=True, autoincrement=True)
    school = Column(String(50))

    uid = Column(Integer, ForeignKey='user.id')

    # 一对一
    user = relationship('User', backref='extend')

当然,可以借助 sqlalchemy.orm.backref来简化代码:

# 一
class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), nullable=False)

# 一
class UserExtend(Base):
    __tablename__ = 'extend'
    id = Column(Integer, primary_key=True, autoincrement=True)
    school = Column(String(50))

    uid = Column(Integer, ForeignKey='user.id')

    # 一对一
    user = relationship('User', backref=backref('extend',uselist=False))

此处的backref为函数,参数与在 User 中使用的 relationship的参数相同。

多对多

多对多的关系需要通过一张中间表来绑定他们之间的关系,步骤如下:

  1. 定义两个多对多关系的模型

  2. sqlalchemy.Table创建一个中间表对象,中间表一般包含两个模型的外键字段,并且让它们两个作为一个"符合主键"

  3. 在两个需要做多对多模型中随便选择一个模型,定义一个 relationship属性,来绑定三者之间的关系,在使用 relationship 时需要传入一个secondary=中间表对象
    示例如下:

# 文章(多)
class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=False)
# 中间表
article_tag = Table('article_tag', Base.metadata,
                    Column('article_id', Integer, ForeignKey('article.id'), primary_key=True),
                    Column('tag_id', Integer, ForeignKey('tag.id'), primary_key=True)
                    )
# 标签(多)
class Tag(Base):
    __tablename__ = 'tag'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False)

    # 关联三张表
    articles = relationship('Article', backref='tags', secondary=article_tag)

ORM层面删除数据的注意事项

orm层面删除数据,会无视mysql级别的外键约束,直接回将对应的数据删除,然后将从表中的那个外键设置为 NULL。
如果想要避免这种行为,应该将从表的外键设置 nullable=True

relationship方法中的 cascade参数

在 SQLAlchemy 中,只要将一个数据添加到 session 中,和它关联的数据都可以存入数据库中。原因在于,通过 relationship 的时候,又一个关键字参数 cascade 可以设置这些属性:

  • save-update : 默认选项。在添加一个数据的时候与之相关的数据都会被添加

  • delete : 当删除一个数据的时候,是否也删掉使用 relationship 与之相关联的数据。

  • delete-orphan : 当一个ORM对象删除了父表中的关联对象的时候,自己便要会被删除掉。当然如果父表中的数据被删除,自己也会被删除。这个选项只能用在一对多上,不能用在一对一或多对多上。并且还需要在子模型的relationship中,增加一个single_parent=True

  • merge:默认选项。当在使用 session.merge ,合并一个对象时,会将使用了 relationship 关联的对象也进行 merge 操作。

  • expunge: 移除操作的时候,会将相关了的对象也进行移除。这个操作只是从 session 中移除,并不会真正从数据表中删除。

  • all: 是对 save-update,merge,refresh-expire,expunge,delete几种的缩写。

排序

现在定义有如下 orm 模型,希望能够按照 create_time 来进行排序:

class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=False)
    create_time = Column(DateTime, nullable=False, default=datetime.now)

    def __repr__(self):
        return '<Article> title: {} create_time:{}'.format(self.title, self.create_time)
  1. order_by : 可以根据表中某一个字段进行排序,如果在前面加了一个 -,表示 降序排序。
    • 正序:按 create_time来排序文章
    articles = session.query(Article).order_by('create_time').all()
    
    • 倒序:
    from sqlalchemy import text
    articles = session.query(Article).order_by(text('-create_time')).all()
    
  1. 在模型定义的时候指定默认排序 : 有两种方式:

    • relationshiporder_by参数 : 在指定 relationship 时,传递 order_by 参数来指定排序的字段。

    • 在模型定义中,添加以下代码:

    __mapper_args__ = {
        'order_by': create_time # 正向排序 
        # 'order_by': create_time.desc() # 倒序排序
    }
    
  2. 正向排序和反向排序: 默认情况下时从小到大,从前到后排序的,如果想要反项排序,可以调用排序的字段的 desc 方法。

    articles = session.query(Article).order_by(Article.create_time.desc()).all()
    

limitoffset 和 切片操作

  1. limit(): 可以限制查询数据条数。

  2. offset(): 限制查询数据过滤掉前面的多少条记录。

  3. 切片/slice() : 可以对 Query 对象使用切片操作来获取想要的数据,可以使用 slice(start,end),也可以使用[start:end]

数据库操作懒加载

在 一对多 或者 多对多的时候,如果想要获取多的者一部分的数据的时候,往往能通过一个属性就可以全部获取了。
比如有一个作者表和一个文章表,如果获取一个作者的全部文章,可以通过 user.articles 获取。
但是有时候我们不想获取全部的数据,比如只想获取今天发表的文章,那么我们可以在定义这个orm模型的时候,在 relationship中指定lazy='dynamic', 以后通过 user.articles 获取到的就不是一个列表,而是一个 AppenderQuery对象。这样可以通过 该对象的方法进行进一步的排序过滤等操作来得到想要的结果。
这种对象除了可以进行 Query 对象的操作,还能够添加数据。

lazy 可用的选项:

  1. select: 默认选项。 没有访问就不加载,访问才加载,组装返回列表对象 InstrumentedList(继承的原始 list)。也是懒加载的方式。
  2. dynamic: 懒加载的方式之一。返回的是AppenderQuery对象。
  3. immediate: 非懒加载,如果访问就直接取数据返回
  4. joined:
  5. subquery:
  6. selectin:
  7. noload:
  8. raise:
  9. raise_on_sql:
    等...

高级查询

group_by:

根据某个字段来分组。比如要查询根据性别来分组,来统计每个分组有多少人,那么可以通过以下代码来完成:

session.query(User.gender,func.count(User.id)).group_by(User.gender).all()

having

having是对查找结果的进一步过滤。比如只想要看未成年人的数量,那么可以先对年龄进行分组统计人数,然后在对分组进行 having 过滤。示例如下:

ret = session.query(User.age,func.count(User.id)).group_by(User.age).having(User.age < 18).all()

join方法:

join 查询分为 两种 :inner join 和 outer join。 默认是 inner join。left join 和 right join 为 out join 。

  • 在sqlalchemy中,使用 join来完成内连接。在写 join de 时候,如果不写join的条件,那么默认使用内连接。
    比如现在要实现一个功能,查找所有的用户,按照发表文章的数量来进行排序。示例代码如下:
ret = session.query(User.name, func.count(Article.id)).join(Article, User.id == Article.uid).group_by(
        User.id).order_by(func.count(Article.id).desc()).all() 

别名

当多表查询时,有时候一个表要用到多次,这时候就可以用别名的烦事解决命名冲突的问题。

子查询

sqlalchemy也支持子查询,子查询可以让多个查询变为一个查询,只要查找一次数据库,性能相对来讲更加高效。也可以实现一些复杂的查询语句。
在sqlalchemy中,使用子查询的步骤:

  1. 将子查询按照传统的方式写好查询代码,然后在query对象后面执行subquery方法,将这个查询变为一个子查询。

  2. 在子查询中,将以后需要用到的字段通过label方法,取个别名。

  3. 在父查询中,如果想要使用子查询的字段,那么通过子查询的返回值的c属性拿到。
    整体的示例代码如下:

stmt = session.query(User.city.label('city'), User.age.label('age')).filter(User.name == '李A').subquery()  # 子查询
# 把子查询放到父查询中
ret = session.query(User).filter(User.city == stmt.c.city, User.age == stmt.c.age).all()
print(ret) 

相关文章

  • 笔记02:sqlalchemy-连接MySQL

    sqlalchemy-连接MySQL 笔记03:sqlalchemy增删改查 连接数据库

  • flask-sqlalchemy笔记

    flask_sqlalchemy笔记 安装 可通过 pip安装: 数据库连接 跟 sqlalchemy 一样,定义...

  • 笔记01:MySQL server安装

    window 安装MySQL server 笔记02:笔记02:sqlalchemy-连接MySQL笔记03:sq...

  • SQLAlchemy学习笔记(二)

    SQLAlchemy学习笔记(二) SQLAlchemy Core 现在我们可以连接到数据库了,接下来让我们看看如...

  • 2018-06-05 sqlalchemy

    此文档为个人笔记,完整的请查看官方文档。摘自SQLAlchemy ORM当前使用的sqlalchemy版本为1.1...

  • SQLAlchemy学习笔记(一)

    前言:该笔记是本人学习SQLAlchemy官方文档整理得来。 查看SQLAlchemy版本 连接数据库 本教程中我...

  • SQLAlchemy学习笔记(一)

    SQLAlchemy学习笔记(一) 为什么要使用SQLAlchemy? 将你的代码抽象出来不依赖与数据库的类型和某...

  • sqlalchemy笔记

    https://docs.sqlalchemy.org/en/devel/ Glossary unit of wo...

  • SQLAlchemy笔记

    SQLAlchemy介绍和基本使用 数据库时一个网站的基础,在 flask 中可以自由的使用MySQL、Postg...

  • SQLAIchemy总结

    SQLAlchemy基础 SQLAlchemy概述 SQLAlchemy安装 SQLAIc...

网友评论

      本文标题:SQLAlchemy笔记

      本文链接:https://www.haomeiwen.com/subject/fvypmctx.html