【2025年9月】
先日のセミナーで「どんなときマクロを使っているか」の話題になりました。大規模や複雑な処理はもちろんですが、意外と多かったのは、受け取ったデータを"整形"するケースだとか。たとえば、どこかから送られてきたデータが「セル内改行されてた」「セルが結合されてた」「半角と全角が混在していた」「余分なスペースが含まれていた」などなど。そのままでは処理できないので、マクロで一気に整形してから先に進むと。これ実は、マクロの使い方として、とても多いです。いわゆる"小ネタ"ですね。今回は、そんな「データの整形」マクロを、AIに作ってもらいましょう。なお、別のコンテンツとして、同じことをワークシート関数でやる方法も聞いてみるつもりです。
セル範囲A2:A5にデータが入力されています。データは「ABC-100」のような形式ですが、"英字", "記号", "数値"の一部が全角で入力されているものがあります。それら「半角と全角が混在」されているデータを、すべて半角に統一して同じ行のB列に代入するVBAのコードを作って。
これ、ときどき出るんですよね。ちなみに何度かPythonで試してみましたが、望むような結果を得られたことは一度もありませんでした(当社比)。こんなんPython使うまでもなく、VBAで簡単にできるっしょ。プロンプトに「Pythonを使わずに」の一言を加えてみます。
セル範囲A2:A5にデータが入力されています。データは「ABC-100」のような形式ですが、"英字", "記号", "数値"の一部が全角で入力されているものがあります。それら「半角と全角が混在」されているデータを、すべて半角に統一して同じ行のB列に代入するVBAのコードを作って。Pythonは使いません!
Sub ConvertToHalfWidth() Dim i As Long Dim ws As Worksheet Set ws = ThisWorkbook.Sheets("Sheet1") For i = 2 To 5 ws.Cells(i, 2).Value = StrConv(ws.Cells(i, 1).Value, vbNarrow) Next i End Sub
StrConv関数は、文字種を変換する関数です。第2引数に指定した定数"vbNarrow"は、全角文字を半角文字に変換しろという指示です。他にも次のような定数を使えます。
定数 | 値 | 内容 |
---|---|---|
vbUpperCase | 1 | 文字列を大文字に変換します |
vbLowerCase | 2 | 文字列を小文字に変換します |
vbProperCase | 3 | 文字列の各単語の先頭の文字を大文字に変換します |
vbWide | 4 | 文字列内の半角文字を全角文字に変換します |
vbNarrow | 8 | 文字列内の全角文字を半角文字に変換します |
vbKatakana | 16 | 文字列内のひらがなをカタカナに変換します |
vbHiragana | 32 | 文字列内のカタカナをひらがなに変換します |
vbUnicode | 64 | システムの既定のコードページを使って文字列をUnicodeに変換します |
vbFromUnicode | 128 | 文字列をUnicodeからシステムの既定のコードページに変換します |
定数vbWideと定数vbNarrowは、国別情報が中国、韓国、日本の場合に適用されます。引数vbKatakanaと引数vbHiraganaは、国別情報が日本の場合に適用されます。
さて、これは想定どおりというか、誰にでもできる簡単な仕事です。ちょっと難易度を上げて「一部の文字種だけ変換」したいときは、どうでしょう。ここでは"英字"と"数値"は変換するけど、"記号"はそのままにしてもらいます。
セル範囲A2:A5にデータが入力されています。データは「ABC-100」のような形式ですが、"英字", "記号", "数値"の一部が全角で入力されているものがあります。それら「半角と全角が混在」されているデータを、"英字"と"数値"だけを半角に変換し、"記号"はそのまま変換しない文字列を、同じ行のB列に代入するVBAのコードを作って。Pythonは絶対に使いません!
Sub ConvertAlphaNumToHalfWidth() Dim i As Long, s As String, c As String, result As String Dim code As Long Dim ws As Worksheet Set ws = ThisWorkbook.Sheets("Sheet1") For i = 2 To 5 s = ws.Cells(i, 1).Value result = "" For code = 1 To Len(s) c = Mid(s, code, 1) ' 英字(全角)を半角に If AscW(c) >= &HFF21 And AscW(c) <= &HFF3A Then result = result & ChrW(AscW(c) - &HFF21 + &H41) ' 英字(全角小文字)を半角に ElseIf AscW(c) >= &HFF41 And AscW(c) <= &HFF5A Then result = result & ChrW(AscW(c) - &HFF41 + &H61) ' 数字(全角)を半角に ElseIf AscW(c) >= &HFF10 And AscW(c) <= &HFF19 Then result = result & ChrW(AscW(c) - &HFF10 + &H30) ' 英字・数字(半角)はそのまま ElseIf (AscW(c) >= &H30 And AscW(c) <= &H39) Or _ (AscW(c) >= &H41 And AscW(c) <= &H5A) Or _ (AscW(c) >= &H61 And AscW(c) <= &H7A) Then result = result & c ' 記号はそのまま Else result = result & c End If Next code ws.Cells(i, 2).Value = result Next i End Sub
上記は、AIの提示したコードを、そのまま表示しています。このコードを、そのままモジュールにコピーしてもエラーになります。理由は「&」などの記号です。これは「実体参照」というHTMLのルールです。詳しくは、下記のページをご覧ください。
[作って]条件に一致したセルだけ操作するマクロの「特定の行だけコピーする」パート
コードをコピーするときは、「&」を「&」に、「<」を「<」に、「>」を「>」に置換してください。セルごとに、入力されている文字列を、左から1文字ずつチェックしています。AscW関数は、Unicode文字の文字コードを返します。判定している「&HFF3A」などは、数値の16進表記です。特定の文字種だけ変換すると、このようにメッチャ複雑になります。正規表現を使う手もありますけど、AIは提示してこないでしょうね。これをワークシート関数でやるのも、かなり難解です。にしても、文字種の判定をElseIfでやっているのは、いただけません。ここはDo Loopでしょ。そのためのステートメントでしょ。このような、複数判定をElseIfで書くと、可読性が低下します。お勧めしません。
苗字と名前の間に、半角のスペースがあります。この半角スペースだけを全角に変換してもらいましょう。
セル範囲A2:A5にデータが入力されています。データは「田中 亨」や「Bill Gates」のように、姓と名の間が半角スペースで区切られています。この半角スペースを全角スペースに置換した結果を、同じ行のB列に代入するVBAのコードを作って。Pythonには触れないで!
Sub ReplaceHalfSpaceWithFullSpace() Dim i As Long For i = 2 To 5 With Worksheets("Sheet2") .Cells(i, 2).Value = Replace(.Cells(i, 1).Value, " ", " ") End With Next i End Sub
これは簡単すぎましたね。先のStrConv関数を使っちゃうと、英字も全角になっちゃうというのを入れたのですが、引っかからなかったですね。さらに、珍しくWithステートメントを使ってきました。AIはオブジェクト変数が"お好み"のようで、あまり見かけません。じゃ、もっと難易度上げちゃいましょう。
セル範囲A2:A5にデータが入力されています。データは「田中 亨」や「Bill Gates」のように、姓と名の間が半角スペースで区切られています。このうち、姓と名がどちらも日本語の場合だけ、半角スペースを全角スペースに置換した結果を、同じ行のB列に代入するVBAのコードを作って。
日本語の判定は、おそらく先と同じように文字コードをチェックするんだろうな。
Sub ReplaceHalfSpaceWithFullSpace_OnlyJapanese() Dim i As Long Dim arr, lastName, firstName For i = 2 To 5 With Worksheets("Sheet2") arr = Split(.Cells(i, 1).Value, " ") If UBound(arr) = 1 Then lastName = arr(0) firstName = arr(1) If IsJapanese(lastName) And IsJapanese(firstName) Then .Cells(i, 2).Value = lastName & " " & firstName Else .Cells(i, 2).Value = .Cells(i, 1).Value End If Else .Cells(i, 2).Value = .Cells(i, 1).Value End If End With Next i End Sub Function IsJapanese(str As String) As Boolean Dim i As Long, ch As String IsJapanese = True For i = 1 To Len(str) ch = Mid(str, i, 1) If AscW(ch) < &H3000 Or AscW(ch) > &HFFFD Then IsJapanese = False Exit Function End If Next i End Function
実行したら、珍しくエラーになりました。
AIは、比較的VBAが得意なので、コードの書き方はともかく、実行してエラーになることは珍しいです。エラーメッセージは上図のとおりです。このメッセージ、ビギナーは心臓に悪いですね。「ByRef引数の~」とのことですが、VBA学習者にとって、ByRefとByValってのは、最も難解な概念のひとつです。簡単に書きます。プロシージャに引数を渡すときは「値渡し(ByVal)」と「参照渡し(ByRef)」があります。値渡し(ByVal)で渡した引数は、その値が渡されますので、受け取ったプロシージャ側で引数を変更しても、元の渡したプロシージャ側では何も変化しません。対して参照渡し(ByRef)で渡すと、実際にはメモリ内への参照が渡されますので、もし受け取ったプロシージャ側で引数を変更してしまうと、元の渡したプロシージャ側でも内容が変わってしまいます。さて、今回のエラーですが、そうした引数の"渡し方"を間違えたのではなく、引数の"型"が違っていました。
上図のように、Finctionの引数は文字列型(String)で定義しています。それなのに、バリアント型の変数を渡しました。だからエラーです。エラー画面の図に、引数の宣言も含めていますので、ご確認ください。ちなみに「IsJapanese(str As String)」は、ByValもByRefも書いていません。省略するとByRefとみなされます。なので正解は、次のように正しく変数宣言をします。
Dim arr As Varinat, lastName As String, firstName As String
引数「arr」は、Split関数の結果を受け取って配列になりますので、バリアント型でなければなりません。「バリアント型って文字列も格納できるんじゃ?」って思った方に、ちょっと補足しますと、バリアント型変数に文字列を代入すると、その変数が文字列型になるのではなく「内部形式が文字列型のバリアント型変数」になります。純粋な文字列型とは異なります。意味の分からない方は、見なかったことにしてください。
セル範囲A2:A4にデータが入力されています。データはセル内改行されているものもあります。セル内改行されていなかったら、そのまま同じ行のB列に代入し、もしセル内改行されていたら、セル内改行で区切った前の文字列をB列に、区切った後ろの文字列をC列に代入するようなVBAのコードを作って。
Sub SplitCellByLineBreak() Dim rng As Range Dim cell As Range Dim arr As Variant Set rng = Range("A2:A4") For Each cell In rng If InStr(cell.Value, vbLf) > 0 Then arr = Split(cell.Value, vbLf) cell.Offset(0, 1).Value = arr(0) cell.Offset(0, 2).Value = arr(1) Else cell.Offset(0, 1).Value = cell.Value cell.Offset(0, 2).Value = "" End If Next cell End Sub
正常に動作します。しますけど、何だろう?この感覚はw どうして急にFor Each使ってきたんだろ。今までずっとFor Nextだったじゃん。だから「一貫性がない」とか「考え方がブレてる」とか言われるんすよ。そういうとこだよ?ちなみに長年VBAを教えてきましたけど、For Eachが分からないって人はメッチャ多いです。その前に、For Eachで必須のオブジェクト変数も、正しく認識してる人が少ないです。対してFor Nextは丁寧に説明すれば、比較的理解しやすいです。今回の場合、どちらも使えるのですから、より易しいFor Nextが良いでしょうね。また、突然「Range("A2:A4")」みたいに、シートの階層構造を省略してきましたけど、じゃ、今までの無駄な「Set ws = ActiveSheet」は何だったのかと。あ、でも、Offsetプロパティを使ってるのはGJです。B2とかC3などのアドレスを、何とかして文字列結合するような人はAIを見習って、ぜひOffsetの学習をしてくださいな。
A列のデータを、B列のように変換するVBAのコードを作って
最後は、思い切ってプロンプトを簡素化しました。どうせ、AIはブックの内容を見てるんだろうから、見れば分かるっしょ。
ちっ!
セル範囲A2:A7にデータが入力されています。データのいくつかは"セル結合"されています。もし、3つのセルが結合されたセルに文字列が入力されていたら、その文字列をB列に3行分割して代入するようなVBAのコードを作って。
Sub SplitMergedCellsToColumnB() Dim ws As Worksheet Set ws = ThisWorkbook.Sheets("Sheet4") Dim rng As Range Dim cell As Range Dim i As Long Set rng = ws.Range("A2:A7") For Each cell In rng If cell.MergeCells Then If cell.MergeArea.Cells.Count = 3 Then Dim val As String val = cell.Value For i = 1 To 3 cell.Offset(i - 1, 1).Value = val Next i End If Else cell.Offset(0, 1).Value = cell.Value End If Next cell End Sub
あ、ダメだ。「3セルが結合されてる」って前提のコード作ってきた。もし、って意味分かんないのかな。If...Then...Elseステートメントの"If"ですよ。
セル範囲A2:A7にデータが入力されています。データのいくつかは"セル結合"されています。それぞれのセルで、結合しているセル数は異なります。仮に、3つのセルが結合されたセルに文字列が入力されていたら、その文字列をB列に3行分割して代入するようなVBAのコードを作って。
Sub SplitMergedCellsToColumnB_VarCount() Dim ws As Worksheet Set ws = ThisWorkbook.Sheets("Sheet4") Dim rng As Range Dim cell As Range Dim i As Long Set rng = ws.Range("A2:A7") For Each cell In rng If cell.MergeCells Then If cell.Address = cell.MergeArea.Cells(1).Address Then Dim val As String val = cell.Value Dim mergeCount As Long mergeCount = cell.MergeArea.Cells.Count For i = 1 To mergeCount cell.Offset(i - 1, 1).Value = val Next i End If Else cell.Offset(0, 1).Value = cell.Value End If Next cell End Sub
エラーにはなりませんけど、望んだ結果ではありません。
エラーにはならないけど望む結果にならない、ってのは最も困ります。どこが間違っているのか、コード全体を解析しなければなりません。ちなみに今回のは、かなり難しいです。一見すると、考え方は正しいように見えますよね。この件、詳しく解説するとメッチャややこしいので、すみません、ここでは割愛します。ヒントと修正だけ書きますね。たとえば、セル範囲A2:A4が結合されているとき、Range("A2")のOffset(1, 0)ってRange("A3")じゃなくてRange("A5")になります。これは「Excelの"結合セルに関する"仕様」ですけど、誤動作の原因はこれです。次のようにすると、正しく動作します。
Sub SplitMergedCellsToColumnB_VarCount() Dim ws As Worksheet Set ws = ThisWorkbook.Sheets("Sheet4") Dim rng As Range Dim cell As Range Dim i As Long Set rng = ws.Range("A2:A7") For Each cell In rng If cell.MergeCells Then If cell.Address = cell.MergeArea.Cells(1).Address Then Dim val As String Dim target As Range Set target = cell.MergeArea.Cells(1).Offset(0, 1) val = cell.Value Dim mergeCount As Long mergeCount = cell.MergeArea.Cells.Count For i = 1 To mergeCount target.Offset(i - 1, 0).Value = val Next i End If Else cell.Offset(0, 1).Value = cell.Value End If Next cell End Sub
今までも、ずっと思っていましたが、今回の検証であらためて「AIは一貫性がない」と感じました。For NextでもFor Eachでも、どっちでもできる。オブジェクト変数でも、Withステートメントでも、どっちでもできる。そんな「どっちでもできる」ってのがプログラミングには多いです。どっちでもいいので、普通は作者が何らかの意図を見いだして選びます。その意図には、作者の"思い"が込められています。AIには"思い"がありません。あるのは"統計的な確率"です。だから、どっちでもいいケースは、ランダムに選ばれます。もちろん、それ自体は間違っていません。ただビギナーは、同じような処理で異なる書き方を見ると「何か重要な意味があるのでは?」って思います。ないんですよ、AIのコードには、そんなの。さらに、たとえば似たような処理で、2種類の書き方が提示されていたら、その2種類とも理解しなければなりません。学習量が2倍です。辛いですよね。とはいえ、AIが提示したコードを精査せず「動けばいいや」って使うと、何かあったら大変です。いやはや、AIを使えば使うほど、人間の学習が必要になってきますね。それが、現在のAIです。