Django之模型层——ORM执行SQL语句

什么是模型层?

模型层(models.py)负责和数据库进行通信,如下图:

 

 

什么是模型?

  • 模型是一个pyyhon类,它是同django.db.models.Mode派生出的子类。
  • 一个模型类代表数据库中一张数据表。
  • 模型类中每一个类属性都代表数据库中的一个字段。
  • 模型是数据交互的接口,是表示操作数据库的方法和方式。

模型层之前期准备

自带的sqlite3数据库对时间字段不敏感,有时候会展示错乱,所以我们习惯切换成常见的数据库比如MySQL,django orm并不会自动帮你创建库,所以需要提前准备好。

Django测试环境搭建

只需要测试Django项目中某个py文件的内容,可以不必通过前端,单独进行测试即可。

方式一(推荐):

  • 使用脚本形式:任意创建一个 py 文件(或者直接在test.py中), 在该文件内书写固定的配置(可以去manage.py文件中去复制前四行)
# 测试环境的准备 去manage.py中拷贝前四行代码 然后自己写两行
import os

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day64.settings")
    import django
    django.setup()
    # 在这个代码块的下面就可以测试django里面的单个py文件了(注意: 导模块也要写在这下面)

 

方式二:

  • 直接使用pycharm提供的Python console窗口

 推荐使用方式一,Python Console测试报错进行修改之后,如果还是继续报错,有可能是之前的缓存影响。可以关闭一下此窗口,再重新打开输入测试语句再试一次。

ORM映射

创建一个模型表

# 在models.py文件准备表

from django.db import models

class User(models.Model):
    name = models.CharField(max_length=32,verbose_name='用户名')
    age = models.IntegerField(verbose_name='年龄')
    register_time = models.DateTimeField(verbose_name='注册时间', auto_now_add=True)

    """
    auto_now:每次修改数据的时候都会自动更新当前时间
    auto_now_add:只在数据创建的时候记录一次创建时间,后续不会自动更改
    """

ORM常用关键字

1.create()

创建数据并直接获取当前创建的数据对象

res = models.User.objects.create(name='阿兵', age=28)
res = models.User.objects.create(name='oscar', age=18)
res = models.User.objects.create(name='jerry', age=38)
res = models.User.objects.create(name='jack', age=88)
print(res)

2.filter()

根据条件筛选数据,结果是QuerySet [数据对象1,数据对象2]

res = models.User.objects.filter()
res = models.User.objects.filter(name='jason')
res = models.User.objects.filter(name='jason', age=19)  # 括号内支持多个条件但是默认是and关系

3.first()、last()

QuerySet支持索引取值但是只支持正数 并且orm不建议你使用索引

res = models.User.objects.filter()[1]
res = models.User.objects.filter(pk=100)[0]  # 数据不存在索引取值会报错
res = models.User.objects.filter(pk=100).first()  # 取queryset里的第一个数据,数据不存在不会报错而是返回None
res = models.User.objects.filter().last()  # 取queryset里的最后一个数据,数据不存在不会报错而是返回None

4.update()

更新数据(支持批量更新)

models.User.objects.filter().update()     # 批量更新
models.User.objects.filter(id=1).update()   # 单个更新

5.delete()

删除数据(支持批量删除)

models.User.objects.filter().delete()      # 批量删除
models.User.objects.filter(id=1).delete()   # 单个删除    

6.all()

查询所有数据,结果是QuerySet [数据对象1,数据对象2]

res = models.User.objects.all()

7.values()

根据指定字段获取数据,结果是QuerySet [{},{},{},{}]

res = models.User.objects.all().values('name')
res = models.User.objects.filter().values()
res = models.User.objects.values()

8.values_list()

根据指定字段获取数据,结果是QuerySet [(),(),(),()]

res = models.User.objects.all().values_list('name','age')

9.distinct()

去重(数据一定要一模一样才可以,如果有主键肯定不行)

res = models.User.objects.values('name','age').distinct()

10.order_by()

根据指定条件排序,默认是升序,字段前面加负号就是降序

res = models.User.objects.all().order_by('age')
print(res)

11.get()

根据条件筛选数据并直接获取到数据对象 一旦条件不存在会直接报错 不建议使用

res = models.User.objects.get(pk=1)
print(res)
res = models.User.objects.get(pk=100, name='jason')
print(res)

12.exclude()

排除

res = models.User.objects.exclude(pk=1)
print(res)

13.reverse()

颠倒顺序(被操作的对象必须是已经排过序的才可以)

res = models.User.objects.all()
res = models.User.objects.all().order_by('age')
res1 = models.User.objects.all().order_by('age').reverse()
print(res, res1)

14.count()

统计结果集中数据的个数

res = models.User.objects.all().count()
print(res)

15.exists()

判断结果集中是否含有数据 如果有则返回True 没有则返回False

res = models.User.objects.all().exists()
print(res)
res1 = models.User.objects.filter(pk=100).exists()
print(res1)

 

ORM执行SQL语句

有时候ORM的操作效率可能偏低 我们是可以自己编写SQL的

方式1:

models.User.objects.raw('select * from app01_user;')

方式2:

from django.db import connection
    cursor = connection.cursor()
    cursor.execute('select name from app01_user;')
    print(cursor.fetchall())

Django终端打印SQL语句

如果你想知道你对数据库进行操作时,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执行的sql语句打印到pycharm终端上

补充:

除了配置外,还可以通过一点.query即可查看查询语句,具体操作如下:

 

神奇的双下划线查询

参数介绍

参数 释义
__in 是否在给定的数据集中
__gt 大于
__lt 小于
__gte 大于等于
__lte 小于等于
__range 在…之间, 闭区间
__contains 包含
__icontains 包含(忽略大小写)
__startswith 以…开头
__endswith 以…结尾
__year 取出年份(仅限于时间类型)
__month 取出月份(仅限于时间类型)
__day 取出日期(仅限于时间类型)
__hour 取出小时(仅限于时间类型)
__minute 取出分钟(仅限于时间类型)
__second 取出秒(仅限于时间类型)
__week_day 一周的第几天(仅限于时间类型)

示例

# 查询年龄大于18的用户数据
    res = models.User.objects.filter(age__gt=18)
    print(res)
# 查询年龄小于38的用户数据
    res = models.User.objects.filter(age__lt=38)
    print(res)
# 大于等于  小于等于
    res = models.User.objects.filter(age__gte=18)
    res = models.User.objects.filter(age__lte=38)
# 查询年龄是18或者28或者38的数据
    res = models.User.objects.filter(age__in=(18, 28, 38))
    print(res)
# 查询年龄在18到38范围之内的数据
    res = models.User.objects.filter(age__range=(18, 38))
    print(res)
# 查询名字中含有字母j的数据
    res = models.User.objects.filter(name__contains='j')  # 区分大小写
    print(res)
    res = models.User.objects.filter(name__icontains='j')  # 不区分大小写
    print(res)
# 查询注册年份是2022的数据
    res = models.User.objects.filter(register_time__year=2022)
    print(res)

ORM字段的创建

常用字段类型表

常用字段 描述 与MySQL字段对应关系
AutoField 必须指定参数primary_key=True指定主键. 如果没有设置主键, 默认创建并以id名作为主键 integer auto_increment
IntegerField 整型字段. 存储宽度4Bytes. 无符号: 0~2^32 有符号: -232/2~232-1 int 或 integer
BigIntegerField 整型字段. 存储宽度8Bytes. 无符号: 0~2^64 有符号: -264/2~264-1 bigint
DecimalField 浮点字段. 必须指定参数max_digits设置总长度. decimal_places设置小数位长度 numeric(%(max_digits)s, %(decimal_places)s)
EmailField 字符字段. Django Admin以及ModelForm中提供验证机制  
CharField 字符字段. 必须指定参数max_length参数设置字符存储个数. Django中的CharField对应的MySQL数据库中的varchar类型,没有设置对应char类型的字段,但是Django允许我们自定义新的字段. varchar(%(max_length)s)
DateField 日期字段. 格式: 年-月-日. 一般指定参数auto_now=Ture更新记录的时间, 或者auto_now_add=True插入记录的时间 date
DateTimeField 日期字段. 格式: 年-月-日 时:分:秒 一般指定参数auto_now=Ture更新记录的时间, 或者auto_now_add=True插入记录的时间 datetime

关系型字段

  • ForeignKey : 一对多

# 一对多,外键字段推荐建在一对多中多的一方
publish = models.ForeignKey(to='Publish)
  • ManyToManyField : 多对多

# 建议外键字段推荐建在查询频率较高的一方,且无需手动创建中间表,models会自动帮你创建一张虚拟表
authors = models.ManToManyField(to='authors')
  • OneToOneField : 一对一

# 建议外键字段建立在查询频率较高的一方
author_detail = models.OneToOneField(to='AuthorDetail')

ps : 建立一对多、一对一关系的外键关联表, 关联表中默认会在建立的外键字段之后拼接**"_id"**, 也就是 我们无需自己手动写个后缀

 字段参数

个别字段才有的参数

CharField(max_length=100)
# 字段长度为utf8编码的100个字符串
    
DateField(unique_for_date=True)
# 这个字段的时间必须唯一
    
DecimalField(max_digits=4, decimal_places=2)
# 前者表示整数和小数总共多少数,后者表示小数点的位数

auto_now和auto_now_add(面试)

  • 提示: 一般作为DateFieldDateTimeField参数
auto_now=True
# 对这条记录内容更新时的时间
    
auto_now_add=True 
# 创建这条记录时的时间

创建表(先创建基础表,再写外键字段)

准备工作

cmd创建一张表:create database day06;

 

settings里面修改配置文件:

 

 

1.创建基础表(书籍表、出版社表、作者表、作者详情)

# models.py

from django.db import models


class Book(models.Model):
    """图书表"""
    title = models.CharField(max_length=32,verbose_name='书名')
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='价格')
    publish_time = models.DecimalField(auto_now_add=True,verbose_name='出版日期')



class Publish(models.Model):
    """出版社表"""
    name = models.CharField(max_length=32,verbose_name='名称')
    address = models.CharField(max_length=64,verbose_name='地址')



class Author(models.Model):
    """作者表"""
    name = models.CharField(max_length=32,verbose_name='姓名')
    age = models.IntegerField(verbose_name='年龄')



class AuthorDetail(models.Model):
    """作者详情表"""
    phone = models.BigIntegerField(verbose_name='手机号')
    address = models.CharField(max_length=64,verbose_name='家庭住址')

2. 确定外键关系

一对多:ORM与MySQL一致,外键字段建在多的一方
多对多:ORM比MySQL有更多变化
  1.外键字段可以直接建在某张表中(查询频率较高的)
    内部会自动帮你创建第三张关系表
  2.自己创建第三张关系表并创建外键字段
    详情后续讲解

一对一:ORM与MySQL一致,外键字段建在查询较高的一方

分析:

  • 书籍与出版社:一对多,外键字段建在多(书籍表)的一方
  • 书籍与作者:多对多,外键字段建在查询频率较高(书籍表)的一方
  • 作者与作者详情:一对一,外键字段建在查询频率较高(作者表)的一方

3. 添加外键字段

from django.db import models


class Book(models.Model):
    """图书表"""
    title = models.CharField(max_length=32,verbose_name='书名')
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='价格')
    publish_time = models.DecimalField(auto_now_add=True,verbose_name='出版日期')

    # 创建书籍与出版社的一对多外键字段
    """on_delete=models.CASECADE 和 on_update=models.CASECADE 设置级联更新级联删除. 
    等同于SQL语言中的ON DELETE CASCADE等约束 (提示: 该操作为Django1.X版本的默认操作, 2.X和3.X需要手动指定)
    """
    publish = models.ForeignKey(to='Publish',on_delete=models.CASCADE)

    # 创建书籍与作者的多对多外键字段
    authors = models.ManyToManyField(to='Author')


class Publish(models.Model):
    """出版社表"""
    name = models.CharField(max_length=32,verbose_name='名称')
    address = models.CharField(max_length=64,verbose_name='地址')



class Author(models.Model):
    """作者表"""
    name = models.CharField(max_length=32,verbose_name='姓名')
    age = models.IntegerField(verbose_name='年龄')

# 创建作者与作者详情表之间一对一外键字段 """on_delete=models.CASECADE 和 on_update=models.CASECADE 设置级联更新级联删除. 等同于SQL语言中的ON DELETE CASCADE等约束 (提示: 该操作为Django1.X版本的默认操作, 2.X和3.X需要手动指定) """ author_detail = models.OneToOneField(to='AuthorDetail',on_delete=models.CASCADE) class AuthorDetail(models.Model): """作者详情表""" phone = models.BigIntegerField(verbose_name='手机号') address = models.CharField(max_length=64,verbose_name='家庭住址')

数据库迁移

python38 manage.py makemigrations

python38 manage.py migrate

Django连接数据库

 

外键字段相关操作

手动添加三张表的数据

 

 通过ORM创建书籍表

import os


def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoday06.settings')
    import django
    django.setup()

    from app01 import models

    """创建数据的两种方式"""
    # 方式1:外键字段绑定要关联的id,注意外键字段之后拼接"_id"
    models.Book.objects.create(title='三国演义',price=88.88,publish_id=1)
    models.Book.objects.create(title='红楼梦', price=66.66, publish_id=2)
    models.Book.objects.create(title='西游记', price=99.99, publish_id=1)

    # 方式2:先获取关联表中要绑定的对象,外键字段直接绑定该对象
    publish_obj = models.Publish.objects.filter(pk=3).first()
    models.Book.objects.create(title='水浒传', price=77.77, publish=publish_obj)

main()

 

 针对多对多关系的增、删、改、查

增加绑定关系:

add()   括号里面可以是一个或多个位置参数,支持数字和对象

"""针对多对多关系绑定的两种方式"""
    # 方式1:直接添加关系表中的id值
    book_obj1 = models.Book.objects.filter(pk=1).first()
    book_obj1.authors.add(1)    # 在第三张关系表中给当前书籍绑定作者
    book_obj1.authors.add(2,3)    # 支持一次性绑定多个
    # 方式2:先获取关联表中要绑定的数据对象,添加该对象
    book_obj2 = models.Book.objects.filter(pk=3).first()
    author_obj1 = models.Author.objects.filter(pk=2).first()   # 获取作者对象1
    author_obj2 = models.Author.objects.filter(pk=3).first()    # 获取作者对象2
    book_obj2.authors.add(author_obj1,author_obj2)    # 绑定上述两个作者对象

 此操作可能会报错,查看id是否在数据修改的时候被删除,其他错误可以参考博客:https://www.cnblogs.com/chen-ao666/p/16985789.html

删除绑定关系:

remove()    括号里面可以是一个或多个位置参数,支持数字和对象

"""删除绑定关系"""
    # 方式1:
    book_obj1.authors.remove(1)    # 删除书籍对象book_obj1绑定的作者id值为1的关系
    book_obj1.authors.remove(2,3)  # 支持一次性删除多个
    # 方式2:
    book_obj2.authors.remove(author_obj1,author_obj2)  # 删除书籍对象book_obj2绑定的两个作者对象

修改绑定关系:

set()   注意括号里面为可迭代对象(元组 列表),支持数字和对象

   # 方式1:
   book_obj1.authors.set((2,))     # 先删除id值不等于2的,如果没有2则添加
   book_obj1.authors.set([2])

   book_obj1.authors.set((3,4))    # 先删除id值不等于3或4的,如果没有3,4则添加
   book_obj1.authors.set([3, 4])
    
   # 方式2:
   book_obj2.authors.set((author_obj1,))
   book_obj2.authors.set((author_obj1, author_obj2))

清空绑定关系:

clear() 

book_obj1.authors.clear()  # 清空book_obj1绑定的作者

 

ORM跨表查询

复习MySQL跨表查询的思路

  • 子查询

分步操作:将一条SQL语句用括号括起来当做另外一条SQL语句的条件
  • 连表操作

先整合多张表之后基于单表查询即可
    inner join 内连接
    left join 左连接
    right join 右连接

 

正反向查询的概念(重要)

1.正向查询

  • 正向查询按照外键字段
  • 正向:外键字段在的一方查不在的一方,外键字段在谁那儿,谁查另外的人就是正向
  • 如果结果可能有多个的时候就要加.all(),如果是一个则直接拿到数据对象

2.反向查询

  • 反向查询按照表名小写
  • 反向:没有外键字段,就是外键字段不在的一方查在的一方
  • 如果结果可能有多个的话必须加_set.all(),结果只有一个的时候不需要加
# 提示:
    书籍与作者, 外键字段在书籍.
    作者与作者详情, 外键字段在作者.
    书籍与出版社外键字段在书籍.

ps:正反向的核心就看外键字段在不在当前数据所在的表中

基于对象的跨表查询

    # 1.查询主键为1的书籍对应的出版社名称
    # 先根据条件获取数据对象
    book_obj = models.Book.objects.filter(pk=1).first()
    # 再判断正反向的概念  由书查出版社 外键字段在书所在的表中 所以是正向查询
    print(book_obj.publish.name)

    # 2.查询主键为3的书籍对应的作者姓名
    # 先根据条件获取数据对象
    book_obj = models.Book.objects.filter(pk=3).first()
    # 再判断正反向的概念  由书查作者 外键字段在书所在的表中 所以是正向查询
    print(book_obj.authors)  # app01.Author.None
    print(book_obj.authors.all())
    print(book_obj.authors.all().values('name'))
    
    # 3.查询alex的电话号码
    author_obj = models.Author.objects.filter(name='alex').first()
    print(author_obj.author_detail.phone)

    # 4.查询北方出版社出版过的书籍
    publish_obj = models.Publish.objects.filter(name='北方出版社').first()
    print(publish_obj.book_set)  # app01.Book.None
    print(publish_obj.book_set.all())
    print(publish_obj.book_set.all().values('title'))

    # 5.查询alex写过的书籍
    author_obj = models.Author.objects.filter(name='alex').first()
    print(author_obj.book_set)  # app01.Book.None
    print(author_obj.book_set.all())
    print(author_obj.book_set.all().values('title'))

    # 6.查询电话号码是110的作者姓名
    author_detail_obj = models.AuthorDetail.objects.filter(phone=110).first()
    print(author_detail_obj.author)
    print(author_detail_obj.author.name)

基于双下划线的跨表查询

    # 1.查询主键为1的书籍对应的出版社名称
    res = models.Book.objects.filter(pk=1).values('publish__name')    # 正向查询,按照主键publish查询,__name获取出版社名字
    print(res)
    # 2.查询主键为3的书籍对应的作者姓名
    res = models.Book.objects.filter(pk=3).values('authors__name')    # 正向查询,按照主键author查询,__name获取作者姓名
    print(res)

    # 3.查询alex的电话号码
    res = models.Author.objects.filter(name='alex').values('author_detail__phone')    # 正向查询,按主键author_detail
    print(res)

    # 4.查询北方出版社出版过的书籍名称和价格
    res = models.Publish.objects.filter(name='北方出版社').values('book__title','book__price')   # 反向查询,按照表名小写book查询,__XX获取相应的信息
    print(res)

    # 5.查询alex写过的书籍
    res = models.Author.objects.filter(name='alex').values('book__title')    # 反向查询,按照表名book
    print(res)

    # 6.查询电话号码是110的作者姓名
    res = models.AuthorDetail.objects.filter(phone=110).values('author__name')   # 反向查询,按照表名author
    print(res)

进阶操作

    # 1.查询主键为1的书籍对应的出版社名称
    # 主键在书籍表中,从出版社查询为反向,按照表名小写book查询,条件写在filter()括号中,values()括号中直接填写需要的键值'name'
    res = models.Publish.objects.filter(book__pk=1).values('name')   # 查询出版过主键为1的书籍的出版社名
    print(res)

    # 2.查询主键为3的书籍对应的作者姓名
    # 主键在书籍表中,从作者查询为反向,按照表名book查询,条件写在filter()括号中,values()括号中直接填写需要的键值'name'
    res = models.Author.objects.filter(book__pk=3).values('name')
    print(res)

    # 3.查询alex的电话号码
    # 主键在作者表中,从电话号码查询为反向,按照表名author查询,条件写在filter()括号中,values()括号中直接填写需要的键值'phone'
    res = models.AuthorDetail.objects.filter(author__name='alex').values('phone')
    print(res)

    # 4.查询北方出版社出版过的书籍名称和价格
    # 主键在书籍表中,从书籍查询为正向,按照外键名publish查询
    res = models.Book.objects.filter(publish__name='北方出版社').values('title','price')
    print(res)
    
    # 5.查询alex写过的书籍
    # 主键在书籍表中,从书籍查询为正向,按照外键名authors查询
    res = models.Book.objects.filter(authors__name='alex').values('title')
    print(res)

    # 6.查询电话号码是110的作者姓名
    # 主键在作者表中,从作者查询为正向,按照外键名author_detail查询
    res = models.Author.objects.filter(author_detail__phone=110).values('name')
    print(res)

补充:

查询主键为3的书籍对应的作者的电话号码

    # 1.从书籍表查询
    # 书籍———>作者(正向按外键authors)————>详情(正向按外键author_detail)
    res = models.Book.objects.filter(pk=3).values('authors__author_detail__phone')
    print(res)
    
    # 2.从详情表查询
    # 详情————>作者(反向按表名author)————>书籍(反向按表名book)
    res = models.AuthorDetail.objects.filter(author__book__pk=3).values('phone')
    print(res)
    
    # 3.从作者表查询
    # 作者————>书籍(反向按表名book)
    # 根据书籍主键获取到作者之后,直接到详情表中获取电话号码(正向按外键author_detail)
    res = models.Author.objects.filter(book__pk=3).values('author_detail__phone')
    print(res)

 

posted @ 2022-12-15 21:33  莫~慌  阅读(734)  评论(0编辑  收藏  举报