执行原始SQL

作者: 大爷的二舅 | 来源:发表于2018-02-12 11:35 被阅读4次

当模型查询API不够用时,您可以退回到编写原始SQL。 Django为您提供了两种执行原始SQL查询的方法:您可以使用Manager.raw()来执行原始查询并返回模型实例,也可以完全避免模型层并直接执行自定义SQL。

无论何时写入原始SQL,您都应该非常小心。 每次使用它时,都应该正确地转义用户可以通过使用参数来控制的参数,以防止SQL注入攻击。

执行原始查询

raw()管理器方法可用于执行返回模型实例的原始SQL查询:

Manager.raw(raw_query, params=None, translations=None)

此方法使用原始SQL查询,执行该查询,并返回一个django.db.models.query.RawQuerySet实例。 这个RawQuerySet实例可以像正常的QuerySet一样迭代来提供对象实例。 以一个例子来最好地说明这一点。 假设你有以下模型:

class Person(models.Model):
    first_name = models.CharField(...)
    last_name = models.CharField(...)
    birth_date = models.DateField(...)

然后你可以像这样执行自定义SQL:

>>> for p in Person.objects.raw('SELECT * FROM myapp_person'):
...     print(p)
John Smith
Jane Jones 

当然,这个例子并不是很令人兴奋 - 和运行Person.objects.all()完全一样。 然而,raw()有一堆其他的选项,使它非常强大。

模型表名称

在上面的例子中,Person表的名字来自哪里? 默认情况下,Django通过将模型的“应用标签”(在manage.py startapp中使用的名称)连接到模型的类名称,并在它们之间加下划线来计算数据库表名。 在这个例子中,我们假设Person模型存在于名为myapp的应用程序中,所以它的表格将是myapp_person。

有关更多详细信息,请查阅db_table选项的文档,该选项还允许您手动设置数据库表名称。

在传递给raw()的SQL语句上没有进行检查。 Django期望这个语句将从数据库中返回一组行,但是没有执行这个操作。 如果查询没有返回行,则会导致(可能是神秘的)错误。

将查询字段映射到模型字段

raw()自动将查询中的字段映射到模型上的字段。 查询中字段的顺序无关紧要。 换句话说,以下两个查询的工作原理是相同的:

>>> Person.objects.raw('SELECT id, first_name, last_name, birth_date FROM myapp_perso\
n')
...
>>> Person.objects.raw('SELECT last_name, birth_date, first_name, id FROM myapp_perso\
n')
...

匹配是通过名称完成的。 这意味着您可以使用SQL的AS子句将查询中的字段映射到模型字段。 因此,如果您有其他具有Person数据的表,您可以轻松将其映射到Person实例中:

>>> Person.objects.raw('''SELECT first AS first_name,
...                              last AS last_name,
...                              bd AS birth_date,
...                              pk AS id,
...                       FROM some_other_table''')

只要名称匹配,模型实例将被正确创建。 或者,可以使用raw()的translations参数将查询中的字段映射到模型字段。 这是一个将查询中的字段名称映射到模型上的字段名称的字典。 例如,上面的查询也可以写成:

>>> name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk':\
 'id'}
>>> Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)
索引查找

raw()支持索引,所以如果你只需要第一个结果就可以编写:

>>> first_person = Person.objects.raw('SELECT * FROM myapp_person')[0]

但是,索引和切片不在数据库级别执行。 如果您的数据库中有大量的Person对象,那么在SQL级别限制查询会更高效:

>>> first_person = Person.objects.raw('SELECT * FROM myapp_person LIMIT 1')[0]
推迟模型字段

字段也可能被忽略:

>>> people = Person.objects.raw('SELECT id, first_name FROM myapp_person')

此查询返回的Person对象将是延迟模型实例(请参阅defer())。 这意味着查询中省略的字段将按需加载。 例如:

>>> for p in Person.objects.raw('SELECT id, first_name FROM myapp_person'):
...     print(p.first_name, # This will be retrieved by the original query
...           p.last_name) # This will be retrieved on demand
...
John Smith
Jane Jones 

从外观看,这看起来像查询已经检索到名字和姓氏。 但是,这个例子实际上发出了3个查询。 只有第一个名字是由raw()查询检索的 - 最后一个名字在打印时都是按需要检索的。

只有一个字段不能忽略 - 主键字段。 Django使用主键来标识模型实例,因此它必须始终包含在原始查询中。 如果忘记包含主键,则会引发InvalidQuery异常。

添加注释

您还可以执行包含未在模型中定义的字段的查询。 例如,我们可以使用PostgreSQL的age()函数来获取由数据库计算其年龄的人员列表:

>>> people = Person.objects.raw('SELECT *, age(birth_date) AS age FROM myapp_person')
>>> for p in people:
...     print("%s is %s." % (p.first_name, p.age))
John is 37.
Jane is 42.
...
将参数传递给raw()

如果您需要执行参数化查询,则可以将params参数传递给raw():

>>> lname = 'Doe'
>>> Person.objects.raw('SELECT * FROM myapp_person WHERE last_name = %s', [lname])

params是参数的列表或字典。 无论数据库引擎如何,您都可以在查询字符串中使用%s占位符,或者在字典中使用%(键)的占位符(当然,其中的密钥由字典密钥替换)。 这些占位符将被params参数中的参数替换。

不要在原始查询中使用字符串格式!将上面的查询写为:
'>>> query ='SELECT * FROM myapp_person WHERE last_name =%s'%lname
Person.objects.raw(query)
别这样做。
使用params参数完全可以保护您免受SQL注入攻击,这是攻击者在数据库中注入任意SQL的常见漏洞。 如果使用字符串插值,迟早会成为SQL注入的牺牲品。 只要你记得总是使用params参数,你就会受到保护。

直接执行自定义SQL

有时甚至是Manager.raw()还不够:您可能需要执行不完全映射到模型的查询,或者直接执行UPDATE,INSERT或DELETE查询。 在这些情况下,您可以直接访问数据库,完全绕过模型层。 对象django.db.connection表示默认的数据库连接。 要使用数据库连接,请调用connection.cursor()以获取游标对象。 然后,调用cursor.execute(sql,[params])来执行SQL,并使用cursor.fetchone()或cursor.fetchall()返回结果行。 例如:

from django.db import connection

def my_custom_sql(self):
    cursor = connection.cursor()
    cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s",
[self.baz])
    cursor.execute("SELECT foo FROM bar WHERE baz = %s", [self.baz])
    row = cursor.fetchone()

    return row 

请注意,如果要在查询中包含文字百分号,则在传递参数的情况下必须将它们加倍:

cursor.execute("SELECT foo FROM bar WHERE baz = '30%'")
cursor.execute("SELECT foo FROM bar WHERE baz = '30%%' AND  
  id = %s", [self.id])

如果使用多个数据库,则可以使用django.db.connections来获取特定数据库的连接(和游标)。 django.db.connections是一个类似于字典的对象,允许您使用其别名来检索特定的连接:

from django.db import connections
cursor = connections['my_db_alias'].cursor()
# Your code here...

默认情况下,Python DB API将返回没有字段名称的结果,这意味着最终会得到一个值列表,而不是字典。 在性能价格比较低的情况下,您可以使用类似下面的方式将结果作为字典返回:

def dictfetchall(cursor):
    # Returns all rows from a cursor as a dict
    desc = cursor.description
    return [
        dict(zip([col[0] for col in desc], row))
        for row in cursor.fetchall()
    ]

以下是两者之间差异的一个例子:

>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");
>>> cursor.fetchall()
((54360982L, None), (54360880L, None))

>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");
>>> dictfetchall(cursor)
[{'parent_id': None, 'id': 54360982L}, {'parent_id': None, 'id': 54360880L}]
连接和游标

连接和游标大部分实现了PEP 249中描述的标准Python DB-API,除了事务处理。 如果您不熟悉Python DB-API,
请注意,cursor.execute()中的SQL语句使用占位符“%s”,而不是直接在SQL中添加参数。

如果使用这种技术,底层的数据库将根据需要自动转义您的参数。 还要注意,Django期望“%s”占位符,而不是?占位符,由SQLite Python绑定使用。 这是为了一致性和完整性。 使用游标作为上下文管理器:

with connection.cursor() as c:
    c.execute(...)

相当于:

c = connection.cursor()
try:
    c.execute(...)
finally:
    c.close()
添加额外管理器方法

添加额外的Manager方法是将表级功能添加到模型的首选方法。
(对于行级功能 - 即作用于模型对象的单个实例的函数 - 使用Model方法,而不是自定义的Manager方法。)自定义的Manager方法可以返回任何您想要的。 它不必返回一个QuerySet。

例如,此自定义Manager提供了一个方法with_counts(),该方法返回所有OpinionPoll对象的列表,每个对象都有一个作为聚合查询结果的额外num_responses属性:

from django.db import models

class PollManager(models.Manager):
    def with_counts(self):
        from django.db import connection
        cursor = connection.cursor()
        cursor.execute("""
            SELECT p.id, p.question, p.poll_date, COUNT(*)
            FROM polls_opinionpoll p, polls_response r
            WHERE p.id = r.poll_id
            GROUP BY p.id, p.question, p.poll_date
            ORDER BY p.poll_date DESC""")
        result_list = []
        for row in cursor.fetchall():
            p = self.model(id=row[0], question=row[1], poll_date=row[2])
            p.num_responses = row[3]
            result_list.append(p)
        return result_list

class OpinionPoll(models.Model):
    question = models.CharField(max_length=200)
    poll_date = models.DateField()
    objects = PollManager()

class Response(models.Model):
    poll = models.ForeignKey(OpinionPoll)
    person_name = models.CharField(max_length=50)
    response = models.TextField()

通过这个例子,你可以使用OpinionPoll.objects.with_counts()返回带有num_responses属性的OpinionPoll对象列表。 另外需要注意的是,这个例子中的Manager方法可以访问self.model来获取它们所附的模型类。

下一步是什么?

在下一章中,我们将向您展示Django的通用视图框架,该框架可以帮助您节省构建遵循常见模式的Web站点的时间。

相关文章

  • 执行原始SQL

    当模型查询API不够用时,您可以退回到编写原始SQL。 Django为您提供了两种执行原始SQL查询的方法:您可以...

  • Executing raw SQL

    执行SQL代码 Django有两种方法执行原始SQL语句 Manager.raw() 返回model实例 直接执行...

  • Django模型(2)

    1. 执行原始SQL语句 Django提供了两种执行原始SQL语句的方法 :第一种是使用Manage.row()方...

  • 3.执行原始SQL

    利用 .raw() 执行原始 SQL Manager.raw(raw_query, params=None, tr...

  • 【MySQL】sql优化小调优一例

    前言:今天在生产环境抓到慢SQL一例,单独拿出来执行速度非常快,我们拿出该SQL分析一下。 原始SQL: 执行计划...

  • mysql联合索引字段顺序

    原始sql 他的执行计划 可见,这个sql执行过程是被命中索引了的,索引如下 这个索引创建的稍微有点问题,在查询筛...

  • MySQL&python交互

    MySQL&python交互 一、Python操作MySQL步骤(原始的执行SQL语句) 引入pymysql模块 ...

  • Mysql进阶知识笔记

    一、SQL执行顺序以及常见SQL的join查询 sql执行顺序: 手写 机读顺序 sql机器执行顺序sql机器执行...

  • JDBC--Connection1与ResultSet

    Statement作用:1.执行SQL语句 执行SQL语句:int executeUpdate(sql):执行DM...

  • 查看Django ORM执行的SQL语句

    查询QuerySet对象执行的SQL语句 查询当前执行的SQL包括Django内置执行的多条执行的SQL语句

网友评论

    本文标题:执行原始SQL

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