Django对数据库的是通过ORM实现。什么叫做ORM呢?简单来说,我们通过sql语句查询一张表的所有数据的语句如下  select * from test。而Django中用到的表都是在models.py文件里定义。所以我们要查查询test表里的数据可以通过 

test_obj = models.test.objects.all()来获取test表里所有数据的对象。再通过

test_obj.values()方法将每一行数据的里的各个字段以key和value的字典形式读取出来。这就叫ORM操作。


既然涉及到数据库的操作,就必然会用到连表操作。ORM中将连表操作简单的划分为一对多 和多对多 这两种操作。


什么叫一对多呢?就是抽象的说法就是数据库的外键操作就是典型的一对多。数据库里的某一个字段可以对应另外一张表里的多个值。简单的举例的说法就是,一个管理员可以管理Host表里的多个主机。但是每一个主机只能对应一个管理员。这种情况就叫做一对多

1) 正向操作

首先我们先创建2张表。一张是主机表里面存放主机ip和端口信息。一张是管理员表,里面存放管理员姓名和管理的主机信息。那么我们的models.py里的代码如下:

# coding:utf-8
from __future__ import unicode_literals
from django.db import models
# Create your models here.
class host(models.Model):
    ip = models.CharField(max_length=32)
    port = models.IntegerField()
class hostadmin(models.Model):
    username = models.CharField(max_length=32)
    host = models.ForeignKey('host')

创建好表之后,我们给表里填充几行测试数据。

wKiom1cV2liDhldpAAAVMjSj31Y531.png


wKiom1cV2vbxooPVAAAZZYzCflM056.png


从有外键的表里查询关联表里的数据,这就叫正向操作。从我们的例子里可以看到外键定义在Hostadmin表里,那么我们从Hostadmin表里查询用户tom管理的所有的主机ip,这个需求就是正向查询。我们在views.py里的正向查询代码如下:

# coding:utf-8
from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def onetomany(request):
    # 通过filter方法匹配条件,将返回的对象存入ret_obj变量中
    ret_obj = models.hostadmin.objects.filter(username='tom')
    # 因为ret_obj是一个对象,这个对象是由数据库里的多行数据组成
    # 所以每循环一次item,这个item就代表一行包含所有字段的数据库数据
    for item in ret_obj:
        # ORM中跨表获取数据的操作用'.'来连接
        # item.host.ip表示通过HostAdmin表的host外键字段的值去获取对应的Host表里的ip字段
        # 注意数据库的表结构host外键字段被Django自动写成了host_id 
        # 但是使用ORM跨表操作的时候不可以使用host_id,还是应该使用models里定义的host这个字段名
        print item.host.ip
    return HttpResponse('ok')

运行结果,可以正确查询到tom名下所有的主机ip

wKioL1cV5RDzkPoJAAADKlO3tqk238.png


再查询对ip为1.1.1.1的主机有管理权限的所有管理员名称,代码如下

# coding:utf-8
from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def onetomany(request):
    # 查询数据的时候通过'__'双下划线来进行跨表查询操作
    ret_obj = models.hostadmin.objects.filter(host__ip='1.1.1.1')
    for item in ret_obj:
        print item.username
    return HttpResponse('ok')

运行结果如下

wKioL1cV803xknrEAAAOxn7EF6I360.png

以上的两种查询,我们都是通过对hostadmin表的操作实现的查询功能。至此正向查询就介绍完了。


2)反向操作

  还是使用刚才的两张表,反向操作顾名思义就是通过不存在的外键的表反过来查询包含外键的表内数据。按照我们的例子就是通过Host表里的字段反过来查询Hostadmin表里的数据。

wKiom1cV2liDhldpAAAVMjSj31Y531.png


wKiom1cV2vbxooPVAAAZZYzCflM056.png


首先我们知道Host表与HostAdmin表是通过HostAdmin里面的host外键来建立联系的,做正向查询的时候通过host这个外键就可以查询到Host表里的数据。其实在这两个表创建联系的时候,Django在Host表里

也创建了一个隐藏的hostadmin字段来与HostAdmin表进行关联。那么如果我们要在Host表里查看tom用户所对应的所有ip的话,就可以利用Host表里的隐藏字段来关联用户名称。代码如下:

# coding:utf-8
from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def onetomany(request):
    # 利用Host表里隐藏的hostadmin字段关联查询HostAdmin表里的用户名称
    # 这种方式和正向查询很像
    ret_obj = models.host.objects.filter(hostadmin__username='tom')
    # 遍历查询的到的所有行
    for line in ret_obj:
        # 打印每一行的ip字段
        print line.ip
    return HttpResponse('ok')

运行结果,一样可以查出来对应的ip

wKioL1cV5RDzkPoJAAADKlO3tqk238.png


如果我们希望通过Host表里的的ip为1.1.1.1的字段,反向查找到对应的管理员的名称。这个和正向查找的区别就比较大了。有以下2点需要注意

1、在Host表里必须通过get()方法精确的指定一行数据(不使用get()方法,就不能调用hostadmin_set反向获取数据)

2、获取的时候通过hostadmin_set.all()获取符合条件的所有HostAdmin表里的数据行的集合

功能实现的代码如下:

# coding:utf-8
from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def onetomany(request):
    # get()方法是只获取一行数据,如果获取不到就报错
    # 所以使用get方法的时候必须要确保可获取的条件
    host_obj = models.host.objects.get(ip='1.1.1.1')
    # 反向跨表通过hostadmin_set.all()才能获得关联表的所有数据行对象
    admin_obj = host_obj.hostadmin_set.all()
    # 打印获取到的对象
    print admin_obj
    # 遍历对象
    for line in admin_obj:
        #打印对象里的字段
        print line.username
    return HttpResponse('ok')

wKioL1cWJtzBBH0AAAAeT8wv_tE826.png

至此反向查询操作也介绍完了。



一对多的优化(select_related()方法)

我们还是以第一个正向查询作为例子讲解,代码如何下

# coding:utf-8
from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def onetomany(request):
    # 在HostAdmin表中查找所有符合条件的数据行
    ret_obj = models.hostadmin.objects.filter(username='tom')
    # 打印原始的sql语句
    print ret_obj.query
    for item in ret_obj:
        print item.host.ip
    return HttpResponse('ok')

获得原始sql代码如下:

wKioL1cWLYnjf-qNAAAcSJaoK7c824.png上面这段代码的执行流程应该是这样子:

查找HostAdmin表里所有username='tom'的数据行

-->查询出来之后获取这些数据行里的host_id的值

-->根据host_id的值到Host表里找到对应的ip

这个流程要要读取两次数据库才能获得Host和HostAdmin里的数据


下面我们优化一下,在models.hostadmin.objects.filter(username='tom')后面添加select_related()方法。

# coding:utf-8
from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def onetomany(request):
    # 使用select_related()方法优化查询
    ret_obj = models.hostadmin.objects.filter(username='tom').select_related()
    # 打印原始的sql语句
    print ret_obj.query
    for item in ret_obj:
        print item.host.ip
    return HttpResponse('ok')

再来看看原始的SQL语句有什么不同

wKiom1cWL7-AfT6NAAA4fivCaEM739.png这次查询的时候直接通过join on语句把HostAdmin表里查询结果对应的Host表里的数据也给一并查询出来了。这样后面的item.ip需要用到Host表里数据的时候直接就可以在内存里获取到需要的数据,减少了一次对数据库的访问。

要注意:select_related()方法只能给一对多这种外键访问方式提供优化。多对多的操作没有作用。



一对多的简单总结

1、查询数据  也就是通过models.xxx.objects.filter()里填写查询条件的时候。这个时候获取的结果是一组数据行对象,不是具体的某个数据。跨表查询用到 对象名称__字段名(双下划线)

2、获取数据  也是具体到要获取某个字段的数据的时候。跨表操作通过'.'来连接各个表

3、反向查找的时候,查找的的表里会创建一个隐藏掉字段,这个字段名就是与创建外键的表同名

4、反向获取数据的时候,通过xxx_set.all()才能获取到   xxx所有被匹配到的对象

5、尽量用正向操作,反向的看着就麻烦。


双下划线的常用操作

# 增
    #
    # models.Tb1.objects.create(c1='xx', c2='oo')  增加一条数据,可以接受字典类型数据 **kwargs
    # obj = models.Tb1(c1='xx', c2='oo')
    # obj.save()
    # 查
    #
    # models.Tb1.objects.get(id=123)         # 获取单条数据,不存在则报错(不建议)
    # models.Tb1.objects.all()               # 获取全部
    # models.Tb1.objects.filter(name='seven') # 获取指定条件的数据
    # 删
    #
    # models.Tb1.objects.filter(name='seven').delete() # 删除指定条件的数据
    # 改
    # models.Tb1.objects.filter(name='seven').update(gender='0')  # 将指定条件的数据更新,均支持 **kwargs
    
    # obj = models.Tb1.objects.get(id=1)    # 修改单条数据
    # obj.c1 = '111'
    # obj.save()                                                 
  
  
    # 获取个数
    #
    # models.Tb1.objects.filter(name='seven').count()
    # 大于,小于
    #
    # models.Tb1.objects.filter(id__gt=1)              # 获取id大于1的值
    # models.Tb1.objects.filter(id__lt=10)             # 获取id小于10的值
    # models.Tb1.objects.filter(id__lt=10, id__gt=1)   # 获取id大于1 且 小于10的值
    # in
    #
    # models.Tb1.objects.filter(id__in=[11, 22, 33])   # 获取id等于11、22、33的数据
    # models.Tb1.objects.exclude(id__in=[11, 22, 33])  # not in
    # contains
    #
    # models.Tb1.objects.filter(name__contains="ven")
    # models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感
    # models.Tb1.objects.exclude(name__icontains="ven")
    # range
    #
    # models.Tb1.objects.filter(id__range=[1, 2])   # 范围bettwen and
    # 其他类似
    #
    # startswith,istartswith, endswith, iendswith,
    # order by
    #
    # models.Tb1.objects.filter(name='seven').order_by('id')    # asc
    # models.Tb1.objects.filter(name='seven').order_by('-id')   # desc
    # limit 、offset
    #
    # models.Tb1.objects.all()[10:20]
    # group by
    from django.db.models import Count, Min, Max, Sum
    # models.Tb1.objects.filter(c1=1).values('id').annotate(c=Count('num'))
    # SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id"