对象关系映射模型是通过面向对象的方式来操作数据库,这就需要对应的关系映射,数据中可以分为库,表,字段信息,一条条数据,而需要用面向对象的关系去对应。于是就有了下面对应关系。

数据库  --   面向对象模型
  表   <-->    类
 字段  <-->   类属性
 记录  <-->   每个实例

Django中的关系映射

使用面向对象的方式描述数据库的关系模型,Django采用了以下的方式。

class Employees(models.Model):   # 类名
    class Meta:                  # Meta类中指定表元数据
        db_table = "Employees"    # 指定数据库中的表名,否则为类名小写

    # 每一个model的Field类型属性都对应数据库表中的一条字段。数据类型通过不同类型的Field属性指定,约束条件通过参数传递的方式
    emp_no = models.AutoField(primary_key=True, null=False)  
    birth_data = models.DateField(null=False)                # 对应日期类型
    first_name = models.CharField(null=False, max_length=14)
    last_name = models.CharField(null=False, max_length=16)  # 对应varchar 类型
    gender = models.SmallIntegerField(null=False)            # 对应int类型
    hire_data = models.DateField(null=False)

Fieldl类型

常用类型 说明
AutoField 自增整数类型
BigAutoField 更大自增整数类型
BigIntegerField 大整数
BooleanField 布尔类型True或者False
CharField 字符串类型
DateField 日期类型
DateTimeField 日期时间
DecimalField 使用python的Decimal实例表示10进制浮点数,需要极高精度的数值使用该字段
EmailField 能做Email检验,基于CharField,最大254
FileField 文件字段
FilePathField 文件路径
FloatField 浮点数
ImageField 继承了FileField,但是对上传的文件进行检验,确保为一个图片
IntegerField int类型
GenericIPAddressField Ipv4/Ipv6校验
NullBooleanField 布尔值可以为空
PositiveIntegerField 正整数
TextField 大文本,超过4000字符
TimeField 时间类型
URLField 能做URL检验,最大200

关系映射

ForeignKey一对多关系

关系类型字段可以将两张表进行关联,该字段在一对多方字段建立。例如官方的实例,一个Manufacture可以生产多个车型的汽车,所以在车辆表中建立一个外键(多端),标识该汽车类属于哪一个Manufacture

class Manufacturer(models.Model):
    # ...
    pass

class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
    # ...

创建外键至少需要指定两个参数,指定关联的类和on_delete,主表字段和从表字段之间联系,上面指定为级联,即主表中的某个数据删除,从表中关联了该主表数据的数据将会被删除。

上面Car中的manufacturer字段与Manufacture类进行了关联,在生成的manufacture表中,将会多出一个Car_set的字段,可以通过一条Manufacture信息查询出多条Car信息。而Car中manufacturer字段将被设置为manufacture_id,该数据将会关联到Manufacture中对应的数据。

ManyToManyField多对多关系

官网的示例:一种Topping可能存在于多个Pizza中,并且每个Pizza含有多种Topping,这就是一个多对多的关系

class Topping(models.Model):
    # ...
    pass

class Pizza(models.Model):
    # ...
    toppings = models.ManyToManyField(Topping)

这个属性可以在关联的任何一个类中创建,不能同时在连个类中添加该字段。一般来讲,应该把 ManyToManyField 实例放到需要在表单中被编辑的对象中

在定义的字段类型中,指定某些参数选项,可以将这个字段做一些特殊的设置或者做一些特殊的验证。主要包括以下的字段选项。

字段参数选项

选项 说明
null 是否可以null
blank 是否可以为空
choices 类似枚举
db_column 指定数据库列名
db_index 是否在该字段建索引
db_tablespace  
default 默认值
editable  
error_messages 指定后覆盖默认的错误信息
help_text 指定帮助信息
primary_key 定义为主键
unique 定义为唯一键
unique_for_date  
verbose_name 字段备注名
validators models.IntegerField(validators=[validate_even]),添加验证函数

Manager管理器对象

管理器对象用于实现表的增删改查。管理器对象是类型为django.db.models.manager.Manager类型,管理器对象可以在定义模型类中创建,如果没有创建,默认会创建一个objects管理器,手动创建后将不会自动创建。

管理器Django查询数据库的接口,每一个模型都至少有一个管理器,用户自定义管理器模型需要继承于django.db.models.manager.Manager类。

内部测试 manager管理器

根下创建一个Python 文件,写入以下内容即可使用

# 从wsgi复制

import os
from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ORM.settings')
application = get_wsgi_application()

# 将模型类导入,也就是定义模型类的model文件,
from salary.models import Employees

# object 是Employees自带的一个管理器,这样我们可以使用这个管理进行查询
mgr = Employees.objects

print(mgr.all())

配置日志输出

import os

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
            'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
        },
    },
}

查询

使用管理器对象查询,将会返回一个查询集,Django内部将这些结果封装为一个Django.db.models.manager.query.Query类型。这个结果是惰性(只有需要时才会真的查询),可以进行迭代。查询的结果不会缓存,再次使用必须再次从数据库查询。

mgr = Employees.objects

result_sset = mgr.all()  # 查询所有结果,将整个表查询返回结果集
print(mgr.all()) # 获取数据时,才会执行sql查询

并且由于我们的日志配置,我们可以在日志中看到执行sql语句信息,这样方便我们对查询进行调优。

查询方法

方法 说明
all() 查询所有结果的查询集
filter() 过滤查询,类似于where
exclude() 过滤的结果取反
order_by() 排序结果 类似于order by
values() 返回这个查询集,每一个元素是{字段:值}的字典

 

方法 说明
get(pk=1) 返回单个值,没有数据和多条数据都将报错
count() 返回查询的总条数
first() 返回第一个对象
last() 返回最后一个对象
exist() 如果能查到返回True

条件表达式

filter,exclude,get等方法中需要控制查询条件。django使用了一套条件表达式作为查询方法中的参数,实现查询条件筛选。

表达式参数 说明

=,

exact

iexact

等于,mgr.filter(id=1)

 

不等于 mgr.filter(id__iexact=3)

gt, gte 大于,大于等于, mge.filter(id__gt=10001)
lt, lte 小于,小于等于 mgr.exclude(id__lte=10010)

startwith

endswith

首尾匹配,大小写敏感

isnull

isnotnull

是否为null
in 在某个范围,mgr.filter(pk__in=(1,2,3))

contains

icontains

包含指定的值,%值%,效率极低

iexact

icontains

istartwith

iendswith

i,表示忽略大小写

year

month

判断一个时间,需要时时间类型的字段,mgr.filter(birth_date__year=2000) 年份为2000的

Q 对象

在一个函数中连续写条件表示这些条件是and(与)关系,并不能表示(or)或(!)非的关系,此时需要使用Q对象,Q对象可以使用 |或, &与, ~非;来描述条件之间的关系。

Q 对象的类型:django.db.models.Q

from django.db.models import Q

# 使用 Q 对象处理这个条件,并使用 | 连接,表示或关系,& 表示与
mgr.filter( Q(id__lt=10005) | Q(lastname__startswith="K") & Q(age=12))  # 与或
mgr.filter( ~Q(id__lt=10005))                  # ~ 表示取反

分组,聚合

聚合函数

from django.db.models import Q, Avg, Sum, Max, Min, Count

aggregate

将所有列的数据分为一组进行聚合

UseInfo.objects.all().aggregate(Sum("age"))   # 所有年龄之和,自动使用主键分组

返回一个字典{"age__sum": 120}, 如果想控制age_sum这个key的名字,使用aggregate(agesum=Sum("age"))key将会替换成agesum。

annotate()

annotate可以和value组合实现分组聚合

from django.db.models import Q, Avg, Sum, Max, Min, Count
# emp_no 员工编号
# salary 工资

# 使用emp_no分组,计算每个员工的工资总和
s2 = sal.all().values("emp_no").annotate(Sum("salary"))   # 返回查询后的结果集

# 只会显示两个字段
# <QuerySet [{'emp_no': 1, 'salary__sum': 1281612}, 
             {'emp_no': 2, 'salary__sum': 413127}]}>
# 显示所有字段使用values,或者在values中指定想要的字段,没有使用聚合函数的行使用第一行数据为准
s2.values()
# 还可以对其继续排序。等操作
s2.values().order_by("salary_sum")

Raw

直接使用原生的sql语句

empmgr = Employee.objects
raw_query = "select * from Employee where id > %s"

emp = empmgr.raw(raw_query, params=None)

extra

表示添加额外的部分,select参数表示再select处添加字段以及值,所以使用字段key-value,where表示添加额外的条件,使用列表,and连接内部元素。tables参数表示关联的表。

Userinfo.objects.all().values("id", name)
            .extra(
                select={
                    "total":"select count("id") from table ",
                    "height":"select count("height") from table where gengder=%s",
                    },
                select_params=[1,男] 
                where=["user_type.id=userinfo.type_id"],
                params=[0, 18]
                tables="app01.user_type"
            )
    等价的sql
        "select 
        id, name, 
        select count("id") from table as "total", 
        select count("height") from table as height
        
        form (userinfo, app01.user_type) 
        where user_type.id=userinfo.type_id

select_related

会根据指定的字段做连表查询

Userinfo.objects.all().select_related("group")    # 查询group 表的内容

select * from `userinfo` inner join `group` on userinfo.group_id = group.id

prefetch_related

效果同select_releted,但是执行多次单表查询,用连表字段的条件进行查询。

Userinfo.objects.all().perfetch_releted("group")    

select 其他字段, group_id from userinfo              # 查处所有group_id
select * from group where group.id in group_ids     # 更具id查出所有组

ManytoMany

class Boy:
    id = AutoField()
    name = models.Charfield()
    friend = models.ManyToMany("Girl")     # 会自动建立第三章表
    # f = models.ManyToMany("Girl", through="Friend", through_field = ["b", "g"])
    # 使用我们自己建立的第三章表来关联,指定表和字段,第三章表的b,g一定是分别做了外键的才可以
    
class Girl:
    id = AutoField()
    name = models.Charfield()
    
    # boy_set  反向找boy对象的属性
    
    
obj = models.objects.get(name="小明")
# 操作这个第三张表,进行查询操作
obj.friend.add(10)      # 对应的一个girl的id值
obj.friend.remove(10)   #  
obj.friend.set([10, 20])        # 设置
obj.friend.clear()
obj.friend.all()        # 获得所有关联的girl对象,是一个查询集。可以继续filter查询

foreigenKey实现多对多

使用第三张表,第三张表两个字段,分别于其他两个表做外键

class Boy:
    id = AutoField()
    name = models.Charfield()
    
class Girl:
    id = AutoField()
    name = models.Charfield()

class Friend:
    b = models.ForeigenKey("Boy")
    g = models.ForeigenKey("Boy")

如果需要查询时小明的所有女性朋友名字,可以有三种方式,

# 小明的所有女性朋友名字
# 第一种
boy = Boy.objects.filter(name="小明").first()   # 小明对象
for f in boy.Friend_set:    # Friend中和小明有关的所有Fiend对象
    g_name = f.g.name        # 每个f对象关联了一个girl对象获得
# for 循环中出现了连表,出现了N+1多次查表得问题

# 第二种: 使用join连表,从中间表起手,使用values连表或者select_related都可以
friends = Friend.objects.filter(b__name="小明").values("g__name")  # 使用了join连表,得到了
for friend in friends:
    g_name = friend.g_name

# 第三种:perfetch_related多次单表查询
friends = Friend.objects.filter(b__name="小明").perfetch_related("g__name")  # 使用了join连表,得到了
for friend in friends:
    g_name = friend.g_name

建立联合唯一索引

class friend:
    g = field()
    b = field()
    class Meta:
        unique_together=["g", "b"]