ORM基本使用

ORM简介

ORM即对象关系映射,它能够让不会写SQL的python程序员使用python语法就可以直接操作MySQL。

  • 优点:提升了开发效率;
  • 缺点:封装了SQL语句,有时候可能效率不高,还需要人为编写SQL调优;

在ORM中有如下对应关系:

MySQL ORM
字段 对象的属性
记录 类产生的对象

ORM基本使用之创建表

如果需要使用ORM创建表,需要去应用文件夹下的models.py中编写代码。

class User(models.Model):
    id = models.AutoField(primary_key=True)
    username = models.CharField(max_length=32)
    password = models.IntegerField()

以上代码等价于SQL语句:

create table user(
	id int primary key auto_increment,
    username varchar(32) not null,
    password int not null
);

编写完models.py中的代码还不够,还需要执行数据库迁移命令,打开如下终端

image

1.首先是记录操作,在终端输入下面的命令,这时应用文件夹下的migrations文件夹会多出一个py文件。

makemigrations

2.然后将操作迁移到数据库,在终端输入下面的命令,此时数据库中就会出现表了,首次执行迁移命令,django还会自动创建一些默认需要使用到的表。

migrate

补充说明

1.表的名称:通过ORM创建的表的名称都是这样的形式

应用名_类名

这是为了区分不同应用下可能会出现相同的表名。

2.表的主键也可以不写,ORM会自动帮你写一个字段为id的主键。

3.每次修改了跟数据库相关的python代码,都需要重新执行迁移命令

表数据操作

语句 含义
models.类名.objects.create(字段=值,...) 添加数据
models.类名.objects.filter(条件).delete() 删除数据
models.类名.objects.filter(条件).update(字段=值) 更新数据
models.类名.objects.filter(条件) 查询数据

这里的models为应用文件夹下的models.py文件

添加数据

根据表的字段添加数据

models.User.objects.create(username='tom', password=123)

等价于SQL语句

insert into user(username, password) value('tom',123)

添加数据时会返回一个对象,可以获取其字段值

user_obj = models.User.objects.create(username='tom', password=123)
print(user_obj)  # User object
print(user_obj.id)  # id字段值
print(user_obj.username)  # username字段值
print(user_obj.password)  # password字段值

删除数据

models.User.objects.filter(id=1).delete()

等价于SQL:

delete from user where id=1;

删除数据时会返回一个元组,格式如下:

(受影响行数,{类名: 受影响行数})

更新数据

models.User.objects.filter(id=1).update(password=111)

等价于SQL:

update user set password=111 where id=1;

更新数据时会返回一个整型,代表mysql中被影响的行数。

查询数据

res = models.User.objects.filter(id=1)
print(res)  # <QuerySet [<User: User object>]>
print(res[0])  # Users object
print(res[0].id)  # id字段值
print(res[0].username)  # username字段值
print(res[0].password)  # password字段值

等价于SQL语句:

select * from user where id=1;

all()与filter()

方法 作用
all() 查询所有数据
filter(条件) 筛选数据

在filter()不写参数时,与all()返回的结果是一致的。

这两个方法返回的结果都是QuerySet,可以看成是一个列表里放了多个对象,如果想要取出里面的对象可以用索引的方式:

QuerySet[0]  # 取里面的第一个数据对象

也可以用一些方法取出对象:

QuerySet.first()  # 取里面的第一个数据对象
QuerySet.last()  # 取里面的最后一个数据对象

这两种取对象的方式使用第二种更好一些,因为就算QuerySet里没有对象取出时也不会报错。

对于filter()筛选,有专门针对主键筛选的参数:

filter(pk=)  # 通过主键筛选

ORM操作常见方法

方法 作用
values(字段1,字段2...) 查询指定字段,返回值相当于列表套字典
values_list(字段1,字段2...) 查询指定字段,返回值相当于列表套元组
distinct() 将查询出来的数据去重
order_by(字段) 默认根据指定字段升序排序,字段前加'负号(-)'可以改成降序
order_by(字段1,字段2...) 多字段排序
exclude(条件) 查询不符合条件的数据
reverse() 查询结果倒置,只有在排序后在起作用
count() 返回结果集个数
exists() 判断结果集中是否有数据,有返回True,没有返回False
get() 直接获取数据对象,但是数据不存在会报错

ORM之双下划线查询

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

image

双下划线方法 作用
__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()

image

多表查询

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对象。

image

双下划线扩展

双下划线不仅可以用于查询,也可以用于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',
        },
    }
}
posted @ 2022-05-17 19:38  Yume_Minami  阅读(367)  评论(0编辑  收藏  举报