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中的代码还不够,还需要执行数据库迁移命令,打开如下终端
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用来筛选数据的,像筛选指定字段等于指定值的数据可以直接用字段=值传入,但如果想要筛选某个范围的数据呢?
双下划线方法 | 作用 |
---|---|
__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',
},
}
}