python sqlalchemy 查询最佳实践

3,971 阅读6分钟

整理了一下 python sqlalchemy的使用方法

一个Query对象使用所创建的 query()上方法 Session。此函数采用可变数量的参数,这些参数可以是类和类检测描述符的任意组合。下面,我们指出 Query哪个加载User实例。在迭代上下文中计算时,将User返回存在的对象列表:

for instance in session.query(User).order_by(User.id):
···    print(instance.name, instance.fullname)
ed Ed Jones
wendy Wendy Williams
mary Mary Contrary
fred Fred Flintstone

该Query还接受ORM,仪表描述作为参数。每当多个类实体或基于列的实体表示为函数的参数时 query(),返回结果表示为元组:

for name, fullname in session.query(User.name, User.fullname):
...     print(name, fullname)
ed Ed Jones
wendy Wendy Williams
mary Mary Contrary
fred Fred Flintstone

返回的元组Query被命名为 元组,由KeyedTuple类提供,并且可以像普通的Python对象一样对待。名称与属性的属性名称以及类的类名称相同:

for row in session.query(User, User.name).all():
...    print(row.User, row.name)
<User(name='ed', fullname='Ed Jones', nickname='eddie')> ed
<User(name='wendy', fullname='Wendy Williams', nickname='windy')> wendy
<User(name='mary', fullname='Mary Contrary', nickname='mary')> mary
<User(name='fred', fullname='Fred Flintstone', nickname='freddy')> fred

您可以使用label()构造控制各个列表达式的名称,该 构造可以从任何ColumnElement派生对象获得,也可以映射到一个(例如User.name)的任何类属性:

for row in session.query(User.name.label('name_label')).all():
...    print(row.name_label)
ed
wendy
mary
fred

给予完整实体的名称,例如User,假设调用中存在多个实体query(),可以使用aliased()以下方法控制 :

from sqlalchemy.orm import aliased
>>> user_alias = aliased(User, name='user_alias')

>>> for row in session.query(user_alias, user_alias.name).all():
...    print(row.user_alias)
<User(name='ed', fullname='Ed Jones', nickname='eddie')>
<User(name='wendy', fullname='Wendy Williams', nickname='windy')>
<User(name='mary', fullname='Mary Contrary', nickname='mary')>
<User(name='fred', fullname='Fred Flintstone', nickname='freddy')>

基本操作Query包括发出LIMIT和OFFSET,最方便的是使用Python数组切片,通常与ORDER BY结合使用:

for u in session.query(User).order_by(User.id)[1:3]:
...    print(u)
<User(name='wendy', fullname='Wendy Williams', nickname='windy')>
<User(name='mary', fullname='Mary Contrary', nickname='mary')>

和过滤结果,使用 filter_by(),使用关键字参数完成:

for name, in session.query(User.name).\
...             filter_by(fullname='Ed Jones'):
...    print(name)
ed

或者filter(),它使用更灵活的SQL表达式语言结构。这些允许您使用常规Python运算符和映射类的类级属性:

for name, in session.query(User.name).\
...             filter(User.fullname=='Ed Jones'):
...    print(name)
ed

该Query对象是完全生成的,这意味着大多数方法调用返回一个新Query 对象,可以在其上添加进一步的标准。例如,要查询名为“ed”且名称为“Ed Jones”的用户,可以调用 filter()两次,使用AND以下命令连接条件 :

for user in session.query(User).\
...          filter(User.name=='ed').\
...          filter(User.fullname=='Ed Jones'):
...    print(user)
<User(name='ed', fullname='Ed Jones', nickname='eddie')>

公共过滤运算符

以下是一些最常用的运算符的概述 filter():

  • equals:

    query.filter(User.name == 'ed')
    
  • not equals:

    query.filter(User.name != 'ed')
    
  • LIKE:

    query.filter(User.name.like('%ed%'))
    
  • ILIKE (不区分大小写的LIKE):

    query.filter(User.name.ilike('%ed%'))
    
  • IN:

    query.filter(User.name.in_(['ed', 'wendy', 'jack']))
    
    # works with query objects too:
    query.filter(User.name.in_(
        session.query(User.name).filter(User.name.like('%ed%'))
    ))
    
  • NOT IN:

    query.filter(~User.name.in_(['ed', 'wendy', 'jack']))
    
  • IS NULL:

    query.filter(User.name == None)
    
    # alternatively, if pep8/linters are a concern
    query.filter(User.name.is_(None))
    
  • IS NOT NULL:

    query.filter(User.name != None)
    
    # alternatively, if pep8/linters are a concern
    query.filter(User.name.isnot(None))
    
  • AND:

    # use and_()
    from sqlalchemy import and_
    query.filter(and_(User.name == 'ed', User.fullname == 'Ed Jones'))
    
    # or send multiple expressions to .filter()
    query.filter(User.name == 'ed', User.fullname == 'Ed Jones')
    
    # or chain multiple filter()/filter_by() calls
    query.filter(User.name == 'ed').filter(User.fullname == 'Ed Jones')
    
  • OR:

    from sqlalchemy import or_
    query.filter(or_(User.name == 'ed', User.name == 'wendy'))
    
  • MATCH:

    query.filter(User.name.match('wendy'))
    

返会列表和常量

有许多方法可以Query 立即发出SQL并返回包含已加载数据库结果的值。这是一个简短的旅游:

  • all() 返回一个列表:

    >>> query = session.query(User).filter(User.name.like('%ed')).order_by(User.id)
    >>> query.all()
    [<User(name='ed', fullname='Ed Jones', nickname='eddie')>,
     <User(name='fred', fullname='Fred Flintstone', nickname='freddy')>]
    
  • first() 应用限制为1并将第一个结果作为标量返回:

    >>> query.first()
    <User(name='ed', fullname='Ed Jones', nickname='eddie')>
    
  • one()完全提取所有行,如果结果中不存在一个对象标识或复合行,则会引发错误。找到多行:

    >>> user = query.one()
    Traceback (most recent call last):
    ...
    MultipleResultsFound: Multiple rows were found for one()
    

    找不到行:

    >>> user = query.filter(User.id == 99).one()
    Traceback (most recent call last):
    ...
    NoResultFound: No row was found for one()
    

    该one()方法适用于希望处理“找不到任何项目”而不是“找到多个项目”的系统; 例如RESTful Web服务,可能希望在找不到结果时引发“未找到404”,但在找到多个结果时引发应用程序错误。

  • one_or_none()就像one(),除非没有找到结果,它不会引起错误; 它只是回来了None。像 one(),但是,它如果有多个结果发现引发错误。

  • scalar()调用该one()方法,并在成功时返回该行的第一列:

    >>> query = session.query(User.id).filter(User.name == 'ed').\
    ...    order_by(User.id)
    SQL>>> query.scalar()
    1
    

使用文本

Query通过指定它们与text()构造的使用,可以灵活地使用文字字符串 ,这是大多数适用方法所接受的。例如, filter()和 order_by():

>>> from sqlalchemy import text
>>> for user in session.query(User).\
...             filter(text("id<224")).\
...             order_by(text("id")).all():
...     print(user.name)
ed
wendy
mary
fred

可以使用冒号使用基于字符串的SQL指定绑定参数。要指定值,请使用以下params() 方法:

>>> session.query(User).filter(text("id<:value and name=:name")).\
...     params(value=224, name='fred').order_by(User.id).one()
<User(name='fred', fullname='Fred Flintstone', nickname='freddy')>

要使用完全基于字符串的语句,text()可以将表示完整语句的构造传递给 from_statement()。如果没有其他说明符,字符串SQL中的列将根据名称与模型列匹配,例如下面我们只使用星号表示加载所有列:

>>> session.query(User).from_statement(
...                     text("SELECT * FROM users where name=:name")).\
...                     params(name='ed').all()
[<User(name='ed', fullname='Ed Jones', nickname='eddie')>]

匹配名称上的列适用于简单的情况,但在处理包含重复列名的复杂语句或使用不易与特定名称匹配的匿名ORM构造时可能会变得难以处理。此外,我们的映射列中存在键入行为,我们在处理结果行时可能会发现这些行为。对于这些情况,text()构造允许我们在位置上将其文本SQL链接到Core或ORM映射的列表达式; 我们可以通过将列表达式作为位置参数传递给TextClause.columns()方法来实现这一点 :

>>> stmt = text("SELECT name, id, fullname, nickname "
...             "FROM users where name=:name")
>>> stmt = stmt.columns(User.name, User.id, User.fullname, User.nickname)
SQL>>> session.query(User).from_statement(stmt).params(name='ed').all()
[<User(name='ed', fullname='Ed Jones', nickname='eddie')>]

从text()构造中进行选择时,Query 仍然可以指定要返回的列和实体; 而不是 query(User)我们也可以单独要求列,如在任何其他情况下:

>>> stmt = text("SELECT name, id FROM users where name=:name")
>>> stmt = stmt.columns(User.name, User.id)
SQL>>> session.query(User.id, User.name).\
...          from_statement(stmt).params(name='ed').all()
[(1, u'ed')]

计数

Query包括一种方便的计数方法count():

>>> session.query(User).filter(User.name.like('%ed')).count()
2

该count()方法用于确定SQL语句将返回多少行。查看上面生成的SQL,SQLAlchemy总是将我们查询的内容放入子查询中,然后从中计算行数。在某些情况下,这可以简化为更简单,但SQLAlchemy的现代版本不会尝试猜测何时合适,因为可以使用更明确的方法发出确切的SQL。

SELECT count(*) FROM table

对于需要具体指出“要计数的东西”的情况,我们可以直接使用构造中func.count()可用 的表达式指定“计数”函数func。下面我们用它来返回每个不同用户名的计数:

>>> from sqlalchemy import func
>>> session.query(func.count(User.name), User.name).group_by(User.name).all()
[(1, u'ed'), (1, u'fred'), (1, u'mary'), (1, u'wendy')]

为了实现我们的简单,我们可以将其应用为:SELECT count(*) FROM table

>>> session.query(func.count('*')).select_from(User).scalar()
4

select_from()如果我们User直接用主键表示计数,则可以删除用法:

>>> session.query(func.count(User.id)).scalar()
4