YOLO813

Django初学(3)- ORM查询条件

ORM的Field参数设置

    null,针对的是数据库层面,会直接影响数据库的结构。当我们在模型中不配置字符串Field的null参数,则django会默认将其配置为null=False,即不能为空,但是假如我们在views中配置如下代码,

def null_test(req):
  m = mTest()
  m.save()
  return HttpResponse("success!")

    数据库里面的数据是一个空数据,我们寻常所说的空数据其实有两种,一种是空字符串,一种是Null,也就是说,当我们设置该字段不能为空时,当我们没有传入内容时,django会默认为我们填入一个空字符串"",当我们再度修改这个字段,增加null=True之后,即该字段可以为空,再次插入数据时,如下:

    可以看到,数据库里面填充的是Null,而非空字符串(如果一开始配置的模型是null=True,而后修改的是null=False,在进行makemigrations时django 3.1会给你三个选项,因为你数据库里面的数据之前都是允许这个字段为空的,当你配置它不为空的时候,你总得给它一个默认值吧),而此时,数据库里面为空的数据就变成了两种,给筛选判断就增加了麻烦,当然,Django是推荐尽量不要使用null=True,就使用默认的参数。

    blank,针对的是表单验证级别的,默认为False。

    db_column,数据库字段名。

    default,可以为值,也可以是一个函数(如果是时间,不要写成函数的返回值),但是不支持lambda表达式,并且不支持列表等可变的数据结构。

    unique,在表中这个值是否唯一,一般都会配置null=True。该值会在数据库建立一个索引类型为UNIQUE的字段名索引。

ORM的模型配置

    主要是通过模型中的Meta类来设置。

    db_table,表名。

    ordering,排序方式,默认是id排序,接受的是列表参数。

外键简单介绍

    外键的on_delete属性规定了外键表里面的数据删除时,对应的主表数据应当如何处理,这个测试可以在views进行删除测试,在数据库层面(例如Navicat中),如果你想尝试删除,你会发现当主表数据里面与外键表数据有关联时,怎么也删除不掉,其实在views中调用queryset的delete方法是可以正常删除的;但是这里可能会存在一个疑惑,例如我在数据库里面的表其中外键“删除时”为RESTRICT,如下图

    django是如何绕过数据库级别的约束将该条外键数据删除的呢?首先我们应该知道在主表中修改了某条数据的引用的外键或者将其删除掉,那么就可以删除对应的外键表里面的数据,django也是如此绕过数据库的约束的。

    如果想要引用另外一个app下面的模型,采用"app_name.model_name"方式。

表关系

    一对多(例如:文章-作者),文章是主表,作者是外键关联表,如何筛选出属于某个作者下的所有文章呢?当某个模型被其它模型引用时,会默认在该模型下新增一个属性为“模型名小写_set”,这个属性类型打印出来是个这个东西:

<class 'django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager'>

    可以用all()提取该外键对应主表的所有内容,这个RelatedManager对象是可以使用filter和first等方法的

# models.py
class Article(models.Model):
  author = models.ForeignKey("Author",on_delete=models.CASCADE, null= True)
class Author(models.Model):
  author_name = models.CharField(max_length=100)

# views.py
aus = Author.objects.filter(pk=1) # queryset
print(aus[0].article_set.all())
>>> <QuerySet [<Article: QuerySet<5, 1, , 2020-12-05 12:55:31.486580+00:00, ZXF>>, <Article: QuerySet<6, 2, , 2020-12-05 12:55:45.888787+00:00, ZXF>>]

     这个属性的名称“模型名小写_set”可以在建立外键时,指定related_name进行修改。

 

    一对一,OneToOneField。其实也可以用外键,但是一个用户肯定只能对应一个userextension(用户信息表和用户扩展信息表之间的关联),所以一对一毫无悬念,但是如果使用外键,外键是不会拒绝一对多,所以就会有问题了。实例如下:

# views.py
def mtest(req):
    user = FrontUser.objects.first()
    print(user)
    print(user.userextensions)
    return HttpResponse("success!")
# models.py
class FrontUser(models.Model):
  username = models.CharField(max_length=100)
  def __str__(self):
    return "FrontUser: <%s>"%(self.username)

class UserExtensions(models.Model):
  school = models.CharField(max_length=50)
  user = models.OneToOneField("FrontUser", on_delete= models.CASCADE)
  def __str__(self):
    return "UserExtensions: <%s,%s>"%(self.user, self.school)

user>>> FrontUser: <zhangxiaofei>
user.userextensions>>> UserExtensions: <FrontUser: <zhangxiaofei>,QH>

    在OneToOneField,当FrontUser被UserExtensions引用时,可以通过user.userextensions来获取扩展表上面的所有信息。当然,并非一定要求是扩展模型的名字小写,可以使用related_name进行修改。

 

    多对多,ManyToManyField,例如文章和标签之间的关系,django底层是采用建立中间表的方式来解决。测试代码如下:

# models.py
class Tags(models.Model):
    name = models.CharField(max_length=100)
    # articles = models.ManyToManyField("Article")
    def __str__(self):
        return "Tags: <%s>"%(self.name)
class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    tags = models.ManyToManyField("Tags")
    def __str__(self):
        return "Article: <%s,%s>"%(self.title, self.content)

# views.py
def tttest(req):
  tag = Tags.objects.first()
  print(tag)
  print(tag.article_set.all())
  return HttpResponse("success!")

>>> <QuerySet [<Article: Article: <文章,1234>>, <Article: Article: <222,222>>]>

    这个理解的比较绕,我的理解是,假如你倾向于查找某篇文章下的所有标签,那么在Tags建立ManyToManyField,因为你在Tags建立了ManyToManyField To Article,那么Article就多了一个tags_set属性;如果查找某标签下的所有文章比较频繁,那么在Article建立ManyToManyField,就如同上面的例子,在views中可以通过tag.article_set.all()一下子获取该标签下所有的文章。

 

ORM查询条件

    orm中,查询一般使用filter,get和exclude实现,查询格式一般为:field+__+condition,在windows系统,mysql的排序规则(collation)无论是什么,都是大小写不敏感的,如果在linux中,mysql排序规则为utf8_bin,那么就是大小写敏感的。

示例表结构:

    先讲如何查看orm转化成底层的sql语句,如果是一个queryset对象,即

# views.py
artsearch = Article.objects.filter(pk=1)
print(type(artsearch))  
# <class 'django.db.models.query.QuerySet'>
print(artsearch.query)
# SELECT `test_table`.`id`, `test_table`.`name`, `test_table`.`contents` FROM `test_table` WHERE `test_table`.`id` = 1

    直接用QuerySet的query方法即可取得转化后的sql语句,但如果是ORM模型对象,就不能再使用query方法了,会报错“'Article' object has no attribute 'query'”,导入connection类即可:

from django.db import connection
artsearch = Article.objects.get(pk=1)
print(connection.queries)
>>>[{'sql': 'SELECT @@SQL_AUTO_IS_NULL', 'time': '0.000'}, {'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.000'}, {'sql': 'SELECT `test_table`.`id`, `test_table`.`name`, `test_table`.`contents` FROM `test_table` WHERE `test_table`.`id` = 1 LIMIT 21', 'time': '0.000'}]

    列表前两个语句是python自带的,我们看最后一个即可,需要注意的是这两个是互补的关系而非包含。

    icontains,insensitive,大小写不敏感。如果是contains则可以看见BINARY关键字。

artsearch = Article.objects.get(name__icontains="fire book") 
print(connection.queries)
>>> SELECT `test_table`.`id`, `test_table`.`name`, `test_table`.`contents` FROM `test_table` WHERE `test_table`.`name` LIKE '%fire book%' LIMIT 21"

    in,实例如下:

categorys = Category.objects.filter(article__id__in = [1,2,3])
## in 接受列表,元组,queryset对象
print(categorys)
>>><QuerySet [<Category: 热门>, <Category: 热门>, <Category: hot>]>

    可能有些疑惑的是article__id__in这个用法,我打印出来的语句如下:

SELECT `front_category`.`id`, `front_category`.`caname` FROM `front_category` INNER JOIN `test_table` ON (`front_category`.`id` = `test_table`.`category_id`) WHERE `test_table`.`id` IN (1, 2, 3)

    SQL语句INNER JOIN释义,在表中存在至少一个匹配时,INNER JOIN 关键字返回行,INNER JOIN 与 JOIN 是相同的。可以看到这个语句与article_set有些相似,我们昨天的文章中介绍了一对多的表关系中,外键所在的模型中可以利用主表_set的方式获取该外键对象(注意必须是对象,而非queryset)对应主表的内容,而通过其它相关联表的字段来判断是否在某个集合中的方法也是django内置的,除非在建立外键时设置了related_query_name="testname"。

    比较费脑子,我的理解是,假如你知道了某个确切的外键实例对象,你想查找到对应的主表引用了这个实例对象的内容,可以使用主表_set方法;但是,假如你想批量找出符合条件的外键对象所引用的主表数据,那么就可以考虑使用related_query_name来反向(关联)查询。

    例如,有个需求,需要找出某篇文章标题包含s的分类,示例代码如下:

# 找出所有符合要求的文章
articles = Article.objects.filter(name__icontains='s')
# 列出文章的分类
categories = Category.objects.filter(testname__in=articles)
print(categories)
>>><QuerySet [<Category: 1, 热门>, <Category: 2, hot>]>

    或者先简单的记成在filter中都是使用的related_query_name(反向查询),而在实例对象中都是使用related_name(反向引用)。

    gt,greater than;

    lt,lower than;

    gte,greater equal than;

    lte,greater equal than;

    startswith;

    istartswith;

    endswith;

    range,range(start_time, end_time)=>底层sql的between语句,接受的是一个aware time(可以利用django.utils.timezone.make_aware方法),关于时间,数据库里面存的是UTC时间,但是在使用datetime模块取出来的数据时间会翻译成settings.py中TIME_ZONE= 'Asia/Shanghai'所配置的时区时间!示例代码如下:

articles = Article.objects.filter(creat_time__date=datetime(year=2020,month=12,day=7))
# articles = Article.objects.filter(creat_time__year__lte=2021)
print(articles)
>>><QuerySet [<Article: first>, <Article: sec>, <Article: third>]>

    如果结果为空,可以使用articles.query打印一下sql语句,检查看看是不是Mysql时区未配置,可参考windows配置mysql时区

    isnull,需要注意的是空字符串和null绝对不是一样的,实测数据库修改该条数据为null和空字符串的结果不同

articles = Article.objects.filter(contents__isnull=True)

    regex与iregex,正则表达式查询条件

articles = Article.objects.filter(name__iregex=r"^s")

 

ORM聚合函数

    ORM中调用聚合函数(如Avg,Count,均在django.db.models下)的使用必须是在支持这些函数的方法中使用。

    Avg,average。因为我指定了别名name_test ,否则会是id__avg(所有的聚合函数均会有这种方式)形式,aggregate函数返回的是字典。我们有两个模型Book和BookOrder,其中Book通过外键的形式关联到BookOrder,即Book的主键是BookOrder的外键,那么我们的Book是可以通过related_query_name(如果未设置则默认为BookOrder的小写形式)进行关联查询的,凡是牵涉到这种外键跨表查询必然会牵涉到联结

from django.db.models import Avg
# 获取所有图书的平均价
books = Book.objects.aggregate(name_test = Avg("id"))
print(connection.queries)
> SELECT AVG(`book`.`id`) AS `name_test` FROM `book`

# 假如想要获取《每本》图书销售的平均值
# 并返回书名定价(book) 销售平均值(bookorder)
books = Book.objects.annotate(each_book = Avg("bookorder__price"))
print(books)
><QuerySet [<Book: Book object (1)>, <Book: Book object (2)>, <Book: Book object (3)>, <Book: Book object (4)>]>
print(connection.queries[-1])
>SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, AVG(`book_order`.`price`) AS `each_book` FROM `book` LEFT OUTER JOIN `book_order` ON (`book`.`id` = `book_order`.`book_id`) GROUP BY `book`.`id` ORDER BY NULL LIMIT 21

    上面两个示例着重理解下第二个,我们想要获取每本图书的平均销售额,肯定是要分组的,使用annotate没有疑问,然后定义了别名each_book 去左外联结LEFT OUTER JOIN,取book的并集(可参阅昨日更新的Mysql联结表),即book模型中即使不存在销售额的也会被匹配。

    关于aggregate和annotate函数,aggregate返回聚合函数的字段和值,annotate会调用group by对主键进行分组,并在原来的字段上面增加一个新的字段名为each_book ,并返回一个QuerySet。

    Count,获取个数。但是Count还可以接受一个distinct参数,表示是否删除重复值。

    Max和Min取最大最小值,寻找每本图书售卖时的最大价格和最小价格示例

books = Book.objects.annotate(max = Max("bookorder__price"), min = Min("bookorder__price"),)
print(books)
print(connection.queries[-1])
for book in books:
    print(f"{book.name},{book.max},{book.min}")
return HttpResponse("success!")

    Sum,求总和。求2020年售卖的图书总价,由于filter过滤出来的是queryset,所以也是可以使用aggregate函数的(链式调用)。

books = BookOrder.objects.filter(creat_time__year=2020).aggregate(total = Sum('price'))

    F表达式,非必须,但是可以优化ORM操作数据库,F表达式不会真正去操作数据库,只是做一个标识符,告诉数据库我想要去标记数据库里面的哪个字段。例如,我们想要将所有员工薪水增加100元,那么常规做法是先从数据库中提取所有员工到python内存中,然后使用python代码为每位员工增加100元,最后再保存到数据库中,整个流程是从数据库中提取数据到python内存,在python内存中做完运算,之后再保存到数据库中。顺便去熟悉了一下python字段的update方法,用 update 更新,会有两种情况,当有相同的键时:会使用最新的字典中key对应的value 值去替换之前的值;有新的键时,会新增(该段话与本例无关)。示例如下

# 成功添加
Employee.objects.update(salary= F('salary') + 100)
# 但是我修改一下,发现名字并非我所设想的修改
Employee.objects.update(name = F('salary') + 100)
# 打印语句,悟了
'UPDATE `front_employee` SET `name` = (`front_employee`.`salary` + 100)

    可以看到,F表达式是将数据库里面的字段标识后增加100,然后再将将其set给name。F表达式一般用于需要添加、修改每条数据的字段值,例如增加每本书的售价,想获取员工表里面的两个字段内容相同的数据(示例中是名称和薪水)

res = Employee.objects.filter(name = F('salary'))

     Q表达式,我们知道在ORM中,实现AND条件的查询是很简单的,例如filter(price__gte=100, rating_gte=9),但是如果想要实现OR的筛选条件,就必须要借用Q表达式,需要注意的是,Q包括的字段是模型中的实际字段,所以不需要像F表达式一样用引号包裹;

BookOrder.objects.filter(Q(price__gte=90) | Q(book_id__gte=1))

    如果想使用AND条件,可以使用&符号,非操作,使用~Q即可。

 

QuerySet常用函数

    models.objects的objects底层拷贝了QuerySet,所以同样具有QuerySet的各种方法。

    filter,返回的也是一个QuerySet对象

(from django.db.models.query import QuerySet),需要注意的是filter不接受布尔类型的参数,例如filter(q!=2)这种写法会报错,可以使用Q表达式取反filter(Q(q=2))来操作。

    exclude,排除某个条件,示例:exclude(id=1)。

    annotate给QuerySet的每个对象都添加一个查询语句(可以是Q,F,聚合函数等)生成的新字段(“十四、ORM聚合函数”的我的理解有错误,并非一定会生成group by聚合)。看一个优化示例

books = Book.objects.annotate(author_name_F = F("author__name"))
    for book in books:
        print(book.name, book.author_name_F)
    print(connection.queries)
# 一条sql语句
>{'sql': 'SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, `author`.`name` AS `author_name_F` FROM `book` INNER JOIN `author` ON (`book`.`author_id` = `author`.`id`)', 'time': '0.000'}]
# 但是如果用下面这种,多少条数据就会产生多少条sql语句
        print(......., book.author.name)

    需要注意的是,我之前一直以为外键关联只能反向引用表名,但是实际不是,例如,Book模型中,通过author外键关联了Author模型,即Author的主键是Book的外键,上例中F("author__name")表达式中通过author名字的小写引用到了Author模型。

    order by,根据某个字段排序,支持多个排序方式,如果倒序,可在字段前增加负号。如果是调用多个order_by,以最后一个order_by为基准(我的理解是python解释到这里的时候还没有运行sql语句),同样支持跨表(联结)。

Book.objects.order_by("-create_time","pub_time")

    更便捷的方法,可以在定义的模型中的Meta属性中定义一个ordering属性,其接受一个列表,在视图层提取相关模型的数据时会默认采取其规定的排序,上例可以写成:

ordering = ["-create_time","pub_time"]

    需要注意的是order by接受的字段是双引号包裹的,例如图书按照图书销量排序,我在annotate新建的book_sum字段在排序时需要将其用双引号包裹才可引用。

Book.objects.annotate(book_sum=Count("bookorder__id")).order_by("-book_sum")

    values,提取指定的字段。返回的还是QuerySet,但是里面不再是模型对象,而是字典;如果不传递参数,则默认返回所有字段

Publisher.objects.values("name", "book__name")
# 如果我们想改变默认的输出字段名,可以使用F表达式
Publisher.objects.values("name", auh = F("book__name"))
>'name': '1出版社', 'auh': '三国演义'values_list

    values_list,返回的是元组,而非字典。由于返回的是元组,自然也就没有了名字,所以利用F表达式重命名的方法也就没有了,同样可以指定返回的字段,当只有一个参数时,可以指定flat=True

Publisher.objects.values_list("name")
>('1出版社',), ('2出版社',)

    all,获取所有的QuerySet对象,因为如Publisher.objects尽管具有QuerySet的所有方法,但是终究是一个Manager,无法迭代,遍历。

    select_related,在提取某个模型的数据时,提前将相关联的数据提取出来(常规的需要执行两次sql语句),如果是filter,是无法跨表查询的(就我目前的理解来说),注意:select_related仅可用于外键形式的多对一,而不能用于多对多或者一对多的关系,即现有模型Book,下面有author和publisher两个外键,那么他们可以通过select_related方法关联取出,而BookOrder就不行,因为BookOrder没有以外键关联到Book

bbs = Book.objects.all() #
  for bb in bbs:
    print(bb.author.name)
  print(connection.queries)
> {'sql': 'SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book`', 'time': '0.000'}, {'sql': 'SELECT `author`.`id`, `author`.`name`, `author`.`age`, `author`.`email` FROM `author` WHERE `author`.`id` = 3 LIMIT 21', 'time': '0.000'}, {'sql': 'SELECT `author`.`id`, `author`.`name`, `author`.`age`, `author`.`email` FROM `author` WHERE `author`.`id` = 4 LIMIT 21', 'time': '0.000'}, {'sql': 'SELECT `author`.`id`, `author`.`name`, `author`.`age`, `author`.`email` FROM `author` WHERE `author`.`id` = 2 LIMIT 21', 'time': '0.000'}, {'sql': 'SELECT `author`.`id`, `author`.`name`, `author`.`age`, `author`.`email` FROM `author` WHERE `author`.`id` = 1 LIMIT 21', 'time': '0.000'}

    但如果是采取select_related方法,通过bb.author.name取值时不会再次执行sql语句,还有就是select_related并非只接受一个模型,还可接受多个模型,这样下面再调用其它模型的属性时也不会再去执行额外的sql语句。

bbs = Book.objects.select_related('author','....') #
>{'sql': '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`)', 'time': '0.000'}

    prefetch_related,主要用于多对多及多对一的表关系(也就是说其也可以替代select_related方法,但是只要应用了prefetch_related,就会产生两次查询)。例如我想要查询图书下面所有订单信息。如果我们采用books = Book.objects.all()会产生很多sql语句。

    # books = Book.objects.all()
    books = Book.objects.prefetch_related("bookorder_set")
    for book in books:
        res = book.bookorder_set.all()
        print("*"*30)
        print(book.name)
        for r in res:
            print(r.id)
    print(connection.queries)

    需要注意的是,假如在prefetch_related之后还利用filter进行过滤产生了新的queryset语句,那么prefetch_related产生的结果会失效,依然会产生很多sql语句。解决办法,利用Prefetch类

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:
        # res = book.bookorder_set.filter(price__gte=90)
        res = book.bookorder_set.all()
        for r in res:
            print(r.id)
    print(connection.queries)

    defer,过滤掉某个字段,但是返回的是模型而非字典。

books = Book.objects.defer('name','price')
for book in books:
    print(book.name)
print(connection.queries)

需要注意的是,我们排除了name,那么在查询book.name的时候就会再次产生N(有多少条数据就会有多少)条sql语句。

    only,与defer相反。需要注意的是这两者都无法操作主键ID。

    get,返回一个符合条件的模型对象,多个或没有符合条件的会报错

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

    create,创建一条数据直接保存到数据库中,即不需要创建模型对象,再调用save方法保存。

Publisher.objects.create(name='zhangxiaofei')
>{'sql': "INSERT INTO `publisher` (`name`) VALUES ('zhangxiaofei')", 'time': '0.000'}

    get_or_create,如果该条数据存在,则返回一个元组(模型,布尔值),不存在则创建,需要注意的是get只能返回一个符合条件的模型对象!!超过两个则会报错。

pub = Publisher.objects.get_or_create(name='zhangxiaofei')
>(<Publisher: Publisher object (3)>, False)
# 数据库里面本来就有这条数据,所以为False
pub = Publisher.objects.get_or_create(name='xiaofei')
>(<Publisher: Publisher object (6)>, True)

    bulk_create,一次性创建多条数据,接受一个列表参数

Publisher.objects.bulk_create([
        Publisher(name='1'),
        Publisher(name='2'),
    ])
>INSERT INTO `publisher` (`name`) VALUES ('1'), ('2')

    count,计算个数,比len(models)这种方法更为高效(将满足条件的数据查出来放在内存中再去计算),count底层则是直接使用count(*)来计算。

Publisher.objects.count()
-----------
len(Publisher.objects.all())

    first和last,返回Queryset的第一条或者最后一条数据,如果没有则返回None,不会报错。

    aggregate和annotate,使用聚合函数。

    exists,判断数据是否存在,比使用count或判断QuerySet更有效。

Publisher.objects.filter(id__gte=4).exists()
>'SELECT (1) AS `a` FROM `publisher` WHERE `publisher`.`id` >= 4 LIMIT 1

    distinct,将重复数据剔除掉,返回Queryset。例如我想将BookOrder模型中价格大于或等于80的图书去重后展示。需要注意的是,mysql中的distinct判断依据是其后的所有字段内容都不一样

res = Book.objects.filter(bookorder__price__gte=80).distinct()
print(res)
> <QuerySet [<Book: Book object (1)>, <Book: Book object (2)>]>
> SELECT DISTINCT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book` INNER JOIN `book_order` ON (`book`.`id` = `book_order`.`book_id`) WHERE `book_order`.`price` >= 80.0e0 LIMIT 21

需要注意的是,如果加入了order_by函数,则去重会自动失效。

    update,示例

Book.objects.update(price = F("price") + 100)

    delete,需要注意的是模型中的on_delete形式,因为外键关联的models.CASCADE会将其它表数据也进行删除,示例

Author.objects.filter(pk=1).delete()

 

QuerySet切片

    QuerySet可以像python的列表一样进行切片取值,例如:

res = Book.objects.get_queryset()[1:2]
<QuerySet [<Book: Book object (2)>]>
>'SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book` LIMIT 1 OFFSET 1'

    可以看到切片在sql层面就利用了LIMIT 和OFFSET 来帮我们处理,所以比取值到python内存当中再进行排除更加高效。需要注意的是Manager对象并没有拷贝QuerySet的切片方法。get_queryset用于Return a new QuerySet object,那么get_queryset和all()有什么区别呢?答案是没有区别,all()底层的源码显示return self.get_queryset()。

 

Django何时会将QuerySet转化成SQL?

    迭代,遍历QuerySet对象时

    使用步长做切片操作,注意:单纯切片并不会直接转成SQL。

    Book.objects.all()[1:2:1]

    调用len函数获取QuerySet中有多少条数据。

    调用list函数讲一个QuerySet对象转换成list对象会立刻执行SQL。

    判断。如果将某个QuerySet放在if判断中,也会立刻执行。

 

Django的ORM迁移

    makemigrations,将模型生成迁移脚本,模型所在的app必须在INSTALLED_APPS当中。可以在后面跟上app_label(一个或者多个),以针对特定的app生成迁移脚本;或者指定迁移的脚本名称:--name,如果自己想定制迁移脚本,可以使用--empty来生成空的迁移文件。

python manage.py makemigrations book --name "rename_migrations"

    migrate,将新生成的迁移脚本映射到数据库中。后面跟上app_label,以将特定的app下面的迁移脚本迁移到数据库中。

    我们可以找到数据库中存在一个名为django_migrations的表,Django之所以能够不重复执行我们的迁移脚本,就在于我们每执行一次迁移脚本,则该表会同步生成一条字段为app名称,name为迁移脚本名和修改日期的数据。当然这个时间是UTC时间。

    当数据库中django_migrations迁移版本和代码中的迁移脚本版本不一致时解决方法(判断菜鸟和大神的迁移命令):--fake和--fake-initial,我们在执行migrate时必定会修改数据库的表结构,而--fake的作用就是将制定的迁移脚本名称添加到django_migrations表,但是不会真正的去执行SQL语句,修改数据库中表的结构。

    先来看下--fake命令,例如,在django_migrations表中,有5条迁移数据,但是现在我删除第5条数据,然后再重新执行迁移脚本,这个时候就会报错。因为第5条数据本来记录了price字段被删除,现在0004_remove_book_price.py再次执行删除price字段的操作,必定是报错的

    而这种原本就执行了某条迁移操作,但是由于django_migrations表中的数据不完整导致的"Can't DROP 'price'; check that column/key exists"错误就可以考虑使用--fake命令来解决,当然这个前提是建立在你对这个迁移文件及数据库很了解的前提下。

    而--fake-initial将第一次生成的迁移脚本映射到数据库,但不会真正的去执行sql语句。主要用于当你不知道数据库中的哪个地方不一致来生成同步的迁移文件。具体操作为:先将模型和数据库里面对应的表内容保持一致;

    再将对应模型的migrations文件夹(示例为book)下面的所有迁移脚本删除

    

    将django_migrations表中所有与该模型有关的app删除,如下

    

    那么此时,django_migrations和我们migrations文件夹下面的迁移脚本就一致了,再使用makemigrations生成迁移脚本。

    但此时如果我们使用migrate进行实际上的迁移,还是会报错,因为数据库中已经存在了这张book表,那么就可以使用--fake-initial或者--fake来解决了。

    showmigrations,查看某个app下面的迁移文件,默认是INSTALLED_APPS中的所有app。

    sqlmigrate,查看转化的实际sql语句,实例如下:

python manage.py sqlmigrate book 0002
>ALTER TABLE `book_book` ADD COLUMN `content` varchar(200) NULL;

 

根据已有的数据库生成ORM模型

当你配置好数据库连接时,使用如下命令,可一键将数据库中的表结构转化成ORM模型

python manage.py inspectdb > log
........
class BookBook(models.Model):
    name = models.CharField(max_length=100)
    content = models.CharField(max_length=200, blank=True, null=True)
    class Meta:
        # managed 删除或者为Ture即
        # 可让django自动帮我们处理增加或者删除的字段
        managed = False
        db_table = 'book_book'
        
class DjangoMigrations(models.Model):
    app = models.CharField(max_length=255)
    name = models.CharField(max_length=255)
    applied = models.DateTimeField()
    class Meta:
        managed = False
        db_table = 'django_migrations'

    对于以上的模型我们可以进行修正。

    修正模型名称,然后根据不同的模型丢入到不同的app中,方便管理,需要注意的是如果建立了外键关联,当需要引用不同app下面的模型时,需要写完整路径 ForeignKey('Article.Tag'),即app.model的形式。

    删除managed = False,这样django才可以管理模型中的字段(如我们使用python manage.py makemigrations生成迁移脚本是因为我们的managed = true,如果为False,那么是无法生成正确的迁移脚本的)。ManyToManyField的可以通过db_table来指定数据库里面的表名。

    此时就可以使用如下命令将相应模型的脚本生成

python manage.py migrate front
# python manage.py migrate front --fake-initial
# django3.1亲测不用使用--fake-initial


参考:

# 官方field-types文档
https://docs.djangoproject.com/zh-hans/3.1/topics/db/models/#field-types