漫天飞雪

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
简介
       数据库中表与表之间的关系,举例详解一对多、一对一、多对多关系,及表关系一些进阶技巧。

1. 数据库中表与表之间的关系 
 
    • 一对多,models.ForeignKey(ColorDic)
    • 一对一,models.OneToOneField(OneModel)
    • 多对多,authors = models.ManyToManyField(Author)

应用场景:

    • 一对一:在某表中创建一行数据时,有一个单选的下拉框(下拉框中的内容被用过一次就消失了)。
      例如:与同事合作开发过程中两人都用到一某个表,这个表原有含10列数据,经过一段时间之后,发现10列无法满足自己的需求,需要为原来的表再添加5列数据,但是为了不影响同事的使用,可以使用一对一关系设计表,新的表中只需要包含要新加的这5列数据即可。

    • 一对多:当一张表中创建一行数据时,有一个单选的下拉框(可以被重复选择)。
      例如:创建用户信息时候,用户信息中包含用户类型(普通用户、管理员、超级管理员),用户与用户类型就属于一对多关系。一个用户对应一种用户类型,但一种用户类型可以包含多个用户。

    • 多对多:在某表中创建一行数据时,有一个可以多选的下拉框。
      例如:创建用户信息,需要用户选择爱好,一个用户可以有多个爱好,一种爱好可以被多个用户选择。

2. 举例详解
    例如,一本书由一家出版社出版,一家出版社可以出版很多书。一本书由多个作者合写,一个作者可以写很多书。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models
 
class Author(models.Model):
    name = models.CharField(max_length=30)
 
class Publisher(models.Model):
    name = models.CharField(max_length=50)
 
class Book(models.Model):
    name = models.CharField(max_length=50)
    #一本书由一家出版社发布,一个出版社发布多本书。属于一对多关系,用ForeignKey()
    pub = models.ForeignKey(Publisher)
    #一本书可以由多个作者合写,一个作者可以写多本书,属于多对多关系,用ManyToManyField
    authors = models.ManyToMany(Author)
    生成结果:
    
 一共生成了4张表:
 
web_author(作者表)


 web_publisher(出版社表)

 
web_book_authors(记录book与author多对多关系表。多对多关系要借助第三张表建立关系)

 
web_book(book表,其中pub_id体现书与出版社之间的一对多关系)

3. 表关系进阶

1. 关联尚未定义的Model
 
class Book(models.Model):
    name = models.CharField(max_length = 50)
    #如果Publisher与Author在Book后面定义,需要使用model 的名称,而不是使用 model 对象本身
    pub = models.ForeignKey('Publisher')
    authors = models.ManyToManyField('Author')
    
class Publisher(models.Model):
    name = models.CharField(max_length = 50)
 
class Author(models.Model):
    name = models.CharField(max_length = 30)

2. Model关联自身
1) Model可以与自身做多对一关系
 
class People(models.Model):
    name = models.CharField(max_length = 30)
    leader = models.ForeignKey('self', blank=True, null=True)
 
        说明:一个领导有多个下属,一个下属对应一个直接领导,同时领导也是领导的下属。就属于多对一关系,且需要与自身做多对一关系。且注            意,设计这表时要设置blank=True和null=True.
       
       2) Model可以与自身做多对多关系
        
        class Person(models.Model):
            friends = models.ManyToManyField('self')
        
                说明:1. 你是我的朋友,我可以有多个朋友,我也是你的朋友,你也可以有多个朋友,这就属于朋友间的多对多关系。
                           2. 会生成两张表,一张person表,只含有id和name。一张person_friends表含有id,from_person_id,to_person_id
                            person_friends:
                               
        

        3. OneToOneField
                class OneToOneField(othermodel[, parent_link=False, **options])
 
       用来定义一对一关系。笼统地讲,它与声明了 unique=True 的 ForeignKey 非常相似,不同的是使用反向关联的时候,得到的不是一个对象列表,而是一个单独的对象。
       在某个 model 扩展自另一个 model 时,这个字段是非常有用的;例如: 多表继承 (Multi-tableinheritance) 就是通过在子 model 中添加一个指向父 model 的一对一关联而实现的。
       必须给该字段一个参数:被关联的 model 类。工作方式和 ForeignKey 一样,连递归关联 (recursive) 和 延后关联 (lazy) 都一样。 此外,OneToOneField 接受 ForeignKey 可接受的参数,只有一个参数是 OnetoOneField 专有的:OneToOneField.parent_link,如果为 True,并且作用于继承自某个父 model 的子 model 上(这里不能是延后继承,父 model 必须真实存在 ),那么该字段就会变成指向父类实例的引用(或者叫链接),而不是象其他OneToOneField 那样用于扩展父类并继承父类属性。
 
from django.db import models, transaction, IntegrityError
 
class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)
    def __unicode__(self):
        return u"%s the place" % self.name
 
class Restaurant(models.Model):
    place = models.OneToOneField(Place, primary_key=True)
    serves_hot_dogs = models.BooleanField()
    serves_pizza = models.BooleanField()
    def __unicode__(self):
        return u"%s the restaurant" % self.place.name
 
class Waiter(models.Model):
    restaurant = models.ForeignKey(Restaurant)
    name = models.CharField(max_length=50)
    def __unicode__(self):
        return u"%s the waiter at %s" % (self.name, self.restaurant)
 
            使用反向关联的时候,得到的不是一个对象列表,而是一个单独的对象:
         >>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
>>> p1.save()
>>> r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
>>> r.save()
>>> p1.restaurant
<Restaurant: Demon Dogs the restaurant>
>>> Place.objects.get(restaurant__place__name__startswith="Demon")
<Place: Demon Dogs the place>
>>> Waiter.objects.filter(restaurant__place__name__startswith="Demon")

 

一.创建模型

实例:

作者表:
    拥有字段:姓名(name),性别(sex),该表与书籍表之间为多对多的关系
作者详情表:
    拥有字段:地址(addr),手机号(phone),该表与作者表之间为一对一的关系
出版社表:
    拥有字段:名字(name),地址(addr),该表与书籍表之间为一对多的关系
书籍表:
    拥有字段:书名(name),单价(price)
分析:一本书可以拥有多个作者,一个作者可以写多本书,所有作者与书籍之间为多对多的关联关系(Many-To-Many);一本书只有一个出版社,但是一个出版社可以出版多种书籍,所以出版社和书籍之间是一对多的关联关系(One-To-Many);作者与作者详情就是一对一关联关系(One-To-One)

所以在Models.py下创建模型如下

class Publish(models.Model):
    name = models.CharField(max_length=255)
    addr = models.CharField(max_length=255)

    def __str__(self):
        return self.name


class Author(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=255)
    sex = models.IntegerField()
    authordetail = models.OneToOneField(to='AuthorDetail')

    def __str__(self):
        return self.name


class AuthorDetail(models.Model):
    id = models.AutoField(primary_key=True)
    addr = models.CharField(max_length=255)
    phone = models.CharField(max_length=255)


class Book(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=255)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    # 外键,关联关系写在一对多中多的那一方
    publish = models.ForeignKey(to=Publish)
    authors = models.ManyToManyField(to=Author)

    def __str__(self):
        return self.name

注意事项:

1.id字段不写的话会自动添加
2.对于外键字段,Django会在字段名上添加"_id"来创建数据库中的列名
3.外键字段ForeignKey有一个null=True的设置,你可以赋给它空值None

二.添加表记录

一对一:

# 方式一:
detail_obj = models.AuthorDetail.objects.filter(id=2).first()
models.Author.objects.create(name='Yven',sex=1,authordetail=detail_obj)

# 方式二:
models.Author.objects.create(name='hwt',sex=2,authordetail_id=1)

一对多:

# 方式一:
publish_obj = models.Publish.objects.filter(id=2).first()
models.Book.objects.create(name='水浒传',price=24.5,publish=publish_obj)

# 方式二:
models.Book.objects.create(name='西游记',price=24.5,publish_id=2)

多对多:

# 获得书籍对象
book = models.Book.objects.create(name='水浒传',price=24.5,publish_id=3)
# 获得作者对象
yven = models.Author.objects.get(name='Yven')
hwt = models.Author.objects.get(name='hwt')

# 绑定多对多关系
# add中可以传递对象,可以传递id,也可以传递*列表如:(*[yven,hwt])
book.authors.add(yven,hwt)

#多对多关系其他常用API:
# 将某个特定的对象从被关联对象集合中去除,可以传递对象,可以传递id,也可以传递*列表如:(*[yven,hwt])
book.authors.remove()
# 清空被关联对象集合,无需传参
book.authors.clear()
# 先清空再设置,传递的参数必须是可迭代对象,一般为列表,列表内可以是对象,也可以是id
book.authors.set()

三.基于对象的跨表查询

一对一查询

正向查询(按字段:authordetail)

# 查询作者姓名为Yven的手机号
yven = models.Author.objects.get(name='Yven')
phone = yven.authordetail.phone

反向查询(按表名小写:author)

# 查询手机号为12221321的作者名
authordetail = models.Author.objects.filter(phont='12221321').first()
name = authordetail.author.name

一对多查询

正向查询(按字段:publish)

# 查询西游记的出版社名
book = models.Book.objects.filter(name='西游记').first()
name = book.publish.name

反向查询(按表名小写_set.all():book_set.all())

# 查询pk为2的出版社出版书籍的单价
publish = models.Publish.objects.filter(pk='2').first()
booklist = publish.book_set.all() #QuerySet对象
for obj in booklist:
    print(obj.price)

多对多查询

正向查询(按字段.all():publish.all())

# 查询水浒传的所有作者
book = models.Book.objects.get(name='水浒传')
author = book.authors.all()
for i in author:
    print(i.name)

反向查询(按表名小写_set.all():book_set.all())

# 查询Yven写的多少有书籍的单价
author = models.Author.objects.get(name='Yven')
book = author.book_set.all()
for i in book:
    print(i.price)

四.基于双下划线的跨表查询

Django还提供了一种直观而搞笑的方式在查询(lookups)种表示关联关系,它能自动确认SQL JOIN联系。要做跨关系查询,就使用两个下划线来连接模型(model)间关联字段的名称,知道最终链接到你想要的model为止。

一对一查询

正向查询(按字段):authordetail

# 查询Yven的地址
models.Author.objects.filter(name='Yven').values('authordetail__addr')

反向查询(按表名):author

# 查询Yven的地址
models.AuthorDetail.objects.filter(author__name='Yven').values('addr')

一对多查询

正向查询(按字段):publish

# 查询西游记的出版社名
models.Book.objects.filter(name='西游记').values('publish__name')

反向查询(按表名):book

# 查询西游记的出版社名
models.Publish.objects.filter(book__name='西游记').values('name')

多对多查询

正向查询(按字段):authors

# 查询水浒传的作者性别
models.Book.objects.filter(name='水浒传').values('authors__sex')

反向查询(按表名):book

# 查询水浒传的作者性别
models.Author.objects.filter(book__name='水浒传').values('sex')

五.聚合查询与分组查询

聚合查询:aggregate(*args,**kwargs)

# 需要导入包
from django.db.models import Avg
# 查询所有图书的平均价格
models.Book.objects.all().aggregate(Avg('price'))

aggregate()是QuerySet的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值,键的名称是按照字段和聚合函数的名称自动生成出来的,如果你想要为聚合值指定一个名称,可以向聚合子句提供一个名字。

models.Book.objects.all().aggregate(aaa=Avg('price'))
#{'aaa':3.14}

如果不止有一个聚合查询,可以在aggregate()括号内继续添加参数.

Django提供了以下聚合函数

1.expression
    引用模型字段的一个字符串,或者一个query expression
2.output_field
    用来表示返回值的model field,一个可选的参数
3.extra
    关键字参数可以给聚合函数生成的SQL提供额外的信息
4.Avg
    返回给定表达式的平均值,它必须是数值,除非指定不同的output_field
5.Count
    返回与expression相关的对象的个数,有一个可选的参数distinct,如果distinct=True,那么Count将只计算唯一的实例,默认值是False
6.Max
    返回给定字段的最大值
7.Min
    返回给定字段的最小值
8.Sum
    返回给定字段的总和

分组查询:annotate()

annotate()为调用QuerySet种每一个对象都生成一个独立的统计值,相当于数据库种的group by

统计每一本书作者个数

from django.db.models import Count
num=Book.objects.annotate(author_num=Count('authors')).values('name','author_num')

总结

values在annotate()之前,表示group by,之后,表示为取值
filter在annotate()之前,表示过滤,之后,表示having

六.F查询与Q查询

F查询

现在位置的操作种,都是将模型字段与常量进行比较,但是,如果想将模型的一个字段与同一个模型的另一个字段进行比较该怎么办?
所有Django为我们提供了F表达式来完成这类操作
导入包: from django.db.models import F

如:查询comment_num数量大于read_num的书籍

models.Book.objects.filter(comment_num__gt=F('read_num'))

还可以对F()对象进行加减乘除,取模等运算操作.

如:将read_num的数量乘以二

models.Book.objects.all().update(read_num=F('read_num')*2)

Q查询

filter()等方法种的关键字参数查询都是通过逗号来实现'AND'的,如果需要执行复杂的查询,就需要使用Q对象
导入包:from django.db.models import Q

可以使用"&"或者"|"或者"~"来组合Q对象,分别表示与,或,非逻辑

如:查询作者为yven或者hwt的书籍信息

models.Book.objects.filter(Q(authors__name='yven') | Q(authors__name='hwt'))

查询函数可以混合使用Q对象和关键字参数,所有提供给查询函数的参数都将"AND"在一起,但是,如果出现Q对象,它必须位于所有关键字参数的前面。

posted on 2018-12-27 21:37  漫天飞雪世情难却  阅读(126)  评论(0编辑  收藏  举报