TL;DR — コードでのソートは恒久的な並べ替えです。マクロが動いたあとに Ctrl+Z はありません。だから
.Sortが発火した時点で、保存しておかない限り元の行順は消えています。 元に戻す必要が出るかもしれないなら、ソートする前にインデックス列(1, 2, 3…)を足して おき、あとでそれで並べ直せるようにします。Header:=xlYesは必ず渡してください。さもないと Excel が見出し行をデータの中へソートしてしまうかもしれません。そしてレコード全体を ソートすること — 単一列ではなくCurrentRegionで表全体を。さもないと 1 列だけ並べ替えて、 残りをぐちゃぐちゃにしてしまいます。
' 表を C 列(範囲内で 3 列目)で降順にソートする - レコード全体が一緒に動く。
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Sales")
With ws.Range("A1").CurrentRegion ' 表全体。だからすべての列がそろったまま
.Sort Key1:=.Columns(3), Order1:=xlDescending, Header:=xlYes
End With
ソートは Excel で最も安全な操作に見えます — 手作業では 1 日に何百回もやり、キー 1 つで 取り消せます。コードでは、その安全網は消えています。VBA のソートについて理解すべき最も 大事なことは構文ではありません — マクロの中のソートは、気軽には元に戻せないミューテーション だ、ということです。以下はすべて、まずその事実からデータを守ること、それから仕組み、という 順で組み立てられています。
この記事で学べること
- 考え方の軸 — ソートは恒久的なミューテーションであり、それをどう可逆にするか
- データを救うたった 1 つのルール — レコード全体をソートし、1 列だけは決してソートしない
Header:=xlYes— 省略するとなぜ見出しがデータに落ちるのかRange.Sort(手軽・キー 3 つまで)とSortオブジェクト(冗長・無制限・永続)SortFields.Clearの罠 — 前回の実行からソートキーを引き継ぐ- 文字列として保存された数値が 1, 10, 2 と並ぶ理由 — そしてその直し方
考え方の軸:ソートはミューテーションであってビューではない
前回のガイドは AutoFilter をビュー — 可逆で、何も恒久的には
隠さない — として枠付けしました。ソートはその逆です — ミューテーション。行の順序を
物理的に書き換えます。下に隠れた「元の並び」があって復元できる、ということはありません。
そして決定的なのは、マクロがソートしたあとは Application.Undo が効かないことです —
VBA を走らせると Excel の取り消しスタックがクリアされ、ユーザーも Ctrl+Z を押せません。
これが、すべてのソートの前に考えるべきことを変えます — この並びが大事なら、自分で保存 しなければならない。 安上がりで鉄壁の方法はインデックス列です — ソートの前に行に 1, 2, 3… と刻印しておけば、「元の順に戻す」はその列でのソートにすぎません。
' ソートの前に「並びを戻すための列」を追加する。
Dim i As Long, lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
For i = 2 To lastRow
ws.Cells(i, "Z").Value = i - 1 ' 空いている列に 1, 2, 3...
Next i
' ...あとは好きにソート。戻すには Z 列を昇順にソートする。
元の順が必要になりうるどんなソートでもこの習慣を身につければ、「取り消しがない」問題は 消えてなくなります。
最も大事なルール:1 列ではなくレコード全体をソートする
これがデータを静かに破壊する間違いです。1 列を選んでソートすると、Excel はその列だけを 並べ替え、ほかのすべての列を元の場所に残します。名前がいまや違う電話番号と並び、請求書 ID が 違う金額を指します。何もエラーは出ません — シートが静かに、そして恒久的に間違いになるだけです。
' 間違い - B 列だけをソートし、各行の残りから引きはがす。
ws.Range("B2:B100").Sort Key1:=ws.Range("B2"), Order1:=xlAscending
' 正しい - 表全体をソート。すべての列が自分の行と一緒に移動する。
ws.Range("A1").CurrentRegion.Sort Key1:=ws.Range("B2"), Order1:=xlAscending, Header:=xlYes
ルールはこうです。それでもなおレコードとして完結する、最小の範囲をソートする。 実際には
それは表全体を意味します — Range("A1").CurrentRegion が連続したブロックをつかんでくれます。
ソートのキーは単一列でかまいません。ソートの範囲はレコードのすべての列にまたがらなければ
なりません。これは、1 つのセルだけを削除するのではなく行全体を削除するのと、まったく同じ
整合性の教訓です — レコードの一部を並べ替えれば、その全部を壊します。
Header:=xlYes を指定しないと見出しがデータに紛れ込む
Range.Sort には Header 引数があり、その既定値は xlGuess です — Excel は先頭行が見出しか
どうかを推測しようとします。推測を外すと、あなたの列見出しはほかの行と同じようにデータの
中へソートされ、どこか真ん中に着地します。決して推測させてはいけません。
.Sort Key1:=.Columns(1), Order1:=xlAscending, Header:=xlYes ' 先頭行は見出し - 動かさず残す
範囲が見出し行を含むとき(通常のケース)は Header:=xlYes を、純粋なデータのときは xlNo を
渡します。このたった 1 つの引数が、あの古典的な「見出しが 43 行目に行ってしまった」バグを
防ぎます。
2 つのソート方法:Range.Sort と Sort オブジェクト
VBA は 2 つの API を用意していて、簡潔さと機能を引き換えにします。
Range.Sort は手軽なほうです — 最大3 つのソートキー(Key1/Key2/Key3)を持つ 1 文の
ステートメント。よくあるケースにぴったりです。
' Region(昇順)で、次に各 Region 内で Amount(降順)でソートする。
ws.Range("A1").CurrentRegion.Sort _
Key1:=ws.Range("B1"), Order1:=xlAscending, _
Key2:=ws.Range("C1"), Order2:=xlDescending, _
Header:=xlYes
Worksheet.Sort オブジェクトは冗長なほうです — SortFields を 1 つずつ追加します — が、
3 キーの上限を取り払い、Range.Sort にはできないセルの色・フォントの色・アイコンによる
ソートに対応します。さらに永続すること — ソート設定がシートに貼り付く — が、まさに鋭い刃を
持つ理由です。
With ws.Sort
.SortFields.Clear ' <-- 重要: まず残っているキーを捨てる
.SortFields.Add Key:=ws.Range("B2:B100"), Order:=xlAscending
.SortFields.Add Key:=ws.Range("C2:C100"), Order:=xlDescending
.SetRange ws.Range("A1").CurrentRegion
.Header = xlYes
.Apply
End With
Sort オブジェクトを定義する罠 — SortFields.Clear を最初に置かなければなりません。 ソート
フィールドはワークシートに永続するので、前回のマクロ実行 — あるいは手でソートしたユーザー —
が残したキーがまだそこにあります。.Clear を飛ばすと、古いキーと新しいキーを合わせた、
あなたが頼んでもいない混合でソートしてしまいます。私のルールはこうです。3 キーまでのすべてに
Range.Sort を選び、本当にもっと多くのキーか色/アイコンのソートが必要なときだけ Sort
オブジェクトへ移る — そしてそのときは、With の中の最初の 1 行が SortFields.Clear です。
データ型の罠:文字列として保存された数値
「変な順序で出てくる」ソートは、ほぼ必ずデータ型の問題です。数値の列が実は文字列として 保存されていると — CSV や Web からのインポート後によくあります — Excel はそれを辞書順に、 1 文字ずつソートします — "1"、"10"、"2"、"21"、"3"。値は数値に見えるのに、言葉のように並ぶのです。
直し方は上流にあります — ソートの前に列を本物の数値に変換すれば、2 は 10 の前に並びます。
それは変換の仕事です — インポートしたテキストを本物の数値型に変えるには
CStr、CDate、Val を参照してください。列が本物の数値を持てば
ソートは正しくなります。ソート引数をいくらいじっても、数値のふりをした文字列は直りません。
ExcelMaster の活用
ソートは、なじみ深く親しみやすい操作の裏にその危険を隠します — 見出しを埋めてしまう Header:=xlYes
の書き忘れ、レコードをぐちゃぐちゃにする単一列ソート、古いキーを引き継ぐ SortFields.Clear の
欠落、文字列としての数値の順序、そして何より、マクロが動いたあとは取り消しがないという事実。
どれもエラーを出しません — ただ、データを誤って並べ替えたまま、取り返しのつかない状態に
するだけです。
ExcelMaster なら、望むソートを
説明するだけです。「地域で、次に金額の高い順でソートして」と言えば、レコード全体を(1 列では
なく)ソートし、Header を明示的に設定し、古いソートフィールドをクリアし — そして先にシートを
バックアップするので、Excel 自身の取り消しが消えていても戻り道を残します。ブックもコードも
あなたの手元に残り、1 列ソートがすべての名前をその数字から静かに切り離す、あの一幕だけを
飛ばせます。
よくある質問
VBA マクロがやったソートを取り消せる?
いいえ。マクロを走らせると Excel の取り消しスタックがクリアされるので、マクロが行った ソートは、あなたのコードもユーザーも Ctrl+Z できません。ソートの前に自分で元の並びを 守りましょう — いちばん簡単なのはインデックス列(1, 2, 3…)を刻印してあとでそれに並べ直せる ようにすることか、先にシートをバックアップすることです。
VBA のソートがデータをぐちゃぐちゃにしたのはなぜ?
ほぼ必ず、レコード全体ではなく単一の列をソートしたからです。1 列をソートするとその列だけが
並べ替わり、ほかは元の場所に残るので、各行の値がもう一緒ではなくなります。表全体を —
Range("A1").CurrentRegion を — ソートし、ソートキーは並べたい列を指すようにしましょう。
VBA が見出し行をデータの中にソートするのを止めるには?
.Sort に Header:=xlYes を渡します(Sort オブジェクトなら .Header = xlYes を設定)。
既定値は xlGuess で、Excel に見出しの検出を試させ、ときに見出しを行の中へソートさせて
しまいます。xlYes を明示的に設定すれば、先頭行は見出しとして固定されたままになります。
VBA で Range.Sort と Sort オブジェクトの違いは?
Range.Sort はソートキー 3 つに限られる 1 文のステートメント — 手軽で、ほとんどの仕事に
適しています。Worksheet.Sort オブジェクトは SortFields.Add でキーを 1 つずつ追加し、
無制限のキーと色やアイコンによるソートに対応し、設定をシートに永続させます — なので古いキーを
引き継がないよう、まず SortFields.Clear を呼ばなければなりません。
VBA のソートが 10 を 2 の前に置くのはなぜ?
それらの数値が文字列として保存されていて、文字列は数値順ではなく辞書順("1"、"10"、"2")に
並ぶからです。ソートの前に列を本物の数値に変換しましょう — CStr/Val の変換ガイドを参照 —
そうすれば値は本当の数値順に並びます。
検証環境
動作確認: Excel 365 (Windows 11), VBA 7.1 — 最終確認 2026-08-08。
関連ガイド: VBA Find · VBA AutoFilter · VBA CStr, CDate and Val · VBA Delete Rows · VBA Range
