Power Queryで「更新日の新しい順に並べ替えてから、顧客IDで重複を削除する」と、顧客ごとに最新の行が残ることを期待します。ところが結果を見ると、古い行が残っていることがあります。こんなときに使われるのが Table.Buffer です。
この記事では次の内容を説明します。
- Table.Bufferで並べ替えの順序を固定する方法
- 並び順に頼らずに残す行を指定する方法
- 同じデータを何度も読むときの使いどころ
- 逆に遅くなる場面の見分け方
Excel本体やVBAでの重複削除は、別の記事で扱います。
並べ替え後の重複削除で残る行がずれる理由
Power Queryは、ステップを上から1つずつ完成させながら進むとは限りません。最終結果を出すために式をまとめて評価し、データソースによっては処理をデータベース側に任せます(クエリフォールディング)。
その過程で、並べ替えた順序が重複削除の時点まで保たれる保証はありません。重複の削除は「重複した行のうち1行を残す」処理で、直前に並べ替えた順の先頭を必ず残すとは限らないのです。
いつもずれるわけではありません。少ないデータで試すと期待どおりに見えても、データ量やデータソースが変わると結果が変わることがあります。動作に頼らない書き方にしておくのが安全です。
Table.Bufferで並び順を固定する
Table.Buffer は、指定したテーブルをその時点でメモリに読み込み、以降のステップに読み込み済みの内容を使わせる関数です。並べ替えた結果をバッファーしておけば、重複の削除はその順序で並んだテーブルを受け取ります。
画面操作から書き換える手順
- 更新日の列を降順に並べ替える
- 顧客IDの列を選び、「ホーム」タブの「行の削除」→「重複の削除」を選ぶ
- 「適用したステップ」で重複の削除のステップを選び、数式バーの式を書き換える
書き換える前の式は次のような形です。
= Table.Distinct(#"並べ替えられた行", {"顧客ID"})
直前のステップを Table.Buffer( ) で囲みます。
= Table.Distinct(Table.Buffer(#"並べ替えられた行"), {"顧客ID"})
ステップ名は操作や環境によって違うことがあります。自分の式に書かれている名前をそのまま囲んでください。数式バーが表示されていないときは、「表示」タブで「数式バー」にチェックを入れます。
クエリ全体の例
let
ソース = Excel.CurrentWorkbook(){[Name="顧客履歴"]}[Content],
型設定 = Table.TransformColumnTypes(ソース, {{"顧客ID", type text}, {"更新日", type date}}),
並べ替え = Table.Sort(型設定, {{"更新日", Order.Descending}}),
固定 = Table.Buffer(並べ替え),
重複削除 = Table.Distinct(固定, {"顧客ID"})
in
重複削除
同じ日付の行があるときは2つ目の並べ替えキーを足す
同じ顧客で更新日が同じ行が複数あると、どれが残るかは決まりません。更新時刻や連番の列があれば、2つ目の並べ替えキーに加えます。
並べ替え = Table.Sort(型設定, {{"更新日", Order.Descending}, {"連番", Order.Descending}}),
並び順に頼らない方法:グループ化で残す行を指定する
Table.Bufferを使う方法は短く書けますが、「並べ替えた順の先頭を残す」という前提の上に成り立っています。残す行の条件を式の中に直接書きたい場合は、グループ化を使います。
let
ソース = Excel.CurrentWorkbook(){[Name="顧客履歴"]}[Content],
型設定 = Table.TransformColumnTypes(ソース, {{"顧客ID", type text}, {"更新日", type date}}),
グループ = Table.Group(型設定, {"顧客ID"}, {{"最新行", each Table.Max(_, "更新日"), type record}}),
展開 = Table.ExpandRecordColumn(グループ, "最新行", {"顧客名", "更新日", "金額"})
in
展開
Table.Max は、指定した列の値が最大の行を1行返します。顧客IDごとに「更新日が最大の行」を取り出すので、並び順の影響を受けません。
ただし、最大の日付が同じ行が複数ある場合は、どれが選ばれるかの問題が残ります。その可能性があるデータでは、比較に使う列を一意に決まるものにします。
画面操作でも作れます。「変換」タブの「グループ化」で、操作に「すべての行」を選んでから、上の式の形に書き換えます。
結果が正しいか確かめる
どちらの方法でも、意図した行が残ったかは確認が必要です。
- 行数を確かめる:結果の行数が、顧客IDの重複しない値の数と一致するか
- 抜き取りで確かめる:行が多い顧客をいくつか選び、元データの最新日と結果の日付が同じか
- 全件で照合する
- 別のクエリで、顧客IDごとに更新日の最大値をグループ化(操作:最大)で出す
- そのクエリを結果のクエリと顧客IDでマージする
- 2つの日付が等しいかを判定する列を追加する
- 等しくない行だけに絞り込み、0行なら一致
重複が残る原因が、大文字・小文字の違いや前後の空白の場合は、Table.Bufferでは直りません。キーの整え方は、重複削除の記事を参照してください。
同じデータを何度も読むときの使いどころ
Power Queryは、あるステップが式の中で何度も参照されると、そのたびに評価し直すことがあります。次のような場面では、バッファーで読み込みを1回にまとめると速くなる場合があります。
1つのクエリの中で、同じステップを複数回使う
前のステップを2か所から参照する場合や、自分自身の別のステップとマージする場合です。参照されるステップをバッファーします。
行ごとに別の一覧を参照するカスタム列や絞り込み
各行で同じ一覧を照合する処理では、一覧が行ごとに評価されることがあります。一覧は List.Buffer で固定します。
対象 = List.Buffer(対象コード表[商品コード]),
絞り込み = Table.SelectRows(明細, each List.Contains(対象, [商品コード]))
同じ要求を何度も送ると遅い、または回数制限があるデータソース
Webやデータベースなどがこれに当たります。
なお、バッファーの効果は、そのクエリを評価している間だけです。クエリAの最後にTable.Bufferを入れても、Aを参照するクエリBではAが改めて評価されます。バッファーした内容はクエリ間で共有されません。
逆に遅くなる・効かない場面
| 場面 | 理由 | 対処 |
|---|---|---|
| データベースなどから取り込み、Bufferの後で行や列を絞り込む | Bufferより後のステップはデータソース側で処理されず、全件を読み込んでから絞り込むことになる | 行・列の絞り込みはBufferより前に置く |
| 大きなテーブルを丸ごとバッファーする | すべてをメモリに読み込むため、メモリ不足や遅延の原因になる | 必要な列と行に減らしてからバッファーする |
| 1回しか参照されないステップをバッファーする | 評価し直しを減らす効果がなく、読み込みの手間だけが増える | Table.Bufferを外す |
| 別のクエリと共有するつもりでバッファーする | クエリ間では共有されない | 参照する側のクエリの中でバッファーする |
| 入れ子のテーブル列(マージ直後など)をバッファーする | Table.Bufferが確定させるのはセルの値までで、セルの中のテーブルやリストまでは読み込まない | 必要な列を展開してからバッファーする |
速くなったかを比べる方法
Table.Bufferの効果はデータやデータソースによって違うため、入れる前と後で比べて判断します。
- 同じデータで、Bufferありとなしの更新時間を比べる
- 2回目以降はキャッシュの影響で速くなることがあるので、何回か更新して比べる
- 細かく調べたい場合は、Windows版のPower Queryエディターの「ツール」タブにあるクエリ診断で、ステップごとの処理を確認できる(表示はバージョンで違うことがある)
速くならなければ、Table.Bufferは外しておきます。不要なバッファーは、データが増えたときにメモリ不足の原因になります。
使い分けのまとめ
| 目的 | 方法 |
|---|---|
| 並べ替えた順の先頭を残して重複を削除したい | 並べ替えのステップを Table.Buffer で囲んでから Table.Distinct |
| 並び順に頼らず、残す行の条件を明示したい | Table.Group と Table.Max で各キーの最新行を取り出す |
| 行ごとに同じ一覧を照合していて遅い | 一覧を List.Buffer で固定する |
| 1つのクエリ内で同じステップを何度も使っていて遅い | 参照されるステップを Table.Buffer する(絞り込みは先に済ませる) |
