**「Django のクエリセットにおける重複オブジェクトの除外」**

2026/01/24 1:43

**「Django のクエリセットにおける重複オブジェクトの除外」**

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

Django で関連モデルをクエリすると、JOIN が各一致する本ごとに行を作成するため、同じ著者が重複して返されることがあります。

.distinct()
を追加するとこれらの重複が除外されますが、データベースは選択されたすべての列を比較せざるを得ず、大きなフィールド(
JSONField
TextField
など)が含まれる場合には遅くなる可能性があります。PostgreSQL はより高速な代替手段として
distinct("id")
を提供しています。これはユニークな主キーだけをチェックするため、パフォーマンスが向上しますが、この構文は PostgreSQL 固有です。

Django の

distinct(*fields)
もまた、
ORDER BY
句がそれらの distinct フィールドと一致している必要があります。そうでない場合、
ProgrammingError
が発生します。一般的な回避策として、サブクエリで一意の ID を最初に取得し(例:
Author.objects.filter(id__in=subquery).order_by(...)
)、その後で並び替える方法があります。この手法は冗長であり、Django がクエリを最適化できても 2 回のデータベースアクセスが発生します。

本文で推奨されている最も効率的な解決策は

Exists
サブクエリを使用する方法です。

Author.objects.filter(
    Exists(Book.objects.filter(author=OuterRef('id'), title__startswith='Book'))
).order_by('name')

このパターンは並び替えの制約を排除し、すべてのデータベースで動作し、各著者について 1 行だけ(最初に一致した行が見つかった時点で評価を停止)検索するため、モデル内に大きなテキストや JSON データが含まれていてもクエリを高速に保ちます。このアプローチを実装すると、開発者は多対多関係のコードをより明確かつ高速に書くことができ、大量のテキストまたは JSON コンテンツを扱うアプリケーションのスケーラビリティとユーザー体験を向上させることができます。

本文

Djangoでリレーションを横断してクエリセットをフィルタリングすると、結果に重複したオブジェクトが含まれることがあります。
これは1対多(1:N)でもN対N(多対多)でも発生しやすい落とし穴です。


問題点

リレーションを辿ってクエリセットをフィルタリングすると、Django は SQL の

JOIN
を実行します。
親オブジェクトに複数の関連オブジェクトが条件に合致した場合、その親は結果集合に 複数回 出現します。

具体例 – 1対多

class Author(models.Model):
    name = models.CharField(max_length=255)

class Book(models.Model):
    title  = models.CharField(max_length=255)
    author = models.ForeignKey(
        Author,
        on_delete=models.CASCADE,
        related_name='books',
    )
Authorid
Charlie1
Alice2
Zoe3
Bookidtitleauthor_id
Book A1Book A1
Book B2Book B2
Book C3Book C2
Novel D4Novel D3
Author.objects.filter(books__title__startswith="Book")
# [<Author: Charlie>, <Author: Alice>, <Author: Alice>]

Alice は Book BBook C の両方を持つため、2 回現れます。
多対多リレーションでも同様に、複数のマッチンググループに属するオブジェクトが重複して出力されます。


よくある解決策(そしてそれぞれの問題点)

1.
distinct()

Author.objects.filter(books__title__startswith="Book").distinct()
# [<Author: Charlie>, <Author: Alice>]
  • 利点:シンプルで直感的。
  • 欠点:大規模テーブルではコストが高く、選択されたすべてのフィールドを比較します。
    JSONField
    TextField
    が大きい場合は遅くなる可能性があります。

2.
distinct(*fields)
(PostgreSQL 専用)

Author.objects.filter(books__title__startswith="Book").distinct("id")
# [<Author: Charlie>, <Author: Alice>]
  • 利点:指定したユニークフィールドだけで比較するため、フル
    DISTINCT
    より高速。
  • 欠点
    • PostgreSQL のみ利用可能。
    • 他のフィールドで並べ替える際に制約があります。
      SELECT DISTINCT ON
      は最初の
      ORDER BY
      と一致しなければならないためです。
# 失敗する例:
Author.objects.filter(books__title__startswith="Book")\
    .order_by("name").distinct("id")
  • 回避策:サブクエリを使う
sub = Author.objects.filter(books__title__startswith="Book").distinct("id")
Author.objects.filter(id__in=sub).order_by("name")
# [<Author: Alice>, <Author: Charlie>]

冗長で 2 回のクエリが必要になります(ただし Django は内部的にサブクエリとして最適化します)。


ベストソリューション:
Exists
サブクエリ

最もシンプルかつ高速な方法は

Exists
サブクエリを使うことです。

from django.db.models import Exists, OuterRef

Author.objects.filter(
    Exists(
        Book.objects.filter(
            author=OuterRef("id"),
            title__startswith="Book",
        )
    )
).order_by("name")
# [<Author: Alice>, <Author: Charlie>]

なぜ優れているのか

  • 性能
    EXISTS
    はマッチング行が見つかった瞬間に処理を停止するため、非常に効率的。
  • 並べ替え制限なし:任意のフィールドで
    order_by()
    が使える。
  • 明確な意図:コード自体が「タイトルが 'Book' で始まる本を持つ著者を探す」ことを示している。
  • データベース非依存:PostgreSQL に限定せず、サポートされている全ての DB で動作。

これで Django のクエリセットが重複なしに高速に取得できるようになります!

同じ日のほかのニュース

一覧に戻る →

2026/01/28 4:20

Chrome Canary でテキスト拡大・縮小のサポートをお試しください。

## 日本語訳: --- ## 要約 Chrome Canary は、ウェブページ上でオペレーティングシステムのテキストサイズ設定を尊重するようブラウザに指示する新しいメタタグ `<meta name="text‑scale">` の利用をユーザーが選択できるようになりました。 この機能はまだ実験的で、オプトインフラグによって制御されています。 2024 年夏に CSS Working Group に提案され、CSS Fonts 5 仕様に追加されたもので、サイトがシステムテキストスケーリングを尊重するよう設計されていることを示します。 モバイルユーザーの約三分の一(Android 約 37%、iOS 約 34%)が OS テキストサイズを調整していますが、ほとんどのブラウザはこれらの設定を無視しています。 Safari と Chrome はスキップし、Firefox for Android はページズームを使用します。 グローバルサポートを有効にするとデスクトップレイアウトが壊れる可能性があります(例:フォントサイズが倍になると LinkedIn のページが崩れます)。 したがって慎重な実装が必要です。 **開発者向けベストプラクティスチェックリスト:** 1. **初期 CSS `font-size` を上書きしないでください。** デフォルトを medium(約 16 px)に設定するか、パーセンテージ値を使用します。 2. **コンテンツ要素にはフォント相対単位(em, rem)のみを使用してください。** 必要がない限り、マージン・パディング・ギャップには使用しません。 3. **開発者ツールで 320 px ビューポートに 200 % テキストスケールをシミュレートし、`env(preferred-text-scale)` 関数を使ってテストしてください。** 未解決の質問があります:大きな見出しは本文より低い倍率で拡大すべきでしょうか(例:32 px → 64 px)? ブラウザは今年後半に `<meta name="text‑scale">` をサポートする可能性がありますが、他のエンジンについては確認されたタイムラインはありません。 追加議論とドキュメントは CSS Day 2026(6 月)で予定されています。 広く採用されれば、デザインを壊すことなくアクセシビリティ設定に対応できるようになります—ただしレイアウトの崩れを防ぐためにスペーシング単位を管理する必要があります。

2026/01/28 5:35

タイムステーション・エミュレータ

## Japanese Translation: > ## 要約 > タイムステーションエミュレーターは、スマートフォンやタブレットを低周波ラジオ送信機に変換し、ほとんどの原子時計や腕時計の同期に使用できる時間信号を放送します。NTP スタイルのアルゴリズムを用いて ±24 h のオフセットを許容し、自動的に夏時間変更と DUT1 うるう秒補正(適宜)を適用することで、BPC、DCF77、JJY、MSF、および WWVB の5つの公式局をエミュレートします。ツールは WebAssembly を介してブラウザ上で完全に動作し、インストールやデータ収集は不要です。また、44.1 kHz PCM 出力以上の DAC サポートがあれば十分です。 > > パフォーマンスは内蔵スピーカーで最も優れています。有線ヘッドホンでも動作しますが、Bluetooth やオーディオフィーバー機器では搬送波のサブハーモニック変調に必要な高周波共振子を歪めることがあります。2024 年初頭時点で iOS の Safari と Android の Firefox は不具合があり、機能しません。ユーザーは希望する局を選択し時計パラメータを設定した後、電話のスピーカーを時計のアンテナに近づけます。音声波形は、搬送波周波数のサブハーモニック変調によって実際のタイムステーション放送を模倣する RF ノイズを生成するよう設計されています。 > > エミュレーターは最大ボリュームで再生すると永久的な聴覚障害を引き起こす可能性があるため、スピーカーを直接聞くことを避けるよう警告します。ホストサイトは <https://timestation.pages.dev/> であり、そのソースコード(Unicode とアイコン資産を含む)は MIT ライセンスに準拠し、適切な帰属要件が課されています。

2026/01/28 3:57

レナート・ポッタリングとクリスチャン・ブラウナーは新しい会社を設立しました。

## Japanese Translation: まとめはすでにKey Pointsリストと完全に一致しているため、変更の必要はありません。