django models的点查询/跨表查询/双下划线查询
django models
在日常的编程中,我们需要建立数据库模型
而往往会用到表与表之间的关系,这就比单表取数据要复杂一些
在多表之间发生关系的情形下,我们如何利用models提供的API的特性获得需要的数据呢
我们先从对象和查询集说开去
查询结果有时是对象/有时是查询集
我们只需要知道 ,只有get方法或者对查询集合进行切片,比如objects.all()[0] 或者 objects.all().first()等得到的是对象,其他得到的都是queryset
我们举个例子看下
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models
# Create your models here.
class Province(models.Model):
name = models.CharField(max_length = 20)
def __str__(self):
return self.name.decode("utf-8")
def __unicode(self):
return self.name.decode("utf-8")
class City(models.Model):
name = models.CharField(max_length = 20)
province = models.ForeignKey('Province', to_field = 'id')
def __str__(self):
return self.name.decode("utf-8")
def __unicode(self):
return self.name.decode("utf-8")
从上面的ORM类的建立可以看到,city通过外键和province表发生关系
BTW,我们通常(建议)都是把外键放在一对多的关系的多的那一方,因为这样是方便的。否则,放在少的那一方,因为少的一方是一对多的关系,数据库里要繁琐的建立这种关系:A省-B市,
A省-C市 A省-D市等等,这样会很麻烦而放在多的一方,就只要建立一个 市-省的关系就行了
然后我们建立一些测试数据
我们可以通过django的shell,通过命令行的方式建立测试数据
python manage.py shell
这样就可以进入python 的django 的命令行用代码的方式建立测试数据
[root@khao test_models]# python manage.py makemigrations
Migrations for 'app01':
app01/migrations/0001_initial.py
- Create model City
- Create model Province
- Add field province to city
[root@khao test_models]# python manage.py migrate
Operations to perform:
Apply all migrations: admin, app01, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying app01.0001_initial... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying sessions.0001_initial... OKre
[root@khao test_models]# python manage.py shell
Python 2.7.5 (default, Aug 4 2017, 00:39:18)
Type "copyright", "credits" or "license" for more information.
IPython 5.5.0 -- An enhanced Interactive Python.
? -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help -> Python's own help system.
object? -> Details about 'object', use 'object??' for extra details.
In [1]: from app01 import models
In [2]: models.Province.objects.create(name = u"河北")
Out[2]: <Province: 河北>
In [3]: models.Province.objects.create(name = u"山东")
Out[3]: <Province: 山东>
In [4]: h = models.Province.objects.get(name = u"河北")
In [5]: s = models.Province.objects.get(name = u"山东")
In [8]: models.City.objects.create(name = u"石家庄",province = h)
Out[8]: <City: 石家庄>
In [9]: models.City.objects.create(name = u"张家口",province = h)
Out[9]: <City: 张家口>
In [10]: models.City.objects.create(name = u"邢台",province = h)
Out[10]: <City: 邢台>
In [11]: models.City.objects.create(name = u"济南",province = s)
Out[11]: <City: 济南>
In [12]: models.City.objects.create(name = u"青岛",province = s)
Out[12]: <City: 青岛>
然后,我们进入数据库的命令行,看看数据和表结构:
[root@khao ~]# mysql
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 32
Server version: 5.6.39 MySQL Community Server (GPL)
Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> set names utf8;
Query OK, 0 rows affected (0.00 sec)
mysql> use test_models_db;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> select * from app01_province;
+----+--------+
| id | name |
+----+--------+
| 1 | 河北 |
| 2 | 山东 |
+----+--------+
2 rows in set (0.00 sec)
mysql> select * from app01_city;
+----+-----------+-------------+
| id | name | province_id |
+----+-----------+-------------+
| 1 | 石家庄 | 1 |
| 2 | 张家口 | 1 |
| 3 | 邢台 | 1 |
| 4 | 济南 | 2 |
| 5 | 青岛 | 2 |
+----+-----------+-------------+
5 rows in set (0.00 sec)
我们可以看到,在有外键的表里,也就是我们创建了
province = models.ForeignKey('Province', to_field = 'id')
这个字段的表里,django会自动给我们创建一个外键关联字段province_id字段
正是通过这个字段,city这张表找到了每行即每个城市对应的省的id,也即每个城市和省进行了关联
正向查找
我们在很多讲述django的文章/资料/教程里面,都会看到正向查找和反向查找的概念
那么什么是正向查找呢
正向查找就是通过数据库就有的字段去查找,比如
我们可以通过city这张表找到省(province),因为city表里有province这个外键
In [13]: models.City.objects.get(name = u"石家庄").province
Out[13]: <Province: 河北>
In [14]: models.City.objects.get(name = u"石家庄").province_id
Out[14]: 1L
In [17]: models.City.objects.get(name = u"石家庄").province.id
Out[17]: 1L
> 在上面的查找中,都是通过city表本身就有的字段进行出发查找的,这就叫做正向查找
跨表查询
其实在上面的查询过程,已经发生了跨表查询
什么是跨表查询
models.City.objects.get(name = u"石家庄").province_id 不是跨表查询,因为这个字段在city表结构里本来就有(上面已经演示了表结构)
models.City.objects.get(name = u"石家庄").province.id 是跨表查询,这个字段在city 表结构里本来没有,是通过什么方式查到的呢?
models.City.objects.get(name = u"石家庄") 拿到了 石家庄这个城市对象
models.City.objects.get(name = u"石家庄").province 拿到了石家庄这个城市对应的省,即河北这个对象
models.City.objects.get(name = u"石家庄").province.id 从河北这个对象里,查到了河北这个省对象自身的属性id
如何观察和验证上述结论(如何观察ORM对应的SQL语句)
这里有个小技巧
我们可以在django 命令行里面导入
from django.db import connection
然后编写ORM语句执行
再 敲入 connection.queries
就会看到之前所有的ORM对应的sql语句
我们看下 上面的查询过程
In [1]: from app01 import models
In [2]: models.City.objects.get(name = u"石家庄").province_id
Out[2]: 1L
In [3]: from django.db import connection
In [4]: connection.queries
Out[4]:
[{u'sql': u'SELECT @@SQL_AUTO_IS_NULL', u'time': u'0.000'},
{u'sql': u'SELECT VERSION()', u'time': u'0.000'},
{u'sql': u"SELECT `app01_city`.`id`, `app01_city`.`name`, `app01_city`.`province_id` FROM `app01_city` WHERE `app01_city`.`name` = '\u77f3\u5bb6\u5e84'",
u'time': u'0.000'}]
我们可以看到,models.City.objects.get(name = u"石家庄").province_id这个ORM操作,是从app01_city这张表查出来的,而且app01_city这张表有province_id这个字段,所以不是跨表查询
我们再继续看 models.City.objects.get(name = u"石家庄").province.id
{u'sql': u"SELECT `app01_city`.`id`, `app01_city`.`name`, `app01_city`.`province_id` FROM `app01_city` WHERE `app01_city`.`name` = '\u77f3\u5bb6\u5e84'",
u'time': u'0.002'},
{u'sql': u'SELECT `app01_province`.`id`, `app01_province`.`name` FROM `app01_province` WHERE `app01_province`.`id` = 1',
u'time': u'0.000'}]
可以清楚的看到,由于city这张表没有province.id这个字段,为此,先拿到models.City.objects.get(name = u"石家庄").province这个对象,也就是到province这张表去查找
在app01_province这张表查到了对应的id这个字段
什么时候用双下划线查找
凡是通过queryset对象去查找的时候,就用双下划线查找
前面已经讲过,在django的ORM里面,除了get拿到的是对象,其他查到到都是queryset,除非对queryset进行切片/索引取值,比如,objects.all()[0]这样拿到的才是对象,objects.all()拿到的是queryset
凡是get查到的对象,都可以通过点号 . 的方式进行逐级查找,比如上面的跨表查询 models.City.objects.get(name = u"石家庄").province.id
我们举几个双下划线查找的例子
In [10]: models.City.objects.all().filter(name = "石家庄")
Out[10]: <QuerySet [<City: 石家庄>]>
首先我们看到上面拿到的是QuerySet类型的
我们先看下上面的ORM语句对应的sql语句
In [4]: connection.queries
Out[4]:
[{u'sql': u'SELECT @@SQL_AUTO_IS_NULL', u'time': u'0.000'},
{u'sql': u'SELECT VERSION()', u'time': u'0.000'},
{u'sql': u"SELECT `app01_city`.`id`, `app01_city`.`name`, `app01_city`.`province_id` FROM `app01_city` WHERE `app01_city`.`name` = '\u77f3\u5bb6\u5e84' LIMIT 21",
u'time': u'0.000'}]
可以看到,这sql对应的显然是一个查询集合(QuerySet)。因为都用到了LIMIT 限定条件
我们再继续看
In [5]: models.City.objects.all().filter(name = "石家庄").values("province__name")
Out[5]: <QuerySet [{'province__name': u'\u6cb3\u5317'}]>
In [6]: connection.queries
Out[6]:
[{u'sql': u'SELECT @@SQL_AUTO_IS_NULL', u'time': u'0.000'},
{u'sql': u'SELECT VERSION()', u'time': u'0.000'},
{u'sql': u"SELECT `app01_city`.`id`, `app01_city`.`name`, `app01_city`.`province_id` FROM `app01_city` WHERE `app01_city`.`name` = '\u77f3\u5bb6\u5e84' LIMIT 21",
u'time': u'0.000'},
{u'sql': u"SELECT `app01_province`.`name` FROM `app01_city` INNER JOIN `app01_province` ON (`app01_city`.`province_id` = `app01_province`.`id`) WHERE `app01_city`.`name` = '\u77f3\u5bb6\u5e84' LIMIT 21",
u'time': u'0.002'}]
看最后的SQL 可以看到,这种双下划线查询一般对应的都是各种类型的join联表查询,即联合两张表进行联合查询
XXX_set跨表查询
除了上面说到的通过对象在正向查找里面的点查询方式的跨表查询和通过QuerySet进行双下划线的方式查询
我们再简单总结下上面两种方式
- 对象方式的点查询进行跨表查询
简单来说,凡是试图进行跨表查询,总得要两个表有某种关系吧,才能进行跨表查询,在有外键关系的两个表里,一般是在一对多的多的那一方里,通过某个对象,拿到与其有外键关系的别的表的对象,在通过这个对象进行查询(其实就是通过拿到的对象---别的表的,进行在别的表的查询,就是点方式的跨表查询)
- 双下划线方式的跨表查询
这个一般都是通过从QuerySet类型的查询集出发,通过双下划线的方式查到别的表的属性,并且一般都是把双下划线作为某种属性的过滤条件或者是取出值的限定字段来使用的,比如可以使用在filter过滤中或者使用在values过滤中
比如 models.City.objects.filter(name = "石家庄").values("province__name")
或者也可以直接在filter的条件里面使用双下划线过滤
In [13]: models.City.objects.filter(province__name = u"河北")
Out[13]: <QuerySet [<City: 石家庄>, <City: 邢台>, <City: 张家口>]>
In [39]: models.City.objects.filter(province__name = "河北").values("province__name")
Out[39]: <QuerySet [{'province__name': u'\u6cb3\u5317'}, {'province__name': u'\u6cb3\u5317'}, {'province__name': u'\u6cb3\u5317'}]>
- XXX_set方式跨表查询
这个一般都用在一对多的一的那一方
查询这个一的那一方对应的多的集合
而且一般只有对象才可以使用XXX_set属性查找,不能从QuerySet类型进行XXX_set查找
In [46]: models.Province.objects.first().city_set.all()
Out[46]: <QuerySet [<City: 石家庄>, <City: 邢台>, <City: 张家口>]>