两表联查
在 FastAPI 中,使用 Tortoise ORM 查询两表联查(通常是通过外键关系进行联接)是非常简单的。可以使用 select_related
或 prefetch_related
来执行联表查询,它们类似于 Django ORM 的 select_related
和 prefetch_related
,用于优化查询的效率。
1. select_related
vs prefetch_related
select_related
:用于执行 SQL JOIN 查询。适用于一对一或外键关系。会一次性加载相关的表,减少数据库查询次数。prefetch_related
:用于执行多次查询,但在应用程序中将多个查询合并。适用于多对多关系或者反向外键关系。
示例:使用 select_related
和 prefetch_related
进行联查
假设我们有以下两个模型:Author
和 Book
,Book
通过外键引用 Author
。
1. 定义模型
from tortoise import fields
from tortoise.models import Model
class Author(Model):
id = fields.IntField(pk=True)
name = fields.CharField(max_length=100)
def __str__(self):
return self.name
class Book(Model):
id = fields.IntField(pk=True)
title = fields.CharField(max_length=100)
author = fields.ForeignKeyField('models.Author', related_name='books')
def __str__(self):
return self.title
在这个例子中,Book
模型通过 ForeignKeyField
和 Author
模型建立了外键关系。
2. 使用 select_related
进行联查
select_related
会对外键关系进行优化,执行 JOIN 操作,在同一个查询中一次性获取 Author
和 Book
数据。它适用于一对一关系和外键关系。
查询某本书及其作者
@app.get("/books/{book_id}")
async def get_book(book_id: int):
book = await Book.filter(id=book_id).select_related('author').first()
if not book:
raise HTTPException(status_code=404, detail="Book not found")
return {"book_title": book.title, "author_name": book.author.name}
select_related('author')
:这将通过 SQL JOIN 操作将Book
和Author
的数据一次性加载。.first()
:如果查询没有找到数据,则返回None
。
SQL 查询的效果:
SELECT "book"."id", "book"."title", "author"."id", "author"."name"
FROM "book"
JOIN "author" ON "book"."author_id" = "author"."id"
WHERE "book"."id" = ?
3. 使用 prefetch_related
进行联查
prefetch_related
适用于多对多关系或反向外键关系,它会执行额外的查询并在应用程序中合并它们。适用于需要加载多个相关对象的场景。
查询所有作者及其所有书籍
@app.get("/authors/")
async def get_authors():
authors = await Author.all().prefetch_related('books')
result = []
for author in authors:
result.append({
"author_name": author.name,
"books": [book.title for book in author.books]
})
return result
prefetch_related('books')
:这将执行两次查询,一个查询所有Author
数据,另一个查询所有与Author
相关的Book
数据,并在应用程序中将它们合并。
SQL 查询的效果:
SELECT "author"."id", "author"."name" FROM "author";
SELECT "book"."id", "book"."title", "book"."author_id"
FROM "book"
WHERE "book"."author_id" IN (?, ?, ?)
4. 复杂查询:联查和过滤
可以将 select_related
或 prefetch_related
与其他查询操作(如 filter
或 order_by
)结合使用,进行更复杂的查询。
查询某位作者的所有书籍,并按书名排序
@app.get("/authors/{author_id}/books")
async def get_author_books(author_id: int):
author = await Author.get(id=author_id)
books = await author.books.all().order_by('title')
return [{"title": book.title} for book in books]
author.books.all()
查询该作者所有的书籍,并按书名升序排序。
5. 联查性能优化
- 使用
select_related
时,可以减少数据库查询次数,因为它会合并查询成一个 JOIN 查询。 prefetch_related
在数据量较大的情况下可能更有效,因为它可以分开多个查询并在应用层合并它们,从而避免了一次性加载大量数据导致的性能问题。
6. 示例:获取所有书籍及其作者信息
查询所有书籍,并获取每本书的作者信息,使用 select_related
来优化查询。
@app.get("/books/")
async def get_books():
books = await Book.all().select_related('author')
return [
{"book_title": book.title, "author_name": book.author.name}
for book in books
]
这个查询将会加载所有书籍及其对应的作者,select_related('author')
会通过 JOIN 查询把 Book
和 Author
的数据合并在一个查询中,减少数据库查询次数。
总结
select_related
适用于外键关系或一对一关系,使用 SQL JOIN 优化查询。prefetch_related
适用于多对多关系或反向外键关系,执行多次查询并在应用程序中合并它们。- 通过使用
select_related
和prefetch_related
,你可以有效地优化数据库查询,减少数据库请求次数和加载的时间。
这些工具使得在 FastAPI 和 Tortoise ORM 中进行联表查询变得非常高效和灵活。
查询同时满足不同表的属性过滤
在 Tortoise ORM 中,进行多条件查询时,可以通过 filter
方法同时指定多个条件。你可以在 filter
中传递多个参数,以实现书名和作者同时符合条件的查询。
假设你想查询符合以下条件的书籍:
- 书名包含某个关键词。
- 作者的名字也包含某个关键词。
下面是如何使用 filter
方法实现这两个条件的查询。
示例:查询书名和作者符合条件的书籍
假设我们有 Book
和 Author
模型,并且希望根据书名和作者名同时过滤书籍。
1. 定义模型
from tortoise import fields
from tortoise.models import Model
class Author(Model):
id = fields.IntField(pk=True)
name = fields.CharField(max_length=100)
def __str__(self):
return self.name
class Book(Model):
id = fields.IntField(pk=True)
title = fields.CharField(max_length=100)
author = fields.ForeignKeyField('models.Author', related_name='books')
def __str__(self):
return self.title
2. 多条件查询:书名和作者名
假设你想查询所有书名包含 “Python” 且作者名包含 “John” 的书籍。你可以使用 filter
来组合这两个条件。
from fastapi import FastAPI, HTTPException
from tortoise.exceptions import DoesNotExist
@app.get("/books/")
async def get_books_by_conditions(title_contains: str, author_contains: str):
books = await Book.filter(
title__icontains=title_contains, # 书名包含指定关键词
author__name__icontains=author_contains # 作者名包含指定关键词
).select_related('author') # 联接查询,获取作者信息
if not books:
raise HTTPException(status_code=404, detail="No books found matching the criteria")
return [
{"book_title": book.title, "author_name": book.author.name}
for book in books
]
3. 解释
title__icontains=title_contains
:__icontains
是 Tortoise ORM 提供的查询方式,表示不区分大小写地查询包含某个子串的值。title_contains
是你传递的参数,表示书名应包含的关键词。author__name__icontains=author_contains
:author__name__icontains
是通过外键author
查询Author
模型中的name
字段,同样表示不区分大小写地查询包含作者名字的子串。
4. 查询请求示例
当你调用 GET /books/
接口时,可以传递书名和作者名的查询参数,例如:
GET /books/?title_contains=python&author_contains=john
这将返回所有书名包含 “python” 且作者名包含 “john” 的书籍。
5. 更多的查询条件
你可以通过组合不同的条件来实现更复杂的查询。例如:
- 精确匹配:
title__exact='Python 101'
。 - 以某个值开始:
title__startswith='Python'
。 - 以某个值结束:
title__endswith='Guide'
。 - 进行日期范围查询等。
示例:查询书名包含 “Python” 且创建日期在某个范围内的书籍
from datetime import datetime
@app.get("/books/date-range")
async def get_books_by_date_range(start_date: datetime, end_date: datetime):
books = await Book.filter(
title__icontains="Python",
created_at__gte=start_date,
created_at__lte=end_date
).select_related('author')
return [{"book_title": book.title, "author_name": book.author.name} for book in books]
总结
- 使用
filter
方法可以实现多条件查询。 - 可以通过字段查询条件(如
icontains
、exact
、startswith
等)来筛选数据。 - 通过使用外键字段(例如
author__name
)进行联查,能够同时查询两表相关联的数据。
这样就可以灵活地根据书名和作者的多个条件查询数据,并通过 FastAPI 提供给客户端使用。