Django框架—模型层之多表查询

一、多表模型创建

1.数据库中的表关系

在数据库中表与表之间的关系分:一对一,一对多,多对多

  • 一对一的表关系通过外键+唯一约束表示

  • 一对多的表关系通过外键约束来表示,一对多和多对一是相对而言的。

  • 多对多的表关系通过建立第三张表设置外键来表示

2.ORM中的类关系模型

在orm中,表与表之间的关系通过类名来建立,类名中定义属性关联另一个类。

  • 一对一的类关系通过OneToOneField()来建立

  • 一对多的类关系通过ForeignKey()来建立

  • 多对多的类关系通过ManyToManyField()来建立

orm中模型建立models.py文件

from django.db import models
# Create your models here.
class Author(models.Model):
    id = models.AutoField(primary_key=True)  # 可以省略
    name = models.CharField(max_length=32)
    age = models.IntegerField()
​
class Publish(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    # 一对一关系建立,本质就是foreignkey+unique,orm中会自动给这个字段拼接一个_id。
    authorDetail = models.OneToOneField(to="PublishDetail", to_field="id", on_delete=models.CASCADE)  # 设置联级更新,默认选项
class PublishDetail(models.Model):
    id = models.AutoField(primary_key=True)
    telephone = models.BigIntegerField()
    city = models.CharField(max_length=32)
​
class Book(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=5,decimal_places=2)
    # 多对一关系,外键关联出版社表
    publish = models.ForeignKey(to="Publish", to_field="id", on_delete=models.CASCADE)
    # 建立表之间的多对多关系
    authors=models.ManyToManyField(to="Author")  # mysql中需要我们手动建立第三张表,而orm中自动帮我们建立第三张表
orm中model.py文件

注意

  • 一对多表关系建立,外键字段建立在多的一方

  • 多对多表关系中,外键会默认关联表的主键id,第三张表创建方式有多种。

  • 创建多对多的authors名字,并不会在Book表中生成对应字段,而authors是用于多表查询的。

创建第三张表的方式

1.手动创建

class Book(models.Model):
    title = models.CharField(max_length=32)
​
class Author(models.Model):
    name = models.CharField(max_length=32)
​
# 自己创建第三张表,分别通过外键关联书和作者
class Author2Book(models.Model):
    author = models.ForeignKey(to="Author")
    book = models.ForeignKey(to="Book")
​
    class Meta:  # 元类,
        unique_together = ("author", "book")  # 这是联合为一

2.设置manytomany自动创建

class Book(models.Model):
    title = models.CharField(max_length=32)
​
# 通过ORM自带的ManyToManyField自动创建第三张表
class Author(models.Model):
    name = models.CharField(max_length=32)
    books = models.ManyToManyField(to="Book", related_name="authors")

3.设置ManyToManyField并指定自行创建的表

class Book(models.Model):
    title = models.CharField(max_length=32)
​
# 自己创建第三张表,并通过ManyToManyField指定关联
class Author(models.Model):
    name = models.CharField(max_length=32)
    books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book"))
    # through_fields接受一个2元组('field1','field2'):
    # 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名。
class Author2Book(models.Model):
    author = models.ForeignKey(to="Author")
    book = models.ForeignKey(to="Book")
​
    class Meta:
        unique_together = ("author", "book")

如果我们需要在第三张关系表中存储额外的字段时,需要使用第三种方式。

创建关系的参数(了解)

1.创建一对一关系字段的参数

  • to:设置要关联的表。

  • to_field:设置要关联的字段。

  • on_delete:同ForeignKey字段

2.创建一对多字段的参数

  • to:设置要关联的表

  • to_field:设置要关联的表的字段

  • related_name: 反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。

  • related_query_name:反向查询操作时,使用的连接前缀,用于替换表名。

  • on_delete:当删除关联表中的数据时,当前表与其关联的行的行为。

3.创建多对多字段的参数

  • to:设置要关联的表

  • related_name:同ForeignKey字段。

  • related_query_name:同ForeignKey字段。

  • through:在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过 through来指定第三张表的表名。

  • through_fields:设置关联的字段。

  • db_table:默认创建第三张表时,数据库中表的名称。

4.创建表的一些元信息设置

元信息:ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下

  • db_table:ORM在数据库中的表名默认是 app_类名,可以通过db_table可以重写表名。db_table = 'book_model'

  • index_together:联合索引。

  • unique_together:联合唯一索引。

  • ordering:指定默认按什么字段排序。

建立好的表结构

二、多表增删改

1.一对一的增删改

一对一增加

通过字段中直接添加一对一关系表中该条记录的对象,或者对象对应的id值到字段。

# 1.一对一增加记录
models.PublishDetail.objects.create(
    telephone=17777,
    addr="江苏"
)  # 先在publishDetail表中增加一条记录
models.Publish.objects.create(
    name="人民出版社",
    publishDetail=models.PublishDetail.objects.get(id=4),  # 直接添加一个出版社对象
    # publishDetail_id=4  # 第二种写法,直接添加id值,注意两种方式的赋值对象
)  # 在publish表中新增记录

一对一修改和删除

# 2.一对一更新和删除记录
models.Publish.objects.filter(id=4).update(name="中国人民出版社")
models.Publish.objects.filter(id=4).delete()

2.一对多的增删改

一对多的增加

# 1.一对多增加
publish_obj = models.Publish.objects.filter(id=2)[0]
models.Book.objects.create(
    title="摆渡人",
    price=130,
    publish=publish_obj,  # 第一种方式传对象
    # publish_id=2  # 第二种方式传id(推荐)
)

一对多的更新和删除

# 一对多的更新和删除
models.Book.objects.filter(id=13).update(price=150)
models.Book.objects.filter(id=13).delete()

3.多对多的增删改

多对多的增加

多对多的添加是基于对象来操作,还需要考虑是在哪一侧添加;

  • 在被关联的表中,添加数据和单表添加数据一样,model.表名.objects.create(字段值...)。
  • 在有关联字段的表中,也就是关联别人的表,需要先添加一个对象,通过对象.关联字段名.add()方法来添加多对多的关联字段。
# 创建一本书对象
book_obj = models.Book.objects.create(
        title="摆渡人",
        price=120,
        publish_id=2
    )
​
# 或者找到一个书本对象
book_obj = models.Book.objects.get(publish_id=2)  
​
# 找到两个作者对象(这里也可以创建新的,演示添加多个作者)
author1 = models.Author.objects.get(id=1)
author2 = models.Author.objects.get(id=2)
​
# 注意:多对多添加需要用当时创建关联的属性名authors来操作
# 通过书本对象在第三张表中添加作者对象或author_id
book_obj.authors.add(author1,author2)  # 方式1
book_obj.authors.add(1,2)  # 方式2
book_obj.authors.add(*author_list)  # 方式3

多对多的更新和删除

多对多的更新和删除也是基于对象操作的,先找到一个对象,在通过对象操作在第三张表中的数据;

注意:

  • 被关联的表中的行记录如果被关联,那么无法删除,修改也只能修改非关联字段的字段
  • 关联的表中,数据的修改,通过对象.关联字段名.remove()/clear()/set()方法来删除。
book_obj = models.Book.objects.get(id=2)  # 找到一个书本对象
# 通过book_obj对象操作对应的第三张表中数据
book_obj.authors.remove(1)  # 删除book_obj对象关联数据中author_id为1的数据,这里是book_id=2的
book_obj.authors.remove(1, 3)  # 删除多条(一)
book_obj.authors.remove(*[1,3])  # 删除多条(二)
book_obj.authors.clear()  # 清除book_obj对象关联的所有数据,这里是book_id=2的
# set方法更新数据
book_obj.authors.set(['3'])  # 清空所有的book_id为2的第三张表里面的所有记录,再重新给book_id为2书籍添加对应的author_id为3的记录

三.多表的查询(重难点)

1.基于对象的跨表查询

跨表查询是分组查询的基础,F和Q查询是最简单的。

查询思路是先找到某个对象,通过对象来进行正向/反向查询,注意都是基于对象来查询。

一对多的跨表查询

正向查询

# 查询主键为1的书籍对应的出版社的名字
book_obj = models.Book.objects.filter(id=1).first()
name = book_obj.publish.name
print(name) # 古典文学出版社

反向查询

# 查询古诗词出版社出版的所有书籍
publish_obj = models.Publish.objects.get(name="古诗词出版社")
​
book_list = publish_obj.book_set.all()  # 查询所有古诗词出版社出版的书籍对象,返回一个queryset对象
print(book_list)  # <QuerySet [<Book: 蝶恋花>, <Book: 如梦令>, <Book: 一剪梅>]>
​
book1 = book_list.filter(title="如梦令")  # 对结果直接只用filter筛选
print(book1)  # <QuerySet [<Book: 如梦令>]>

一对一跨表查询

正向查询

# 查询书本id为1的出版社地址
publish_obj = models.Publish.objects.get(id=1)
print(publish_obj.publishDetail.addr)  # 金陵

反向查询

# 查询出版社电话为18888对应的出版社名字
publish_obj1 = models.PublishDetail.objects.get(telephone=18888)
print(publish_obj1.publish.name)  # 外国文学出版社

多对多正向/反向

多对多基于对象查询,无论是正向还是反向,都有可能查询到多个结果,所以查询返回的对象是queryset对象,对其中的的对象可以再次筛选。

正向查询

# 查询红楼梦的作者是谁
book_obj = models.Book.objects.filter(title="红楼梦").first()
# 多对多查询可能是多个对象,所以用all取所有,获得queryset对象。
print(book_obj.authors.all().first().name)  # 曹雪芹

反向查询

# 李清照写了那些书
author_obj = models.Author.objects.filter(name="李清照")[0]
book_list = author_obj.book_set.all()
print(book_list)  # <QuerySet [<Book: 如梦令>, <Book: 一剪梅>]>

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

Django也提供了一种直观而高效的方式查询(lookups)中表示关联关系,能够自动确认SQL中的Join联系。

在orm中连表查询使用两个下划线来连接模型(model)间关联字段的名字,直到最终连接到你想要的model。

基于双下划线的查询记住一句话:正向查询按关联字段名,反向查询按表名小写用来告诉ORM引擎join哪张表。

  • 一对一、一对多、多对多都是一个写法。

  • 表写在前和写在后没有区别,本质是通过join来实现的。

一对多查询

  • 正向连表:关联的属性名__字段

  • 反向连表:关联类名小写__字段

1.查询"古典文学出版社出版"的所有书籍名字和价格

# 正向连表:按关联属性名__字段
result1 = models.Book.objects.filter(publish__name="古典文学出版社").values("title","price")
print(result1)  # <QuerySet [{'title': '红楼梦', 'price': Decimal('200.00')}, {'title': '雨霖霖', 'price': Decimal('80.00')}]>
# 反向连表:按类名小写__字段
result2 = models.Publish.objects.filter(name="古典文学出版社").values("book__title","book__price")
print(result2)  # <QuerySet [{'book__title': '红楼梦', 'book__price': Decimal('200.00')}, {'book__title': '雨霖霖', 'book__price': Decimal('80.00')}]>

这个例子中,对book表来说,出版社名字需要正向跨表,所以是 publish__name,对于publish表来说,书名和书的价格都需要反向跨表查询,所以是 book__title, book_price。

一对一查询

  • 正向连表:关联的属性名__字段

  • 反向连表:关联类名小写__字段

2.查询"古诗词出版社"的电话号码

# 正向连表:按关联属性名__字段
result1 = models.Publish.objects.filter(name="古诗词出版社").values("publishDetail__telephone")
print(result1)  # <QuerySet [{'publishDetail__telephone': 15555}]>
# 反向:按类名小写__字段
result2 = models.PublishDetail.objects.filter(publish__name="古诗词出版社").values("telephone")
    print(result2)  # <QuerySet [{'telephone': 15555}]>

多对多查询

  • 正向连表:关联的属性名__字段

  • 反向连表:关联类名小写__字段

3.查询作者"柳永"出版的所有书的名字

# 正向连表:按关联属性名__字段
result1 = models.Book.objects.filter(authors__name="柳永").values("title")
print(result1)  # <QuerySet [{'title': '蝶恋花'}, {'title': '雨霖霖'}]>
# 反向:按类名小写__字段
result2 = models.Author.objects.filter(name="柳永").values("book__title")
print(result2)  # <QuerySet [{'book__title': '蝶恋花'}, {'book__title': '雨霖霖'}]>

related_name别名

反向查询时,如果初始化模型时的关联字段中定义了related_name ,反向查询时需要使用related_name替换类名,不能再使用类名小写,例如

publish = ForeignKey(Blog, related_name='bookList')  # 定义类模型时,关联字段中定义related_name属性

例子:查询"古典文学出版社"出版过的所有书籍的名字与价格(一对多)

result=models.Publish.objects.filter(name="古典出版社").values("bookList__title","bookList__price")  # 注意反向连表时用的是related_name,而不是类名小写(publish)
# 结果:<QuerySet [{'book__title': '红楼梦', 'book__price': Decimal('200.00')}, {'book__title': '雨霖霖', 'book__price': Decimal('80.00')}]>

3.聚合查询

聚合查询效果等同于原生sql语句中的聚合函数。使用需要先导入聚合函数

语法结构:由queryset类型调用

from django.db.models import Avg,Max,Min,Count
queryset对象.aggregate(*args, **kwargs)

注意点:

  • aggregate()一般是QuerySet的一个终止语句,返回的是含键值对的字典。

  • 返回字典的键是字段和聚合函数名自动生成,需要为聚合的值指定一个名称。

1.查询"古典文学出版社"出版的所有书的平均价格

result = models.Book.objects.filter(publish__name="古典文学出版社").aggregate(avg_price=Avg("price"))
print(result)  # {'avg_price': 140.0} 显示的是命名后的字段

2.查询多个聚合函数结果

result = models.Book.objects.aggregate(max_price=Max("price"),min_price=Max("price"),num=Count(1))  # 查询所有书籍的最大价格、最低价格、以及数量
print(result)  # {'max_price': Decimal('200.00'), 'min_price': Decimal('200.00'), 'num': 6}

4.分组查询

orm中的分组查询对应原生sql中的group by,依据字段对数据分组。

分组依据的字段可以是多个,表示联合分组,也就是两个字段同时相同才算一组。

语法结构:由QuerySet对象调用

QuerySet.values("分组依据字段1",...).annotate("分组后需要统计的数据")

注意:

  • values()函数在这里表示分组依据的字段,可以多个。

  • annotate里面一般写聚合函数,如果不写,那么单纯分组没有任何意义。

  • 聚合函数使用必须有别名

单表分组查询

1.查询每个出版社对应的id和出版书籍的数量

  • 原生sql语句写法

select publish_id,count(1) from book group by publish_id
  • orm中查询语句,返回QuerySet类型

result = models.Book.objects.values("publish_id").annotate(num=Count(1))
print(result)  # <QuerySet [{'publish_id': 1, 'num': 2}, {'publish_id': 2, 'num': 3}, {'publish_id': 3, 'num': 1}]>

连表分组查询

2.查询每个出版社名字以及所有出版书籍的平均价格

  • 原生sql语句

select name,avg(price) from app01_book inner join app01_publish on app01_book.publish_id = app01_publish.id group by app01_book.publish_id;
--使用连表后分组查询相应字段
  • orm中查询语句

result = models.Book.objects.values("publish__name").annotate(avg_price=Avg("price"))
print(result)  # <QuerySet [{'publish__name': '古典文学出版社', 'avg_price': 140.0}, {'publish__name': '古诗词出版社', 'avg_price': 126.666667}, {'publish__name': '外国文学出版社', 'avg_price': 180.0}]>

显示分组和聚合函数外的其他字段

1.显示其他指定字段:对分组后结果.values(指定字段名1,...)

result = models.Book.objects.values("publish__name").annotate(max_price=Max("price")).values("publish__name","publish__id")
print(result)  # <QuerySet [{'publish__name': '古典文学出版社', 'publish__id': 1}, {'publish__name': '古诗词出版社', 'publish__id': 2}, {'publish__name': '外国文学出版社', 'publish__id': 3}]>

2.显示所有字段:对分组后的结果.values()

result = models.Book.objects.values("publish__name").annotate(max_price=Max("price")).values()
print(result)  # <QuerySet [{'id': 1, 'title': '红楼梦', 'price': Decimal('200.00'), 'publish_id': 1, 'max_price': Decimal('200.00')}, {'id': 4, 'title': '雨霖霖', 'price': Decimal('80.00'), 'publish_id': 1, 'max_price': Decimal('80.00')}, {'id': 2, 'title': '蝶恋花', 'price': Decimal('120.00'), 'publish_id': 2, 'max_price': Decimal('120.00')}, {'id': 3, 'title': '如梦令', 'price': Decimal('100.00'), 'publish_id': 2, 'max_price': Decimal('100.00')}, {'id': 6, 'title': '一剪梅', 'price': Decimal('160.00'), 'publish_id': 2, 'max_price': Decimal('160.00')}, {'id': 5, 'title': '飘', 'price': Decimal('180.00'), 'publish_id': 3, 'max_price': Decimal('180.00')}]>

orm中实现group_concat聚合函数

1.创建Concat类

from django.db.models import Aggregate, CharField

class Concat(Aggregate):
    """ORM用来分组显示其他字段 相当于group_concat"""
    function = 'GROUP_CONCAT'
    template = '%(function)s(%(distinct)s%(expressions)s)'

    def __init__(self, expression, distinct=False, **extra):
        super(Concat, self).__init__(
            expression,
            distinct='DISTINCT ' if distinct else '',
            output_field=CharField(),
            **extra
        )    

2、模型管理器查询

result = models.Book.objects.values("authors__id").annotate(book_name=Concat("title"))
print(result)  # <QuerySet [{'authors__id': 1, 'book_name': '红楼梦'}, {'authors__id': 2, 'book_name': '如梦令,一剪梅'}, {'authors__id': 3, 'book_name': '雨霖霖,蝶恋花'}, {'authors__id': 4, 'book_name': '飘'}]>

5.F/Q查询

F/Q用来补充查询中一些细节,使用方法如下:

from django.db.models import F,Q

F查询

F查询一般用于同一行数据中的两列数据(字段)的比较和筛选,然后对筛选的结果进行相应处理。

  • F查询语句

1.查找好评数大于点赞数量的所有书籍

前提在书籍中增加两个字段,comments和likes字段,添加相应数据。

class(models.Model):
    comments = models.IntegerField(default=20)
    likes = models.IntegerField(default=20)
    # book类中增加两个字段,默认为20,然后对表中自行进行修改数据

查询语句写法

result = models.Book.objects.filter(comments__gt=F('likes'))
print(result)
  • F查询更新语句

2.将"古典文学出版社"的所有书籍的价格增加10元

models.Book.objects.filter(publish__name="古典文学出版社").update(price=F("price")+10)
# 数据表中古典文学出版社也就是publish_id为1的书籍价格全部增加了10

Q查询

filter() 等方法中的关键字参数查询之间逻辑是"and",也就是与运算。 如果你需要执行更复杂的查询(例如or语句),你可以使用Q 对象。

  • Q对象的获取:将一个条件使用Q()包裹,就可以得到一个条件的Q对象

Q(title__startswith='红楼')
  • Q对象支持&(与)、|(或)、~(非)操作符组合使用。

1.查询作者是"李清照"或者是"柳永"的所有书籍

book_list = models.Book.objects.filter(Q(authors__name="李清照")|Q(authors__name="柳永"))
print(book_list)  # <QuerySet [<Book: 如梦令>, <Book: 一剪梅>, <Book: 蝶恋花>, <Book: 雨霖霖>]>
  • Q对象条件可以与正常的条件混合使用,且Q对象的查询条件必须写在正常条件之前

2.查询作者是"李清照"或者"曹雪芹"且书籍价格大于120的书籍

book_list = models.Book.objects.filter(Q(authors__name="李清照")|Q(authors__name="曹雪芹"),price__gt=120)
print(book_list)  # <QuerySet [<Book: 红楼梦>, <Book: 一剪梅>]>

四、orm执行原生sql语句

orm模型查询能够帮我们方便的查询大部分数据的场景,但是仍然有很多场景orm模型查询api无法满足,或者效率满足不了使用,这个时候我们可以执行原生的sql语句。

Django中提供了两种方式执行原生sql语句:

  1. 使用raw()方法,执行原生sql语句

  2. 避开模型层,执行之定义的sql语句

1.raw方法执行原生sql

raw()管理器方法用于执行原生的sql语句,并返回模型的实例;

  • raw方法的执行对象是任意一张表的objects管理器

  • raw()方法语句查询必须带主键

返回对象是一个django.db.models.query.RawQuerySet 实例,可以像QuerySet一样迭代获取对象实例。

ret = models.Book.objects.raw("select id,title from app01_book")  # 表名Book可以是任意表,没有影响
print(ret)  # <RawQuerySet: select title from app01_book>
for obj in ret:  # 遍历获取实例对象
    print(obj.id, obj.title)  # 通过对象属性取值
# 1 红楼梦
# 2 蝶恋花
# 3 如梦令
# 4 雨霖霖
# 5 飘
# 6 一剪梅

可以通过translations参数,指定把一个查询的字段名替换成字典中字段名对应的值,通过值来取得该字段的数据。

d = {'price':'haha'}  # 定义一个字典,使用haha替换表中字段price
ret = models.Book.objects.raw("select * from app01_book",translations=d)  # 将字段price翻译成haha
print(ret)  # <RawQuerySet: select title from app01_book>
for obj in ret:
    print(obj.id, obj.title, obj.haha)  # 通过haha取到price字段的数值
# 1 红楼梦 210.00
# 2 蝶恋花 120.00
# 3 如梦令 100.00
# 4 雨霖霖 90.00
# 5 飘 180.00
# 6 一剪梅 160.00

raw()还可以接受参数,避免自己拼接字符串,出现sql注入。

d = {'price': 'haha'}  # 定义一个字典,使用haha替换表中字段price
ret = models.Book.objects.raw("select * from app01_book where price>%s", translations=d, params=[120, ])  # 使用params参数传递参数给sql语句
print(ret)  # <RawQuerySet: select title from app01_book>
for obj in ret:
    print(obj.id, obj.title, obj.haha)  # 通过haha取到price字段的数值
# 1 红楼梦 210.00
# 5 飘 180.00
# 6 一剪梅 160.00

2.直接执行自定义SQL语句

有时候raw()方法并不十分好用,很多情况下我们不需要将查询结果映射成模型,或者我们需要执行DELETE、 INSERT以及UPDATE操作。

在这些情况下,我们可以直接访问数据库,完全避开模型层。

Django中直接提供了pymysql和数据库连接的接口,就类似pymysql中connect方法得到的链接管道,然后使用这个连接操作数据库。

from django.db import connection, connections
cursor = connection.cursor()  # 或者 cursor = connections["default"].cursor()  获取与数据库的连接管道
cursor.execute("select id,title from app01_book where price > %s",[120,])
ret = cursor.fetchall()
print(ret)  # ((1, '红楼梦'), (5, '飘'), (6, '一剪梅'))

五、python脚本调用Django环境

外部文件(脚本)如果想要使用django项目的环境来运行,比如使用项目中的models文件,必须先加载django项目的环境配置;这里的外部文件也必须在django项目下。

加载django环境只需要在文件开头配置如下:

import os
​
if __name__ == '__main__':
    import django
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BMS.settings")  # 设置Django环境
    django.setup()  # 启动Django环境
from app01 import models  #引入也要写在上面三句之后
    
    books = models.Book.objects.all()
    print(books)

 

posted @ 2019-05-27 20:19  ryxiong728  阅读(311)  评论(0编辑  收藏  举报