Django原生查询

如果在QuerySet不满足的情况下,可以用原生的方式去执行SQL。在Django中有两种方式,一种用raw(),另外一种用更底层的方式Python DB API。

raw()方式返回django.db.models.query.RawQuerySet,对于简单的情景我们可以这样做。

In [3]: persons = models.Person.objects.raw('SELECT * FROM myapp_person')
In [4]: persons
Out[4]: <RawQuerySet: SELECT * FROM myapp_person>
In [6]: for person in persons:
   ...:     print(person)
   ...:     
   ...:     
Person object (1)
Person object (2)
In [7]: for person in persons:
   ...:     print(person.first_name)
   ...:     
   ...:     
   ...:     
w
f
x

这种方式是根据名称来进行映射的,如果映射不到的话也会保留这个字段

In [8]: persons = models.Person.objects.raw('SELECT *, 1+1 AS a FROM myapp_person')
In [9]: for person in persons:
   ...:     print(person.a)
   ...:     
   ...:     
   ...:     
   ...:     
2
2
2

当然也可以用AS关键字来重命名字段名

In [12]: persons = models.Person.objects.raw('SELECT id, first_name AS other_name FROM myapp_person')
In [13]: for person in persons:
    ...:     print(person.other_name)
    ...:     
    ...:     
    ...:     
    ...:     
    ...:     
w
f
x

也可以用raw的第二个参数来重命名

In [22]: persons = models.Person.objects.raw('SELECT id, first_name FROM myapp_person', translations={'first_name':'other_name'})
In [23]: for person in persons:
    ...:     print(person.other_name)
    ...:     
    ...:     
    ...:     
    ...:     
    ...:     
w
f
x

对于raw()需要说明的是,貌似必须带id,否则就是一个无情的错误

~/.pyenv/versions/py3-daily/lib/python3.5/site-packages/django/db/models/query.py in __iter__(self)
   1302             model_init_names, model_init_pos, annotation_fields = self.resolve_model_init_order()
   1303             if self.model._meta.pk.attname not in model_init_names:
-1304                 raise InvalidQuery('Raw query must include the primary key')
   1305             model_cls = self.model
   1306             fields = [self.model_fields.get(c) for in self.columns]
InvalidQuery: Raw query must include the primary key

对于切片也需要注意,如果是原生的数据切片, 则是在内存中完成的, 比如

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

但是我们能能在SQL语句里加一些约束就可以了

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

其实这个很好理解,因为raw()在取出来的生活不知道你需要切片多少数据,所以他就全部取出来了.

raw()也能延迟加载,但是就如上面说的,不要把id漏了,因为他是根据id去拉取数据的。

In [26]: persons = models.Person.objects.raw('SELECT id, first_name FROM myapp_person LIMIT 1')[0]
In [27]: persons.first_name
Out[27]: 'w'
In [28]: persons.last_name
Out[28]: 'z'
In [29]:

实际在项目中使用的时候我们肯定是有参数的,所以raw()也能传递参数

In [31]: persons = models.Person.objects.raw('SELECT id, first_name FROM myapp_person WHERE first_name=%s LIMIT 1', ['w'])[0]
In [32]: persons.last_name
Out[32]: 'z'

文档中提醒,不要自己去拼接参数,原因是SQL注入问题。

总结下raw()的方式,其实我们可以看到,raw()的方式更适合有Model的情况,或者其中有一张表是有Model对应的情况,不过下面介绍的这种方式可以跳出这个束缚。

In [37]: with connection.cursor() as cursor:
    ...:     cursor.execute('SELECT id, first_name FROM myapp_person')
    ...:     row = cursor.fetchall()
    ...:     print(row)
    ...:     
    ...:     
    ...:     
[(1'w'), (2'f'), (3'x')]

可以看到数据已经拉取出来了,但是数据有点不太好看,都放在一坨里。当然我们能稍微做一点性能的牺牲,就可以得到一些比较好看的结果。

In [39]: def dictfetchall(cursor):
    ...:     columns = [col[0for col in cursor.description]
    ...:     return [
    ...:             dict(zip(columns, row))
    ...:             for row in cursor.fetchall()
    ...:         ]
    ...:
In [40]: with connection.cursor() as cursor:
    ...:     cursor.execute('SELECT id, first_name FROM myapp_person')
    ...:     rows = dictfetchall(cursor)
    ...:     print(rows)
    ...:     
[{'id'1'first_name''w'}, {'id'2'first_name''f'}, {'id'3'first_name''x'}]

得到的是字典形式的数据,稍微改变一点代码我们也可以得到对象形式的数据

In [47]: with connection.cursor() as cursor:
    ...:     cursor.execute('SELECT id, first_name FROM myapp_person')
    ...:     rows = namedtuplefetchall(cursor)
    ...:     print(rows)
    ...:     
    ...:     
[Result(id=1, first_name='w'), Result(id=2, first_name='f'), Result(id=3, first_name='x')]

对于原生方式我们稍微总结下,原生形式几乎可以实现任何情况,太自由了,另外性能也肯定比raw()方式高。

但是我们知道QuerySet方式在数据第二次获取的时候是有缓存的,但是raw()和原生方式都是没有缓存的,都是需要自己去处理的,不过这样做也挺合理的。

Happy hacking

暂无评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注

备案号:浙ICP备15006402号-2 备注:博客君在0.083里共执行39个查询, 总共占用内存 8.73MB