LoginSignup
1
0

More than 1 year has passed since last update.

ORDER BY & PARTITION BY と ROW_NUMBER() に NULLが絡んだことでハマった話

Last updated at Posted at 2023-03-13

個人の備忘録として、改めてNULLの扱いには気をつけないといけないなと思ったのでブログとして残しておきます。

問題のクエリ

サンプルとして以下のクエリをBiqQueryに投げてみます。

sample.sql
create or replace table `project_id.dataset.table`
(
  id int64
  , ratio float64
  , check string
);

insert into `project_id.dataset.table`
values
  (1, 0.2, 'takashi'),
  (1, 0.3, 'ichiro'),
  (1, null, 'ken'),
  (1, null, 'takashi'),
  (2, 0.5, 'ichiro'),
  (2, null, 'yusuke'),
  (2, null, 'atsushi'),
  (2, 1.2, 'taro'),
  (3, null, 'jiro'),
  (3, null, 'saburo'),
  (3, null, 'taro')
;

/*
  実行の度に結果が変わってしまう
*/
select
  *
  , row_number() over(partition by id order by ratio) as rn
from
  `project_id.dataset.table`;

前半2つはテーブルを作成しているだけなので特に問題ないと思います。3つ目もよくあるパターンだと思いますが、window関数を使ってidが同じgroupの中で、ratioの順序情報をrnカラムとして定義している形になります。

そして肝心の結果ですが、以下のように実行のタイミングで異なるケースが出力されてしまうパターンがありました。

case1 case2
case1.png case2.png

対応

普段のソートと違ってグループ内順序を考慮していきたいケースにおいて、予期せぬバグを産んでしまう可能性もある挙動だと思います。sortがstableなのかどうか意識が抜けてしまう可能性も大いにあるかなと。

なので、もし同順位であることを明示的に示したい場合、row_numberでなく積極的にrankを使うのも一つの手であるかもしれません。

そうすることでnullに関して以下のような形でrankingが当てられます。勿論割り当てられる数字がユニークでなくなる事には注意が必要ですが。

sample2.sql
/*
  row_number -> rank へ変更
*/
select
  *
  , rank() over(partition by id order by ratio) as ranking
from
  `project_id.dataset.table`;

case3.png

いずれのパターンにせよ、row_numberとrankの挙動の違いを意識することは大切そう。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0