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(<condition_1>)|Q(<condition_2>)  

3:AND查询

from django.db.modelsimport Q

第一种:filter(<condition_1>, <condition_2>)

第二种:queryset_1 & queryset_2

第三种:filter(Q(<condition_1>) & Q(<condition_2>))  

4:NOT 查询

from django.db.modelsimport Q

第一种: exclude(<condition>)

第二种: filter(~Q(<condition>))

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)

结果:<QuerySet [

                                {'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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容