As I said in the title, I'm wondering if someone can help me figure out why my code is running so slowly (Ran for an hour with no result). I'm very new when it comes to writing in VBA, but I don't see a reason why it would take so long. Here is the code in question:
Sub fast()
Application.ScreenUpdating = False
Dim prices As Worksheet
Dim stockreturns As Worksheet
Dim index As Worksheet
Dim stockprices As Range
Set index = Worksheets("IndexPrices")
Set prices = Worksheets("HistPrices")
Set stockreturns = Worksheets("Sheet1")
index.Range("A:B").Copy stockreturns.Range("A:B")
For col = 1 To 975
For n = 2 To 260
prices.Range("A:A").Offset(0, col).Copy stockreturns.Range("A:A").Offset(0, 2 * col + 1)
If stockreturns.Cells(n + 1, 2 * col).Value = Null Or IsEmpty(stockreturns.Cells(n + 1, 2 * col).Value) Then
stockreturns.Cells(n, 2 * col + 1) = Null
Else
stockreturns.Cells(n, 2 * col + 1).Formula = Cells(n, 2 * col) / Cells(n + 1, 2 * col) - 1
stockreturns.Cells(n, 2 * col + 1).NumberFormat = "0.00%"
End If
Next n
Next col
Application.ScreenUpdating = True
End Sub
I'd be happy to post the workbook if anyone wants to see what I'm trying to accomplish in the sheet and potentially suggest a different or more efficient way of doing it. Thanks.
Assuming your code did what you want, the below redrafting should be much quicker.
Avoid using .Copy wherever possible. Instead directly assign the .Value of cells.
Make sure your lines of code are within the correct loops to avoid running code more often than it has to be run.
Stop doing every operation on entire columns, that's a lot of cells you're copying, of which 99% will be blank! I've taken the most basic approach possible and chosen to just use the first 1000 rows, improve this as suits - possibly by finding the actual last row.
Disable the automatic Calculation as well as the ScreenUpdating.
See code comments for details.
Sub fast()
Application.ScreenUpdating = False
' Stop Excel from recalculating the workbook every time a cell value changes
Application.Calculation = xlCalculationManual
Dim prices As Worksheet, stockreturns As Worksheet, index As Worksheet
' Fully qualify your sheets by specifying the workbook
With ThisWorkbook
Set index = .Sheets("IndexPrices")
Set prices = .Sheets("HistPrices")
Set stockreturns = .Sheets("Sheet1")
End With
' Assign some last row number so you don't have to be copying the value of tens of thousands of rows
' Previously every values copy was on the entire column, wasting a lot of time!
' Could get this value by a cleverer, more dynamic method, but that depends on needs.
Dim lastrow As Long: lastrow = 1000
' Assign values, don't use copy/paste. Avoiding the clipboard speeds things up
stockreturns.Range("A1:B" & lastrow).Value = index.Range("A1:B" & lastrow).Value
For col = 1 To 975
' This line isn't affected by the value of n, so move it outside the n loop! Again, use .Value not copy
stockreturns.Range("A1:A" & lastrow).Offset(0, 2 * col + 1).Value = prices.Range("A1:A" & lastrow).Offset(0, col).Value
For n = 2 To 260
If stockreturns.Cells(n + 1, 2 * col).Value = Null Or IsEmpty(stockreturns.Cells(n + 1, 2 * col).Value) Then
stockreturns.Cells(n, 2 * col + 1) = Null
Else
stockreturns.Cells(n, 2 * col + 1).Formula = Cells(n, 2 * col) / Cells(n + 1, 2 * col) - 1
stockreturns.Cells(n, 2 * col + 1).NumberFormat = "0.00%"
End If
Next n
Next col
' Reset Application settings
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Main issue:
This nested loop of yours executes 975 * 260 = 253.500 times:
For col = 1 To 975
For n = 2 To 260
prices.Range("A:A").Offset(0, col).Copy stockreturns.Range("A:A").Offset(0, 2 * col + 1)
If stockreturns.Cells(n + 1, 2 * col).Value = Null Or IsEmpty(stockreturns.Cells(n + 1, 2 * col).Value) Then
stockreturns.Cells(n, 2 * col + 1) = Null
Else
stockreturns.Cells(n, 2 * col + 1).Formula = Cells(n, 2 * col) / Cells(n + 1, 2 * col) - 1
stockreturns.Cells(n, 2 * col + 1).NumberFormat = "0.00%"
End If
Next n
Next col
Summary of what you're doing, according to code in the question:
Basically, what you're doing is get column B, C, D, etc. and copy them to D, E, G, etc. using the offset. Next you check in the stockreturns worksheet what the value of the copied cell in the next row is (e.g. you check D3, then D4 etc.) and based on that populate E2, E3, etc. with nulls, or, alternatively you take ((D2 / D3) - 1) as a value there. The initial check is to avoid division by zero errors, I assume.
Note:
In those lines in your code you refer to Cells(n, 2 * col) so that would always be the ActiveSheet, whereas I assume you want to populate the worksheet stockreturns with those values. I.e. if you run the formula with worksheet prices activated, the formula's won't give the desired output.
Working towards solution:
For sure it would be way faster to not do 253.500 loops, but to populate everything at once for as far as possible. Since the column number varies everytime, we'll leave that loop in, but the nested 260 loops we can easily get rid of:
Optimization to do 975 loops instead of 253.500:
With stockreturns
For col = 1 To 975
prices.Range("A:A").Offset(0, col).Copy .Range("A:A").Offset(0, 2 * col + 1)
'Now we fill up the entire 260 rows at once using a relative formula:
.Range(.Cells(2, 2 * col + 1), .Cells(260, 2 * col + 1)).FormulaR1C1 = "=IF(R[+1]C[-1]="""","""",(RC[-1]/R[+1]C[-1])-1)"
'If you want a value instead of a formula, we replace the formula's with the value. If calculation is set to manual, you'll have to add an Application.Calculate here.
.Range(.Cells(2, 2 * col + 1), .Cells(260, 2 * col + 1)).Value = .Range(.Cells(2, 2 * col + 1), .Cells(260, 2 * col + 1)).Value
.Range(.Cells(2, 2 * col + 1), .Cells(260, 2 * col + 1)).NumberFormat = "0.00%"
Next col
End With
This will already save major execution time. However, we can also save ourselves 975 calculate actions, by turning off calculations and only replacing the formulas with the values at the very end:
Second optimization to avoid calculations during execution:
Application.Calculation = xlCalculationManual
With stockreturns
For col = 1 To 975
prices.Range("A:A").Offset(0, col).Copy .Range("A:A").Offset(0, 2 * col + 1)
'Now we fill up the entire 260 rows at once using a relative formula:
.Range(.Cells(2, 2 * col + 1), .Cells(260, 2 * col + 1)).FormulaR1C1 = "=IF(R[+1]C[-1]="""","""",(RC[-1]/R[+1]C[-1])-1)"
.Range(.Cells(2, 2 * col + 1), .Cells(260, 2 * col + 1)).NumberFormat = "0.00%"
Next col
End With
Application.Calculate
stockreturns.UsedRange.value = stockreturns.UsedRange.value
This last version runs in seconds.
If it is acceptable for you to alter the stockreturns worksheet layout and use a continuous range to copy to at once, you won't need those 975 loops either but you can achieve the desired result with the following actions:
Copy prices range
Add the formula in another range
Calculate
Replace formulas with values
Set numberformat
Hope this helps.
Before we move on the optimization, let's make sure at least it works. Please check where I comment.
Assume that code above is correct. And in your code, you just modify to 260 rows so that I set last row to 260. I think this will need deeper debug to work. but if you follow this way, it will end up your program finish much faster (like hundreds of time faster than all normal methods above)
The concept is similar.
1. Dump all data to memory ( array "stockdata and "pricedata")
2. Play with data in memory
3. write back to file
4. add format if required.
Sub fast()
Dim stockdata,pricedata As Variant
Application.ScreenUpdating = False
' Stop Excel from recalculating the workbook every time a cell value changes
Application.Calculation = xlCalculationManual
Dim prices As Worksheet, stockreturns As Worksheet, index As Worksheet
' Fully qualify your sheets by specifying the workbook
With ThisWorkbook
Set index = .Sheets("IndexPrices")
Set prices = .Sheets("HistPrices")
Set stockreturns = .Sheets("Sheet1")
End With
' Assign some last row number so you don't have to be copying the value of tens of thousands of rows
' Previously every values copy was on the entire column, wasting a lot of time!
' Could get this value by a cleverer, more dynamic method, but that depends on needs.
Dim lastrow As Long: lastrow = 260
' Assign values, don't use copy/paste. Avoiding the clipboard speeds things up
stockreturns.Range("A1:B" & lastrow).Value = index.Range("A1:B" & lastrow).Value
pricedata = prices.Range("A1",prices.Cells(lastrow,975))
redim stockdata(1 to lastrow, 1 to 1952)
For col = 1 To 975
'stockreturns.Range("A1:A" & lastrow).Offset(0, 2 * col + 1).Value = prices.Range("A1:A" & lastrow).Offset(0, col).Value
for n = 1 to lastrow
'offset so that +1
stockdata(n,col*2+1+1) = pricedata(n,col+1)
next n
next col
'done with that
'check value and change if need
For col = 1 To 975
For n = 2 To 260
If stockdata(n + 1, 2 * col) = Null Or IsEmpty(stockdata(n + 1, 2 * col)) Then
stockdata(n, 2 * col + 1) = Null
Else
stockdata(n, 2 * col + 1).Formula = stockdata(n, 2 * col) / stockdata(n + 1, 2 * col) - 1
'stockdata(n, 2 * col + 1).NumberFormat = "0.00%"
End If
Next n
Next col
stockreturns.Range("A1",stockreturns.Cells(lastrow,1952)) = stockdata
Dim rng As Range
Set rng = stockreturns.Range("B1:B" & lr)
For col = 2 To 975
Set rng = Union(rng, Range(stockreturns.Cells(1,2*col + 1),stockreturns.Cells(lr,2*col + 1),)
next n
rng.NumberFormat = "0.00%"
' Reset Application settings
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Related
In excel I would like to copy the date from one sheet to another one using macro in a way that it will copy everything until row 9, then it will skip row 10 and copy row 11 and 12, and then skip one again.
So it should not copy row 10,13,16,19, etc..
I have the following code
Dim i As Integer
i = 9
J = 1
K = 9
Do While i < 5000
If J = 3 Then
J = 0
Sheets("sheet1").Select
Rows(i).Select
Selection.Copy
Sheets("sheet2").Select
Cells(K, 1).Select
ActiveSheet.Paste
K = K + 1
End If
J = J + 1
i = i + 1
Loop
This code is copying everything till the 8th row and then every 3rd, can somebody help me how to modify that code?
Fastest way will be to Copy >> Paste the entire rows once, according to your criteria.
You can achieve it by merging all rows that needs to be copies to a Range object, in my code it's CopyRng, and you do that by using Application.Union.
Code
Option Explicit
Sub CopyCertailRows()
Dim i As Long
Dim CopyRng As Range
Application.ScreenUpdating = False
With Sheets("sheet1")
' first add the first 8 rows to the copied range
Set CopyRng = .Rows("1:8")
For i = 9 To 5000
If (i / 3) - Int(i / 3) <> 0 Then ' don't add to copied range the rows that divide by 3 without a remainder
Set CopyRng = Application.Union(CopyRng, .Rows(i))
End If
Next i
End With
' copy >> paste in 1- line
CopyRng.Copy Destination:=Sheets("sheet2").Range("A9")
Application.ScreenUpdating = True
End Sub
You could simplify this massively by using If i < 10 Or (i - 1) Mod 3 <> 0 Then... which will select the rows you're interested in. Like so:
Dim i As Integer, j As Integer
j = 0
Dim sourceSht As Worksheet
Dim destSht As Worksheet
Set sourceSht = Sheets("Sheet1")
Set destSht = Sheets("Sheet2")
For i = 1 To 5000
If i < 10 Or (i - 1) Mod 3 <> 0 Then
j = j + 1
sourceSht.Rows(i).Copy destSht.Rows(j)
End If
Next
Personally, I'd turn screen updating and calculations off before running this and enable them again after to reduce the time needed to perform the loop.
Also, as MichaĆ suggests, unless your dataset happens to be exactly 5,000 rows, you might want to 'find' the last row of data before starting to further reduce the time needed.
All necessary comments in code:
'declare all variables, be consistent with lower/uppercases, use Long instead of Integeer (its stored as long anyway)
'use meaningful variable names
Dim i As Long, copyUntil As Long, currentRow As Long
copyUntil = 9
currentRow = 1
'copy all rows until we reach 9th row
For i = 1 To copyUntil
Sheets("sheet1").Rows(i).Copy
Sheets("sheet2").Rows(currentRow).Paste
currentRow = currentRow + 1
Next
'now we will takes steps by 3, on every loop we will copy i-th row and next one, third will be omitted
'we also use currentRow variable to avoid empty rows in sheet2
'also, 5000 seems wrong, I'd recommend to determine last row, until which we will loop
'last row is often determined like Cells(Rows.Count, 1).End(xlUp).Row
For i = copyUntil + 2 To 5000 Step 3
Sheets("sheet1").Rows(i).Copy
Sheets("sheet2").Rows(currentRow).Paste
currentRow = currentRow + 1
Sheets("sheet1").Rows(i + 1).Copy
Sheets("sheet2").Rows(currentRow).Paste
currentRow = currentRow + 1
Next
This code will only paste values. Let me know if any questions or if you really, really need the formatting I can tweak it.
Sub DoCopy()
'This code is pretty much specifit to your request/question, it will copy 1-9, skip 10, 13, 16....
'i for the loop, x for the row that will not be added, y to paste on the second sheet
Dim i, x, y As Long, divn As Integer
For i = 1 To 5000
If i < 10 Then
y = y + 1
Sheets("Sheet1").Rows(i).Copy
Sheets("Sheet2").Range("A" & y).PasteSpecial ''Paste values only
ElseIf i >= 10 Then
x = i - 10
If x Mod 3 <> 0 Then
y = y + 1
Sheets("Sheet1").Rows(i).Copy
Sheets("Sheet2").Range("A" & y).PasteSpecial ''Paste values only
Else
'Do nothing
End If
End If
Next i
End Sub
I'm definitely a beginner when it comes to VBA beyond editing other peoples Macros. I have a project I'm working on where I need to combine data taken from the first pivot row and column headers and paste it in a new worksheet.
I need to take the row values in column A (which can be variable in data and count) multiply each row by the number of columns (also can be variable in data and count) such that if I have 3 columns (A,B,C) and two rows(1,2) before the macro after I will have 6 rows and 3 columns with the first 3 rows all having the same data as the original row 1 and the second 3 rows with the same data as the original row 2.
Then I have to paste down the columns to match the rows as well as the data to match the row and columns.
Original Pivot:
Goal After Macro:
I know I need to count the number of columns declare that a variable and then use that variable to copy the rows over to the new worksheet using a loop. I don't believe using a second pivot will work as it wont use the column header a cell value even if I do a tabular view. I've tried writing some code but I cant get to it as I'm on my home computer.
Any advise on how to get past the first part of having it count the number of columns and expand the rows would be much appreciated.
Here you go. This should do what you are looking for. You will just need to set the value of Sheet2("A1") manually to Accnt..
TESTED:
Private Sub FormShift()
Dim tRow As Long 'target Row
Dim lastCol As Long
Dim lastRow As Long
lastCol = Sheets("Sheet1").Cells(1, Columns.Count).End(xlToLeft).Column
lastRow = Sheets("Sheet1").Range("A" & Rows.Count).End(xlUp).Row
tRow = 2
For iRow = 2 To lastRow
For iCol = 2 To lastCol
Sheets("Sheet2").Cells(tRow, 1) = Sheets("Sheet1").Cells(iRow, 1)
Sheets("Sheet2").Cells(tRow, 2) = Sheets("Sheet1").Cells(1, iCol)
Sheets("Sheet2").Cells(tRow, 3) = Sheets("Sheet1").Cells(iRow, iCol)
tRow = tRow + 1
Next iCol
Next iRow
End Sub
Sub horz_vert()
noOfColumns = Worksheets("Sheet1").Cells(1, Columns.Count).End(xlToLeft).Column - 1
noOfRows = Worksheets("Sheet1").Cells(Rows.Count, 1).End(xlUp).Row - 1
Worksheets("Sheet2").Cells(1, 1) = Worksheets("Sheet1").Cells(1, 1)
For j = 1 To noOfRows
For I = 1 To noOfColumns
Worksheets("Sheet2").Cells((I + 1) + (j - 1) * noOfColumns, 1) = Worksheets("Sheet1").Cells(j + 1, 1)
Worksheets("Sheet2").Cells((I + 1) + (j - 1) * noOfColumns, 2) = Worksheets("Sheet1").Cells(1, I + 1)
Worksheets("Sheet2").Cells((I + 1) + (j - 1) * noOfColumns, 3) = Worksheets("Sheet1").Cells(j + 1, I + 1)
Next
Next
End Sub
What I am trying to do is take a column of data and transpose it into a single header row, but space out each record such that 'Sheet1'A1 -> 'Sheet2'B1, 'Sheet1'A2 -> 'Sheet2'G1 and so on. (i.e. spaced out every 5 columns)
I am brand new so I was playing around with a loop such that:
Dim i As Integer
Dim j As Integer
i = 1
j = 1
Do While i < 200
Cells(1, i + 1).Value = "='Project List'!A1"
i = i + 5
j = j + 1
Loop
I was trying to use the Int 'j' as a way to cursor through the cell reference in "='Project List'!A1" but can't seem to find a way to do it. I attempted recording a macro but it was using the FormulaR1C1 = "='Project List'!RC[-1]" format and I couldn't figure out how step through R1C1 references either. The 200 was an arbitrary number so that I would capture the whole list of projects, I'm still trying to find a "repeat to blank" style of reference.
Does anyone have any suggestions on how to step through my column of data? Loop? Is there a better way of doing this?
Thanks,
Dane
There are tons of ways to do this but yours is a viable method with some slight modifications. You should create Worksheet objects to reference your input and output worksheets. My code also implements the repeat til blank logic you requested. As you can see, I commented out the first of the outWS lines. Either of those two lines will do the job. Keep the formula one if you need the values to update dynamically. Otherwise use the other one. Obviously you can change Book7.xlsm and Sheet2 as needed. You can also swap Workbooks("Book7.xlsm") for ThisWorkbook if the code is in the same spreadsheet as the data.
Sub transposeAndSpace()
Dim i As Integer
Dim j As Integer
Dim inWS As Worksheet, outWS As Worksheet
i = 1
j = 1
Set inWS = Workbooks("Book7.xlsm").Worksheets("Project List")
Set outWS = Workbooks("Book7.xlsm").Worksheets("Sheet2")
Do While inWS.Cells(j, 1).Value <> ""
'outWS.Cells(1, i + 1).Formula = "='Project List'!A" & j
outWS.Cells(1, i + 1).Value = inWS.Cells(j, 1).Value
i = i + 5
j = j + 1
Loop
Set inWS = Nothing
Set inWS2 = Nothing
End Sub
Assuming the Do/While loop is working for you:
i = 1
j = 1
Do While i < 200
Worksheets("Sheet2").Cells(1, i + 1).Value = "='Project List'!" & Range("A1").Offset(,j).Address
i = i + 5
j = j + 1
Loop
When i & j = 1, this should yield:
Worksheets("Sheet2").Cells(1, i + 1).Value --> Sheet2.Range("B1").Value
And it will put the formula:
"='Project List'!$A$1`
When i = 6, j = 2, should yield:
Worksheets("Sheet2").Cells(1, i + 1).Value --> Sheet2.Range("G1").Value
And it will put the formula:
"='Project List'!$A$2`
etc.
I would appreciate some help on the following VBA Macro problem,
screenshot here:
I have to compare the data in 2 columns - Index & Sec_Index. In case of a match it should check which Values is assigned to the Sec_Index and fill a "1" to the matching Value column corresponding to Index and "0" for the other Value columns (I hope the screenshot explains it better)
I wrote a short macro which works good. However I have huge amounts of data - both Index columns contain at least 400000-500000 lines. This makes my code useless since it will take extreme long durations to execute.
Is there a way to make this work? I read about Variant arrays, but I'm not that familiar with them.
You can put this formula (if Excel 2007 or above):
=COUNTIFS($H$2:$H$5,$B2,$I$2:$I$5,"A")
into C2 and copy it down and across; just change "A" to "B" and "C".
Added In view of the number of rows, I would import the data into MS Access, create a Crosstab Query, then copy this data back to Excel.
Try this, not overly robust but does work. Not sure how quickly this will compare to what you may have had?
It did about 60,000 rows with 25 keys in about 5 seconds.
Edit: Added timer to function.
Public Declare Function GetTickCount Lib "kernel32.dll" () As Long
public Sub main()
Dim t As Long
t = GetTickCount
Application.ScreenUpdating = False
Dim Arr1(), Arr() As Double
Dim x, y, i, j As Double
Dim v As String
x = Cells(Rows.Count, 2).End(xlUp).Row - 2
y = Cells(Rows.Count, 8).End(xlUp).Row - 2
Range("c2", "e" & x + 2) = 0
ReDim Arr1(x)
ReDim Arr2(y)
i = 0
Do Until Cells(i + 2, 2) = ""
Arr1(i) = Cells(i + 2, 2)
i = i + 1
Loop
i = 0
Do Until Cells(i + 2, 8) = ""
Arr2(i) = Cells(i + 2, 2)
i = i + 1
Loop
i = 0
Do Until i > UBound(Arr1)
j = 0
Do Until j > UBound(Arr2)
If Arr1(i) = Arr2(j) Then
v = Cells(Arr2(j) + 1, 9)
Select Case v
Case "a"
Cells(i + 2, 3) = 1
Case "b"
Cells(i + 2, 4) = 1
Case "c"
Cells(i + 2, 5) = 1
End Select
Exit Do
End If
j = j + 1
Loop
i = i + 1
Loop
MsgBox GetTickCount - t, , "Milliseconds"
End Sub
I am receiving a large file 500k+ lines but all the content is in column A. I need to run a macro that will transpose the data into matrix form but will only create a new row when it finds "KEY*" in the ActiveCell. For example:
| KEY 4759839 | asljhk | 35049 | | sklahksdjf|
| KEY 359 | skj | 487 |y| 2985789 |
The above data in my file would originally look like this in column A:
KEY 4759839
asljhk
35049
sklahksdjf
KEY 359
skj
487
y
2985789
Considerations:
Blank cells need to be transposed as well, so the macro cant stop based on emptyCell
The number of cells between KEY's is not constant so it actually needs to read the cell to know if it should create a new row
It can either stop based on say 20 empty cells in a row or prompt for a max row number
(Optional) It would be nice if there was some sort of visual indicator for the last item in a row so that its possible to tell if the last item(s) were blank cells
I searched around and found a macro that had the same general theme but it went based on every 6 lines and I did not know enough to try to modify it for my case. But in case it helps here it is:
Sub kTest()
Dim a, w(), i As Long, j As Long, c As Integer
a = Range([a1], [a500000].End(xlUp))
ReDim w(1 To UBound(a, 1), 1 To 6)
j = 1
For i = 1 To UBound(a, 1)
c = 1 + (i - 1) Mod 6: w(j, c) = a(i, 1)
If c = 6 Then j = j + 1
Next i
[c1].Resize(j, 6) = w
End Sub
I would greatly appreciate any help you can give me!
This works with the sample data you provided in your question - it outputs the result in a table starting in B1. It runs in less than one second for 500k rows on my machine.
Sub kTest()
Dim originalData As Variant
Dim result As Variant
Dim i As Long
Dim j As Long
Dim k As Long
Dim countKeys As Long
Dim countColumns As Long
Dim maxColumns As Long
originalData = Range([a1], [a500000].End(xlUp))
countKeys = 0
maxColumns = 0
'Calculate the number of lines and columns that will be required
For i = LBound(originalData, 1) To UBound(originalData, 1)
If Left(originalData(i, 1), 3) = "KEY" Then
countKeys = countKeys + 1
maxColumns = IIf(countColumns > maxColumns, countColumns, maxColumns)
countColumns = 1
Else
countColumns = countColumns + 1
End If
Next i
'Create the resulting array
ReDim result(1 To countKeys, 1 To maxColumns) As Variant
j = 0
k = 1
For i = LBound(originalData, 1) To UBound(originalData, 1)
If Left(originalData(i, 1), 3) = "KEY" Then
j = j + 1
k = 1
Else
k = k + 1
End If
result(j, k) = originalData(i, 1)
Next i
With ActiveSheet
.Cells(1, 2).Resize(UBound(result, 1), UBound(result, 2)) = result
End With
End Sub
Tested and works:
Sub test()
Row = 0
col = 1
'Find the last not empty cell by selecting the bottom cell and moving up
Max = Range("A650000").End(xlUp).Row 'Or whatever the last allowed row number is
'loop through the data
For i = 1 To Max
'Check if the left 3 characters of the cell are "KEY" and start a new row if they are
If (Left(Range("A" & i).Value, 3) = "KEY") Then
Row = Row + 1
col = 1
End If
Cells(Row, col).Value = Range("A" & i).Value
If (i > Row) Then
Range("A" & i).Value = ""
End If
col = col + 1
Next i
End Sub