一覧表から条件に合う行だけを別シートへコピーしたい、または処理済みとして移動したい場面はよくあります。
この記事では、VBAで条件に一致した行を別シートへコピー・移動する方法を解説します。データの最終行を取得する方法、貼り付け先の空き行を求める方法、コピーと移動の切り替えまで確認できます。
自分も以前は、月末になると売上データから部門ごとにフィルタをかけ、手作業でコピペして部門別シートに振り分けていました。数百行あると1時間仕事で、貼り付け先を1行ずらして気づかず提出しかけたこともあります。VBAにしてからはボタン1つで同じ条件を再現でき、作業時間と操作ミスを減らせました。同じ作業に時間を取られている人は、まず最小版から試してみてください。
手作業のフィルタとコピペをボタン1つに(Before / After)
| 項目 | Before(手作業) | After(VBAで自動化) |
|---|---|---|
| 操作 | フィルタ→コピー→貼り付け→解除を繰り返し | ボタン1つで自動コピー |
| 所要時間 | 条件ごとに同じ操作を繰り返す | 筆者環境では500行を数秒で処理 |
| ミスのリスク | フィルタ条件の選び間違い、貼り付け先のズレ | 条件選択や貼り付け位置の操作ミスを減らせる |
| 行移動 | コピー後に元の行を1行ずつ手動削除 | コピー元の行も自動削除(オプション) |
条件に合う行を別シートへ送る処理は、条件判定、コピー先の空き行取得、必要な列のコピーという3つに分けると理解しやすい。
実行前の注意:行移動は元に戻せない
ファイルのコピーを別フォルダへ
特に行移動(コピー元の行を削除する処理)を使う場合、元に戻せない。必ずファイルのコピーを別フォルダに保存してから実行する。
ループ方式とオートフィルタ方式、どちらで作るか
「条件に合う行を別シートへ」という処理には、大きく2つの作り方がある。この記事で解説するのは1行ずつ判定するループ方式だが、状況によってはオートフィルタ方式のほうが適している。コードを書き始める前に、自分のケースがどちらかを確認してほしい。
| 状況 | 向いている方式 | 理由 |
|---|---|---|
| 条件が複雑(AND/OR・部分一致・行ごとの分岐) | ループ方式(本記事) | Ifの条件式を自由に書ける |
| コピーだけでなく「移動」(元行の削除)までしたい | ループ方式(本記事) | 削除の制御(下から上ループ)まで一体で書ける |
| 条件は単純で、データが数万行ある | オートフィルタ方式 | 可視セルをまとめてコピーでき、1行ずつのセル操作を減らせる |
| 一度きりの振り分け | 手動フィルタ | マクロを書く時間のほうが長い |
もう1つ、事前に知っておくべき注意がある。行の移動(削除)は、その行を参照している数式を壊す。別シートに =Sheet1!D3 のような直接参照があると、3行目の削除で #REF! エラーになる。集計が数式ベースのブックで行移動を使うなら、数式側を SUMIF のような範囲参照に変えてからにするか、移動ではなくコピー+ステータス列での管理に切り替えるほうが安全だ。
コードを貼る前に決める4つの運用ルール
行の振り分けは、コードが正しく動くだけでは不十分だ。毎月繰り返したときに重複しないか、どの行を移したか確認できるかまで決めておくと、あとから一覧表が崩れにくい。特に次の4点は、最小版を試す前に紙やメモへ書き出しておきたい。
| 決めること | 確認する質問 | おすすめの初期設定 |
|---|---|---|
| 対象行の定義 | 部門だけで判定するか、部門とステータスの両方を見るか | 最初は完全一致の1条件で確認し、必要なら2条件へ広げる |
| コピーか移動か | 元の一覧に対象行を残す必要があるか | 初回はコピー。結果を確認してから移動を検討する |
| 追記か作り直しか | コピー先に前月までのデータを残すか、毎回空にするか | 履歴を残すなら追記し、年月列も一緒に持たせる |
| 二重実行の扱い | 同じマクロを2回押したとき、同じ行が重複してもよいか | 受付番号など一意のID列を用意し、実行後に重複を確認する |
この記事のコードはコピー先へ追記するため、コピーのみの設定で同じマクロを2回実行すると、同じデータが2回追加される。これは不具合ではなく、追記を選んだ結果だ。毎日の運用で再実行する可能性があるなら、実行済み列へ日時を記録する、コピー先のIDと照合する、実行前に当日分を削除して作り直す、のいずれかを運用に加える。
一意のIDがない一覧表では、氏名や部門だけで重複を判断すると同姓同名や異動で誤判定しやすい。注文番号、申請番号、案件番号のように「1行につき1つ」の値を用意しておくと、コピー後の照合にも使える。ID列を増やせない場合は、日付・担当者・金額など複数列を組み合わせて確認する。
STEP1:1条件のコピーを動かす(最小版)
Sheet1のA列が「営業部」の行をSheet2にコピーする。まずはこれで動きを確認する。
'============================================================
' ■ 条件一致行を別シートにコピー(最小版)
' → Sheet1のA列が指定値と一致する行をSheet2にコピー
'============================================================
Sub CopyRowsByCondition()
'--- ★書き換えポイント ---
Dim srcSheetName As String
srcSheetName = "Sheet1" '← コピー元のシート名
Dim destSheetName As String
destSheetName = "Sheet2" '← コピー先のシート名
Dim checkCol As Long
checkCol = 1 '← 判定する列(A列=1)
Dim conditionValue As String
conditionValue = "営業部" '← コピー条件の値
'--- ★ここまで ---
Dim wsSrc As Worksheet
Set wsSrc = ThisWorkbook.Worksheets(srcSheetName)
Dim wsDest As Worksheet
Set wsDest = ThisWorkbook.Worksheets(destSheetName)
If wsSrc Is wsDest Then
MsgBox "コピー元とコピー先には別のシートを指定してください。", vbExclamation
Exit Sub
End If
'--- コピー元の最終行を取得
Dim lastRow As Long
lastRow = wsSrc.Cells(wsSrc.Rows.Count, checkCol).End(xlUp).Row
'--- 見出し行を基準に、コピーする最終列を取得
Dim lastCol As Long
lastCol = wsSrc.Cells(1, wsSrc.Columns.Count).End(xlToLeft).Column
'--- コピー先の次の空き行を取得
Dim destRow As Long
destRow = wsDest.Cells(wsDest.Rows.Count, 1).End(xlUp).Row + 1
Dim r As Long
Dim copyCount As Long
copyCount = 0
For r = 2 To lastRow '← 1行目はヘッダーなので2行目から
If CStr(wsSrc.Cells(r, checkCol).Value) = conditionValue Then
wsSrc.Cells(r, 1).Resize(1, lastCol).Copy _
Destination:=wsDest.Cells(destRow, 1)
destRow = destRow + 1
copyCount = copyCount + 1
End If
Next r
MsgBox copyCount & " 行をコピーしました。", vbInformation
End Sub
最小版で書き換える4つの変数
| 変数 | 説明 | 初期値 |
|---|---|---|
srcSheetName |
コピー元のシート名 | "Sheet1" |
destSheetName |
コピー先のシート名 | "Sheet2" |
checkCol |
条件を判定する列 | 1(A列) |
conditionValue |
コピー条件の値 | "営業部" |
ポイント: コードは行全体ではなく、1行目の見出しが入っている最終列までをコピーする。たとえば見出しがA~D列なら、コピー対象もA~D列だけだ。Excelの行全体は16,384列あるため、Rows(r).Copy で全列をコピーすると、離れた列の数式やメモまで意図せず上書きする可能性がある。Cells(r, 1).Resize(1, lastCol).Copy なら、値、数式、罫線、背景色など必要なセル範囲だけを送れる。値だけ転記したい場合は セルの転記を自動化する方法 を参照。
CStr() は数値の 100 と文字列の "100" をどちらも "100" にそろえて比較できる。一方、日付や小数の表示形式、前後の空白は見た目と実際の値が異なることがある。数値として比較したい場合は IsNumeric で確認してから CDbl を使い、文字列の前後に空白が混じる可能性がある場合は Trim を組み合わせる。
また、For r = 2 To lastRow でループの開始を2行目にしているのは、1行目がヘッダー行だから。ヘッダーが2行ある場合は For r = 3 To lastRow に変更すれば対応できる。最終行を毎回取得するため、データ件数が変わってもループ範囲を手で直す必要はない。
STEP2:複数条件・行移動・進捗表示まで対応(実務版)
この方法を覚えてからは、月末の部門別集計だけでなく、ステータスが「完了」の行を別シートにアーカイブする運用にも使っている。「終わったデータが消えて一覧がスッキリする」と同僚にも好評だった。アーカイブ運用にすると一覧シートが常に「進行中だけ」の状態に保たれるので、スクロール量が減るだけでなく、完了済みの行をうっかり編集してしまう事故もなくなる。過去分を確認したいときはアーカイブシートを見ればよく、月をまたいだ検索もしやすい。
※ 行移動オプションをONにすると、コピー元の行が削除されます。実行前に必ずバックアップを取ってください。
'============================================================
' ■ 条件一致行を別シートにコピー・移動(実務版)
' → 複数条件対応、行移動(コピー元削除)、ヘッダー自動コピー
' → 進捗表示、高速化処理、エラーハンドリング付き
'============================================================
Sub CopyOrMoveRowsAdvanced()
'--- ★書き換えポイント ---
Dim srcSheetName As String
srcSheetName = "Sheet1" '← コピー元のシート名
Dim destSheetName As String
destSheetName = "Sheet2" '← コピー先のシート名
Dim checkCol As Long
checkCol = 1 '← 条件1の判定列(A列=1)
Dim conditionValue As String
conditionValue = "営業部" '← 条件1の値
Dim checkCol2 As Long
checkCol2 = 3 '← 条件2の判定列(C列=3)。使わない場合は0
Dim conditionValue2 As String
conditionValue2 = "完了" '← 条件2の値
Dim useAndCondition As Boolean
useAndCondition = True '← True=AND条件、False=OR条件
Dim deleteAfterCopy As Boolean
deleteAfterCopy = False '← True=行移動(コピー元を削除)、False=コピーのみ
Dim copyHeader As Boolean
copyHeader = True '← True=ヘッダー行(1行目)を自動コピー
'--- ★ここまで ---
'--- シートの取得
Dim wsSrc As Worksheet
Dim wsDest As Worksheet
On Error Resume Next
Set wsSrc = ThisWorkbook.Worksheets(srcSheetName)
Set wsDest = ThisWorkbook.Worksheets(destSheetName)
On Error GoTo 0
If wsSrc Is Nothing Then
MsgBox "コピー元シート「" & srcSheetName & "」が見つかりません。", vbExclamation
Exit Sub
End If
If wsDest Is Nothing Then
MsgBox "コピー先シート「" & destSheetName & "」が見つかりません。", vbExclamation
Exit Sub
End If
If wsSrc Is wsDest Then
MsgBox "コピー元とコピー先には別のシートを指定してください。", vbExclamation
Exit Sub
End If
If checkCol < 1 Or checkCol > wsSrc.Columns.Count Or _
checkCol2 < 0 Or checkCol2 > wsSrc.Columns.Count Then
MsgBox "判定列の番号を確認してください。", vbExclamation
Exit Sub
End If
'--- コピー元の最終行を取得
Dim lastRow As Long
lastRow = wsSrc.Cells(wsSrc.Rows.Count, checkCol).End(xlUp).Row
Dim lastCol As Long
lastCol = wsSrc.Cells(1, wsSrc.Columns.Count).End(xlToLeft).Column
If lastRow < 2 Then
MsgBox "データがありません。", vbExclamation
Exit Sub
End If
'--- ヘッダー行のコピー
Dim destRow As Long
If copyHeader Then
If wsDest.Cells(1, 1).Value = "" Then
wsSrc.Cells(1, 1).Resize(1, lastCol).Copy _
Destination:=wsDest.Cells(1, 1)
End If
End If
destRow = wsDest.Cells(wsDest.Rows.Count, 1).End(xlUp).Row + 1
If destRow = 2 And wsDest.Cells(1, 1).Value = "" Then
destRow = 1
End If
'--- Excelの現在の設定を保存してから高速化
Dim oldScreenUpdating As Boolean
Dim oldCalculation As XlCalculation
Dim oldEnableEvents As Boolean
Dim oldStatusBar As Variant
oldScreenUpdating = Application.ScreenUpdating
oldCalculation = Application.Calculation
oldEnableEvents = Application.EnableEvents
oldStatusBar = Application.StatusBar
'--- エラー発生時も必ず設定を戻す
On Error GoTo CleanUp
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Dim r As Long
Dim copyCount As Long
Dim val1 As String
Dim val2 As String
Dim isMatch As Boolean
Dim errNumber As Long
Dim errDescription As String
copyCount = 0
'--- ループで条件判定 → コピー
For r = 2 To lastRow
val1 = CStr(wsSrc.Cells(r, checkCol).Value)
'--- 条件判定
If checkCol2 = 0 Then
isMatch = (val1 = conditionValue)
Else
val2 = CStr(wsSrc.Cells(r, checkCol2).Value)
If useAndCondition Then
isMatch = (val1 = conditionValue) And (val2 = conditionValue2)
Else
isMatch = (val1 = conditionValue) Or (val2 = conditionValue2)
End If
End If
If isMatch Then
wsSrc.Cells(r, 1).Resize(1, lastCol).Copy _
Destination:=wsDest.Cells(destRow, 1)
destRow = destRow + 1
copyCount = copyCount + 1
End If
'--- 進捗表示(50行ごと)
If r Mod 50 = 0 Then
Application.StatusBar = "処理中... " & r & " / " & lastRow & " 行"
DoEvents
End If
Next r
'--- 行移動(コピー元の行を削除)-- 下から上にループ ★重要
If deleteAfterCopy And copyCount > 0 Then
For r = lastRow To 2 Step -1
val1 = CStr(wsSrc.Cells(r, checkCol).Value)
If checkCol2 = 0 Then
isMatch = (val1 = conditionValue)
Else
val2 = CStr(wsSrc.Cells(r, checkCol2).Value)
If useAndCondition Then
isMatch = (val1 = conditionValue) And (val2 = conditionValue2)
Else
isMatch = (val1 = conditionValue) Or (val2 = conditionValue2)
End If
End If
If isMatch Then
wsSrc.Rows(r).Delete
End If
Next r
End If
CleanUp:
'--- エラー情報を保持して、実行前の設定へ戻す
errNumber = Err.Number
errDescription = Err.Description
On Error Resume Next
Application.ScreenUpdating = oldScreenUpdating
Application.Calculation = oldCalculation
Application.EnableEvents = oldEnableEvents
Application.StatusBar = oldStatusBar
On Error GoTo 0
If errNumber <> 0 Then
MsgBox "エラーが発生しました。" & vbCrLf & _
"エラー番号: " & errNumber & vbCrLf & _
"内容: " & errDescription, vbCritical
Exit Sub
End If
'--- 結果表示
Dim msg As String
msg = copyCount & " 行をコピーしました。"
If deleteAfterCopy Then
msg = msg & vbCrLf & "コピー元から " & copyCount & " 行を削除しました。"
End If
MsgBox msg, vbInformation
End Sub
実務版のコードを使い始めてから、月末の部門別振り分けが本当に楽になった。もっと早く知りたかった。
検証環境: Microsoft 365(Windows 11)で、500行×4列のサンプルデータを対象に約3秒で完了した。実行時間は、該当行数、コピーする列数、数式の量、PC環境によって変わる。数万行を扱う場合は、1行ずつコピーする前に、オートフィルタで可視セルをまとめてコピーする方式とも比較してほしい。
実務版では Application.ScreenUpdating = False で画面更新を止め、Application.Calculation = xlCalculationManual で再計算を手動に切り替えている。重要なのは、処理後に一律で「自動計算」へ変えるのではなく、実行前の設定を保存して同じ状態へ戻すことだ。もともと手動計算で運用しているブックを自動計算へ変えると、別のシートで予期しない再計算が始まる可能性がある。実務版は画面更新、計算方法、イベント、ステータスバーの4項目を保存し、成功時もエラー時も CleanUp で復元する。高速化設定の考え方は 画面更新・再計算を止めてマクロを高速化する方法 も参照。
行移動(deleteAfterCopy = True)を使うときの削除ループが別になっている理由も重要だ。コピーと削除を1つのループでまとめると、行を削除するたびに行番号がずれて正しくコピーできなくなる。そのため「まずコピーだけ完了→そのあと下から上に削除」という2段階構成にしている。行列の挿入・削除でも同じ考え方が使える。
コピーするのは「行全体」ではなく見出しがある列まで
このコードは、1行目の右端の見出しから lastCol を求め、A列からその列までをコピーする。たとえばA~D列が一覧表なら、E列より右側には触れない。コピー先に確認用の数式やメモを置いている場合でも、一覧表の範囲外まで上書きしにくい設計だ。
ただし、見出し行の途中に空欄があっても、右端の見出しまでがコピー範囲になる。逆に、一覧表の右側へ一時的なメモ見出しを置くと、その列までコピーされる。実行前に1行目を見て「どの列までが一覧表か」を確認しておくとよい。固定の表なら lastCol = 4 のように列数を明示する方法もある。
途中でエラーになっても、コピー済みデータは自動で戻らない
CleanUp が元に戻すのはExcelの画面更新や計算方法であり、コピー済みのセルや削除済みの行ではない。コピーだけの設定なら、エラー後にコピー先の追加行を確認して削除できる。行移動の設定では、削除の途中でエラーが起きるとコピー元とコピー先が中途半端な状態になる可能性がある。
本番では、最初に deleteAfterCopy = False で対象行と件数を確認し、問題がないことを確かめてから True に切り替える。バックアップに加えて、この2段階確認を行うと条件の指定ミスにも気づきやすい。
実務版の9つのスイッチ
| 変数 | 説明 | 初期値 |
|---|---|---|
srcSheetName |
コピー元のシート名 | "Sheet1" |
destSheetName |
コピー先のシート名 | "Sheet2" |
checkCol |
条件1の判定列 | 1(A列) |
conditionValue |
条件1の値 | "営業部" |
checkCol2 |
条件2の判定列(不要なら 0) |
3(C列) |
conditionValue2 |
条件2の値 | "完了" |
useAndCondition |
AND/OR条件の切り替え | True(AND) |
deleteAfterCopy |
行移動するか | False(コピーのみ) |
copyHeader |
ヘッダーを自動コピーするか | True |
シート構成の例
コピー元(Sheet1):
| A列(部門) | B列(担当者) | C列(ステータス) | D列(金額) |
|---|---|---|---|
| 営業部 | 田中 | 完了 | 500000 |
| 製造部 | 鈴木 | 進行中 | 300000 |
| 営業部 | 佐藤 | 完了 | 800000 |
| 営業部 | 高橋 | 進行中 | 200000 |
コピー先(Sheet2)– 実行後(条件: A列=営業部 AND C列=完了):
| A列(部門) | B列(担当者) | C列(ステータス) | D列(金額) |
|---|---|---|---|
| 営業部 | 田中 | 完了 | 500000 |
| 営業部 | 佐藤 | 完了 | 800000 |
部門別に複数シートへ振り分けたい場合の考え方
「営業部はSheet2、製造部はSheet3、総務部はSheet4」のように振り分け先が複数ある場合、いちばん簡単なのはこの実務版マクロを部門の数だけ実行することだ。conditionValue と destSheetName を書き換えて3回実行すればいい。毎月やるなら、部門名とシート名のペアを配列に持たせて外側にもう1つループを足す改造になるが、部門数が5つを超えるあたりからコードの見通しが悪くなる。その規模なら、全シートに同じ処理をかける 複数シートに同じ処理を一括実行する方法 の考え方を組み合わせるのがきれいだ。振り分け先シートが将来増えそうかどうかで、書き換え運用か配列ループ化かを最初に決めておくと、あとで作り直しにならない。
振り分け後の検算:件数の足し算が合うか
行移動を使うようになってから、実行後に必ず1つだけ検算している。「コピー先に増えた行数」+「コピー元に残った行数」=「実行前の行数」が成り立つかどうかだ。完了メッセージのコピー件数と、コピー先シートの実際の行数が一致しているかも合わせて見る。ここがずれるのは、条件の書き間違いで意図しない行が動いたか、コピー先の開始位置がずれて既存データを上書きしたかのどちらかで、どちらも早く気づくほど傷が浅い。数百行の振り分けでも検算は10秒で終わるので、ボタンを押したら件数を見る癖をつけておきたい。行数はステータスバー右側の「データの個数」でも、Ctrl + End で最終セルに飛んでも確認できる。
本番前は5行のテストデータで3回確認する
本番の数百行でいきなり試すより、対象3行・対象外2行ほどの小さな一覧を別ブックに作るほうが原因を切り分けやすい。1回目はコピーのみで実行し、対象3行だけが追加されるかを見る。2回目は条件を外れる値へ1件だけ変えて、追加が2行になるかを見る。3回目に行移動を有効にし、コピー先へ2行増え、コピー元から同じ2行だけ消えるかを確認する。
このテストでは、先頭のデータ行、一覧の途中、最終行に対象データを1件ずつ置く。端の行まで正しく処理できれば、ループの開始行や最終行取得の間違いを見つけやすい。空欄、前後に空白がある文字列、数式で表示された値も1件ずつ混ぜると、本番データ特有の不一致を事前に確認できる。
最後に、実行前後の対象件数と金額合計をメモする。行数だけ合っていても、別の行を誤って移動している可能性は残る。件数と合計の両方が一致すれば、条件と転記結果を二方向から確認できる。金額列がない一覧では、受付番号の最小値・最大値や、代表的な3件のIDを照合するとよい。
行がずれて半分しか消えない:削除系の罠5つ
1. 行削除を上から順にやると行がずれて半分しか消えない
自分もこれで1時間溶かした。上から順に Rows(r).Delete したら、削除のたびに下の行が繰り上がって、ループが1行飛ばしになる。結果、条件に合う行が半分しか消えなかった。原因に気づいたときは「なんでこんな単純なことに…」と脱力した。
| # | 症状 | 原因 | 対策 |
|---|---|---|---|
| 1 | 行移動後、コピー元に削除漏れの行が残る | 上から順に削除すると行番号がずれる | For r = lastRow To 2 Step -1 で下から上にループ |
2. コピー先シートが存在しないとエラーになる
| # | 症状 | 原因 | 対策 |
|---|---|---|---|
| 2 | 実行時エラー 9「インデックスが有効範囲にありません」 | シート名のタイプミス or シートが存在しない | シート名を正確に指定する。実務版コードではシート存在チェック済み |
3. ヘッダー行まで条件判定の対象になってしまう
| # | 症状 | 原因 | 対策 |
|---|---|---|---|
| 3 | コピー先にヘッダー行がデータ行として紛れ込む | ループの開始行が1(ヘッダー行)になっている | For r = 2 To lastRow でデータ行(2行目)から開始する |
4. 文字列の前後に空白が入っていて条件が一致しない
| # | 症状 | 原因 | 対策 |
|---|---|---|---|
| 4 | 条件値は正しいのに一部の行がコピーされない | セルの値に見えない半角スペースや改行が含まれている | Trim() で前後の空白を除去してから比較する |
全角・半角の不一致が原因の場合は、StrConv(値, vbNarrow) で半角に統一してから比較する方法もある。文字列変換の詳細は 全角⇔半角を一括変換してデータを統一する方法 を参照。
5. ScreenUpdatingをFalseにしたままエラーで止まると画面が固まる
| # | 症状 | 原因 | 対策 |
|---|---|---|---|
| 5 | マクロがエラーで中断した後、Excelの画面が更新されなくなる | ScreenUpdating = False のまま処理が中断した |
On Error GoTo CleanUp で必ず設定を戻す(実務版コードで対応済み) |
手動で戻す場合はVBEのイミディエイトウィンドウに Application.ScreenUpdating = True と入力してEnter。エラー処理の詳しい書き方は エラー処理(On Error)で止まらないマクロを作る方法 を参照。
VBAで行コピーが別シートにできないときの対処法
「Copyを実行したのにコピー先シートにデータが入らない」という場合、まずコピー先シート名と Destination の指定を確認する。この記事のコードでは wsSrc.Cells(...).Copy Destination:=wsDest.Cells(destRow, 1) のように、コピー元とコピー先の両方へシート変数を付けている。wsDest. を付け忘れると、アクティブシートへ貼り付けられるため、Sheet2が空のままに見えることがある。
VBAのRows.Copyで貼り付け先がずれるときの対処法
「条件に合う行をコピーしたら、コピー先の行がずれて空白行が混ざる」という場合、原因はコピー先の開始行(destRow)を毎回更新していないことだ。destRow = wsDest.Cells(wsDest.Rows.Count, 1).End(xlUp).Row + 1 でコピー先の最終行を取得し、コピーのたびに destRow = destRow + 1 でインクリメントする必要がある。もう1つの原因は、コピー元の行削除を上から順にやっていて行番号がずれているケース。行を削除するときは必ず For r = lastRow To 2 Step -1 で下から上にループすること。
VBAで行移動後にデータが半分しか消えないときの対処法
「deleteAfterCopyをTrueにしたのに、コピー元に行が半分残っている」という場合、原因は行を上から順に削除していることだ。3行目を削除すると元の4行目が3行目に繰り上がるため、ループが1行飛ばしになる。対処法は行削除のループを For r = lastRow To 2 Step -1 で下から上に回すこと。実務版コードはコピーと削除を2段階に分け、削除だけを逆順にしている。
よくある質問(移動・複数条件・値のみコピー)
Q1: コピーではなく移動(コピー元の行を削除)にしたい
実務版コードの deleteAfterCopy = True に変更する。コピー後にコピー元の該当行を下から上に向かって削除する。必ずバックアップを取ってから実行すること。
Q2: 複数の条件で絞り込みたい(AND/OR)
実務版コードで対応している。checkCol2 に2つ目の条件列、conditionValue2 に値を指定する。useAndCondition = True でAND条件、False でOR条件になる。条件が不要な場合は checkCol2 = 0 にすれば条件1のみで動作する。
Q3: コピー先シートがなければ自動で作りたい
以下のコードを、シート取得の部分に追加する。
On Error Resume Next
Set wsDest = ThisWorkbook.Worksheets(destSheetName)
On Error GoTo 0
If wsDest Is Nothing Then
Set wsDest = ThisWorkbook.Worksheets.Add( _
After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count))
wsDest.Name = destSheetName
End If
シート名に使えない文字(\ / * ? : [ ])が含まれるとエラーになるので注意。
Q4: 値だけコピーしたい(書式は不要)
書式や数式をコピーせず、表示元の値だけを転記するなら、実務版のコピー部分を次の2行に置き換える。lastCol は実務版ですでに取得している。
wsDest.Cells(destRow, 1).Resize(1, lastCol).Value2 = _
wsSrc.Cells(r, 1).Resize(1, lastCol).Value2
Value2 を使うと値だけが入り、コピー先に用意した書式は維持される。日付もExcel内部のシリアル値として転記されるため、コピー先の日付表示形式をあらかじめ設定しておく。
Q5: 完全一致ではなく「営業」を含む行を対象にしたい(部分一致)
条件判定の val1 = conditionValue を InStr(val1, conditionValue) > 0 に書き換えると部分一致になる。「営業部」「営業1課」「第二営業部」がすべて対象になるイメージだ。逆に「営業部だけで営業1課は除きたい」なら完全一致のままにする。部分一致は便利だが対象が広がりすぎる事故も起きやすいので、初回は移動ではなくコピーで実行して、対象行が想定どおりかを確認してから移動に切り替えるのが安全だ。
Q6: オートフィルタで絞り込んでからコピーする方法との違いは?
オートフィルタは SpecialCells(xlCellTypeVisible) で可視行をまとめてコピーできるため、大量データでは高速。一方、1行ずつループする方法は条件分岐が柔軟(AND/OR/部分一致など自由に書ける)。用途に応じて使い分けるとよい。オートフィルタを使った方法は オートフィルタでデータを絞り込み・解除する方法 を参照。
まとめ:コピーは上から、削除は下から
- 最小版:
Resize(...).Copy Destination:=で条件一致行の必要な列だけをコピーできる - 実務版: 複数条件・行移動・ヘッダー自動コピー・進捗表示に対応
- 行移動の鉄則: 行を削除するときは
Step -1で下から上にループ。上から削除すると行がずれる - バックアップ必須: 特に行移動(行削除)を使う場合は、実行前に必ずファイルをコピーしておく
まずは最小版で1条件のコピーを試し、コピー先の件数と内容を確認する。次に実務版で複数条件を試し、最後に必要な場合だけ行移動を有効にする。この順番なら、条件指定の誤りに気づかないまま元データを削除するリスクを抑えられる。
行の振り分けとセットで使う関連記事
- 空白行・空白セルを一括で削除する方法 — 行移動後の空白行処理
- マクロをボタン1つで実行する方法 — 完成した振り分けマクロをシート上のボタンから実行する
- 文字列と数値の型変換で計算エラーを防ぐ方法 — 条件値の型が合わず一致しない場合の確認に使う
次のステップ:フィルタ方式で高速化する
- 複数条件でデータを抽出して別シートにまとめる: オートフィルタを使ったデータ抽出で、大量データをさらに高速に処理したい場合
- 配列を使ってVBAの処理を高速化する方法: セルを1行ずつ読み書きする時間を減らしたい場合


コメント