Django在Mysql中的基本操作

1. Model性能相关操作:select_related、prefetch_related

1、普通查询的缺点

1. 例:现在有两张表user,和group两张表,在user表中使用m作为ForeignKey与group表进行一对多关联

2. 如果通过user表中的实例查找对应的group表中的数据,就必须重复发sql请求

3. prefetch_related()和select_related()的设计目,都是为了减少SQL查询的数量,但是实现的方式不一样

2、select_related作用

1. select_related主要针一对一和多对一关系进行优化。

2. select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。

3、prefetch_related()作用

1. prefetch_related()主要对于多对多字段和一对多字段进行优化

2. 进行两次sql查询,将查询结果拼接成一张表放到内存中,再查询就不用发sql请求

4、select_related与prefetch_related 使用原则

1. prefetch_related()和select_related()的设计目的很相似,都是为了减少SQL查询的数量,但是实现的方式不一样

2. 因为select_related()总是在单次SQL查询中解决问题,而prefetch_related()会对每个相关表进行SQL查询,因此select_related()的效率高

3. 所以尽可能的用select_related()解决问题。只有在select_related()不能解决问题的时候再去想prefetch_related()

5、select_related举例说明

作用:使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化

select_related举例说明

def index(request):
    #1 这种方法低效
    users = models.User.objects.all()    #拿到的仅仅是user表中内容
    for row in users:
        print(row.user,row.ut_id)        #这里打印user表中的内容不必再次sql请求
        print(row.ut.name)               #第一次查表,没有拿到关联表ut字段中的内容
                                         #所以每次循环都会再次发sql请求,拿到ut.name的值,低效

    #2 使用这种方法也仅需要一次数据库查询(拿到的是字典),但是如果查找的不在那些字段中直接报错
    users = models.User.objects.all().values('user','pwd','ut__name')

    #3 select_related()可以一次sql查询拿到所有关联表信息
    users = models.User.objects.all().select_related()
    
    # 这里还支持指定只拿到那个关联表的所有信息,比如:有多个外键关联,只拿到与ut外键关联的表
    users = models.User.objects.all().select_related('ut')


# select_related() 接受depth参数,depth参数可以确定select_related的深度。
# Django会递归遍历指定深度内的所有的OneToOneField和ForeignKey。以本例说明:
# zhangs = Person.objects.select_related(depth = d)
# d=1  相当于 select_related(‘hometown’,'living’)
# d=2  相当于 select_related(‘hometown__province’,'living__province’)

6、prefetch_related举例说明
作用:进行两次sql查询,将查询结果拼接成一张表

prefetch_related举例说明

def index(request):
    users = models.User.objects.filter(id__gt=30).prefetch_related('ut')
                       #ut和tu是user表的两个foreign key,分别关联不同的表

    users = models.User.objects.filter(id__gt=30).prefetch_related('ut','tu')
    #1 先执行第一次sql查询:select * from users where id > 30;
    #2 比如:第一次查询结果,获取上一步骤中所有ut_id=[1,2]
    #3 然后执行第二次sql查询:select * from user_type where id in [1,2]
    #4 这样就仅执行了两次sql查询将两个表的数据拼到一起,放到内存中,再查询就不用发sql请求
    for row in users:
        print(row.user,row.ut_id)       #这里打印user表中的内容不必再次sql请求

2. F()和Q()查询语句

models.py创建表

from django.db import models
class Student(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    def __str__(self):
        return self.name

2.1 F() ---- 专门取对象中某列值的操作
作用:F()允许Django在未实际链接数据的情况下具有对数据库字段的值的引用

F()将指定字段自动加1

from django.shortcuts import HttpResponse
from app01 import models
from django.db.models import F,Q

def orm(request):
    # 每访问一次数据库中zhangsan的年纪就会自动增加1

    models.Student.objects.filter(name='zhangsan').update(age=F("age") + 1)



    # 自动生成Student表中数据
    '''
    stu_list = [{'name':'zhangsan','age':11},
                 {'name': 'lisi', 'age': 22},
                 {'name': 'wangwu', 'age': 33},]
    for u in stu_list:
        models.Student.objects.create(**u)
    '''
    return HttpResponse('orm')

2.2、Q() ---- 复杂查询(用法1)

1、Q对象(django.db.models.Q)可以对关键字参数进行封装,从而更好地应用多个查询

2、可以组合使用 &(and),|(or),~(not)操作符,当一个操作符用于两个Q的对象,它产生一个新的Q对象

3、如: Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')

复杂查询

from django.shortcuts import HttpResponse
from app01 import models
from django.db.models import F,Q

def orm(request):
    # 查找学生表中年级大于1小于30姓zhang的所有学生
    stus = models.Student.objects.filter(
        Q(age__gt=1) & Q(age__lt=30),
        Q(name__startswith='zhang')
    )
    print('stu',stus)   #运行结果:[<Student: zhangsan>]


    # 自动生成Student表中数据
    '''
    stu_list = [{'name':'zhangsan','age':11},
                 {'name': 'lisi', 'age': 22},
                 {'name': 'wangwu', 'age': 33},]
    for u in stu_list:
        models.Student.objects.create(**u)
    '''
    return HttpResponse('orm')

2.3 Q() ---- 动态添加多个and和or查询条件(用法2)

crm项目中动态添加or查询条件

def table_search(request,admin_class,object_list):
   search_key = request.GET.get('_q','')
   q_obj = Q()
   q_obj.connector = 'OR'
   for column in admin_class.search_fields:
      q_obj.children.append(('%s__contains'%column,search_key))
   res = object_list.filter(q_obj)
   return res

or动态添加多个查询条件

# or动态添加多个查询条件
>>> from crm import models
>>> from django.db.models import Q
>>> con = Q()                                               #1. 实例化一个Q()查询类
>>> con.connector = "OR"                                    #2. 指定使用‘OR’条件
>>> con.children.append(('qq__contains','123'))             #3. qq字段中包含‘123’
>>> con.children.append(('name__contains','name0'))         #4. name字段中包含‘naem0’
>>> con
<Q: (OR: ('qq__contains', '123'), ('name__contains', 'name0'))>   
                                                            #5. 查找name字段中包含‘naem0’或qq字段包含‘123’的所有条目
>>> models.Customer.objects.values('qq','name').filter(con)

and和or结合查询

# and和or结合查询
#1. 导入模块
>>> from crm import models
>>> from django.db.models import Q

#2. q1:查询id=1或者id=2的所有条目  (or条件)
>>> q1 = Q()
>>> q1.connector = 'OR'
>>> q1.children.append(('id',1))
>>> q1.children.append(('id',2))

#3. q2:查询id=1的所有条目  (or条件)
>>> q2 = Q()
>>> q2.connector = 'OR'
>>> q2.children.append(('id',1))

#4. con:结合q1和q2条件结果是查询id=1的所有条目     (结合q1,q2的and条件)
>>> con = Q()
>>> con.add(q1,'AND')
   <Q: (OR: ('id', 1), ('id', 2))>
>>> con.add(q2,'AND')
   <Q: (AND: (OR: ('id', 1), ('id', 2)), ('id', 1))>
>>> models.Customer.objects.values('qq','name').filter(con)
   <QuerySet [{'qq': '123456765432', 'name': 'haha'}]>

**3、解决时差配置问题的报错 **

  1. 错误:Are time zone definitions for your database and pytz installed?
  2. 解决方法:安装pytz模块,在Django settings中配置数据库的时区: pip3 install pytz

在settings中配置数据库时区

# 注释的是数据库默认的配置
# LANGUAGE_CODE = 'en-us'
# TIME_ZONE = 'UTC'
# USE_TZ = True

USE_TZ = False
LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'Asia/Chongqing'

4、Trunk函数基本使用

4.1models.py中创建数据库表

from django.db import models

class Experiment(models.Model):
    start_datetime = models.DateTimeField()

4.2views.py使用

from django.shortcuts import HttpResponse
from .models import *

from datetime import datetime
from django.db.models import Count, DateTimeField
from django.db.models.functions import Trunc

def orm(request):
    #1 首先向Experiment表中插入三条数据
    Experiment.objects.create(start_datetime=datetime(2015, 6, 15, 14, 30, 50, 321))
    Experiment.objects.create(start_datetime=datetime(2015, 6, 15, 14, 40, 2, 123))
    Experiment.objects.create(start_datetime=datetime(2015, 12, 25, 10, 5, 27, 999))

    #2 使用Trunk过滤出start_datetime字段,仅保存到日,后面的时分秒全部变成0
    #3 annotate(experiments=Count('id'))可以根据天进行分组

    experiments_per_day = Experiment.objects.annotate(
         start_day=Trunc('start_datetime', 'day', output_field=DateTimeField())
         ).values('start_day').annotate(experiments=Count('id'))

    for exp in experiments_per_day:
       print(exp['start_day'], exp['experiments'])
    return HttpResponse('orm')
# 运行结果:
# 2015-06-15 00:00:00+00:00  2
# 2015-12-25 00:00:00+00:00  1
# 从运行结果中可以看出,2015-06-15日有两条记录,2015-12-25日有1条记录

4.3、Trunk函数应用:查询当天签到同学的id
trunk函数应用

def attendance(request):
    """签到页面"""
    current_datetime = datetime.now()
    year = current_datetime.year
    month = current_datetime.month
    day = current_datetime.day
    if request.method == 'GET':
        if course_id:
            # 获取当天已签到的记录
            attended_students = models.Attendance.objects.annotate(
                attend_day=functions.Trunc('attend_time','day',output_field=DateTimeField(), )
            ).filter(attend_day=datetime(year, month, day), course_id=course_id).values_list('student_id', flat=True)

            #Trunc函数的作用是将'attend_time'中的时间过滤成“day”: 2015-06-15 00:00:00+00:00 这种格式

            tt = datetime(2016, 3, 12)      #运行结果:2016-03-12 00:00:00

# 运行结果:  <QuerySet [1, 2]>    #这里的1,2 是数字表示当天只有id=1,和2的学生签到


# 注:使用这个查询语法和上面的查询结果完全一样
attended_students = models.Attendance.objects\
    .filter(attend_time__date=datetime(year, month, day),course_id=course_id)\
    .values_list('student_id', flat=True)