ORM相关操作


目录
  • ORM之双下划线查询
  • 外键字段的创建
  • 外键字段操作
    • 一对多、一对一外键操作
    • 多对多外键操作
  • 多表查询
  • 基于对象的跨表查询
  • 基于双下划线的跨表查询
  • 双下划线扩展
  • 查看SQL语句

ORM之双下划线查询

filter()我们都知道是ORM用来筛选数据的,像筛选指定字段等于指定值的数据可以直接用字段=值传入,但如果想要筛选某个范围的数据呢?

双下划线方法 作用
__gt 大于
__gte 大于等于
__lt 小于
__lte 小于等于
__in 成员运算
__range 筛选范围内数据,包含开头和结尾
__contains 筛选字段包含值的数据,区分大小写
__icontains 筛选字段包含值的数据,不区分大小写
__startswith 筛选字段是否以某个值开始的,区分大小写
__endswith 筛选字段是否以某个值结尾的,区分大小写
__month 按照月份筛选数据
__year 按照年份筛选数据

案例:

# 查询age字段大于20的用户
res = models.User.objects.filter(age__gt=20)

# 查询age字段是18、22、25的用户
res = models.User.objects.filter(age__in=[18, 22, 25])

# 查询age字段在18到26之间的用户
res = models.User.objects.filter(age__range=[18, 26]) 

# 查询name字段中包含字母j的用户
res = models.User.objects.filter(name__contains='j')

# 查询name字段中以字母j开头的用户
res = models.User.objects.filter(name__startswith='j')

# 查询create_time字段是5月的数据
res = models.User.objects.filter(create_time__month=5)

外键字段的创建

因为外键关系有三种,所以在ORM也有三种创建外键字段的方法。

创建一对多关系:

models.ForeignKey(to=表(类)名)

创建一对一关系:

models.OneToOneField(to=表(类)名)

创建多对多关系:

models.ManyToManyField(to=表(类)名)

以上的创建外键字段都是默认与另外一个表的主键建立关系。

注意事项:

ManyToManyField不会在表中创建实际的字段,而是告诉django orm自动创建第三张关系表。

ForeignKey、OneToOneField会在字段的后面自动添加_id后缀,所以不要自己加_id后缀。

外键字段操作

一对多、一对一外键操作

一对多与一对一关系操作方式都一样,与其他字段操作方式都一样,唯一特殊的地方就是它可以用对象添加值。

使用普通的方式添加数据要用表中的字段名,使用对象添加数据要用类中的字段名。

比如我用ORM创建了一个有外键的表Student(uid[主键],name,clazz_id[外键]),与表Clazz建立一对多关系。类Student中的外键字段属性名是clazz。

# 普通添加数据
models.Student.objects.create(name='tom', clazz_id=1)
# 用获取的对象添加数据
cla_obj = models.Clazz.objects.filter(cid=2).first()
models.Student.objects.create(name='tom', clazz=cla_obj)

修改数据同理。

操作外键字段需要注意字段名称,不能用你在类里面定义的外键字段名称,因为ORM创建外键字段会自动添加_id后缀。

多对多外键操作

多对多外键操作需要用到新的方法,因为它是作用在第三张表的。

比如我用ORM创建了一个有外键的表Student(uid[主键],name,course_id[外键]),与表Course建立多对多关系。

添加数据:add(),括号内可以放主键值也可以放数据对象,并且都支持多个。

# 建立表Student中主键为1与Course中主键为1的关系
stu_obj = models.Student.objects.filter(pk=1).first()
stu_obj.course.add(1)

# 建立表Student中主键为2与Course中主键为1和2的关系
stu_obj = models.Student.objects.filter(pk=2).first()
stu_obj.course.add(1, 2)
"""数据对象同理"""

修改数据:set(),括号内必须是一个可迭代对象,元素同样支持主键值或者数据对象。

# 修改表Student中主键为1只与Course中主键为2建立关系
stu_obj = models.Student.objects.filter(pk=1).first()
stu_obj.course.set([2,])  # 必须是可迭代对象

# 修改表Student中主键为2只与Course中主键为1,2,3建立关系
stu_obj = models.Student.objects.filter(pk=2).first()
stu_obj.course.set([1,2,3])  # 必须是可迭代对象
"""数据对象同理"""

删除数据:remove(),括号内可以放主键值也可以放数据对象,并且都支持多个。

# 移除表Student中主键为1与Course中主键为1的关系
stu_obj = models.Student.objects.filter(pk=1).first()
stu_obj.course.remove(1)

# 移除表Student中主键为2与Course中主键为1,2的关系
stu_obj = models.Student.objects.filter(pk=2).first()
stu_obj.course.remove(1,2)
"""数据对象同理"""

清空指定数据:clear(),括号内无需传值,直接清空当前表在第三张关系表中的绑定记录。

# 清空表Student中主键为1与Course的所有关系
stu_obj = models.Student.objects.filter(pk=1).first()
stu_obj.course.clear()

多表查询

ORM与MySQL多表查询思路区别:

子查询:

  • MySQL:将SQL语句用括号括起来当做条件使用
  • ORM:基于对象的跨表查询

连表查询:

  • MySQL:使用inner join\left join\right join\union
  • ORM:基于双下划线的跨表查询

在了解ORM的多表查询前,先了解是什么正反向查询。

正反向查询的概念

正向查询:使用含有外键字段的数据对象进行查询。
反向查询:使用不含有外键字段的数据对象进行查询。

比如学生表与班级班建立一对多关系,外键字段在学生表中,通过学生查询班级就是正向查询,通过班级查询学生就是反向查询。

核心在于当前数据对象是否含有外键字段,有则是正向,没有则是反向。

查询口诀

正向查询按外键字段名查询,反向查询按表名小写查询。

基于对象的跨表查询

基于对象的跨表查询优先获取对象,再通过对象去查询。

如果获取的对象结果可能有多个,那么需要在调用all()才能获取对象,比如一对一、一对多关系的正向查询中,肯定都是只获取一个对象结果的,所以不需要再调用all(),而多对多关系的正向查询都是可能会有多个对象结果,所以需要用到all()。

all()方法返回的是一个QuerySet对象。

现在有如下表:一对多关系

  • Student(sid[主键],name,clazz_id[外键])
  • Clazz(cid[主键],name)

注意clazz_id字段是ORM帮我取得,我自己的类中的外键字段名称为clazz,ORM自动帮我添加了_id后缀,这里基于对象的正向查询的时候使用的都是clazz,而不是clazz_id。使用clazz获取的是对象,使用clazz_id获取的是外键字段值。

正向查询

查询Student中name为'tom'的班级

# 1.先获取name='tom'的数据对象
stu_obj = models.Student.objects.filter(name='tom').first()
# 2.获取该对象的外键对象
res = stu_obj.clazz  # 返回一个Clazz表的对象

如果是多对多关系的正向查询,需要在调用all()。

反向查询

查询Clazz中name为'一班'的所有学生

# 1.先获取Clazz中name='一班'的数据对象
cla_obj = models.Clazz.objects.filter(name='一班').first()
# 2.通过表名加_set查询
res = cla_obj.student_set  # 返回None

因为一对多的反向查询获取的对象可能有多个,那么需要在调用all()才能获取对象。

# 1.先获取Clazz中name='一班'的数据对象
cla_obj = models.Clazz.objects.filter(name='一班').first()
# 2.通过表名加_set查询,并加上all()
res = cla_obj.student_set.all()  # 返回QuerySet

针对一对一的反向查询,不需要加_set。

基于双下划线的跨表查询

基于双下划线的跨表查询本质就是连表操作。如果是正向查询,查询时用外键字段名加双下划线加字段查询;如果是反向查询,查询时用表名加双下划线加字段查询;现在看不懂啥意思没关系,往下看就行了。

现在有如下表:一对多关系

  • Student(sid[主键],name,clazz_id[外键])
  • Clazz(cid[主键],name)

我们都知道如果想要查询Student中name='tom'的sid字段和clazz_id字段,可以这么写:

res = models.Student.objects.filter(name='tom').values('sid', 'clazz_id')

基于双下划线的跨表查询也是类似这么写的。

正向查询

查询Student中sid为1的班级名称

res = models.Student.objects.filter(sid=1).values('clazz__name')
# 或者
res = models.Student.objects.filter(sid=1).values('clazz_id__name')

可以使用类中外键字段名查询,也可以用表中的外键字段名查询,代码中的clazz代表的是外键字段名,而不是表名,name代表的是clazz外键字段所连接的表中的字段。

反向查询

查询Clazz中cid为1的所有学生姓名

res = models.Clazz.objects.filter(cid=1).values('student__name')

代码中的student代表的是表名,name代表student表中的字段。

基于双下划线的跨表查询返回的都是QuerySet对象。

双下划线扩展

双下划线不仅可以用于查询,也可以用于filter()筛选数据。

比如现在有如下表:一对多关系

  • Student(sid[主键],name,clazz_id[外键])
  • Clazz(cid[主键],name)

现在只能通过操作Clazz表,并且要查询出Student表中name='tom'的班级名称。

res = models.Clazz.objects.filter(student__name='tom').values('name')

在filter()的student__name中,student代表表名,name代表student中的name字段。

连续跨表操作

双下划线还可能连续跨表操作,跨表的前提,表与左右的表有关系。

比如:

res = models.Clazz.objects.filter(student__course__name='语文')

上述代码已表Clazz为起点,先到表Student,在从表Student到表Course中的name字段。以上代码的前提是,clazz与student有关系,student与course有关系。

筛选如此,查询同理。

查看SQL语句

方式1:如果结果集对象是QuerySet,那么可以直接.query查看:

QuerySet.query

方式2:配置文件固定配置,适用面更广,只要执行了orm操作,都会打印内部SQL语句。在settings.py文件夹下添加:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}