当前位置:首页 > 技术 > 正文内容

深入解析Django ORM:模型构建、跨表查询与高级操作

访客 技术 2026年10月15日 4

一、 数据库模型设计与关系映射

在Django中,关系型数据库的表结构通过Python类来定义。表与表之间的关联主要分为一对一(One-to-One)、一对多(One-to-Many)以及多对多(Many-to-Many)。Django的ORM(对象关系映射)提供了丰富的字段类型和参数来处理这些复杂的业务逻辑。

1. 模型定义与外键配置

以下示例构建了文章(Article)、作者(Writer)、出版社(Publisher)及作者详情(WriterProfile)的完整关系网:

from django.db import models

class Publisher(models.Model):
    pub_id = models.AutoField(primary_key=True)
    company_name = models.CharField(max_length=50, verbose_name="出版社名称")
    location = models.CharField(max_length=50, verbose_name="所在城市")
    contact_email = models.EmailField(verbose_name="联系邮箱")

class WriterProfile(models.Model):
    profile_id = models.AutoField(primary_key=True)
    birth_date = models.DateField(verbose_name="出生日期")
    biography = models.TextField(verbose_name="个人简介")

class Writer(models.Model):
    writer_id = models.AutoField(primary_key=True)
    full_name = models.CharField(max_length=50, verbose_name="作者姓名")
    # 一对一关系:外键可建立在任意一方,底层通过UNIQUE约束实现
    profile = models.OneToOneField(
        WriterProfile, 
        on_delete=models.CASCADE, 
        related_name='writer_info'
    )

class Article(models.Model):
    article_id = models.AutoField(primary_key=True)
    headline = models.CharField(max_length=100, verbose_name="文章标题")
    release_date = models.DateField(verbose_name="发布日期")
    cost = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="定价")
    views = models.IntegerField(default=0, verbose_name="阅读量")
    likes = models.IntegerField(default=0, verbose_name="点赞数")
    
    # 一对多关系:建立在"多"的一方,db_constraint=False可解除数据库层面的物理外键约束,但保留ORM层面的操作能力
    publisher = models.ForeignKey(
        Publisher, 
        on_delete=models.SET_NULL, 
        null=True, 
        db_constraint=False,
        related_name='articles'
    )
    
    # 多对多关系:指定自定义的中间表
    writers = models.ManyToManyField(
        Writer, 
        through='ArticleWriterRel', 
        through_fields=('article', 'writer'),
        related_name='published_articles'
    )

2. 多对多关系的建表策略

处理多对多关联时,有三种常见的实现方式:

  • 全自动模式:直接使用ManyToManyField,Django会在迁移时自动生成第三张关联表。缺点是关联表结构固定,无法扩展额外字段。
  • 纯手动模式:完全通过两个ForeignKey建立第三张表。这种方式灵活性高,但无法使用ORM提供的add()、set()等便捷API。
  • 中介模型模式(推荐):如上例所示,手动创建包含两个外键的模型(如ArticleWriterRel),并通过through参数将其与ManyToManyField绑定。这既保留了多对多API,又允许在中间表中存储额外数据(如作者贡献角色)。
class ArticleWriterRel(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE)
    writer = models.ForeignKey(Writer, on_delete=models.CASCADE)
    role_type = models.CharField(max_length=20, default='Main Author')

    class Meta:
        db_table = 'custom_mapping_table'
        unique_together = ('article', 'writer') # 联合唯一索引

3. 外键级联行为与元信息

on_delete参数决定了当关联的主表数据被删除时,从表数据的命运:

  • models.CASCADE:级联删除关联数据。
  • models.PROTECT:阻止删除,抛出ProtectedError。
  • models.SET_NULL:将外键字段置为NULL(需配合null=True)。
  • models.SET_DEFAULT:恢复为默认值。

通过内部的Meta类,可以精细控制模型的数据库行为,例如使用db_table覆写默认表名,或使用ordering指定默认排序规则。

二、 关联数据的增删改操作

1. 一对多数据的创建

可以通过传递对象实例或直接传递主键ID来建立关联:

# 方式一:传递Publisher对象实例
pub_obj = Publisher.objects.get(pub_id=1)
Article.objects.create(
    headline="深入理解并发编程", 
    release_date="2023-11-01", 
    cost=89.00, 
    publisher=pub_obj
)

# 方式二:直接赋值外键ID(Django自动在内存中附加_id后缀)
Article.objects.create(
    headline="微服务架构实战", 
    release_date="2023-11-05", 
    cost=120.00, 
    publisher_id=1
)

2. 多对多关系的管理API

对于多对多关系,必须通过ORM提供的专属方法操作隐式的中间表:

article = Article.objects.get(article_id=1)
writer_alice = Writer.objects.get(full_name="Alice")
writer_bob = Writer.objects.get(full_name="Bob")

# 添加关联
article.writers.add(writer_alice, writer_bob)
article.writers.add(3, 4) # 直接传入主键ID

# 批量覆盖关联(先清空现有关系,再重新绑定)
article.writers.set([writer_alice])

# 移除特定关联
article.writers.remove(writer_bob)

# 清空所有关联
article.writers.clear()

三、 跨表查询机制

Django ORM提供了两种跨表查询的核心思路:基于对象的查询(执行多次SQL)和基于双下划线的查询(通过SQL JOIN实现)。

1. 基于对象的查询

通过模型属性访问关联数据。正向查询使用外键字段名,反向查询使用小写模型名加_set(若配置了related_name则使用自定义名称)。

# 正向查询:获取文章对应的出版社信息
article = Article.objects.get(headline="微服务架构实战")
print(article.publisher.company_name)

# 反向查询:获取某出版社发行的所有文章
tech_press = Publisher.objects.get(company_name="TechPress")
article_list = tech_press.articles.all() # 使用了 related_name='articles'
for art in article_list:
    print(art.headline)

2. 基于双下划线的JOIN查询

双下划线__不仅能连接多张表,还能作为字段查找的后缀。正向跨表使用字段名,反向跨表使用表名小写。

# 查询特定出版社发行的所有文章标题与定价
Article.objects.filter(
    publisher__company_name="TechPress"
).values_list('headline', 'cost')

# 反向JOIN:查询发行了包含"Django"关键字文章的出版社名称
Publisher.objects.filter(
    articles__headline__icontains="Django"
).values_list('company_name', flat=True)

# 连续跨表:查询1990年出生的作者所写文章的出版社位置
Article.objects.filter(
    writers__profile__birth_date__year=1990
).values('headline', 'publisher__location')

四、 聚合查询与复杂过滤

1. 聚合(Aggregate)与分组(Annotate)

aggregate()是QuerySet的终止子句,返回包含统计结果的字典;而annotate()则为查询集中的每个对象附加一个计算属性。

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

# 聚合:计算所有文章的平均定价
result = Article.objects.aggregate(avg_cost=Avg('cost'))
# 返回: {'avg_cost': 65.50}

# 分组统计:统计每个出版社的文章数量与最高定价
# 注意:values()在annotate()之前,表示按该字段进行GROUP BY
Publisher.objects.values('company_name').annotate(
    total_articles=Count('articles'),
    max_price=Max('articles__cost')
)

# 筛选分组后的结果:找出拥有超过5位合作作者的文章
Article.objects.annotate(
    author_count=Count('writers')
).filter(author_count__gt=5).values('headline', 'author_count')

2. F表达式

F对象允许在数据库层面直接比较同一模型内不同字段的值,或执行批量数学运算,避免将数据加载到Python内存中。

from django.db.models import F

# 筛选阅读量低于点赞数10倍的文章
Article.objects.filter(views__lt=F('likes') * 10)

# 批量更新:所有文章定价上调15元
Article.objects.update(cost=F('cost') + 15)

3. Q对象与复杂逻辑组合

默认的filter()参数之间是AND关系。使用Q对象可以构建包含OR(|)、NOT(~)的复杂WHERE子句。

from django.db.models import Q

# 查询2023年发布,或者定价低于20元,且出版社位于北京的文章
queryset = Article.objects.filter(
    Q(release_date__year=2023) | Q(cost__lt=20),
    publisher__location='Beijing' # Q对象必须放在关键字参数之前
)

五、 原生SQL执行与系统级集成

1. 执行原生SQL

当ORM无法表达极端复杂的查询时,可退化为原生SQL。使用raw()方法可以将结果映射回模型实例,前提是SELECT语句必须包含主键。

# 使用raw执行查询
articles = Article.objects.raw('SELECT article_id, headline, cost FROM myapp_article WHERE cost > %s', [50])
for art in articles:
    print(art.headline)

# 绕过模型层,直接使用数据库游标执行UPDATE/DELETE
from django.db import connection
with connection.cursor() as cursor:
    cursor.execute("UPDATE myapp_article SET cost = cost * 0.9 WHERE release_date < %s", ['2020-01-01'])

2. 在外部脚本中调用Django环境

若需在定时任务或独立的Python脚本中使用Django模型,必须先初始化Django的配置环境。

import os
import django

# 指向项目的核心配置文件
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core_project.settings")
django.setup()

# 环境初始化后方可导入models
from blog.models import Article
active_count = Article.objects.filter(is_published=True).count()
print(f"Active articles: {active_count}")

3. 跨应用(App)模型关联

在大型项目中,模型通常分散在多个App内。若要在一个App中关联另一个App的模型,推荐使用字符串形式引用,以避免循环导入问题。

# 在 app_b 的 models.py 中
from django.db import models

class Category(models.Model):
    category_name = models.CharField(max_length=30)
    # 使用 'app_label.ModelName' 格式进行跨应用关联
    featured_writer = models.ForeignKey(
        'app_a.Writer', 
        on_delete=models.SET_NULL, 
        null=True
    )

执行数据库迁移命令后,Django会自动解析字符串并建立正确的跨表物理外键约束。

标签: django
返回列表

上一篇:主流数据库索引特性实测对比分析

没有最新的文章了...

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。