O Django ORM (Object Relational Mapping) é um dos recursos mais poderosos do Django. Isso nos permite interagir com o banco de dados usando código Python, não SQL.Para demonstrar, descreverei o seguinte modelo:from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=250)
url = models.URLField()
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=250)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=250)
content = models.TextField()
published = models.BooleanField(default=True)
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
authors = models.ManyToManyField(Author, related_name="posts")
Vou usar django-extensões para obter informações úteis com:python manage.py shell_plus --print-sql
Então, vamos começar:>>> post = Post.objects.all()
>>> post
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."blog_id"
FROM "blog_post"
LIMIT 21
Execution time: 0.000172s [Database: default]
<QuerySet [<Post: Post object (1)>]>
1. Use valores ForeignKey diretamente
>>> Post.objects.first().blog.id
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."blog_id"
FROM "blog_post"
ORDER BY "blog_post"."id" ASC
LIMIT 1
Execution time: 0.000225s [Database: default]
SELECT "blog_blog"."id",
"blog_blog"."name",
"blog_blog"."url"
FROM "blog_blog"
WHERE "blog_blog"."id" = 1
LIMIT 21
Execution time: 0.000144s [Database: default]
1
E assim temos uma consulta no banco de dados:>>> Post.objects.first().blog_id
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."blog_id"
FROM "blog_post"
ORDER BY "blog_post"."id" ASC
LIMIT 1
Execution time: 0.000155s [Database: default]
1
2. Relações OneToMany
Se usarmos o relacionamento OneToMany, usaremos o campo ForeignKey e a consulta será mais ou menos assim:>>> post = Post.objects.get(id=1)
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."blog_id"
FROM "blog_post"
WHERE "blog_post"."id" = 1
LIMIT 21
Execution time: 0.000161s [Database: default]
E se queremos acessar o objeto de blog a partir do objeto de postagem, podemos fazer:>>> post.blog
SELECT "blog_blog"."id",
"blog_blog"."name",
"blog_blog"."url"
FROM "blog_blog"
WHERE "blog_blog"."id" = 1
LIMIT 21
Execution time: 0.000211s [Database: default]
<Blog: Django tutorials>
No entanto, isso acionou uma nova solicitação para obter informações do blog. Portanto, use select_related para evitar isso. Para usá-lo, podemos atualizar nossa solicitação original:>>> post = Post.objects.select_related("blog").get(id=1)
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."blog_id",
"blog_blog"."id",
"blog_blog"."name",
"blog_blog"."url"
FROM "blog_post"
INNER JOIN "blog_blog"
ON ("blog_post"."blog_id" = "blog_blog"."id")
WHERE "blog_post"."id" = 1
LIMIT 21
Execution time: 0.000159s [Database: default]
Observe que o Django está usando o JOIN agora! E o tempo de execução da consulta é mais curto do que antes. Além disso, agora o post.blog será armazenado em cache!>>> post.blog
<Blog: Django tutorials>
select_related também funciona com QurySets:>>> posts = Post.objects.select_related("blog").all()
>>> for post in posts:
... post.blog
...
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."blog_id",
"blog_blog"."id",
"blog_blog"."name",
"blog_blog"."url"
FROM "blog_post"
INNER JOIN "blog_blog"
ON ("blog_post"."blog_id" = "blog_blog"."id")
Execution time: 0.000241s [Database: default]
<Blog: Django tutorials>
3. Relações ManyToMany
Para obter autores de postagem, usamos algo como isto:>>> for post in Post.objects.all():
... post.authors.all()
...
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."blog_id"
FROM "blog_post"
Execution time: 0.000242s [Database: default]
SELECT "blog_author"."id",
"blog_author"."name"
FROM "blog_author"
INNER JOIN "blog_post_authors"
ON ("blog_author"."id" = "blog_post_authors"."author_id")
WHERE "blog_post_authors"."post_id" = 1
LIMIT 21
Execution time: 0.000125s [Database: default]
<QuerySet [<Author: Dmytro Parfeniuk>, <Author: Will Vincent>, <Author: Guido van Rossum>]>
SELECT "blog_author"."id",
"blog_author"."name"
FROM "blog_author"
INNER JOIN "blog_post_authors"
ON ("blog_author"."id" = "blog_post_authors"."author_id")
WHERE "blog_post_authors"."post_id" = 2
LIMIT 21
Execution time: 0.000109s [Database: default]
<QuerySet [<Author: Dmytro Parfeniuk>, <Author: Will Vincent>]>
Parece que recebemos uma solicitação para cada objeto de postagem. Portanto, devemos usar prefetch_related . Isso é semelhante ao select_related, mas usado com ManyToMany Fields:>>> for post in Post.objects.prefetch_related("authors").all():
... post.authors.all()
...
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."blog_id"
FROM "blog_post"
Execution time: 0.000300s [Database: default]
SELECT ("blog_post_authors"."post_id") AS "_prefetch_related_val_post_id",
"blog_author"."id",
"blog_author"."name"
FROM "blog_author"
INNER JOIN "blog_post_authors"
ON ("blog_author"."id" = "blog_post_authors"."author_id")
WHERE "blog_post_authors"."post_id" IN (1, 2)
Execution time: 0.000379s [Database: default]
<QuerySet [<Author: Dmytro Parfeniuk>, <Author: Will Vincent>, <Author: Guido van Rossum>]>
<QuerySet [<Author: Dmytro Parfeniuk>, <Author: Will Vincent>]>
O que acabou de acontecer ??? Reduzimos o número de consultas de 2 para 1 para obter 2 QuerySet-a!4. Objeto de pré-busca
prefetch_related é suficiente para a maioria dos casos, mas nem sempre ajuda a evitar solicitações adicionais. Por exemplo, se usarmos filtragem, o Django não poderá usar nossas postagens em cache , pois elas não foram filtradas quando foram solicitadas na primeira solicitação. E temos:>>> authors = Author.objects.prefetch_related("posts").all()
>>> for author in authors:
... print(author.posts.filter(published=True))
...
SELECT "blog_author"."id",
"blog_author"."name"
FROM "blog_author"
Execution time: 0.000580s [Database: default]
SELECT ("blog_post_authors"."author_id") AS "_prefetch_related_val_author_id",
"blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."published",
"blog_post"."blog_id"
FROM "blog_post"
INNER JOIN "blog_post_authors"
ON ("blog_post"."id" = "blog_post_authors"."post_id")
WHERE "blog_post_authors"."author_id" IN (1, 2, 3)
Execution time: 0.000759s [Database: default]
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."published",
"blog_post"."blog_id"
FROM "blog_post"
INNER JOIN "blog_post_authors"
ON ("blog_post"."id" = "blog_post_authors"."post_id")
WHERE ("blog_post_authors"."author_id" = 1 AND "blog_post"."published" = 1)
LIMIT 21
Execution time: 0.000299s [Database: default]
<QuerySet [<Post: Post object (1)>, <Post: Post object (2)>]>
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."published",
"blog_post"."blog_id"
FROM "blog_post"
INNER JOIN "blog_post_authors"
ON ("blog_post"."id" = "blog_post_authors"."post_id")
WHERE ("blog_post_authors"."author_id" = 2 AND "blog_post"."published" = 1)
LIMIT 21
Execution time: 0.000336s [Database: default]
<QuerySet [<Post: Post object (1)>, <Post: Post object (2)>]>
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."published",
"blog_post"."blog_id"
FROM "blog_post"
INNER JOIN "blog_post_authors"
ON ("blog_post"."id" = "blog_post_authors"."post_id")
WHERE ("blog_post_authors"."author_id" = 3 AND "blog_post"."published" = 1)
LIMIT 21
Execution time: 0.000412s [Database: default]
<QuerySet [<Post: Post object (1)>]>
Ou seja, usamos prefetch_related para reduzir o número de solicitações, mas na verdade aumentamos. Para evitar isso, podemos personalizar a solicitação usando o objeto Prefetch :>>> authors = Author.objects.prefetch_related(
... Prefetch(
... "posts",
... queryset=Post.objects.filter(published=True),
... to_attr="published_posts",
... )
... )
>>> for author in authors:
... print(author.published_posts)
...
SELECT "blog_author"."id",
"blog_author"."name"
FROM "blog_author"
Execution time: 0.000183s [Database: default]
SELECT ("blog_post_authors"."author_id") AS "_prefetch_related_val_author_id",
"blog_post"."id",
"blog_post"."title",
"blog_post"."content",
"blog_post"."published",
"blog_post"."blog_id"
FROM "blog_post"
INNER JOIN "blog_post_authors"
ON ("blog_post"."id" = "blog_post_authors"."post_id")
WHERE ("blog_post"."published" = 1 AND "blog_post_authors"."author_id" IN (1, 2, 3))
Execution time: 0.000404s [Database: default]
[<Post: Post object (1)>, <Post: Post object (2)>]
[<Post: Post object (1)>, <Post: Post object (2)>]
[<Post: Post object (1)>]
Usamos uma solicitação específica para receber postagens por meio do parâmetro request e salvamos as mensagens filtradas em um novo atributo. Como podemos ver, agora temos apenas 2 consultas no banco de dados.