django基础之ORM

django基础之ORM

  • 一:创建和映射ORM模型
  • 二:ORM基本的增删改查
    • 2.1 增加数据
    • 2.2 查询
    • 2.3 删除
    • 2.4 修改
  • 三:ORM常用Field
  • 四:Field中常用参数
  • 五:模型中 Meta 配置
  • 六:外键
    • 6.1 外键
    • 6.2 外键关联操作
    • 6.3 一对多
    • 6.4 一对一
    • 6.5 多对多
  • 七:ORM查询条件
    • 7.1 exact
    • 7.2 iexact
    • 7.3 contains
    • 7.4 startswith
    • 7.5 range
    • 7.6 date
    • 7.7 year
    • 7.8 isnull
    • 7.9 regex
  • 八:聚合函数
    • 8.1 Avg
    • 8.2 aggregate与annotate
    • 8.3 Count
    • 8.4 Max与Min
    • 8.5 Sum
  • 九:F表达式
  • 十:Q对象
  • 十一:QuerySet API
    • 11.1 返回新的QuerySet的方法
    • 11.2 filter
    • 11.3 exclude
    • 11.4 annotate
    • 11.5 order_by
    • 11.6 values
    • 11.7 values_list
    • 11.8 all
    • 11.9 select_related
    • 11.10 prefetch_related
    • 11.11 defer
    • 11.12 only
    • 11.13 get
    • 11.14 create
    • 11.15 get_or_create
    • 11.16 bulk_create
    • 11.17 count
    • 11.18 first和last
    • 11.19 exists
    • 11.20 distinct
    • 11.21 update
    • 11.22 delete
    • 11.23 什么时候Django会将QuerySet转换为SQL去执行
  • 十二:根据已有的数据表生成ORM模型

一:创建和映射ORM模型

在django APP目录下的models.py文件中编写数据模型类

from django.db import models

class Book(models.Model):
    id = models.AutoField(primary_key = True)
    name = models.CharField(max_length=100, null = False)
    author = models.CharField(max_length=100, null = False)
    price = models.FloatField(default = 0, null = False)

class Publisher(models.Model):
    '''
    如果没有写id字段,django会自动生成id字段
    '''
    name = models.CharField(max_length=100, null = False)
    address = models.CharField(max_length=100, null=True)

数据库迁移前请确保APP已在settings.py文件中已注册
这里使用mysql来保存数据,settings.py文件中的配置如下:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'dingdingtable',
        'USER': 'root',
        'PASSWORD': 'root',
        'HOST': 'localhost',
        'PORT': 3306,
        "OPTIONS":{"init_command":"SET default_storage_engine=INNODB;"}	# 此句代码为mysql5.7的写法
    }
}

数据模型类创建好后,在命令行生迁移脚本文件及映射到数据库中

python manage.py makemigrations		# 生成迁移脚本文件
python manage.py migrate			# 将新生成的迁移脚本文件映射到数据库中

django默认会将 APP名.类名 的形式作为数据库中的表名
django基础之ORM_第1张图片

二:ORM基本的增删改查

2.1 增加数据

def index(request):
    book = Book(name='三国演义', author='罗贯中', price=200)
    book.save()
    return HttpResponse('create success')

2.2 查询

1:根据主键查询

def index(request):
    book = Book.objects.get(pk=1)   # pk根据主键查询(不管主键是id,还是bid)
    print(book)
    return HttpResponse('create success')

输出book如下:
在这里插入图片描述
可以在模型类中添加 str 方法:

class Book(models.Model):
    id = models.AutoField(primary_key = True)
    name = models.CharField(max_length=100, null = False)
    author = models.CharField(max_length=100, null = False)
    price = models.FloatField(default = 0, null = False)
    def __str__(self):
        return '{class_name}:({name},{author},{price})'.format(class_name=__class__, name=self.name, author=self.author, price=self.price)

添加完成后,再次打印 book时,输出如下:

<class 'book.models.Book'>:(三国演义,罗贯中,200.0)

2:根据其他字段查询

book = Book.objects.filter(name='西游记')
# :(西游记,吴承恩,158.0)>]>
book = Book.objects.filter(name='西游记').first()	# 使用first返回一条数据
# :(西游记,吴承恩,158.0)

2.3 删除

def index(request):
    book = Book.objects.get(pk=1)
    book.delete()
    return HttpResponse('delete success')

2.4 修改

def index(request):
    book = Book.objects.get(pk=2)
    book.price = 200
    book.save()
    return HttpResponse('modify success')

三:ORM常用Field

在 Django 中,定义了一些 Field 来与数据库表中的字段类型来进行映射。

AutoField
映射到数据库中是 int 类型,可以有自动增长的特性。一般不需要使用这个类型,如果不指定主键,那么模型会自动的生成一个叫做 id 的自动增长的主键。如果你想指定一个其他名字的并且具有自动增长的主键,使用 AutoField也是可以的。

BigAutoField
64位的整形,类似于AutoField,只不过是产生的数据的范围是从 1-9223372036854775807。

BooleanField
在模型层面接收的是 True/False。在数据库层面是tinyint类型。如果没有指定默认值,默认值是 None 。
如果想使用可以为null的BooleanField,那么应用使用NullBooleanField

CharField
在数据库层面是 varchar 类型。在 Python 层面就是普通的字符串。这个类型在使用的时候必须要指定最大的长度,也即必须要传递 max_length 这个关键字参数进去。

EmailField
类似于CharField 。在数据库底层也是一个 varchar 类型。最大长度是254个字符。如果没有指定max_length,默认使用254个字符为最大长度。并不会限制字符串一定要满足邮箱格式

FloatField
浮点类型。映射到数据库中是 float 类型。

IntegerField
整形。值的区间是 -2147483648——2147483647。

TextField
大量的文本类型。映射到数据库中是longtext类型。

URLField
类似于 CharField ,只不过只能用来存储 url 格式的字符串。并且默认的max_length 是200。注意在表单验证的时候,会判断提交的字符串是否是一个URL格式类型。就是以http,https等开头的格式

DateField
日期类型。在python中是datetime.date类型,可以记录年月日。在映射数据库也是date类型。使用这个Field可以传递以几个参数:

  • auto_now:在每次这个数据保存的时候,都使用当前的时间。如作为一个记录修改日期的字段,可以将这个属性设置为True
  • auto_now_add:数据第一次被添加时,使用当前时间,后面数据更新时,该时间不再更新。如作为一个记录第一次入库的字段,可以将这个属性设置为True

DateTimeField
日期时间类开,类亿于DateField。不仅仅存储日期,还可以存储时间。映射到数据库中是datetime类型。这个Field也可以使用auto_now and auto_now_add两个属性

TimeField
时间类型。在数据库中是time类型。在python中是datetime.time类型

Django中所有的Field见下图:
django基础之ORM_第2张图片

四:Field中常用参数

null
如果设置为 True , Django 将会在映射表的时候指定是否为空。默认是为 False 。在使用字符串相关的 Field (CharField/TextField)的时候,官方推荐尽量不要使用这个参数,也就是保持默认值 False 。因为 Django 在处理字符串相关的 Field 的时候,即使这个 Field 的 null=False ,如果你没有给这个 Field 传递任何值,那么 Django 也会使用一个空的字符串 “” 来作为默认值存储进去。因此如果再使用 null=True, Django 会产生两种空值的情形(NULL或者空字符串)。如果想要在表单验证的时候允许这个字符串为空,那么建议使用 blank=True 。如果你的 Field 是 BooleanField ,那么对应的可空的字段则为 NullBooleanField 。

blank
标识这个字段在表单验证的时候是否可以为空。默认是 False 。这个和 null 是有区别的, null 是一个纯数据库级别的。而 blank 是表单验证级别的。

db_column
这个字段在数据库中的名字。如果没有设置这个参数,那么将会使用模型中属性的名字。

default
默认值。可以为一个值,或者是一个函数,但是不支持 lambda 表达式。并且不支持列表/字典/集合等可变的数据结构。

primary_key
是否为主键。默认是 False 。

unique
在表中这个字段的值是否唯一。一般是设置手机号码/邮箱等。

五:模型中 Meta 配置

可以在模型中定义一个类,叫做 Meta 。然后在这个类中添加一些类属性来控制模型的作用。
例如我们想要在数据库映射的时候使用自己指定的表名,而不是使用模型的名称。那么我们可以在 Meta 类中添加一个 db_table 的属性。也可以使用ordering设置在提取数据的排序方式。

class Book(models.Model):
      name = models.CharField(max_length=20,null=False)
      desc = models.CharField(max_length=100,name='description',db_column="description1")
      class Meta:
      	db_table = 'book_model'
      	ordering = ['pub_date']

六:外键

在 MySQL 中,表有两种引擎,一种是 InnoDB,另外一种是myisam。如果使用的是 InnoDB 引擎,是支持外键约束的。

6.1 外键

类定义为 class ForeignKey(to,on_delete,**options) 
第一个参数是引用的是哪个模型(也就是哪一张表)
第二个参数是在使用外键引用的模型数据被删除了,这个字段该如何处理。
class Category(models.Model):
    name = models.CharField(max_length=100)

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    category = models.ForeignKey("Category", on_delete=models.CASCADE)  # 映射到数据库中字段为:category_id
# 如果是不同的app数据表关联,使用 app_name.class_name
# 如果关联的自己,那么使用 self 或者模型类名称

创建数据:

def index(request):
    category = Category(name='最新文件')
    category.save()     # Category表必须先有数据,不然Article表save时会报错
    article = Article(title = 'abc', content = '111')
    article.category = category
    article.save()
    return HttpResponse('create success')

查询数据:

article = Article.objects.first()
print(article.category.name)	# 可获取category表中对应的数据

6.2 外键关联操作

Django2.0以上版本ForeignKey需添加新参数on_delete,on_delete指的是通过ForeignKey连接起来的对象被删除后,当前字段怎么变化。

常见的选项有:

models.CASCADE:级联删除,如果外键对应的那条数据被删除,那么所有关联该字段的数据都会被删除
models.PROTECT:受保护(删除时会引起ProtectedError)。只要这条数据引用了外键的那条数据,那么就不能删除外键的那条数据
models.SET_NULL:设置为空。如果外键的那条数据被删除了,那么在本条数据上将该字段设置为空。设置为该选项的前提是要指定该字段可以为空
models.SET_DEFAULT:设置为默认值。如果外键的那条数据被删除了,那么在本条数据上将该字段设置为默认值。设置为该选项的前提是要指定该字段的默认值
moels.SET:如果外键的那条数据被删除,那么将会获取set函数中的值来作为这个外键值。set函数可以接收一个可以调用的对象(函数或方法),如果是可以调用的对象,那么会将这个对象调用后的结果作为值返回回去
models.DO_NOTHING:什么也不做,一切看数据库级别的约束。

6.3 一对多

上面6.1节的模型类就是一对多

class Category(models.Model):
    name = models.CharField(max_length=100)
class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    category = models.ForeignKey("Category", on_delete=models.CASCADE)  # 映射到数据库中字段为:category_id

创建Article数据时关联到Category表数据:

def index(request):
    article = Article(title = '钢铁是怎样练成的', content = 'tonight, I fell close to you')
    category = Category.objects.first()
    article.category = category
    article.save()
    return HttpResponse('create success')

通过一端(Category)获取对应的多端(Article)数据:

category = Category.objects.first()
articles = category.article_set.all()
# Category表被Article表关联,此时Category表可以通过 关联模型类的小写_set 获取数据
print(articles)

如果不想使用Django内置的关联模型类的小写_set方法获取数据,可以在Article中关联的字段中添加related_name属性,如:

category = models.ForeignKey("Category", on_delete=models.CASCADE, related_name = 'articles')
# 如果添加了related_name属性,那么 article_set 方法就没有了

查询时通过设置的related_name的值来查询多端对应的数据:

category = Category.objects.first()
articles = category.articles.all()
print(articles)

通过一端保存多端的数据:

def index(request):
    article = Article(title = '111', content = '222')
    category = Category.objects.get(pk = 1)
    category.articles.add(article, bulk = False)	# 可以是多条数据;bulk默认为True,为False时会调用save()方法
    return HttpResponse('create success')

6.4 一对一

class User(models.Model):
    username = models.CharField(max_length=30)

class UserExtension(models.Model):
    school = models.CharField(max_length=30)
    user = models.OneToOneField('User', on_delete=models.CASCADE)

创建数据:

def index(request):
    user = User.objects.first()
    extension = UserExtension(school = 'tsinghua')
    extension.user = user
    extension.save()
    return HttpResponse('create success')

两张表相互查询:

extension = UserExtension.objects.get(pk = 1)	# 关联关系写在该
print(extension.user)
user = User.objects.get(pk = 1)
print(user.userextension)	# 注意这里不是userextension_set,而是直接使用类名的小写来反查询

如果不想使用Django内置的方法来查询,同样可以模型类中添加related_name属性

class UserExtension(models.Model):
    school = models.CharField(max_length=30)
    user = models.OneToOneField('User', on_delete=models.CASCADE, related_name='extension')

设置了related_name属性,就只能通过related_name的值来做查询:

extension = UserExtension.objects.get(pk = 1)
    print(extension.user)
    user = User.objects.get(pk = 1)
    print(user.extension)

6.5 多对多

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    category = models.ForeignKey("Category", on_delete=models.CASCADE, related_name = 'articles')
    def __str__(self):
        return '%s: (%s,%s)' % (__class__, self.id, self.title)

class Tag(models.Model):
    name = models.CharField(max_length=100)
    articles = models.ManyToManyField('Article')

Django默认会生成一张中间表:
Django ORM多对多
创建数据:

article = Article.objects.get(pk = 1)
tag = Tag(name = 'hot news')
tag.save()
article.tag_set.add(tag)	# 多对多中的add()方法没有bulk参数,所以必须先tag.save()
tag = Tag.objects.get(pk = 1)
article = Article.objects.get(pk = 3)
tag.articles.add(article)	# add()方法内部已调用save()方法

如果不想使用tag_set内置方法查询数据,同样可以给模型类加related_name属性

class Tag(models.Model):
    name = models.CharField(max_length=100)
    articles = models.ManyToManyField('Article', related_name = 'tags')
article = Article.objects.get(pk = 1)
tags = article.tags.all()
print(tags)

七:ORM查询条件

7.1 exact

精确的提供条件。如果提供的是一个None,那么在SQL层面会被解释为Null。

article = Article.objects.get(id__exact = 2)		# __exact可以省略
article = Article.objects.filter(id__exact = 2)
article = Article.objects.get(title__exact = None)		# select * from article where title is NULL;
article = Article.objects.filter(title__exact = 'abc')
# 可以通过print(article.query)打印对应的SQL语句(只能在queryset对象上使用)
# 如果使用的是get方法,打印时会报错,因为get返回的是ORM模型,而不是queryset

在Linux系统上,如果数据库的排序规则是utf8_bin,那么大小写敏感;如果是utf8_general_ci则大小写不敏感

7.2 iexact

使用like进行查询

article = Article.objects.filter(title__iexact = 'abc')
print(article.query)
# SELECT `article`.`id`, `article`.`title`, `article`.`content` FROM `article` WHERE `article`.`title` LIKE abc

7.3 contains

大小写敏感,判断某个字段是否包含了某个数据

article = Article.objects.filter(title__contains='Ab')
article = Article.objects.filter(title__icontains='ab')

SQL语句:

# contains
SELECT `article`.`id`, `article`.`title`, `article`.`content` FROM `article` WHERE `article`.`title` LIKE BINARY %Ab%
# icontains
SELECT `article`.`id`, `article`.`title`, `article`.`content` FROM `article` WHERE `article`.`title` LIKE %Ab%

7.4 startswith

判断某个字段值是否以某个值开始的。大小敏感,如果想大小写不敏感,使用istartswith
如果想判断某个字段值是否以某个值结尾,使用endswith,大小写敏感;iendswith大小写不敏感

article = Article.objects.filter(title__startswith='ab')
article = Article.objects.filter(title__istartswith='ab')

被翻译成的SQL语句:

# startswith
SELECT `article`.`id`, `article`.`title`, `article`.`content` FROM `article` WHERE `article`.`title` LIKE BINARY ab%
# istartwith
SELECT `article`.`id`, `article`.`title`, `article`.`content` FROM `article` WHERE `article`.`title` LIKE ab%

7.5 range

判断某个field的值是否在给定的区间中
首先给模型类添加时间类型的字段:

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    create_time = models.DateTimeField(auto_now_add = True, null = True)

    def __str__(self):
        return '%s: (%s,%s)' % (__class__, self.id, self.title)

    class Meta:
        db_table = 'article'

在settings配置文件中设置时区:

TIME_ZONE = 'Asia/Shanghai'
# USE_TZ=False django获取到的当前时间就是一个navie类型的时间
USE_TZ = True

数据库迁移后Article表中的create_time时间为UTC时间(比北京时间晚8个小时)
Django ORM

start_time = datetime(year=2019, month=10, day=20, hour = 8, minute=0, second = 0)
end_time = datetime(year=2019, month=10, day=20, hour = 12, minute=0, second = 0)
# 上面的start_time, end_time按北京时间写的
articles = Article.objects.filter(create_time__range=(start_time, end_time))
print(articles.query)

翻译成的SQL语句:

SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`create_time` FROM `article` WHERE `article`.`create_time` BETWEEN 2019-10-20 00:00:00 AND 2019-10-20 04:00:00

数据可以正确查询出来,但通过翻译成的SQL语句看到后面的查询时间并非北京时间,而是被转换成了UTC时间,且Django还出现了一个警告:

RuntimeWarning: DateTimeField Article.create_time received a naive datetime (2019-10-20 12:00:00) while time zone support is active.

可以通过from django.utils.timezone import make_aware方法将navie时间转换为aware时间

start_time = make_aware(datetime(year=2019, month=10, day=20, hour = 8, minute=0, second = 0))
end_time = make_aware(datetime(year=2019, month=10, day=20, hour = 12, minute=0, second = 0))

make_aware方法内部会获取settings配置文件中设置的时区,即TIME_ZONE = ‘Asia/Shanghai’

7.6 date

针对某些date或者datetime类型的字段。可以指定date的范围。并且这个时间过滤,还可以使用链式调用

articles = Article.objects.filter(create_time__date = datetime(year = 2019, month = 10, day = 20))  # datetime方法中如果只给定年月日参数,那么就相当于datetime.date方法
print(articles.query)
# SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`create_time` FROM `article` WHERE DATE(CONVERT_TZ(`article`.`create_time`, 'UTC', 'Asia/Shanghai')) = 2019-10-20

上面的ORM查询语句是按北京时间写的,但发现结果查询不到数据。
DATE(CONVERT_TZ(article.create_time, ‘UTC’, ‘Asia/Shanghai’)):将UTC时间转换为本地时间(北京时间)
没有查询出正确的结果是因为默认情况下Mysql表中并没有存储时区相关的信息,因此我们需要下载一些时区表的文件,然后添加到Mysql的配置文件中
widows系统下载地址:https://dev.mysql.com/downloads/timezones.html,下载如下文件(我使用的mysql为5.7版本):
django基础之ORM_第3张图片
解压后会得到一个sql文件:timezone_posix.sql,将该sql文件导入到数据库中的mysql库,然后重启mysql服务。正常的话,上面的SQL语句即可正确查询出结果。
Linux或者mac系统操作同上。

7.7 year

year:根据年份进行查询
month:根据月份进行查询
day:根据日期进行查询
week_day:Django1.11新增的查询方式。根据星期几进行查询。1表示星期天,7表示星期六
time:根据时间进行查询

articles = Article.objects.filter(create_time__year = 2019)
articles = Article.objects.filter(create_time__year__gte = 2019)  # 年份大于等于2019的
articles = Article.objects.filter(create_time__week_day=1)

被翻译成的SQL语句:

SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`create_time` FROM `article` WHERE `article`.`create_time` BETWEEN 2018-12-31 16:00:00 AND 2019-12-31 15:59:59.999999
SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`create_time` FROM `article` WHERE `article`.`create_time` >= 2018-12-31 16:00:00  # 这里为什么是16点??
SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`create_time` FROM `article` WHERE DAYOFWEEK(CONVERT_TZ(`article`.`create_time`, 'UTC', 'Asia/Shanghai')) = 1

上表中的案例,如果要精确到秒,最好使用区间来查询:

start_time = datetime.time(hour = 10, minute = 22, second=22)
end_time = datetime.time(hour = 10, minute = 22, second=25)
articles = Article.objects.filter(create_time__time__range = (start_time, end_time))
SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`create_time` FROM `article` WHERE TIME(CONVERT_TZ(`article`.`create_time`, 'UTC', 'Asia/Shanghai')) BETWEEN 10:22:22 AND 10:22:25

7.8 isnull

articles = Article.objects.filter(create_time__isnull = True)
SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`create_time` FROM `article` WHERE `article`.`create_time` IS NULL

7.9 regex

articles = Article.objects.filter(title__regex=r'^A')
# SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`create_time` FROM `article` WHERE `article`.`title` REGEXP BINARY ^A

regex区分大小写,如果不区分大小写使用iregex

八:聚合函数

数据模型类:

class Author(models.Model):
    name = models.CharField(max_length=20)
    age = models.IntegerField()
    email = models.EmailField()
    class Meta:
        db_table = 'author'

class Publisher(models.Model):
    name = models.CharField(max_length=50)
    class Meta:
        db_table = 'publisher'

class Book(models.Model):
    name = models.CharField(max_length=20)
    pages = models.IntegerField()
    price = models.FloatField()
    rating = models.FloatField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
    class Meta:
        db_table = 'book'

class BookOrder(models.Model):
    book = models.ForeignKey('Book', on_delete=models.CASCADE)
    price = models.FloatField()
    class Meta:
        db_table = 'order'

8.1 Avg

Avg:求平均值
获取所有图书的价格平均值:

from django.db.models import Avg	# 所有的聚合函数都在django.db.models中
from django.db import connection

def index(request):
    result = Book.objects.aggregate(Avg('price'))	# {'price__avg': 97.25}
    print(connection.queries[-1].get('sql'))	# 这里不能使用print(result.query)
    return HttpResponse('OK')

翻译成SQL语句:

SELECT AVG(`book`.`price`) AS `price__avg` FROM `book`

如果不想使用Django默认生成的别名,可自行指定别名:

result = Book.objects.aggregate(PriceAvg = Avg('price'))
# SELECT AVG(`book`.`price`) AS `PriceAvg` FROM `book`
# {'PriceAvg': 97.25}

8.2 aggregate与annotate

1:aggregate 返回使用聚合函数后的字段和值
2:annotate 在原来模型字段的基础上添加一个使用了聚合函数的字段,并且在使用聚合函数的时候会使用当前这个模型的主键进行分组(group by)

result = Book.objects.annotate(avg = Avg('bookorder__price'))
for book in result:
	print('%s/%s' % (book.name, book.avg))

翻译后的SQL语句:

SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, AVG(`order`.`price`) AS `avg` FROM `book` LEFT OUTER JOIN `order` ON (`book`.`id` = `order`.`book_id`) GROUP BY `book`.`id` ORDER BY NULL;

8.3 Count

result = Book.objects.aggregate(book_nums = Count('id'))
print(connection.queries)
# SELECT COUNT(`book`.`id`) AS `book_nums` FROM `book`

Count方法中可以指定去重参数:distinct

result = Author.objects.aggregate(email_nums = Count('email', distinct=True))
# SELECT COUNT(DISTINCT `author`.`email`) AS `email_nums` FROM `author`

使用annotate统计每本书的销量:
django基础之ORM_第4张图片

def index(request):
    result = Book.objects.annotate(book_nums = Count('bookorder__id'))
    print('='*100)
    print(result)
    for book in result:
        print('%s/%s' % (book.name, book.book_nums))
    return HttpResponse('OK')

输出结果 :

三国演义/3
水浒传/2
西游记/0
红楼梦/0

翻译后的SQL语句:

SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, COUNT(`order`.`id`) AS `book_nums` FROM `book` LEFT OUTER JOIN `order` ON (`book`.`id` = `order`.`book_id`) GROUP BY `book`.`id` ORDER BY NULL;

原生SQL语句执行后的查询结果:
django基础之ORM_第5张图片

8.4 Max与Min

result = Author.objects.aggregate(max_age = Max('age'), min_age = Min('age'))	# {'max_age': 46, 'min_age': 28}
# SELECT MAX(`author`.`age`) AS `max_age`, MIN(`author`.`age`) AS `min_age` FROM `author`;

获取每本书籍销售时的最大价格以及最小价格

result = Book.objects.annotate(max_price = Max('bookorder__price'), min_price = Min('bookorder__price'))
SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, MAX(`order`.`price`) AS `max_price`, MIN(`order`.`price`) AS `min_price` FROM `book` LEFT OUTER JOIN `order` ON (`book`.`id` = `order`.`book_id`) GROUP BY `book`.`id` ORDER BY NULL LIMIT 21

8.5 Sum

求所有书籍的销售总额:

result = BookOrder.objects.aggregate(total_price = Sum('price'))	# {'total_price': 455.0}
# SELECT SUM(`order`.`price`) AS `total_price` FROM `order`;

求每一本书籍的销售总额:

result = Book.objects.annotate(every_book_total_price = Sum('bookorder__price'))
# SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, SUM(`order`.`price`) AS `every_book_total_price` FROM `book` LEFT OUTER JOIN `order` ON (`book`.`id` = `order`.`book_id`) GROUP BY `book`.`id` ORDER BY NULL LIMIT 21;

求2019年度所有书籍的销售总额:
首页给order表添加时间字段:

class BookOrder(models.Model):
    book = models.ForeignKey('Book', on_delete=models.CASCADE)
    price = models.FloatField()
    create_time = models.DateTimeField(auto_now_add = True, null = True)
    class Meta:
        db_table = 'order'

django基础之ORM_第6张图片

result = BookOrder.objects.filter(create_time__year=2019).aggregate(total = Sum('price'))
# SELECT SUM(`order`.`price`) AS `total` FROM `order` WHERE `order`.`create_time` BETWEEN '2018-12-31 16:00:00' AND '2019-12-31 15:59:59.999999'; args=('2018-12-31 16:00:00', '2019-12-31 15:59:59.999999')

求2019年度每一本书籍的销售总额:

result = Book.objects.filter(bookorder__create_time__year=2019).annotate(total = Sum('bookorder__price'))
# SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, SUM(`order`.`price`) AS `total` FROM `book` INNER JOIN `order` ON (`book`.`id` = `order`.`book_id`) WHERE `order`.`create_time` BETWEEN '2018-12-31 16:00:00' AND '2019-12-31 15:59:59.999999' GROUP BY `book`.`id` ORDER BY NULL LIMIT 21; args=('2018-12-31 16:00:00', '2019-12-31 15:59:59.999999')

九:F表达式

F表达式是用来优化ORM操作数据库的。

class Employee(models.Model):
    name = models.CharField(max_length=20)
    age = models.IntegerField()
    salary = models.FloatField()
    email = models.EmailField()
    username = models.CharField(max_length=20, null = True)

比如:我们要将公司所有员工的薪水都增加1000元,正常流程是从数据库中提取所有员工工资到内存中,然后使用python代码在员工工资的基础之上增加1000元,最后再保存到数据库中。

def index(request):
    employee = Employee.objects.all()
    for em in employee:
        em.salary += 1000
        em.save()
    return HttpResponse('OK')

F表达式可以优化此流程,他可以不需要先把数据从数据库中提取出来,计算完后再保存,可以直接执行SQL语句将员工工资增加1000元

def index(request):
    Employee.objects.update(salary = F('salary')+1000)
    return HttpResponse('OK')

SQL语句:

UPDATE `book_employee` SET `salary` = (`book_employee`.`salary` + 1000); args=(1000,)

F表达式并不会马上从数据库中获取数据,而是在生成SQL语句的时候,动态的获取传给F表达式的值
比如:想要获取每个员工中username字段与email字段值相同的数据,如果不使用F表达式,那么需要以下代码来完成:

def index(request):
    employees = Employee.objects.all()
    for em in employees:
        if em.username == em.email:
            print(em.name, em.username, em.email)
    return HttpResponse('OK')

使用F表达式:

employees = Employee.objects.filter(username = F('email'))
# SELECT `book_employee`.`id`, `book_employee`.`name`, `book_employee`.`age`, `book_employee`.`salary`, `book_employee`.`email`, `book_employee`.`username` FROM `book_employee` WHERE `book_employee`.`username` = (`book_employee`.`email`) LIMIT 21

十:Q对象

作用:用于查询时条件之间的逻辑关系。not and or,可以对Q对象进行 & | ~ (与或非)操作
比如:查询所有价格高于100元且评分达到9.0以上的图书:

books = Book.objects.filter(price__gte = 100,rating__gte = 9)	# 且关系
# SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book` WHERE (`book`.`price` >= 100 AND `book`.`rating` >= 9) LIMIT 21; args=(100.0, 9.0)

现在需求改变,查询所有的价格高于100元或者评价9.0以上的书籍,那么就得使用Q对象

books = Book.objects.filter(Q(price__gte = 100)|Q(rating__gte = 9))
# SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book` WHERE (`book`.`price` >= 100 OR `book`.`rating` >= 9) LIMIT 21; args=(100.0, 9.0)

比如:获取价格大于100,且书籍名字中不包含“记”字的书籍

books = Book.objects.filter(Q(price__gte = 100) & ~Q(name__icontains='记'))
# SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book` WHERE (`book`.`price` >= 100 AND NOT (`book`.`name` LIKE '%记%')) LIMIT 21; args=(100.0, '%记%')

在实际开发过程中,可能会遇到很多条件的查询语句,下面介绍Q对象的另一个写法:

query = self.request.GET.get('query', '')
q = Q()
q.connector = 'OR'
q.children.append(Q(('qq__contains', query)))
q.children.append(Q(('name__contains', query)))
# 上面的四行代码等同于:Q(Q(qq__contains = query)| Q(name__contains = query)
cus_list = models.Customer.objects.filter(q, consultant__isnull=True)

十一:QuerySet API

我们通过做查询时,都是通过model_name.objects的方式进行操作。其实model_name.objects是一个django.db.models.manager.Manager对象,而Manager这个类是一个空壳类,他本身没有任何的属性和方法。他的方法全部都是Python动态添加的方式从QuerySet类中拷贝过来的。
django基础之ORM_第7张图片

11.1 返回新的QuerySet的方法

在使用 QuerySet 进行查找操作的时候,可以提供多种操作。比如过滤完后还要根据某个字段进行排序,那么这一系列的操作我们可以通过一个非常流畅的 链式调用 的方式进行。比如要从文章表中获取标题为 123 ,并且提取后要将结果根据发布的时间进行排序,那么可以使用以下方式来完成:

articles = Articles.objects.filter(title = '123').order_by('create_time')

可以看到 order_by 方法是直接在 filter 执行后调用的。这说明 filter 返回的对象是一个拥有 order_by 方法的对象。而这个对象正是一个新的 QuerySet 对象。因此可以使用 order_by 方法。

11.2 filter

将满足条件的数据提取出来,返回一个新的 QuerySet

def index(request):
    result = Book.objects.filter(id__gt = 1).filter(~Q(id=3))
    for row in result:
        print(row.name)
    return HttpResponse('OK')

11.3 exclude

排除满足条件的数据,返回一个新的 QuerySet

result = Book.objects.filter(id__gt = 1).exclude(id=3)

11.4 annotate

给 QuerySet 中的每个对象都添加一个使用查询表达式(聚合函数、F表达式、Q表达式、Func表达式等)的新字段。

result = Book.objects.annotate(author_name = F('author__name'))
# SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, `author`.`name` AS `author_name` FROM `book` INNER JOIN `author` ON (`book`.`author_id` = `author`.`id`); args=()

11.5 order_by

指定将查询的结果根据某个字段进行排序。如果要倒叙排序,那么可以在这个字段的前面加一个负号

result = Book.objects.order_by('price')		# 升序
result = Book.objects.order_by('-price')	# 降序
result = Book.objects.order_by('price','author__name')	# 如果price字段有重复则根据author_name字段排序

11.6 values

用来指定在提取数据出来,需要提取哪些字段。默认情况下会把表中所有的字段全部都提取出来,可以使用 values 来进行指定,并且使用了 values 方法后,提取出的 QuerySet 中的数据类型不是模型,而是在 values 方法中指定的字段和值形成的字典

result = Book.objects.values('name', 'pages', 'price')
# 

11.7 values_list

类似于values。只不过返回的QuerySet中,存储的不是字典,而是元组

result = Book.objects.values_list('name', 'pages', 'price')
# 

如果在values_list中只有一个字段。那么你可以传递flat=True来将结果扁平化

result = Book.objects.values_list('name',flat=True)
# 

11.8 all

获取这个ORM模型的QuerySet对象

result = Book.objects.all()
# , , ]>

11.9 select_related

在提取某个模型的数据的同时,也提前将相关联的数据提取出来。

def index(request):
    books = Book.objects.all()
    for book in books:
        print(book.author.name)	# 这里有多少个book对象就要执行多少次查询操作
    return HttpResponse('OK')

因为数据库中只有三条数据,所以上面会执行三次数据库查询
Django ORM
而使用select_related可以将author信息提取出来,以后再次使用book.author时就不需要再次去访问数据库,可以减少数据库查询的次,优化查询性能

def index(request):
    books = Book.objects.select_related('author')
    for book in books:
        print(book.author.name)  # 只产生一个查询操作
    return HttpResponse('OK')
# SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, `author`.`id`, `author`.`name`, `author`.`age`, `author`.`email` FROM `book` INNER JOIN `author` ON (`book`.`author_id` = `author`.`id`); args=()

select_related可以传递多个参数:

books = Book.objects.select_related('author', 'publisher')

注意事项:selected_related只能用在一对多或者一对一中,不能用在多对多或者多对一中。比如可以提前获取书籍的作者,但是不能通过作者获取这个作者的书籍

11.10 prefetch_related

这个方法和select_related非常的类似,就是在访问多个表中的数据的时候,减少查询的次数。这个方法是为了解决多对一和多对多的关系的查询问题

# 模型类
class Book(models.Model):
    name = models.CharField(max_length=20)
    pages = models.IntegerField()
    price = models.FloatField()
    rating = models.FloatField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
    def __str__(self):
        return self.name
    class Meta:
        db_table = 'book'

class BookOrder(models.Model):
    book = models.ForeignKey('Book', on_delete=models.CASCADE)
    price = models.FloatField()
    create_time = models.DateTimeField(auto_now_add = True, null = True)
    class Meta:
        db_table = 'order'
def index(request):
    books = Book.objects.prefetch_related('bookorder_set')
    for book in books:
        orders = book.bookorder_set.all()
        for order in orders:
            print(order.id)		# 不管有多少个对象,这里只会进行一次查询操作
    return HttpResponse('OK')

但是如果在使用prefetch_related的时候,如果又创建了一个新的QuerySet那么会把之前的SQL优化给破坏掉, 如:

def index(request):
    books = Book.objects.prefetch_related('bookorder_set')
    for book in books:
        orders = book.bookorder_set.filter(price__gte = 10)	# 这里有多少个对象就会进行多少次查询
        for order in orders:
            print(order.id)
    return HttpResponse('OK')

那如果确实是想要在查询的时候指定过滤条件该如何做呢,这时候我们可以使用django.db.models.Prefetch来实现,Prefetch这个可以提前定义好queryset

def index(request):
    prefetch = Prefetch('bookorder_set', queryset = BookOrder.objects.filter(price__gte=90))
    books = Book.objects.prefetch_related(prefetch)	# 虽然使用了Prefetch,但下面依然不能使用filter
    for book in books:
        orders = book.bookorder_set.all()
        for order in orders:
            print(order.id)
    return HttpResponse('OK')

11.11 defer

在一些表中,可能存在很多的字段,但是一些字段的数据量可能是比较庞大的,而此时你又不需要,比如我们在获取文章列表的时候,文章的内容我们是不需要的,因此这时候我们就可以使用defer来过滤掉一些字段。这个字段跟values有点类似,只不过defer返回的不是字典,而是模型对象

books = Book.objects.defer('name')
# SELECT `book`.`id`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book`; args=()

defer虽然能过滤字段,但是有些字段是不能过滤的,比如id,即使你过滤了,也会提取出来

11.12 only

跟defer类似,只不过defer是过滤掉指定的字段,而only是只提取指定的字段,同样无法操作id字段

11.13 get

获取满足条件的数据。这个函数只能返回一条数据,并且如果给的条件有多条数据,那么这个方法会抛出MultipleObjectsReturned错误,如果给的条件没有任何数据,那么就会抛出DoesNotExit错误

book = Book.objects.get(id = 1)

11.14 create

创建一条数据,并且保存到数据库中。这个方法相当于先用指定的模型创建一个对象,然后再调用这个对象的save方法

# publisher = Publisher(name = '北京出版社')
# publisher.save()
pub = Publisher.objects.create(name = '长沙出版社')

11.15 get_or_create

根据某个条件进行查找,如果找到了那么就返回这条数据,如果没有查找到,那么就创建一个

result = Publisher.objects.get_or_create(name = '武汉出版社')	# 如果没有该条数据,会有两条SQL语句
# SELECT `publisher`.`id`, `publisher`.`name` FROM `publisher` WHERE `publisher`.`name` = '武汉出版社'; args=('武汉出版社',)
# INSERT INTO `publisher` (`name`) VALUES ('武汉出版社'); args=['武汉出版社']

这个方法的返回值是一个元组,元组的第一个参数obj是这个对象,第二个参数created代表是否创建的

(<Publisher: Publisher object (4)>, True)   # 括号里的4表示该条数据的id

11.16 bulk_create

一次性创建多个数据,create方法一次只能创建一条数据

result = Publisher.objects.bulk_create([
        Publisher(name = '长春出版社'),
        Publisher(name = '沈阳出版社')
    ])
# INSERT INTO `publisher` (`name`) VALUES ('长春出版社'), ('沈阳出版社');

11.17 count

获取提取的数据的个数。如果想要知道总共有多少条数据,那么建议使用count,而不是使用len(articles)这种。因为count在底层是使用select count(*)来实现的,这种方式比使用len函数更加的高效

result = Publisher.objects.count()
# SELECT COUNT(*) AS `__count` FROM `publisher`;

11.18 first和last

返回QuerySet中的第一条和最后一条数据

result = Publisher.objects.first()
# SELECT `publisher`.`id`, `publisher`.`name` FROM `publisher` ORDER BY `publisher`.`id` ASC LIMIT 1;

11.19 exists

判断某个条件的数据是否存在。如果要判断某个条件的元素是否存在,那么建议使用exists,这比使用count或者直接判断QuerySet更有效得多

result = Publisher.objects.filter(name__contains='长沙').exists()	# 存在:True
# SELECT (1) AS `a` FROM `publisher` WHERE `publisher`.`name` LIKE BINARY '%长沙%' LIMIT 1; args=('%长沙%',)

11.20 distinct

去除掉那些重复的数据。这个方法如果底层数据库用的是MySQL,那么不能传递任何的参数。
比如想要提取所有销售的价格超过80元的图书,并且删掉那些重复的,那么可以使用distinct来帮我们实现

result = Book.objects.filter(bookorder__price__gte = 80).distinct()
# SELECT DISTINCT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book` INNER JOIN `order` ON (`book`.`id` = `order`.`book_id`) WHERE `order`.`price` >= 80 LIMIT 21;

11.21 update

执行更新操作,在SQL底层走的也是update命令

result = Book.objects.update(price = F('price') + 5)
# UPDATE `book` SET `price` = (`book`.`price` + 5);

11.22 delete

删除所有满足条件的数据。删除数据的时候,要注意on_delete指定的处理方式

result = Author.objects.filter(id__gte = 3).delete()

SQL语句(模型类中使用的级联删除):

SELECT `author`.`id`, `author`.`name`, `author`.`age`, `author`.`email` FROM `author` WHERE `author`.`id` >= 3;
SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book` WHERE `book`.`author_id` IN (3);
DELETE FROM `order` WHERE `order`.`book_id` IN (3); args=(3,)
DELETE FROM `book` WHERE `book`.`id` IN (3); args=(3,)
DELETE FROM `author` WHERE `author`.`id` IN (3); args=(3,)

11.23 什么时候Django会将QuerySet转换为SQL去执行

生成一个QuerySet对象并不会马上转换为SQL语句去执行

def index(request):
    result = Book.objects.all()
    # print(result)			# 注释此行代码,下面的connect.queries打印就为空列表
    print('=' * 100)
    print(connection.queries)
    return HttpResponse('OK')

Django ORM
打印connection.quries的时候打印的是一个空的列表。说明上面的QuerySet并没有真正的执行
在以下情况下QuerySet会被转换为SQL语句执行:

  1. 迭代:在遍历QuerySet对象的时候,会首先先执行这个SQL语句,然后再把这个结果返回进行迭代。比如取消上面代码中的print(result)语句
  2. 使用步长做切片操作:QuerySet可以类似于列表一样做切片操作。做切片操作本身不会执行SQL语句,但是如果如果在做切片操作的时候提供了步长,那么就会立马执行SQL语句。需要注意的是,做切片后不能再执行filter方法,否则会报错。
  3. 调用len函数:调用len函数用来获取QuerySet中总共有多少条数据也会执行SQL语句。
  4. 调用list函数:调用list函数用来将一个QuerySet对象转换为list对象也会立马执行SQL语句。
  5. 判断:如果对某个QuerySet进行判断,也会立马执行SQL语句。

十二:根据已有的数据表生成ORM模型

在实际开发中,有些时候可能数据库已经存在了。那么该如何将模型与数据库中的表映射呢?根据旧的数据库生成对应的ORM模型,需要以下几个步骤:
1:首先将sql文件导入数据库
2:settings配置文件中配置好数据库
3:在终端执行python manage.py inspectdb命令
django基础之ORM_第8张图片
python manage.py inspectd命令只会将表转换为模型后的代码显示在终端

4:将上面生成的模型类代码重定向到文件

python manage.py inspectdb >models.py  # 会在当前目录下生成models.py文件

5:修改模型类

模型名:自动生成的模型,是根据表的名字生成的,可能不是你想要的。这时候模型的名字你可以改成任何你想要的。
模型所属app:根据自己的需要,将相应的模型放在对应的app中。放在同个app中也是没有任何问题的。只是不方便管 理。
模型外键引用:将所有使用Foreignkey 的地方,模型引用都改成字符串。这样不会产生模型顺序的问题。另外,如果引用的模型已经移动到其他的app中了,那么还要加上这个app的前缀。
让Django管理模型:将Meta下的managed=False删掉,如果保留这个,那么以后这个模型有任何的修改,使用migrate都不会映射到数据库中。
表名:切记不要修改表的名字(这里的表名不是模型类名,而是class Meta中的db_table。不然映射到数据库中,会发生找不到对应表的错误。

多对多关系:生成后的多对多关系数据表的第三张表也会在models.py文件中生成代码,把第三张表删除
Django默认生成的第三张表后面会加上s,如:article_tags,如果与数据库中的表名不一致,可以将数据库中的第三张表修改名称;如果不修改可以在模型类代码中指定第三张的表名称,如下:

tags = models.ManyToManyField('Tag', db_table = 'article_tag')

6:执行命令python manage.py migrate APP_name --fake-initial,因为如果不使用–fake-initial,那么会将迁移脚本会映射到数据库中。这时候迁移脚本会新到建表,而这个表之前是已经存在了的,所以肯定会报错。此时我们只要将这个0001-initial 的状态修改为已经映射,而不真正执行映射,下次再migrate的时候,就会忽略他

python manage.py migrate book --fake-initial

7:将Django的核心表映射到数据库中: Django中还有那些核心的表也是需要创建的。不然有些功能是用不了的。比如auth相关表。如果过个数据库之前就是使用Django开发的,那么这些表就已经存在了,可以不用管了。如果之前这个数据库不是使用Django开发的,那么应该使用python manage.py migrate命令将Django中的核心模型映射到数据库中

python manage.py migrate

你可能感兴趣的:(django)