- ベストアンサー
エクセルで同じ行にあるデータを複数行に分ける方法
- 商品などのデータがエクセル上に入力してあり、現在は一行に一商品が対応しています。商品が持っているオプション情報を同じ行の中に記載しています。商品毎に持っているオプション情報の数が違うので、可変長ということになります。自力でなんとかしようとエクセルと格闘していたのですが、もう少し効率的に行う方法がないかと探しています。
- 商品数×最大オプション数(14)の行を設定して、14行ごとに新しい商品名をVLOOKUP関数を使って取得する方法を考えましたが、効率的でなく手間がかかります。商品数が多く、逐次発生する作業なので、もっと効率的な方法があれば教えていただきたいです。
- Excel 2011(Mac版)を使っていて、VBAの知識はほとんどありませんが、コードをコピペして一部改変したことはあります。Windows版の最新バージョンも使える環境です。質問者はエクセルで同じ行にあるデータを複数行に分ける方法を探しており、効率的な方法やVBAを使った方法を知りたいとしています。
- みんなの回答 (5)
- 専門家の回答
質問者が選んだベストアンサー
各々の商品の数が不定なので、VBAで考えるのがふさわしい(初等的に考えられる) エクセル関数でもできないことはないように思うが。 ーー Sheet1に元データがあるとして マクロ・VBAの入門本(WEB)の初めをさんしょうしながら、やってください。 1.最終行番号を取得する(1) 2.第1行目から最終行まで1行ずつ、下記を繰り返す Sheet1の行ポインターを変数xとする 3.B列からデータのあるもっとも右の列まで1セルづつ繰り返す(2) 4.データがあれば「A列データ+当セルデータ」をSheet2に書き出す 5.書き出すときSheet2の行ポインター(Zとする)は1行書き出すと、次のために1つ下を指しておく。 (注)(1)、(2)は常套手段(VBAコード)がある。 ーー Sheet1に、元データのモデルデータとして(回答者の勝手で) 商品1 a1 a2 商品2 b1 b2 b3 商品3 c1 c2 c3 c4 商品4 d1 を作る。 ーー 結果はSheet2を使う。結果シートとする。 ーー VBE画面を開き(ALT+F11)、メニューを選び、モジュールを挿入し、そのModule1に Sub test01() Set sh1 = Worksheets("Sheet1") Set sh2 = Worksheets("Sheet2") Z = 1 lr = sh1.Range("A100000").End(xlUp).Row 'MsgBox lr For x = 1 To lr cr = sh1.Cells(x, 100).End(xlToLeft).Column MsgBox cr For y = 2 To cr sh2.Cells(Z, "A") = sh1.Cells(x, 1) sh2.Cells(Z, "B") = sh1.Cells(x, y) Z = Z + 1 Next y Next x End Sub を作る。 (注)100000行や100列は適当に、この問題ではありえないデータ数、列数などを使うもの。 ーー 実行すると 結果は、Sheet2に 商品1 a1 商品1 a2 商品2 b1 商品2 b2 商品2 b3 商品3 c1 商品3 c2 商品3 c3 商品3 c4 商品4 d1
その他の回答 (4)
- imogasi
- ベストアンサー率27% (4737/17070)
作業列(下記ではG列)を使わない方法の回答がすでに出ていますが、なかなか理解がむつかしいのではと思う。そこで 小生にとっては、理解しやすいと思う方法を上げてみます。 <回答では列的にデータが左寄せになってわかりにくいと思うが、A2-G6の範囲に下記の元データ例を入力し、下記に説明している関数式をG1-M1列に実際に入力して、式を下方向に複写して、シートに現れる結果を見て考えてください> ーー (セルのデータA2:F7) <-A2スタートに注意 商品1 a b c 商品2 d e 商品3 f g h 商品4 i j 商品5 k l m n G列<-下記関数式の結果 0 3 5 8 10 14 I1:M14<-下記関数式の結果 I,J列とL、M列。LM列が最終結果 1 1 商品1 a 1 2 商品1 b 1 3 商品1 c 2 1 商品2 d 2 2 商品2 e 3 1 商品3 f 3 2 商品3 g 3 3 商品3 h 4 1 商品4 i 4 2 商品4 j 5 1 商品5 k 5 2 商品5 l 5 3 商品5 m 5 4 商品5 n 式を入れる行は第1行目がスタートです。 ーー (セルの式) セル 式 内容・目的 G1 =COUNT($B1:F$1) その行までのデータ個数の累積数 I1 =MATCH(ROW()-1,$G$1:$G$10,1) 何行目を見るか J1 =ROW()-INDEX($G$1:$G$10,I1) 何列目を見るか L1 =INDEX($A$2:$A$100,I1) 商品名の最終結果を出す式 M1 =INDEX($B$2:$F$10,I1,K1) (オプション情報の)最終結果を出す式 それぞれの列で下方向に式を複写 最終結果はL,M列に出る。 ーーー 理解しやすいように、I,J列は、独立して結果の値を見せている。 意味が分かったら L,Mの式で出てくる第1行目のI1,J1はI列やJ列の第1行目の式を,それぞれの式の引数部分で置き換えて(組み込んで)ください 。 すると、その後I,J列は省いても良い。 しかしこの方法ではG列は省けない。 <--欠点 ーー 関数式で 本番では、この例ではF列までとしているが、14列以上にを広げる 本番では、この例では10行までとしているだ、適当な行数にを広げる (上記回答では、例を挙げて説明する簡略さのために、F列までかつ10行の例を挙げた)
お礼
ありがとうございます。 丁寧にご説明いただいたので、理解する事ができました。 index関数は余り良く理解しないまま参照範囲を求めるのに使おうとして挫折した事があります。 このように指定すれば良かったのですね。
- msMike
- ベストアンサー率20% (368/1813)
添付図参照 Sheet2!A2: =OFFSET(Sheet1!$A$2,(ROW(A1)-1)/7,COLUMN(A1)-1) Sheet2!B2: =OFFSET(Sheet1!$A$2,(ROW(B1)-1)/7,COLUMN(B1)-1+MOD(ROW(B1)-1,7)) Sheet2!A2:B2 を下方にズズーッと(列Aに数値の 0 が現れるまで)ドラッグ&ペースト 以下は Sheet2 における操作です。 1.列A、Bを選択して、[コピー]→[値の貼り付け]を実行 2.[ジャンプ]→[セル選択]を実行 3.“定数”に目玉入れ 4.“数値”以外に付いているチェックを外す 5.[OK]をクリック 6.[編集]→[削除]を実行 7.“行全体”に目玉を入れて、Enterキーを「エイヤッ!」と叩き付け
お礼
ありがとうございます。 offset関数は何となく使わずに来たのですが、こういう使い方ができるのですね。 ひとつ勉強になりました。
- keithin
- ベストアンサー率66% (5278/7941)
縦横総当たりで巡回する、一番基本形なマクロの例です。 例えばじゃなく実際のデータの入り具合によっては、より高速化する事も可能かもしれません。 sub macro1() dim r as long dim c as long dim w as worksheet set w = activesheet worksheets.add after:=w range("A1:B1") = array("商品名","オプション") for r = 2 to w.range("A65536").end(xlup).row for c = 2 to 15 if w.cells(r, c) <> "" then with range("A65536").end(xlup).offset(1) .value = w.cells(r, "A").value .offset(0, 1).value = w.cells(r, c).value end with end if next c next r end sub
お礼
ありがとうございます。 こちらの望む通りの結果が出力されたようです。 かなり高速でした。
- eden3616
- ベストアンサー率65% (267/405)
出力先の指定がないので、元データを全て削除した後に同じシートのA1セルに出力しています。 VBAコードの扱いはしたことあるとの事ですので、コードの貼付方の説明は省かせて頂きます。 最下のVBAコードよりsampleマクロを実行してください。 ■VBAコード Sub sample() Dim myDat As Variant Dim outDat() As String Dim i As Long, j As Integer Dim cnt As Long myDat = Range(Cells(1, 1), Cells(Cells(Rows.Count, "A").End(xlUp).Row, "O")) ReDim outDat(UBound(myDat, 1) * 14, 1) For i = 2 To UBound(myDat, 1) For j = 2 To 15 If myDat(i, j) = "" Then Exit For outDat(cnt, 0) = myDat(i, 1) outDat(cnt, 1) = myDat(i, j) cnt = cnt + 1 Next j Next i Cells.ClearContents Cells(1, 1).Resize(cnt, 2) = outDat End Sub
お礼
ありがとうございます。 書いていただいたコードを回して見たところ、こちらの望んでいた通りの出力ができました。
お礼
ありがとうございます。 丁寧に説明していただけたので、私の頭でもどうにか理解できました。 メッセージが何度も出てくる部分が私の環境では不要だったので、MsgBox の部分を消してみたところ、想定通りに動作しました。
補足
回答いただいた皆様、ありがとうございました。 いずれの方法でもこちらの望んだ出力が得られました。 少々悩みましたが、マクロについて丁寧に解説いただいたこちらをベストアンサーとさせていただきました。