背景
値にNULLを含むレコードを並び替える時、結果の最後にNULLを出力したいケースがありました。
ORDER BY xxx IS NULL ASC
を使えばいい、と思っていたらうまくいかずハマってしまったので、反省として記します。
例
下記のテーブルを例に考えてみます。
- 複数の飲食店でスマホから注文、決済ができるシステムを導入している。
- 利用者が注文、決済を実行すると、注文情報としてオーダーテーブル(Orders)に注文先の店舗ID(store_id)と決済種別(payment_type)が登録される。
- スマホでの注文時、非対面の決済(クレジットカード決済、QRコード決済)の場合は対応する数値が登録される。現金払いの場合はNULLが登録される。
id | store_id | payment_type |
---|---|---|
1 | 3 | 1 |
2 | 1 | null |
3 | 1 | 3 |
4 | 1 | 2 |
5 | 1 | 1 |
6 | 2 | null |
7 | 2 | 2 |
8 | 2 | 1 |
要求事項
- 複数の飲食店から、決済種別を含めた注文情報を出力したいという要望があった。
- 決済種別の並び順は固定したい。
要求事項を満たす出力結果
- 下記のように、
store_id
の昇順、payment_type
の昇順としたい。 - 現地決済を意味する
NULL
が存在する場合、各店舗の最後に出力したい。
store_id | payment_type |
---|---|
1 | 1 |
1 | 2 |
1 | 3 |
1 | null |
2 | 1 |
2 | 2 |
2 | null |
3 | 1 |
ソートでNULLを最後に並べたい
store_id
の昇順、payment_type
の昇順としたいので、それぞれをORDER BY
句に指定します。
SELECT
store_id, payment_type
FROM
Orders
ORDER BY
store_id ASC,
payment_type ASC;
しかし、payment_type ASC
と指定した場合、NULL
は最小値とみなされ各店舗の最初に並びます。
下記の要求事項を満たしません。
現地決済を意味する
NULL
が存在する場合、各店舗の最後に出力したい。
store_id | payment_type |
---|---|
1 | null |
1 | 1 |
1 | 2 |
1 | 3 |
2 | null |
2 | 1 |
2 | 2 |
3 | 1 |
NULL
とNULL
でない値を分けたいので、IS NULL
を使用します。
IS NULL
はboolean型の0(false)
または1(true)
を返すので、ORDER BY
句に指定すると0(false)
→1(true)
の順に並びます。つまりNULL
以外の値が先に、NULL
が後になります。
-
NULL
でない場合 →0(false)
-
NULL
の場合 →1(true)
SELECT
store_id, payment_type
FROM
Orders
ORDER BY
store_id ASC,
payment_type IS NULL ASC,
payment_type ASC;
想定通りの出力結果になりました!
store_id | payment_type |
---|---|
1 | 1 |
1 | 2 |
1 | 3 |
1 | null |
2 | 1 |
2 | 2 |
2 | null |
3 | 1 |
ハマったところ
-
payment_type ASC
を記述していなかった。
ORDER BY xxx IS NULL ASC
がNULL
とNULL
でない値を分けるだけ、であることを理解していませんでした。
NULL
とNULL
でない値を分けたあと、NULL
でない値で昇順にする必要がありました。 -
payment_type IS NULL ASC
→payment_type ASC
の順に記述していた。
下記の処理順となるので、処理順を考慮する必要がありました。
SELECT
store_id, payment_type
FROM
Orders
ORDER BY
store_id ASC,
payment_type ASC, -- 後続の条件よりも優先され、NULLは最小とみなされて先に出力される
payment_type IS NULL ASC;
a. payment_type ASC
の指定でNULL
が先に来る。
b. payment_type IS NULL ASC
は0(false)
→1(true)
に並ぶのでNULL
が後に来るはず。
c. しかし、SQLの処理順的にpayment_type ASC
が優先されNULL
は最小とみなされて先に出力される。