I still consider myself a newbie with VBA, and would appreciate any help. There is one thing I am wondering how to do...
I have a worksheet like below, with data starting at row 16. I have a known number of rows (num_rows). I would like to loop through each row. Where Code = "s" I would like data exported to a s.txt, and where Code = "e" I would like data exported to e.txt. Other codes appear in the Code column which can be ignored. The outputted file would have each row on a new line, but also have sufficient spaces to align the data into their columns still in the text file. Any pointers?
Row#
Code
Title
Name
Country
16
s
Mr
James Smith
Australia
17
s
Mr
Karl Burns
USA
18
e
Mrs
Sara Sid
England
Scan the file to determine the maximum width of each column. Then scan again writing each line out with the columns padded to the required width with spaces. Copying the data to an array first will reduce the run time if you have a lot of data. See CreateTextFile and Space
Option Explicit
Sub Macro1()
Const HEADER_ROW = 15
Const COL_SPC = 2 ' column spacing
Dim wb As Workbook, ws As Worksheet
Set wb = ThisWorkbook
Set ws = wb.Sheets(1)
Dim iRow As Long, iLastRow As Long, iLastCol As Integer
Dim r As Long, c As Integer, s As String, n As Integer
Dim arWidth() As Integer, arData, arHeader
' extent of data
iLastRow = ws.Cells(Rows.Count, "A").End(xlUp).Row
iLastCol = ws.Cells(HEADER_ROW, Columns.Count).End(xlToLeft).Column
arData = ws.Range(ws.Cells(HEADER_ROW + 1, 1), ws.Cells(iLastRow, iLastCol))
' max width of each col
ReDim arWidth(iLastCol)
ReDim arHeader(iLastCol)
For c = 1 To UBound(arData, 2)
s = ws.Cells(HEADER_ROW, c)
arWidth(c) = Len(s) ' initalise with header width
For r = 1 To UBound(arData, 1)
If Len(arData(r, c)) > arWidth(c) Then
arWidth(c) = Len(arData(r, c))
End If
Next
' add spacing
arWidth(c) = arWidth(c) + COL_SPC
' space out header
arHeader(c) = s & Space(arWidth(c) - Len(s))
Next
'Export Data
Dim FSO As Object, ts(2), sFileName(2) As String
Dim sPath As String
Dim sColB, msg As String
Set FSO = CreateObject("Scripting.FileSystemObject")
sPath = wb.Path & "\"
' create 2 text streams
n = 1
For Each sColB In Array("e", "s")
sFileName(n) = sColB & ".txt"
Set ts(n) = FSO.CreateTextFile(sPath & sFileName(n), True, True) ' overwrite,unicode
' print header
ts(n).WriteLine Join(arHeader, "")
n = n + 1
Next
' export data
For r = 1 To UBound(arData, 1)
n = 0
' choose text stream
sColB = LCase(Trim(arData(r, 2)))
If sColB = "e" Then n = 1
If sColB = "s" Then n = 2
' write out 1 line of text
If n > 0 Then
s = ""
For c = 1 To UBound(arData, 2)
' space out columns
s = s & arData(r, c) & Space(arWidth(c) - Len(arData(r, c)))
Next
ts(n).WriteLine (s)
'Debug.Print s
End If
Next
' close text streams
For n = 1 To 2
msg = msg & vbCrLf & sFileName(n)
ts(n).Close
Next
' finish
MsgBox "2 Files created in " & sPath & msg
End Sub
Related
I have a workbook of about 30 sheets which I am attempting to put in alphanumeric order. Ex: "New York 9, New York 10, New York 11"
My code fails to order double digit numbers after single digit ones. "10, 11, 9"
Is anyone familiar with the method for accounting for this? Many thanks!
Sub AscendingSortOfWorksheets()
'Sort worksheets in a workbook in ascending order
Dim SCount, i, j As Integer
Application.ScreenUpdating = False
SCount = Worksheets.Count
For i = 1 To SCount - 1
For j = i + 1 To SCount
If Worksheets(j).Name < Worksheets(i).Name Then
Worksheets(j).Move before:=Worksheets(i)
End If
Next j
Next i
End Sub
As mentioned in the comments, you need to pad the numbers with zeros, in your case single digit numbers need to be padded with 1 zero. Use this function
Function PadNumber(sName As String, lNumOfDigits As Long) As String
Dim v As Variant
Dim vPrefixList As Variant
Dim sTemp As String
Dim i As Long
' Add all other possible prefixes in this array
vPrefixList = Array("New York")
sTemp = sName
For Each v In vPrefixList
sTemp = Replace(LCase(sTemp), LCase(v), "")
Next v
sTemp = Trim(sTemp)
PadNumber = sTemp
For i = Len(sTemp) + 1 To lNumOfDigits
PadNumber = "0" & PadNumber
Next i
PadNumber = Replace(sName, sTemp, PadNumber)
End Function
Then change the line If Worksheets(j).Name < Worksheets(i).Name Then to
If PadNumber(LCase(Worksheets(j).Name), 2) < PadNumber(LCase(Worksheets(i).Name), 2) Then
Note I added LCase in the comparison. Case-sensitivity might not matter for you in this particular case but it is something you always need to keep in mind.
Here is one way to achieve it
Logic:
Create a 2D array to store the number after space and sheet name
Sort the array
Arrange the sheets
Code:
Sub Sample()
Dim SheetsArray() As String
'~~> Get sheet counts
Dim sheetsCount As Long: sheetsCount = ThisWorkbook.Sheets.Count
'~~> Prepare our array for input
'~~> One part will store the number and the other will store the name
ReDim SheetsArray(1 To sheetsCount, 1 To 2)
Dim ws As Worksheet
Dim tmpAr As Variant
Dim sheetNo As Long
Dim i As Long: i = 1
Dim j As Long
'~~> Loop though the worksheest
For Each ws In ThisWorkbook.Sheets
tmpAr = Split(ws.Name)
'~~> Extract last number after space
sheetNo = Trim(tmpAr(UBound(tmpAr)))
'~~> Store number and sheet name as planned
SheetsArray(i, 1) = sheetNo
SheetsArray(i, 2) = ws.Name
i = i + 1
Next ws
'~~> Sort the array on numbers
Dim TempA, TempB
For i = LBound(SheetsArray) To UBound(SheetsArray) - 1
For j = i + 1 To UBound(SheetsArray)
If SheetsArray(i, 1) > SheetsArray(j, 1) Then
TempA = SheetsArray(j, 1): TempB = SheetsArray(j, 2)
SheetsArray(j, 1) = SheetsArray(i, 1): SheetsArray(j, 2) = SheetsArray(i, 2)
SheetsArray(i, 1) = TempA: SheetsArray(i, 2) = TempB
End If
Next j
Next i
'~~> Arrange the sheets
For i = UBound(SheetsArray) To LBound(SheetsArray) Step -1
ThisWorkbook.Sheets(SheetsArray(i, 2)).Move After:=ThisWorkbook.Sheets(sheetsCount)
sheetsCount = sheetsCount - 1
Next i
End Sub
Assumptions:
The sheet names have space in their names
The sheet names are in the format New York #
I am looking to extract multiple text values from a column in Excel and populate another column with these text values.
To be more specific, I am looking to extract the STLS ticket numbers.
For example, one row may contain "ABCD-4, STLS-5644, ABBD-33, STLS-421", another row may contain "ABB-567, STLS-56435" and another row may contain no STLS tickets.
What would be the best way to approach this problem?
You could try this code:
Option Explicit
Sub testExtract()
Dim i As Long, j As Long, jUp As Long, lFirstRow As Long, lLastRow As Long
Dim lColFrom As Long, lColTo As Long, nTicks As Long
Dim str1 As String
Dim varArray
'
' define source column number and the destination one:
'
lColFrom = 1
lColTo = 2
'
' initialize range to analyze:
'
lFirstRow = 1
lLastRow = Cells(Rows.Count, 1).End(xlUp).Row
'
' loop over the rows:
'
For i = lFirstRow To lLastRow
'
' split the string in the cell in an array:
'
varArray = Split(Cells(i, lColFrom).Value, ",")
jUp = UBound(varArray)
nTicks = 0
str1 = ""
'
' check the array element by element if we have some ticket:
'
For j = 0 To jUp
'
' trim spaces:
'
varArray(j) = Trim(varArray(j))
'
' check if we have ticks and count them:
'
If (InStr(1, varArray(j), "STLS-") > 0) Then
If (nTicks > 0) Then
str1 = str1 & ", "
End If
str1 = str1 & varArray(j)
nTicks = nTicks + 1
End If
Next
'
' save ticks:
'
If (str1 <> "") Then
Cells(i, lColTo).Value = str1
End If
Next
End Sub
If your Excel has the FILTERXML function (windows Excel 2013+) and the TEXTJOIN function, you don't need VBA.
You can use:
=IFERROR(TEXTJOIN(",",TRUE,FILTERXML("<t><s>" & SUBSTITUTE(A1,",","</s><s>")&"</s></t>","//s[contains(.,'STLS')]")),"")
If you don't have those functions, you can use this VBA UDF:
Option Explicit
Function getTickets(s As String, ticket As String) As String
Dim v, w, x, col As Collection, i As Long
v = Split(s, ",")
Set col = New Collection
For Each w In v
If Trim(w) Like ticket & "*" Then col.Add Trim(w)
Next w
i = 0
If col.Count = 0 Then
getTickets = ""
Else
ReDim x(col.Count - 1)
For Each w In col
x(i) = w
i = i + 1
Next w
getTickets = Join(x, ",")
End If
End Function
I have an invoice from a service provider that I need to format so I can use the data in Excel. But, the formatting is not consistent.
There are three (3) columns:
ID
Description
Amount
Many ID#s on the invoice have a one line (row) description.
But just as many have 2-11 lines (rows) of description.
The ID# is only listed once with each set of description lines.
Up to this point, I have used Excel Formulas. But, all my formulas is making things go very slow.
VBA would be way faster.
What I have done is created an index system looking for new ID#s.
Then I have created a cascading concatenate formula based on the given index system.
The amount has been easy to pull out using a LEFT formula, since the amount lists USD.
I then have a second sheet that does a VLOOKUP off of the first sheet to pull the ID's, final concatenated descriptions, and Amounts.
Our last invoice had 17,427 lines of data with only 1,717 ID#s.
Here is an example of what I am working with:
I want it to look like this:
one of the possible solutions below:
'assume that Id in column `A`, Description in column `B`, Amount in `C` and header in row 1
Sub somecode()
Dim wb As Workbook: Set wb = ActiveWorkbook
Dim sh As Worksheet: Set sh = wb.ActiveSheet
Dim lastRow&: lastRow = sh.Cells(Rows.Count, "B").End(xlUp).Row
Dim idColumn As Range: Set idColumn = sh.Range("A1:A" & lastRow)
Dim dic As Object: Set dic = CreateObject("Scripting.Dictionary")
Dim cl As Range, keyID, valueDescription$, valueAmount$
For Each cl In idColumn
If cl.Value <> "" And Not dic.exists(cl.Value) Then
dic.Add cl.Value, sh.Cells(cl.Row, "B").Value & "|" & sh.Cells(cl.Row, "C").Value
keyID = cl.Value
valueDescription = sh.Cells(cl.Row, "B").Value
valueAmount = sh.Cells(cl.Row, "C").Value
ElseIf cl.Value = "" Then
valueDescription = valueDescription & " " & sh.Cells(cl.Row, "B").Value
dic(keyID) = valueDescription & "|" & valueAmount
End If
Next cl
Set sh = wb.Sheets.Add: sh.Name = "Result " & Date & " " & Replace(Time(), ":", "-")
Dim dkey, xRow&: xRow = 1
For Each dkey In dic
sh.Cells(xRow, "A").Value = dkey
sh.Cells(xRow, "B").Value = Split(dic(dkey), "|")(0)
sh.Cells(xRow, "C").Value = Split(dic(dkey), "|")(1)
xRow = xRow + 1
Next dkey
sh.Columns("A:C").AutoFit
End Sub
test:
I wrote code for you to do this job. Please install it in a standard code module. That is one that you have to insert. None of the existing is suitable.
Option Explicit
Enum Nws ' Worksheet setup (set values as required)
NwsFirstDataRow = 2
NwsNumColumns = 8 ' total number of columns in the sheet
NwsID = 1 ' Columns: 1 = column A
NwsDesc ' undefined = previous + 1
NwsAmt = 5 ' 5 = column E
End Enum
Sub MergeRows()
' Variatus #STO 24 Jan 2020
Dim Wb As Workbook
Dim Ws As Worksheet
Dim Rng As Range
Dim RowArr As Variant
Dim Desc As String, Amt As Double
Dim Tmp As Variant
Dim R As Long
' define workbook and worksheet as required
Set Wb = ActiveWorkbook ' this need not be ThisWorkbook
Set Ws = Wb.Worksheets("Invoice") ' change as appropriate
Application.ScreenUpdating = False
With Ws
R = .Cells(.Rows.Count, NwsDesc).End(xlUp).Row
For R = R To NwsFirstDataRow Step -1
If (R Mod 25) = 3 Then 'NwsFirstDataRow Then
Application.StatusBar = "Another " & R & " rows to process."
End If
Tmp = Trim(.Cells(R, NwsID).Value)
If Len(Tmp) Then
Set Rng = Range(.Cells(R, 1), .Cells(R, NwsNumColumns))
RowArr = Rng.Value
RowArr(1, NwsAmt) = TextToAmount(RowArr(1, NwsAmt))
If Len(Desc) Then
' if you want a comma instead of a line break
' replace Chr(10) with "," in the next line:-
RowArr(1, NwsDesc) = RowArr(1, NwsDesc) & Chr(10) & Desc
RowArr(1, NwsAmt) = RowArr(1, NwsAmt) + Amt
Desc = ""
Amt = 0
End If
With Rng
.Value = RowArr
.Cells.VerticalAlignment = xlTop
.Cells(NwsAmt).NumberFormat = "$#,##0.00"
End With
.Rows(R).AutoFit
Else
Tmp = Trim(.Cells(R, NwsDesc).Value)
If Len(Desc) Then Desc = Chr(10) & Desc
Desc = Tmp & Desc
Tmp = TextToAmount(.Cells(R, NwsAmt).Value)
If Tmp Then Amt = Amt + Tmp
.Rows(R).EntireRow.Delete
End If
Next R
End With
With Application
.ScreenUpdating = True
.StatusBar = "Done"
End With
End Sub
Private Function TextToAmount(ByVal Amt As Variant) As Double
Dim Tmp As Variant
Tmp = Trim(Amt)
If Len(Tmp) Then Tmp = Mid(Tmp, InStr(Tmp, "$") + 1)
TextToAmount = Val(Tmp)
End Function
Before you can run it you need to set the enumerations at the top to tell the code where your data and columns are. Toward the same end, please set the variables for workbook (Wb) and worksheet (Ws) in the procedure itself.
Note that the code adds the price, if any, in the rows that are deleted to the amount set against the remaining item.
Finally, you will see that I programmed the different rows to become lines in a single cell. That isn't what you asked for. If you want the items separate by commas look for the remark in the code where you can change this.
Im having a collapsed data as shown below, where I have ID & its header below the header its value exists. Each ID has its own different no of headers & values.
I have to arrange them into a table where i have consolidated headers of all the IDs into one row and the IDs are in one column. Based one the ID i need to update the respective header's value below.
ID--+--H1--+--H2--+--H3--+--H4--+--H5--|
18219--V1--+--V3--+-- --+-- --+-- --|
18218--V2--+--V4--+-- --+-- --+-- --|
18217--V1--+--V2--+--V3--+--V4--+--V5--|
Can anyone help me out?
Try this code:
Option Explicit
Sub Consolidate()
Dim arrContent As Variant
Dim strSource As String
Dim strDest As String
Dim x As Long
Dim y As Long
Dim p As Long
Dim objHeader As Object
Dim objItem As Variant
Dim lngColsCount As Long
' set initial values
strSource = "source" ' source worksheet name
strDest = "destination" ' destination worksheet name
y = 1 ' source worksheet first ID cell's row number
x = 2 ' source worksheet first ID cell's column number
Set objHeader = CreateObject("Scripting.Dictionary")
' pack source data into array of dictionaries
objHeader.Add "ID", 0
arrContent = Array()
With Sheets(strSource)
Do While .Cells(y, x).Value <> "" And .Cells(y + 1, x).Value = ""
Set objItem = CreateObject("Scripting.Dictionary")
objItem.Add 0, .Cells(y, x).Value
p = x + 1
Do While .Cells(y, p).Value <> ""
If Not objHeader.Exists(.Cells(y, p).Value) Then objHeader.Add .Cells(y, p).Value, objHeader.Count
objItem(objHeader(.Cells(y, p).Value)) = .Cells(y + 1, p).Value
p = p + 1
Loop
ReDim Preserve arrContent(UBound(arrContent) + 1)
Set arrContent(UBound(arrContent)) = objItem
y = y + 2
Loop
End With
' output
With Sheets(strDest)
.Cells.Delete
lngColsCount = UBound(objHeader.keys)
.Range(.Cells(1, 1), .Cells(1, lngColsCount + 1)).Value = objHeader.keys
y = 2
For Each objItem In arrContent
For x = 1 To lngColsCount + 1
.Cells(y, x).Value = objItem(x - 1)
Next
y = y + 1
Next
End With
End Sub
For source table:
it generates output:
I hv rows of data:-
TAG SKU SIZE GRADE LOCATION
A001 123 12 A X1
A002 789 13 B X3
A003 456 15 C X5
I need to convert it into:-
A001 123 SIZE 12
A001 123 GRADE A
A001 123 LOCATION X1
A002 789 SIZE 13
A002 789 GRADE B
A002 789 LOCATION X3
A003 456 SIZE 15
A003 456 GRADE C
A003 456 LOCATION X5
I used the below (based on Ben McCormack's suggestion posted on Nov 23 '09) but it doesn't produce the above result :-
Sub NormalizeSheet()
Dim wsOriginal As Worksheet
Dim wsNormalized As Worksheet
Dim strKey As String
Dim clnHeader As Collection
Dim lngColumnCounter As Long
Dim lngRowCounterOriginal As Long
Dim lngRowCounterNormalized As Long
Dim rngCurrent As Range
Dim varColumn As Variant
Set wsOriginal = ThisWorkbook.Worksheets("Original") 'This is the name of your original worksheet'
Set wsNormalized = ThisWorkbook.Worksheets("Normalized") 'This is the name of the new worksheet'
Set clnHeader = New Collection
wsNormalized.Cells.ClearContents 'This deletes the contents of the destination worksheet'
lngColumnCounter = 2
lngRowCounterOriginal = 1
Set rngCurrent = wsOriginal.Cells(lngRowCounterOriginal, lngColumnCounter)
' We'll loop through just the headers to get a collection of header names'
Do Until IsEmpty(rngCurrent.Value)
clnHeader.Add rngCurrent.Value, CStr(lngColumnCounter)
lngColumnCounter = lngColumnCounter + 1
Set rngCurrent = wsOriginal.Cells(lngRowCounterOriginal, lngColumnCounter)
Loop
'Here we'll reset our Row Counter and loop through the entire data set'
lngRowCounterOriginal = 2
lngRowCounterNormalized = 1
lngColumnCounter = 1
Do While Not IsEmpty(wsOriginal.Cells(lngRowCounterOriginal, lngColumnCounter))
Set rngCurrent = wsOriginal.Cells(lngRowCounterOriginal, lngColumnCounter)
strKey = rngCurrent.Value ' Get the key value from the current cell'
lngColumnCounter = 2
'This next loop parses the denormalized values for each row'
Do While Not IsEmpty(wsOriginal.Cells(lngRowCounterOriginal, lngColumnCounter))
Set rngCurrent = wsOriginal.Cells(lngRowCounterOriginal, lngColumnCounter)
'We're going to check to see if the current value'
'is equal to NULL. If it is, we won't add it to'
'the Normalized Table.'
If rngCurrent.Value = "NULL" Then
'Skip it'
Else
'Add this item to the normalized sheet'
wsNormalized.Range("A" & lngRowCounterNormalized).Value = strKey
wsNormalized.Range("B" & lngRowCounterNormalized).Value = clnHeader(CStr(lngColumnCounter))
wsNormalized.Range("C" & lngRowCounterNormalized).Value = rngCurrent.Value
lngRowCounterNormalized = lngRowCounterNormalized + 1
End If
lngColumnCounter = lngColumnCounter + 1
Loop
lngRowCounterOriginal = lngRowCounterOriginal + 1
lngColumnCounter = 1 'We reset the column counter here because we're on a new row'
Loop
End Sub
Here's an approach going from worksheet to worksheet directly. This might be necessary if the dataset is too big and available memory too small for using arrays. It's likely to be slow.
It uses the same call parameters as reOrgV1, and pretty much the same logic.
It's updated to add "DEFECTS" to the properies. The input looks like:
TAG SKU SIZE GRADE LOCATION DEFECTS
A001 123 12 A X1 3
A002 789 13 B X3 5
A003 456 15 C X5 7
Here's the code.
Public Sub reOrgV2(inSource As Range, inTarget As Range)
'' This version works directly on the worksheet
'' and transfers the result directly to the target
'' given as the top-left cell of the result.
'' **** Changed to add "Defects"
Dim resNames()
Dim propNum As Integer
Dim srcRows As Integer
Dim resRows As Integer
Dim i As Integer
Dim j As Integer
Dim g As Integer
'' Shape the result
resNames = Array("Size", "Grade", "Location", "Defects")
propNum = 1 + UBound(resNames)
'' Row counts
srcRows = inSource.Rows.Count
resRows = srcRows * propNum
'' re-org and transfer source to result range
inTarget = inTarget.Resize(resRows, 4)
g = 1
For i = 1 To srcRows
For j = 0 To 3
inTarget.Item(g + j, 1) = inSource.Item(i, 1) '' Tag
inTarget.Item(g + j, 2) = inSource.Item(i, 2) '' SKU
inTarget.Item(g + j, 3) = resNames(j) '' Property
inTarget.Item(g + j, 4) = inSource.Item(i, j + 3) '' Value
Next j
g = g + propNum
Next i
End Sub
This is the revised call sourcing the wider range.
'' Call ReOrgV2 with input and output ranges
Public Sub test4()
Dim i As Integer
i = Range("InData!A:A").Find("").Row - 2
reOrgV2 Range("InData!A2").Resize(i, 6), [OutData!A1]
End Sub
You can use ADO with Excel. Roughly:
Sub ColsToRows()
Dim cn As Object
Dim rs As Object
Dim strFile As String
Dim strCon As String
Dim strSQL As String
Dim s As String
Dim i As Integer, j As Integer
''This is not the best way to refer to the workbook
''you want, but it is very convenient for notes
''It is probably best to use the name of the workbook.
strFile = ActiveWorkbook.FullName
''Note that if HDR=No, F1,F2 etc are used for column names,
''if HDR=Yes, the names in the first row of the range
''can be used.
''This is the Jet 4 connection string, you can get more
''here : http://www.connectionstrings.com/excel
strCon = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Docs\TestBook.xls " _
& ";Extended Properties=""Excel 8.0;HDR=Yes;IMEX=1"";"
''Late binding, so no reference is needed
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cn.Open strCon
strSQL = "SELECT [TAG], [SKU], 'SIZE ' & [SIZE] As S, " _
& "'GRADE ' & [GRADE] As G, 'LOCATION ' & [LOCATION] As L " _
& "FROM [Sheet1$] a " _
& "ORDER BY [Tag] "
rs.Open strSQL, cn, 3, 3
''Pick a suitable empty worksheet for the results
With Worksheets("Sheet3")
j = 1 '' Row counter
Do While Not rs.EOF
For i = 2 To 4
.Cells(j, 1) = rs!Tag
.Cells(j, 2) = rs!SKU
.Cells(j, 3) = rs(i)
j = j + 1
Next
rs.MoveNext
Loop
End With
''Tidy up
rs.Close
Set rs = Nothing
cn.Close
Set cn = Nothing
End Sub
Here's a really simple solution that assumes the dataset isn't huge. It takes the input range into an array, transforms it into a result array, then moves the array to the specified target. The target is defined by the top left cell.
When it's possible, this approach is orders of magnitude faster than working directly with cells on worksheets.
The test function at the bottom needs you to put an input set on sheet InData and have a sheet OutData defined for the results but your input and output ranges can be anywhere you want.
Option Explicit
Public Sub reOrgV1(inSource As Range, inTarget As Range)
'' This version uses VBA arrays to do the work.
'' Takes a source range, reorganizes it to the target
'' given as the top-left cell of the result.
Dim srcArray As Variant
Dim resArray As Variant
Dim resNames()
resNames = Array("SIZE", "GRADE", "LOCATION")
Dim srcRows As Integer
Dim resRows As Integer
Dim i As Integer
Dim j As Integer
Dim g As Integer
'' Move range into source array
srcArray = inSource.Value
srcRows = UBound(srcArray, 1)
resRows = srcRows * 3
''Build result array
ReDim resArray(1 To resRows, 1 To 3)
'' transfer source to result array
g = 1
For i = 1 To srcRows
For j = 0 To 2
resArray(g + j, 1) = srcArray(i, 1)
resArray(g + j, 2) = srcArray(i, 2)
resArray(g + j, 3) = resNames(j) & " " & srcArray(i, j + 3)
Next j
g = g + 3
Next i
'' Move the results to the target range
inTarget.Resize(resRows, 3).Value = resArray
End Sub
Public Sub test1()
reOrgV1 Range("InData!A2:E4"), Range("OutData!A1")
End Sub