Django ORMのスライス入門
指定範囲のデータを取得する方法
Django ORMでPythonのスライス構文を使って、指定した範囲のデータを取得する方法を解説します。ページネーションの実装にも活用できます。
こんな人向けの記事です
- Django ORMでデータの件数を制限して取得したい人
- LIMITやOFFSETの使い方を知りたい人
- ページネーションを実装したい人
Step 1スライスの基本(LIMIT)
Django ORMではPythonのスライス構文[start:stop]を使って取得範囲を指定できます。SQLのLIMIT句に変換されます。
Python
# models.py
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
view_count = models.IntegerField(default=0)
def __str__(self):
return self.titlePython
# 最初の5件を取得(LIMIT 5)
articles = Article.objects.all()[:5]
for a in articles:
print(a.title)
# order_by()と組み合わせて、閲覧数の多い上位3件を取得
top_articles = Article.objects.order_by("-view_count")[:3]
for a in top_articles:
print(f"{a.title}: {a.view_count}回")実行結果
Django入門: 1500回
Python基礎: 1200回
ORM活用術: 980回スライスはSQLのLIMITに変換されるため、全データをメモリに読み込んでから制限するのではなく、データベースレベルで件数を制限します。
Step 2オフセット付きスライス(OFFSET)
スライスの開始位置を指定すると、SQLのOFFSETに変換されます。
Python
# 6件目から10件目を取得(OFFSET 5 LIMIT 5)
articles = Article.objects.all()[5:10]
# 11件目から20件目を取得
articles = Article.objects.all()[10:20]
# 最新の記事を、3件目から5件取得
articles = Article.objects.order_by("-created_at")[2:7]
for a in articles:
print(a.title)
# インデックスで1件取得(OFFSET n LIMIT 1)
third_article = Article.objects.order_by("-view_count")[2]
print(f"3位: {third_article.title}")実行結果
3位: ORM活用術負のインデックスは使えない
Django ORMのスライスではArticle.objects.all()[-1]のような負のインデックスはサポートされていません。最後のデータを取得したい場合はorder_by()で逆順にするか、last()メソッドを使ってください。Step 3first()とlast()
最初の1件または最後の1件だけを取得するメソッドです。
Python
# 最初の1件を取得(該当なしの場合はNone)
article = Article.objects.order_by("created_at").first()
if article:
print(f"最古: {article.title}")
# 最後の1件を取得
article = Article.objects.order_by("created_at").last()
if article:
print(f"最新: {article.title}")
# filter()と組み合わせ
latest = Article.objects.filter(
view_count__gte=100
).order_by("-created_at").first()
# first()とスライスの違い
# first(): データがない場合はNoneを返す
# [0]: データがない場合はIndexErrorが発生する
article = Article.objects.filter(id=9999).first() # None
# article = Article.objects.filter(id=9999)[0] # IndexError!実行結果
最古: はじめてのPython
最新: Django ORM活用術first()とlast()の安全性
first()とlast()はデータが存在しない場合にNoneを返すため、[0]のインデックスアクセスよりも安全です。常にfirst()を使うことを推奨します。Step 4ページネーションの実装
Djangoには標準のPaginatorクラスがあり、スライスを内部的に活用してページネーションを実現します。
Python
# views.py
from django.core.paginator import Paginator
from django.shortcuts import render
from .models import Article
def article_list(request):
# 全記事を作成日降順で取得
articles_all = Article.objects.order_by("-created_at")
# 1ページあたり10件でページネーション
paginator = Paginator(articles_all, 10)
# URLパラメータからページ番号を取得
page_number = request.GET.get("page", 1)
page_obj = paginator.get_page(page_number)
context = {
"page_obj": page_obj,
"total_count": paginator.count, # 総件数
"total_pages": paginator.num_pages, # 総ページ数
}
return render(request, "article/list.html", context)Python(テンプレート)
<!-- templates/article/list.html -->
{% for article in page_obj %}
<div class="article">
<h2>{{ article.title }}</h2>
<p>{{ article.created_at }}</p>
</div>
{% endfor %}
<!-- ページネーションリンク -->
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">前へ</a>
{% endif %}
<span>{{ page_obj.number }} / {{ page_obj.paginator.num_pages }}</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">次へ</a>
{% endif %}
</div>Step 5パフォーマンスの注意点
Python
# OFFSETが大きいとパフォーマンスが低下する
# BAD: 100万件目から10件取得(遅い)
articles = Article.objects.all()[1000000:1000010]
# GOOD: カーソルベースのページネーション(高速)
# 前のページの最後のIDを基準にする
last_id = request.GET.get("last_id", 0)
articles = Article.objects.filter(
id__gt=last_id
).order_by("id")[:10]
# exists()で存在チェック(count()より高速)
if Article.objects.filter(view_count__gte=1000).exists():
print("人気記事があります")
# スライス後のQuerySetではfilter()が使えない
# BAD: スライス後にfilter()
# articles = Article.objects.all()[:10].filter(view_count__gte=100) # エラー
# GOOD: filter()してからスライス
articles = Article.objects.filter(view_count__gte=100)[:10]カーソルベースのページネーション
大量データのページネーションでは、OFFSETベースよりもカーソルベース(前ページの最後のIDを基準にする方式)が高速です。Django REST Frameworkにはカーソルページネーションが標準で用意されています。まとめ
- Pythonのスライス構文
[:n]でLIMIT、[m:n]でOFFSET+LIMITを指定できる first()とlast()で安全に先頭・末尾のデータを取得できる- DjangoのPaginatorクラスで簡単にページネーションを実装できる
- スライスはデータベースレベルで制限されるため効率的
- 大量データではカーソルベースのページネーションを検討する