Django ORM详解:外键使用(外键逻辑关联)与查询优化
Django数据库迁移
# 创建迁移
python manage.py makemigrations your_app_name
# 应用迁移
python manage.py migrate
# 查看迁移状态
python manage.py showmigrations
# 回滚迁移
python manage.py migrate your_app_name 0001
# 修改表后,删除迁移记录和表删除迁移记录后重新迁移
python manage.py migrate --fake contract_manage zero
外键关系的定义
在 Django 模型(Models)中定义外键关系,意味着两个表之间的链接关系。例如,我们有一个博客应用,其中有Author
和Article
两个模型:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(Author, on_delete=models.CASCADE)
这里的Article
模型中的author
字段定义了一个外键关系,指向Author
模型。
外间删除操作的参数意思:
如果一个模型使用了外键。那么在对方那个模型被删掉后,该进行什么样的操作。可以通过on_delete来指定。可以指定的类型如下:
CASCADE:级联操作。如果外键对应的那条数据被删除了,那么这条数据也会被删除。
PROTECT:受保护。即只要这条数据引用了外键的那条数据,那么就不能删除外键的那条数据。如果我们强行删除,Django就会报错。
SET_NULL:设置为空。如果外键的那条数据被删除了,那么在本条数据上就将这个字段设置为空。如果设置这个选项,前提是要指定这个字段可以为空。
SET_DEFAULT:设置默认值。如果外键的那条数据被删除了,那么本条数据上就将这个字段设置为默认值。如果设置这个选项,== 前提是要指定这个字段一个默认值 ==。
SET():如果外键的那条数据被删除了。那么将会获取SET函数中的值来作为这个外键的值。SET函数可以接收一个可以调用的对象(比如函数或者方法),如果是可以调用的对象,那么会将这个对象调用后的结果作为值返回回去。== 可以不用指定默认值 ==
DO_NOTHING:不采取任何行为。一切全看数据库级别的约束。
以上这些选项只是Django级别的,数据级别依旧是RESTRICT!
数据库层面的约束有四种:
RESTRICT:默认的选项,如果想要删除父表的记录时,而在子表中有关联该父表的记录,则不允许删除父表中的记录;
NOACTION:同 RESTRICT效果一样,也是首先先检查外键;
CASCADE:父表delete、update的时候,子表会delete、update掉关联记录;
SET NULL:父表delete、update的时候,子表会将关联记录的外键字段所在列设为null,所以注意在设计子表时外键不能设为not null;
基本外键查询
假设我们想查询某位作者编写的所有文章,可以这样做:
# 假设我们已知作者的 ID
author_id = 1 articles = Article.objects.filter(author_id=author_id)
# 这会生成一个查询集(QuerySet),包含了所有这位作者的文章。
反向查询
在 Django 中,每当你定义了一个外键关系,Django 会自动为相关联的模型添加一个反向查询的管理器。在上述例子中,Author
模型会有一个article_set
的管理器,允许我们从Author
的角度查询文章:
# 获取某个作者实例
author = Author.objects.get(id=author_id)
# 反向查询这个作者的所有文章
authors_articles = author.article_set.all()
自定义反向查询名称
使用related_name
属性可以自定义反向查询的名称,这会使你的代码更加清晰:
class Article(models.Model):
# ...
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='articles')
现在我们可以这样使用自定义的反向查询名称:
author = Author.objects.get(id=author_id)
authors_articles = author.articles.all()
查询优化
为了优化查询性能,Django 提供了select_related
和prefetch_related
两种不同的查询优化方法。select_related
适用于“一对多”的关系查询优化,而prefetch_related
适用于“多对多”和“多对一”的关系查询优化。
在我们的例子中,如果我们想一次性获取所有文章及其作者信息,可以这样做:
# 使用 select_related 获取所有文章和对应的作者信息
articles_with_authors = Article.objects.select_related('author').all()
如果我们想获取所有作者及其所有文章,那么可以使用prefetch_related
:
# 使用 prefetch_related 获取所有作者和他们所有的文章
authors_with_articles = Author.objects.prefetch_related('articles').all()
django orm数据表不设置外键进行关联查询
方法一: 修改books_book表 models字段定义,改为逻辑关联
Tips: django中的ForeignKey与数据库中FOREIGN KEY约束并不一样,ForeignKey是一种逻辑上的关联关系,是否使用数据库中的外键约束通过db_contraint参数设置。
class Book(models.Model):
"""书籍表"""
name = models.CharField('名称', max_length=50)
price = models.IntegerField('价格', default=50)
publisher = models.ForeignKey('Publisher',on_delete=models.DO_NOTHING, db_constraint=False)
class Meta:
db_table = 'books_book'
class Publisher(models.Model):
"""出版社表"""
name = models.CharField('出版社名称', max_length=50)
addr = models.CharField('出版社地址', max_length=50)
class Meta:
db_table = 'books_publisher'
查询语句:
queryset = Book.objects.values(‘id’, ‘name’, ‘price’, ‘publisher__name’)
方法二:通过extra api函数实现
queryset = Book.objects.extra(select={‘publisher_name’: ‘SELECT books_publisher.name FROM books_publisher WHERE books_publisher.id = books_book.publisher_id’})
# 测试结果:
# 可以发现查询出的queryset中多了一个publisher_name属性,然后在序列化阶段将这个字段加上即可。
from books.models import Book, Publisher
queryset = Book.objects.extra(select={'publisher_name': 'SELECT books_publisher.name FROM books_publisher WHERE books_publisher.id = books_book.publisher_id'})
for item in queryset:
print(item.__dict__)
{'_state': <django.db.models.base.ModelState object at 0x000002993198F208>, 'id': 1, 'name': '书本1', 'price': 20, 'publisher_id': 1, 'publisher_name': '工业出版社'}
方法三:执行原生sql实现(方法三与方法二比较类似)
queryset = Book.objects.raw( ‘select books_book.*, books_publisher.name as publisher_name from books_book left join books_publisher on books_book.publisher_id = books_publisher.id;’)
# 测试结果
queryset = Book.objects.raw( 'select books_book.*, books_publisher.name as publisher_name from books_book left join books_publisher on books_book.publisher_id = books_publisher.id;')
for item in queryset:
print(item.__dict__)
{'_state': <django.db.models.base.ModelState object at 0x0000029931119748>, 'id': 1, 'name': '书本1', 'price': 20, 'publisher_id': 1, 'publisher_name': '工业出版社'}
方法四:在序列化阶段根据相关联的id字段再执行一次查询
# ProjectVersion model 序列化器
class BookSerializer(serializers.ModelSerializer):
publisher_name = serializers.SerializerMethodField()
def get_publisher_name(self, obj):
"""
:param obj: Book实例
"""
publisher = Publisher.objects.filter(id=obj.publisher_id).first()
if publisher:
return publisher.name
else:
return ''
查询优化
对于大型数据库,优化查询是非常重要的。Django ORM提供了几种工具来帮助你优化查询,包括select_related()
和prefetch_related()
。
from myapp.models import Blog
# 获取所有Blog记录,并一次性获取每个Blog的author信息
# select_related()可以一次性获取与查询对象有ForeignKey关联的对象,这可以减少数据库查询次数
blogs = Blog.objects.select_related('author').all()
# 输出Blog的标题和作者名
for blog in blogs:
print(blog.title, blog.author.name)
prefetch_related()
对于ManyToMany关联和一对多关联也非常有用,它可以一次性获取所有相关对象,减少数据库查询次数。
利用数据库约束保证数据一致性
Django ORM提供了多种数据库约束,如unique
和check
等,可以帮助我们确保数据库的数据一致性。
# 例子:使用unique约束确保每个作者的email是唯一的
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
使用批量操作提升性能
Django ORM提供了bulk_create
和bulk_update
等方法,可以让我们以更高效的方式进行批量创建和更新。
# 例子:使用bulk_create方法批量创建对象
authors = [Author(name=f'Author {i}') for i in range(1000)]
Author.objects.bulk_create(authors) # 这个操作只需要一次数据库查询
使用原生SQL
尽管Django ORM提供了许多强大的查询工具,但有时候你可能需要直接执行SQL语句。Django ORM允许你执行原生SQL,你可以使用raw()
方法或者cursor()
方法来执行原生SQL:
from django.db import connection
# 使用cursor()执行原生SQL
with connection.cursor() as cursor:
cursor.execute("SELECT title FROM myapp_blog")
row = cursor.fetchone()
print(row)
或者
Author.objects.raw("SELECT name FROM author")
这段代码将直接执行SQL查询,并打印出第一个博客的标题。虽然Django ORM提供了.raw()方法允许我们直接执行SQL查询,但是这个方法应该尽量避免使用,因为它可能会引发SQL注入等安全问题,同时也失去了Django ORM的许多优点。