21.QuerySetAPI

 

QuerySet API:

咱们一般作查询操做的时候,都是经过模型名字.objects的方式进行操做。其实模型名字.objects是一个django.db.models.manager.Manager对象,而Manager这个类是一个“空壳”的类,他自己是没有任何的属性和方法的。他的方法所有都是经过Python动态添加的方式,从QuerySet类中拷贝过来的。javascript

模型.objects:

这个对象是django.db.models.manager.Manager的对象,这个类是一个空壳类,他上面的全部方法都是从QuerySet这个类上面拷贝过来的。所以咱们只要学会了QuerySet,这个objects也就知道该如何使用了。
Manager源码解析:html

class_name = "BaseManagerFromQuerySet"

class_dict = {
    '_queryset_class': QuerySet
}

class_dict.update(cls._get_queryset_methods(QuerySet))

# type动态的时候建立类
# 第一个参数是用来指定建立的类的名字。建立的类名是:BaseManagerFromQuerySet
# 第二个参数是用来指定这个类的父类。
# 第三个参数是用来指定这个类的一些属性和方法
return type(class_name,(cls,),class_dict)

_get_queryset_methods:这个方法就是将QuerySet中的一些方法拷贝出来

filter

将知足条件的数据提取出来,返回一个新的QuerySet。具体的filter能够提供什么条件查询java

exclude

排除知足条件的数据,返回一个新的QuerySet。示例代码以下:python

Article.objects.exclude(title__contains='hello')


以上代码的意思是提取那些标题不包含hello的图书。ios

annotate

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

articles = Article.objects.annotate(author_name=F("author__name"))


以上代码将在每一个对象中都添加一个author__name的字段,用来显示这个文章的做者的年龄。sql

aggregate

使用聚合函数。数据库

order_by:

# 根据建立的时间正序排序
articles = Article.objects.order_by("create_time")
# 根据建立的时间倒序排序
articles = Article.objects.order_by("-create_time")
# 根据做者的名字进行排序
articles = Article.objects.order_by("author__name")
# 首先根据建立的时间进行排序,若是时间相同,则根据做者的名字进行排序
articles = Article.objects.order_by("create_time",'author__name')

必定要注意的一点是,多个order_by,会把前面排序的规则给打乱,而使用后面的排序方式。好比如下代码:django

articles = Article.objects.order_by("create_time").order_by("author__name")

他会根据做者的名字进行排序,而不是使用文章的建立时间。
固然,也能够在模型定义的在Meta类中定义ordering来指定默认的排序方式。示例代码以下:api

class Meta:
        db_table = 'book_order'
        ordering = ['create_time','-price']

还能够根据annotate定义的字段进行排序。好比要实现图书的销量进行排序,那么示例代码以下:

books = Book.objects.annotate(order_nums=Count("bookorder")).order_by("-order_nums")
    for book in books:
        print('%s/%s'%(book.name,book.order_nums))

values

values的返回值一样也是一个QuerySet对象,可是这个QuetySet中装的就不是模型了,而是一个一个的dict字典.

若是咱们想要提取的是这个模型上关联的对象的属性,那么也是能够的,查找顺序跟filter的用法是同样的.示例代码以下:

books = Book.objects.values("id","name","author__name")


以上将会提取author模型下的name字段,若是咱们不想要这个名字,想要更改一个名字,那么可使用关键字参数.示例代码以下:

books = Book.objects.values("id","name",author_name=F("author__name"))


自定义的名字,不能和模型上自己拥有的字段同样,好比以上author_name若是取名叫作author就会报错,由于Book上自己就拥有一个字段叫作author.

values中,也可使用聚合函数来造成一个新的字段.好比我想要获取每本图书的销量,那么示例代码以下:

books = Book.objects.values("id","name",order_nums=Count("bookorder"))

若是调用values方法的时候,没有传递任何的参数,那么会获取这个模型上的全部字段以及对应的值造成的字典.示例代码以下:

books = Book.objects.values()

values_list

d跟values是同样的做用.只不过这个方法返回的QuerySet中,装的不是字典,而是元组,示例代码以下:

books = Book.objects.values_list("id","name")


那么以上代码的返回结果是:

(1,"西游记")

若是给values_list只指定一个字段,那么咱们能够指定flat=True,这样返回回来的结果就不在是一个元组,而是这个字段的值,示例代码以下:

books = Book.objects.values_list("name",flat=True)


那么以上返回的结果是:

`三国演义`


必定要注意的是,flat只能用在只有一个字段的状况下,不然就会报错.

all方法

查询模型下的全部数据,返回一个QuerySet对象,这个QuerySet对象没有通过任何的修改(好比过滤等)

在查找某个表的数据的时候,能够一次性把相关联的其余表的数据都提取出来,这样能够在之后访问相关联的表的数据的时候,不用再次查找数据库,能够节省一些开销.示例代码以下:

books = Book.objects.select_related("author","publisher")
for book in books:
    print(book.author.name)
    # 由于在提取book的时候,使用了select_related,那么之后在访问book.author的时候,不会再次向数据库从新发起查询


注意:这个方法只能用在外键关联的对象上,对于那种多对多,或者多对一的状况不能使用它在实现,而应该使用prefetch_related来实现.

这个方法相似与select_related方法,也是用来查询语句的时候,提早将查找的数据提取出来.不过这个方法是用来解决多对多,非外键的的状况.这个方法会产生两个查询语句.因此,若是查询外键关联的模型就用select_related,若是查询的是多对多或者非外键关联的状况,就用prefetch_related.示例代码以下:

books = Book.objects.prefetch_related("bookorder_set")


须要注意的是:在使用prefetch_related查找出来的boororder_set,建议不要再对它进行任何操做,好比filter,否则又会产生N多查询语句,影响查询的性能.好比如下的代码是不对的:

books = Book.objects.prefetch_related("bookorder_set")
for book in books:
    print(book.name)
    # 这个地方若是对bookorder_set进行了操做,那么就又会产生新的sql语句,前面的prefetch_related就至关于白作了
    oredrs = book.bookorder_set.fliter(price__gte=90)
    for order in orders:
        print(order.id)


那么若是确实是想要对预先查找的集合进行操做,那么咱们可使用django.db.models.Prefetch来完成,示例代码以下:

# 先使用Prefetch把查找的条件写好,在放到prefetch_related中
from django.db.models import Prefetch
prefetch = Prefetch("bookorder_set",queryset=Bookorder.objects.filter(price__gte=90))
books = Book.objects.prefetch_related(prefetch)
for book in books:
    print(book.name)
    orders = book.bookorder_set.all()
    for order in orders:
        print(order.id)

defer和only

这两个方法都会返回一个QuerySet,而且这个QuerySet中装的都是模型,而不是字段
1. defer: 这个方法用来告诉ORM,在查询某个模型的时候,过滤到某些字段.
2. only: 这个方法用来告诉ONR,在查询某个模型的时候,只提取某些字段.
注意: 使用defer了的字段,之后在使用这个字段,会从新发起一次请求,所以要谨慎操做,only也同理.示例代码以下:

articles = list(Article.objects.defer("title"))
for article in articles:
    # 由于在上面提取的时候过滤了title
    # 这个地方从新获取title,将从新向数据库中进行一次查找操做
    print(article.title)
for sql in connection.queries:
    print('='*30)
    print(sql)

get

获取知足条件的数据,返回的是具体的模型。这个函数只能返回一条数据,而且若是给的条件有多条数据,那么这个方法会抛出MultipleObjectsReturned错误,若是给的条件没有任何数据,那么就会抛出DoesNotExit错误。因此这个方法在获取数据的只能,只能有且只有一条。

create

建立一条数据,而且保存到数据库中。这个方法至关于先用指定的模型建立一个对象,而后再调用这个对象的save方法。示例代码以下:

article = Article(title='abc')
article.save()

# 下面这行代码至关于以上两行代码
article = Article.objects.create(title='abc')

bulk_create

一次性建立多个数据,无论多少条数据,一条SQL语句解决

get_or_create

若是给定的条件有数据,那么就会把这个数据直接提取出来.若是给定的条件没有数据,那么就会先建立数据,而后再把数据返回回来.

count

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

first和last

返回QuerySet中的第一条和最后一条数据,返回值是数据的模型.

exisit

exists:判断某个条件的数据是否存在。若是要判断某个条件的元素是否存在,那么建议使用exists,这比使用count或者直接判断QuerySet更有效得多。示例代码以下:

if Article.objects.filter(title__contains='hello').exists():
    print(True)
比使用count更高效:
if Article.objects.filter(title__contains='hello').count() > 0:
    print(True)
也比直接判断QuerySet更高效:
if Article.objects.filter(title__contains='hello'):
    print(True)

distinct

distinct:去除掉那些重复的数据。这个方法若是底层数据库用的是MySQL,那么不能传递任何的参数。好比想要提取全部销售的价格超过80元的图书,而且删掉那些重复的,那么可使用distinct来帮咱们实现,示例代码以下:

books = Book.objects.filter(bookorder__price__gte=80).distinct()


须要注意的是,若是在distinct以前使用了order_by,那么由于order_by会提取order_by中指定的字段,所以再使用distinct就会根据多个字段来进行惟一化,因此就不会把那些重复的数据删掉。示例代码以下:

orders = BookOrder.objects.order_by("create_time").values("book_id").distinct()


那么以上代码由于使用了order_by,即便使用了distinct,也会把重复的book_id提取出来。

update

一次性能够把全部的数据都更新完
update:执行更新操做,在SQL底层走的也是update命令。好比要将全部category为空的article的article字段都更新为默认的分类。示例代码以下:

Article.objects.filter(category__isnull=True).update(category_id=3)


注意这个方法走的是更新的逻辑。因此更新完成后保存到数据库中不会执行save方法,所以不会更新auto_now设置的字段。

delete

delete:删除全部知足条件的数据。删除数据的时候,要注意on_delete指定的处理方式。

切片操做

切片操做:有时候咱们查找数据,有可能只须要其中的一部分。那么这时候可使用切片操做来帮咱们完成。QuerySet使用切片操做就跟列表使用切片操做是同样的。示例代码以下:

books = Book.objects.all()[1:3]
for book in books:
    print(book)


切片操做并非把全部数据从数据库中提取出来再作切片操做。而是在数据库层面使用LIMIE和OFFSET来帮咱们完成。因此若是只须要取其中一部分的数据的时候,建议你们使用切片操做。

何时Django会将QuerySet转换为SQL去执行:

生成一个QuerySet对象并不会立刻转换为SQL语句去执行。
好比咱们获取Book表下全部的图书:

books = Book.objects.all()
print(connection.queries)


咱们能够看到在打印connection.quries的时候打印的是一个空的列表。说明上面的QuerySet并无真正的执行。
在如下状况下QuerySet会被转换为SQL语句执行:

  1. 迭代:在遍历QuerySet对象的时候,会首先先执行这个SQL语句,而后再把这个结果返回进行迭代。好比如下代码就会转换为SQL语句:
    for book in Book.objects.all():
         print(book)
  2. 使用步长切片操做:QuerySet能够相似于列表同样作切片操做。作切片操做自己不会执行SQL语句,可是若是若是在作切片操做的时候提供了步长,那么就会立马执行SQL语句。须要注意的是,作切片后不能再执行filter方法,不然会报错。

  3. 调用len函数:调用len函数用来获取QuerySet中总共有多少条数据也会执行SQL语句。

  4. 调用list函数:调用list函数用来将一个QuerySet对象转换为list对象也会立马执行SQL语句。

  5. 判断:若是对某个QuerySet进行判断,也会立马执行SQL语句。

相关文章
相关标签/搜索