Django系列 3:ORM 常用技巧详解

1:查询集的原生SQL

注意:像values, values_list的查询集没有query属性

queryset=Event.objects.all()

print(queryset.query)

2:OR查询

from django.db.modelsimport Q

第一种:queryset_1 | queryset_2

第二种:filter(Q()|Q()  

3:AND查询

from django.db.modelsimport Q

第一种:filter()

第二种:queryset_1 & queryset_2

第三种:filter(Q() & Q())  

4:NOT 查询

from django.db.modelsimport Q

第一种: exclude()

第二种: filter(~Q())

5:UNION查询 (并集)

关于MySQL union 操作可以参考:MySQL 内连接、左连接、右连接

注意:union操作只能在查询集拥有相同的字段和数据类型前提下执行,如果两个查询集的字段和数据类型不一样,union操作出现错误。你可以两个models上执行union,只要他们有相同字段或相同字段子集。

Hero.objects.all().values_list("name","gender").union(Villain.objects.all().values_list("name","gender"))

6:查询集中的部分字段

第一种: 查询集的 values 和 values_list 方法

               values :查询集queryset是一个django.db.models.query.QuerySet对象, 迭代的每个元素是一个字典

               values_list :查询集queryset是一个django.db.models.query.QuerySet对象, 迭代的每个元素是一个元组

第二种: only 方法,

               查询集queryset是一个django.db.models.query.QuerySet对象, 迭代的每个元素是一个模型类的实例

             (1):你可以访问每个实例的only方法指定的其他字段

             (2):每个实例的 __dict__ 只含有 _state、id、指定的字段

             (3):可以对迭代的实例进行 obj.aaa = 1000, obj.save() 操作

7:子查询

各家公司对子查询的标准不太一样,对于数据量比较大的公司一般可能不适用子查询,因为在生产中会产生查询延迟拖慢系统的或网站的整体访问效率

8:基于字段值比较标准来筛选查询集

注意:比较的两个字段的类型要求一致

          from django.db.modelsimport F

          from django.db.models.functionsimport Substr

        (1):全字段相等  User.objects.filter(last_name=F("first_name"))

        (2):首字母相同 UsersModel.objects.annotate(name=Substr("email", 1, 1), mail=Substr("username",

                     1, 1)).filter(name=F("mail")).values("id", "username", "email")

         SQL:   SELECT "circle_users"."id", "circle_users"."username", "circle_users"."email" FROM                                          "circle_users"   WHERE SUBSTRING("circle_users"."email", 1, 1) =                                                                       SUBSTRING("circle_users"."username", 1, 1)

9:annotate 高级查询方法

用于分组与统计,通常与聚合函数一起使用

from django.db.modelsimport Count

(1):CircleUsersModel.objects.annotate(cnt=Count("department_chz")).values("department_chz", "cnt")

  SQL:SELECT "circle_users"."department_chz", COUNT("circle_users"."department_chz") AS "cnt" FROM                 "circle_users" GROUP BY "circle_users"."id"  (其中AS 的 `cnt` 是自定义的名字)

(2):CircleUsersModel.objects\

             .values("username", "first_name")\

             .annotate(name_count=Count("username"))\

             .filter(name_count__gte=2)

SQL: SELECT "circle_users"."username", "circle_users"."first_name", COUNT("circle_users"."username") AS            "name_count" FROM "circle_users" GROUP BY "circle_users"."username", "circle_users"."first_name"               HAVING COUNT("circle_users"."username") >= 2

(3):CircleUsersModel.objects\

             .annotate(name_count=Count("username")).filter(name_count__gte=2)\

            .values("username", "first_name")

SQL: SELECT "circle_users"."username", "circle_users"."first_name" FROM "circle_users" GROUP BY                        "circle_users"."id" HAVING COUNT("circle_users"."username") >= 2

10:aggregate 聚合函数

注意: 改方法返回的不再是 django.db.models.query.QuerySet 对象, 而是一个字典;如果我要对 QerySet 中每个元素都进行聚合计算、并且返回的仍然是 QuerySet ,那就要用到 annotate() 方法了。

from django.db.modelsimport F, Count, Q, Avg, Max, Min, Sum

result = CircleUsersModel.objects.aggregate(

                 mix_id=Min("id"),

                 max_id=Max("id"),

                count=Count("id"),

                avg=Avg("id")

            )

result: {'mix_id': 15, 'max_id': 4175, 'count': 4159, 'avg': 2095.8946862}

原生SQL: SELECT MIN("id"), MAX("id") ,COUNT("id"), SUM("id") FROM "circle_users"

aggregate 的结果集是一个字典,所以你无法通过 result.query 来打印原生sql语句。

11:extra  

有些情况下,Django的查询语法难以简单的表达复杂的 WHERE 子句,对于这种情况, Django 提供了extra()QuerySet修改机制 — 它能在QuerySet生成的SQL从句中注入新子句。更多用户可以参考其他博客

注意:为了防止SQL注入,select 和 where 中的参数在select_params 和 params 中填充

queryset = CircleUsersModel.objects\

                  .extra(select={"bbs": "create_time > %s"}, select_params=["2020-11-24 12:00:00"],

                            where=['username like %s', "id> %s"], params=["丁%", "4000"])\

                  .values("id", "username", "bbs")

SQL:  SELECT (create_time > 2020-11-24 12:00:00) AS "bbs", "circle_users"."id", "circle_users"."username" FROM "circle_users" WHERE (username like 丁%) AND (id> 4000)

结果:

                                {'bbs': False, 'id': 4007, 'username': '丁星平'}, {'bbs': False, 'id': 4016, 'username': '丁备'},                                      {'bbs': False, 'id': 4035, 'username': '丁菊花'}, {'bbs': False, 'id': 4083, 'username': '丁楠'},                                      {'bbs': False, 'id': 4100, 'username': '丁君'}, {'bbs': False, 'id': 4101, 'username': '丁君'},                                        {'bbs': False, 'id': 4125, 'username': '丁锦如'}

            ]> 

12:查找具有重复字段值的列 (其实就是分组与过滤)

duplicates = User.objects.values( 'first_name' )\

                    .annotate(name_count=Count('first_name'))

                    .filter(name_count__gt=1) 

13:从查询集中找到独一无二的字段值 (同上)

distinct = User.objects.values('first_name').annotate(name_count=Count('first_name')).filter(name_count=1)

records = User.objects.filter(first_name__in=[item['first_name'] for item in distinct]) 

这个和 User.objects.distinct("first_name").all() 不同, User.objects.distinct("first_name").all() 会获取遇到的不同的first_name时的第一条记录。(注意部分数据库如MySQL,distinct用法是 User.objects.value("first_name").distinct())

14:Q的复杂查询

前面我们使用Q 对象来做 OR 、 AND 和 NOT 运算。Q 对象给你在where查询上绝对的控制权。

queryset = User.objects.filter( Q(first_name__startswith='R') & ~Q(last_name__startswith='Z'))

queryset = User.objects.filter( Q(first_name__startswith='R') & Q(last_name__startswith='D'))

15:一次创建多个对象

Category.objects.bulk_create([

          Category(name="God"),

         Category(name="Demi God"),

         Category(name="Mortal")

])

16:复制一个现有的模型对象

 Hero.objects.all().count()         =>  4

hero = Hero.objects.first()

hero.pk = None

hero.save()

Hero.objects.all().count()          =>  5

17:model signals

Django 提供了用于模型对象创建和删除周期的信号钩子。这些Django提供的信号是:

pre_init、post_init、pre_save、post_save、pre_delete、post_delete

在这其中最常用的信号是 pre_save 和 post_save 。我们来仔细看看他们

信号 vs 重写.save()

既然信号和重写 .save 有类似的作用,用哪一个是一个时常困惑的问题。这里给出了何时需要用哪一个。

如果你想其他人,如第三方应用,重写或定制对象的 save 行为,你应该抛出你自己singnals。

如果你想要介入一个你无权控制更改的应用中的 save 行为,你应该使用 post_save 或 pre_save 钩子信号。

如果你想定制化你自己的应用的 save 行为,你可以重写 save 方法。

18:不区分大小写的方式排序查询集

第一种:

             from django.db.models.functions import Lower

             User.objects.all().order_by(Lower('username')).values_list('username',flat=True)

第二种:

            User.objects.annotate(uname=Lower('username')).order_by('uname').values_list('username',flat=True)

19:转化已有的数据表到Django模型

python manage.py inspectdb > models.py

20:使用Django的slug字段提高代码可读性

slug 是url的一部分,它以用户可读的形式标识一个网页中的特殊页面。为了能让它工作,Django 为我们提供了slugfield字段。可以和下面一样使用它。我们已经有一个模型 Article ,我们要添加一个slugfield使其具有有用户可读性。

from django.utils.text import slugify 

class Article(models.Model):

         headline = models.CharField(max_length=100) 

         ......

          slug = models.SlugField(unique=True) 

def save(self, *args, **kwargs): 

         self.slug = slugify(self.headline) 

         super(Article, self).save(*args, **kwargs)  

 >>> u1 = User.objects.get(id=1)

 >>> from datetime import date

 >>> a1 = Article.objects.create(headline="todays market report", pub_date=date(2018, 3, 6), reporter=u1) 

>>> a1.save() // slug here is auto-generated, we haven't created it in the above create method. 

>>> a1.slug 'todays-market-report'     

Slug字段如此有用是因为:

它是人性化的(如/blog/1 而不是/1/)。

在标题,头部和url中创建一致性是一种不错的SEO。

21:ORM 各种条件查询关键字

注意:必须是model字段名后面紧跟着二个下划线

__exact        精确等于 like ‘aaa’ 

__iexact        精确等于 忽略大小写 ilike ‘aaa’ 

__contains    包含 like ‘%aaa%’ 

__icontains    包含 忽略大小写 ilike ‘%aaa%’,但是对于sqlite来说,contains的作用效果等同于icontains

__gt                 大于 

__gte               大于等于 

__lt                 小于 

__lte              小于等于 

__in              存在于一个list范围内 

__startswith         以…开头 

__istartswith         以…开头 忽略大小写 

__endswith         以…结尾 

__iendswith         以…结尾,忽略大小写 

__range              在…范围内 

__year                 日期字段的年份 

__month             日期字段的月份 

__day                 日期字段的日 

__isnull=True/False

你可能感兴趣的:(Django系列 3:ORM 常用技巧详解)