一、外键自关联(一对多)
1、建表
# 评论表
class Comment(models.Model):
id = models.AutoField(primary_key=True)
content = models.CharField(max_length=255)
push_time = models.DateTimeField(auto_now_add=True)
# 父评论:自关联,一个评论可以没有父评论所以null=True
pcomment = models.ForeignKey(to='self', null=True)
def __str__(self):
return self.content
2、表数据
2、操作
1.找到id等于2的那个评论的所有子评论(跨表查询)
ret = Comment.objects.filter(pcomment__id=2)
print(ret)
2.方法2(直接查询)
ret = Comment.objects.filter(pcomment_id=2)
print(ret)
3.方法3
ret = Comment.objects.filter(id=2).values('comment__id')
print(ret)
二、多对多自关联
1、建表
class Person(models.Model):
name = models.CharField(max_length=12)
friends = models.ManyToManyField(to='self', symmetrical=False)
def __str__(self):
return self.name
2、注意
在多对多的自关联时,如果需要反向查找,则需要添加symmetrical这个字段,
指定内部是否创建反向操作的字段,默认为True,需要反向查找则改成False
3、操作
1.找到小明的朋友(正向找)
ret = Person.objects.filter(name='小明').values('friends__name')
print(ret)
2.朋友是小明的那个人(反向找)
ret = Person.objects.get(name='小明').person_set.all()
print(ret)
或者
ret = Person.objects.filter(name='小明').values('person__name')
print(ret)
补充:
create(关键字参数)
创建一个新的对象,保存对象,并将它添加到关联对象集之中,返回新创建的对象。
publisher.objects.create(id=1, name="...")
三、补充的几个方法
1、这几个方法适用于一对多和多对多的情况,一对多的时候,在"一"这张表才有下面这些方法,多对多的时候,两张表跨到第三张表时都有下面这些方法,
且只是对象才能使用(queryset列表不能使用),就是obj.get()、obj.first()、obj.last()才能使用,
需要注意的是:'一'这张表使用下面这些方法时,参数是对象,而多对多的第三张表使用下面这些方法时,参数是id
它存在于下面两种情况:
外键关系的反向查询
多对多关联关系
简单来说就是当 点后面的对象 可能存在多个的时候就可以使用以下的方法
2、方法
1.add(位置参数)
把指定的model对象添加到关联对象集中。
但是一对多中add里面的实参(位置参数)是对象
而多对多中add里面的实参(位置参数)可以是id
2.set([列表])
更新model对象的关联对象。
但是一对多中set里面的实参(列表)是对象
而多对多中add里面的实参(列表)可以是id
3.remove()
从关联对象集中移除执行的model对象
4.clear()
从关联对象集中移除一切对象。
注意:
对于ForeignKey对象,clear()和remove()方法仅在null=True时存在。
举个例子:
ForeignKey字段没设置null=True时,
class Book(models.Model):
title = models.CharField(max_length=32)
publisher = models.ForeignKey(to=Publisher)
没有clear()和remove()方法
>>> models.Publisher.objects.first().book_set.clear()
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'RelatedManager' object has no attribute 'clear'
当ForeignKey字段设置null=True时,
class Book(models.Model):
name = models.CharField(max_length=32)
publisher = models.ForeignKey(to=Class, null=True)
此时就有clear()和remove()方法
>>> models.Publisher.objects.first().book_set.clear()
5.注意
对于所有类型的关联字段,add()、create()、remove()和clear(),set()都会马上更新数据库。换句话说,在关联的任何一端,都不需要再调用save()方法。
6.建表
class MyClass(models.Model):
cname = models.CharField(max_length=12)
class Student(models.Model):
sname = models.CharField(max_length=12)
myclass = models.ForeignKey(to='MyClass')
def __str__(self):
return self.sname
class Teacher(models.Model):
tname = models.CharField(max_length=12)
myclass = models.ManyToManyField(to='MyClass')
7.例子
1.一对多
# 查询id=2的班级的所有学生
ret = MyClass.objects.get(id=2).student_set.all()
print(ret)
# id=2的班级的所有老师
ret = MyClass.objects.get(id=2).teacher_set.all()
print(ret)
# 给id=1的班级添加一个id=4的学生
student_obj = Student.objects.get(id=4)
MyClass.objects.get(id=1).student_set.add(student_obj)
ret = MyClass.objects.get(id=1).student_set.all()
print(ret)
# 把所有学生都绑定到id=1的班级中
MyClass.objects.get(id=1).student_set.set(Student.objects.all())
ret = MyClass.objects.get(id=1).student_set.all()
print(ret)
2.多对多
# 给id=2的老师添加一个id=4的班级
Teacher.objects.get(id=2).myclass.add(4)
# 给id=3的班级添加一个id=1的老师
Myclass.objects.get(id=3).teacher_set.add(1)
# 把id=3的班级的老师修改为id=2和id=3的老师
Myclass.objects.get(id=3).teacher_set.set([2, 3])
# 从id=2的老师的中移除2班
Teacher.objects.get(id=2).myclass.remove(2)
# 清空id=2的老师的班级
Teacher.objects.get(id=2).myclass.clear()
# 给id=2的老师新增一门课:生理课
Teacher.objects.get(id=2).myclass.create(cname='生理课')
四、如何在Django终端打印SQL语句
在Django项目的settings.py文件中,在最后复制粘贴如下代码:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
即为你的Django项目配置上一个名为django.db.backends的logger实例即可查看翻译后的SQL语句。
五、基于对象的跟新(save)和基于QuerySet的update跟新的区别
# 把南山出版社的ceo改成小红
# 1. 基于对象的修改(会跟新所有字段,效率慢)
publisher_obj = Publisher.objects.get(name='南山出版社')
publisher_obj.ceo = '小红'
publisher_obj.save()
# 2. 基于QuerySet的update跟新(只跟新指定的字段,效率快于基于对象的修改)
Publisher.objects.filter(name='南山出版社').update(ceo='小勾')
六、聚合查询
注意:ORM的聚合函数一定要搭配aggregate或者annotate使用
6-1、aggregate
1、介绍
aggregate()是QuerySet的一个终止子句,意思是说,它返回一个包含一些键值对的字典。
键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。
用到的内置函数:
from django.db.models import Avg, Sum, Max, Min, Count
2、例子
from django.db.models import Avg, Sum, Max, Min, Count
# 求所有书中价格最高的书
ret = Book.objects.all().aggregate(Max('price'))
print(ret)
# 为聚合值指定一个名称,返回指定的key值
ret = Book.objects.all().aggregate(max_price=Max('price'))
print(ret)
# 生成不止一个聚合,求所有书的平均价格和最小的价格
ret = Book.objects.all().aggregate(avg_price=Avg('price'), min_price=Min('price'))
print(ret)
6-2、annotate分组查询
1、介绍
values/values_list 对应 SQL语句 select 部分
filter 对应 SQL语句 where 部分
2、ORM中分组使用annotate
1. annotate前面查询(values)的是什么,就按什么分组,没有values就默认按id分组,而id是唯一的,因此不写values等于没有分组
2. annotate中要写上分组之后要做的事情
3、建表
class Employee(models.Model):
name = models.CharField(max_length=12)
age = models.IntegerField()
salary = models.IntegerField()
province = models.CharField(max_length=12)
dept = models.CharField(max_length=12)
4、例子
from django.db.models import Avg, Sum, Max, Min, Count
# 使用ORM查询每个部门的平均工资
ret = Employee.objects.values('dept').annotate(avg=Avg('salary'))
print(ret)
ret = Employee.objects.annotate(avg=Avg('salary')).values('dept', 'avg')
print(ret) # 等于没有分组
# 每个部门的平均年龄
ret = Employee.objects.values('dept').annotate(Avg('age')).values_list('dept', 'avg_age')
print(ret)
4、多表使用分组
class Publisher(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=16)
addr = models.TextField()
# 成立日期:对应Python中的datetime.date类型
date = models.DateField()
def __str__(self):
return self.name
class Book(models.Model):
# 书名
title = models.CharField(max_length=16)
# 价格:最多显示6个数字,小数位有2个
price = models.DecimalField(max_digits=6, decimal_places=2)
# ISBN:书籍的唯一编号
isbn = models.CharField(max_length=20, unique=True)
# 外键关联出版社,db_constraint=False不在数据库中建立约束
publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE, related_name='books')
def __str__(self):
return self.title
class Author(models.Model):
name = models.CharField(max_length=12)
# 性别,choice选项用的是本模块定义的常量,默认选保密
gender = models.SmallIntegerField(choices=((1, '男'), (2, '女'), (3, '保密')), default=3)
# 手机号,唯一约束
phone = models.CharField(max_length=11, unique=True)
# 邮箱
email = models.EmailField()
# 多对多关联书籍
books = models.ManyToManyField(to='Book', related_name='authors')
例子:
from django.db.models import Avg, Sum, Max, Min, Count
# 求每个出版社出版的书的平均价格
# 基于Book
ret = Book.objects.values('publisher_id').annotate(avg_price=Avg('price')).values_list('publisher__name', 'avg_price')
print(ret)
# 基于Publisher
ret = Publisher.objects.annotate(avg_price=Avg(books__price)).values('name', 'avg_price')
print(ret)
# 求每个出版社出版的书的数量
ret = Book.objects.values('publisher_id').annotate(count=Count('id')).values_list('publisher__name', 'count')
print(ret)
七、Q查询
filter()等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR语句),你可以使用Q对象。
同样是上面书籍出版社那个数据库
例如:
1、或 |
from django.db.models import Q
# 查询书的价格大于100或者作者是小明的书
ret = Book.objects.filter(Q(price__gt=100) | Q(authors__name='小明'))
print(ret)
2、且 &
# 原生的 且
# 查询书的价格大于100且作者是小明的书
ret = Book.objects.filter(price__gt=100, authors__name='小明')
print(ret)
# Q对象的 且
from django.db.models import Q
ret = Book.objects.filter(Q(price__gt=100) & Q(authors__name='小明'))
print(ret)
3、非 ~
from django.db.models import Q
ret = Book.objects.filter(Q(price__gt=100) & ~Q(authors__name='小明'))
print(ret)
4、混合使用:
查询函数可以混合使用Q对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q对象)都将进行'END'运算,
但是,如果出现Q对象,它必须位于所有关键字参数的前面。
# 查询出版社id是1或者2,且作者名字中有小的书
ret = Book.objects.filter(Q(publisher_id=1) | Q(publisher_id=2), authors__name__contains='小')
print(ret)
5、Q的拓展使用方法
![]()
Q查询的两种写法
# Q原始写法
data = data.filter(
Q(name__icontains=query_value)|Q(qq__icontains=query_value)|Q(qq_name__icontains=query_value)
)
或者
data = data.filter(
Q(('name__icontains', query_value))|Q(('qq__icontains', query_value))|Q(('qq_name__icontains', query_value))
)
# Q使用对象的写法
q = Q() # 实例一个Q对象
q.connector = 'OR' # 连接符,OR AND
q.children.append(Q(('name__icontains', query_value))) # Q查询里面的实参是一个元组,元组第一个元素是查询的字段(字符串形式)
q.children.append(Q(('qq__icontains', query_value))) # 元组第二个元素是需要匹配的值
q.children.append(Q(('qq_name__icontains', query_value)))
data = data.filter(q)
View Code
![]()
def all(self)
# 获取所有的数据对象
def filter(self, *args, **kwargs)
# 条件查询
# 条件可以是:参数,字典,Q
def exclude(self, *args, **kwargs)
# 条件查询
# 条件可以是:参数,字典,Q
def select_related(self, *fields)
性能相关:表之间进行join连表操作,一次性获取关联的数据。
总结:
1. select_related主要针一对一和多对一关系进行优化。
2. select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。
def prefetch_related(self, *lookups)
性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。
总结:
1. 对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。
2. prefetch_related()的优化方式是分别查询每个表,然后用Python处理他们之间的关系。
def annotate(self, *args, **kwargs)
# 用于实现聚合group by查询
from django.db.models import Count, Avg, Max, Min, Sum
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id'))
# SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1)
# SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1)
# SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
def distinct(self, *field_names)
# 用于distinct去重
models.UserInfo.objects.values('nid').distinct()
# select distinct nid from userinfo
注:只有在PostgreSQL中才能使用distinct进行去重
def order_by(self, *field_names)
# 用于排序
models.UserInfo.objects.all().order_by('-id','age')
def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
# 构造额外的查询条件或者映射,如:子查询
Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])
def reverse(self):
# 倒序
models.UserInfo.objects.all().order_by('-nid').reverse()
# 注:如果存在order_by,reverse则是倒序,如果多个排序则一一倒序
def defer(self, *fields):
models.UserInfo.objects.defer('username','id')
或
models.UserInfo.objects.filter(...).defer('username','id')
#映射中排除某列数据
def only(self, *fields):
#仅取某个表中的数据
models.UserInfo.objects.only('username','id')
或
models.UserInfo.objects.filter(...).only('username','id')
def using(self, alias):
指定使用的数据库,参数为别名(setting中的设置)
##################################################
# PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
##################################################
def raw(self, raw_query, params=None, translations=None, using=None):
# 执行原生SQL
models.UserInfo.objects.raw('select * from userinfo')
# 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名
models.UserInfo.objects.raw('select id as nid from 其他表')
# 为原生SQL设置参数
models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,])
# 将获取的到列名转换为指定列名
name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)
# 指定数据库
models.UserInfo.objects.raw('select * from userinfo', using="default")
################### 原生SQL ###################
from django.db import connection, connections
cursor = connection.cursor() # cursor = connections['default'].cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
row = cursor.fetchone() # fetchall()/fetchmany(..)
def values(self, *fields):
# 获取每行数据为字典格式
def values_list(self, *fields, **kwargs):
# 获取每行数据为元祖
def dates(self, field_name, kind, order='ASC'):
# 根据时间进行某一部分进行去重查找并截取指定内容
# kind只能是:"year"(年), "month"(年-月), "day"(年-月-日)
# order只能是:"ASC" "DESC"
# 并获取转换后的时间
- year : 年-01-01
- month: 年-月-01
- day : 年-月-日
models.DatePlus.objects.dates('ctime','day','DESC')
def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
# 根据时间进行某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间
# kind只能是 "year", "month", "day", "hour", "minute", "second"
# order只能是:"ASC" "DESC"
# tzinfo时区对象
models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC)
models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai'))
"""
pip3 install pytz
import pytz
pytz.all_timezones
pytz.timezone(‘Asia/Shanghai’)
"""
def none(self):
# 空QuerySet对象
####################################
# METHODS THAT DO DATABASE QUERIES #
####################################
def aggregate(self, *args, **kwargs):
# 聚合函数,获取字典类型聚合结果
from django.db.models import Count, Avg, Max, Min, Sum
result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid'))
===> {'k': 3, 'n': 4}
def count(self):
# 获取个数
def get(self, *args, **kwargs):
# 获取单个对象
def create(self, **kwargs):
# 创建对象
def bulk_create(self, objs, batch_size=None):
# 批量插入
# batch_size表示一次插入的个数
objs = [
models.DDD(name='r11'),
models.DDD(name='r22')
]
models.DDD.objects.bulk_create(objs, 10)
def get_or_create(self, defaults=None, **kwargs):
# 如果存在,则获取,否则,创建
# defaults 指定创建时,其他字段的值
obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2})
def update_or_create(self, defaults=None, **kwargs):
# 如果存在,则更新,否则,创建
# defaults 指定创建时或更新时的其他字段
obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1})
def first(self):
# 获取第一个
def last(self):
# 获取最后一个
def in_bulk(self, id_list=None):
# 根据主键ID进行查找
id_list = [11,21,31]
models.DDD.objects.in_bulk(id_list)
def delete(self):
# 删除
def update(self, **kwargs):
# 更新
def exists(self):
# 是否有结果