🙆

order byを使って、QuerySetを任意の順番でソートする

2023/02/04に公開

書くこと

  • 並べ替えたい順番のid配列(pk等)を持っている状態で、QuerySetのソートする方法
  • pythonで処理せず、order byで実現する

利用する技術

  • Django
  • MySQL
  • SQlite

今日のゴール

## 前提: 以下は変数として持つ
# - queryset: 並び替えたい対象のQuerySet
# - ids: 並び順を示すpk(等)の配列 

from .common import sorter

# 取得結果:
# 第一引数のquerysetを第二引数で与えたidの順番に並べ替えたもの
sorted_queryset = sorter.sort(queryset, ids)

方法論

MySQL Version

以下のクエリを発行できるように、

# example: idが[2, 3, 1]の順に並べ替える。
SELECT *
FROM table_name
ORDER BY FIELD(id, 2, 3, 1);

以下の関数を追加。

common/sorter.py

from django.db.models.query import QuerySet


def sort(query_set: QuerySet, orders: list) -> QuerySet:
    # プログラムガード
    if not orders:
        return query_set

    sort_key = __sort_key(orders)
    return query_set.extra(select={"sort_key": sort_key}, order_by=("sort_key",))


def __sort_key(orders):
    order_str = ",".join(str(order) for order in orders)
    return f"FIELD(id, {order_str})"

付録

SQLite3 Version

以下のクエリを発行できるように、

SELECT *
FROM table_name
ORDER BY CASE
             WHEN id = 2 THEN 0
             WHEN id = 3 THEN 1
             WHEN id = 1 THEN 2
         END;

__sort_keyだけ書き換えます。

def __sort_key(orders):
    case = " ".join([f"WHEN id={pk} THEN {i}" for i, pk in enumerate(orders)])
    return f"CASE {case} END"

参考文献

Discussion