How to increment by 1 a loop in Visual Basic for Application - excel

My first question on Stackoverflow, and a newbe, so please bare with me.
I am trying to sort out an excel sheet with 411,278 rows, about stock market data.
My code is as follows:
Sub Macro1()
'
' Macro1 Macro
'
Range("C6:C1577").Select
Selection.Copy
Range("D5").Select
Selection.PasteSpecial Paste:=xlPasteAll, Operation:=xlNone, SkipBlanks:= _
False, Transpose:=True
Range("B6:C1577").Select
Range("C6").Activate
Application.CutCopyMode = False
Selection.Delete Shift:=xlUp
End Sub
I am trying to insert the above code in a loop that will increment all the numbers within the loop by 1.
For example (the next phase in the loop will be):
Sub Macro1()
'
' Macro1 Macro
'
Range("C7:C1578").Select
Selection.Copy
Range("D6").Select
Selection.PasteSpecial Paste:=xlPasteAll, Operation:=xlNone, SkipBlanks:= _
False, Transpose:=True
Range("B7:C1578").Select
Range("C7").Activate
Application.CutCopyMode = False
Selection.Delete Shift:=xlUp
End Sub
Thanks!

A Data Transpose
The following will copy only the values (no formatting or formulas).
Adjust the values in the constants section. Note that you can choose a different worksheet for the result to leave your source data intact.
The Code
Option Explicit
Sub transposeValues()
' Define constants.
Const srcName As String = "Sheet1"
Const srcColumns As String = "B:D"
Const srcFirstRow As Long = 5
Const dstName As String = "Sheet1"
Const dstFirstCell As String = "B5"
Const dCount As Long = 1573
' Define workbook.
Dim wb As Workbook: Set wb = ThisWorkbook
' Define Source Range.
Dim rng As Range
With wb.Worksheets(srcName).Columns(srcColumns)
Set rng = .Resize(.Worksheet.Rows.Count - srcFirstRow + 1) _
.Offset(srcFirstRow - 1).Find( _
What:="*", _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious)
If rng Is Nothing Then
Exit Sub
End If
Set rng = .Resize(rng.Row - srcFirstRow + 1).Offset(srcFirstRow - 1)
End With
' Write values from Source Range to Data Array.
Dim Data As Variant: Data = rng.Value
' Define Result Array.
Dim srCount As Long: srCount = UBound(Data)
Dim Remainder As Long: Remainder = srCount Mod dCount
Dim drCount As Long
If Remainder = 0 Then
drCount = srCount / dCount
Else
drCount = Int(srCount / dCount) + 1
End If
Dim dcCount As Long: dcCount = dCount + 1
Dim Result As Variant: ReDim Result(1 To drCount, 1 To dcCount)
' Declare counters.
Dim i As Long, j As Long, k As Long
' Write values from Data Array to Result Array.
If drCount = 1 And Remainder > 0 Then
i = 1
Else
For i = 1 To drCount + (Remainder <> 0) * 1 ' In VBA 'True = -1'.
k = k + 1
For j = 1 To 2
Result(i, j) = Data(k, j)
Next j
For j = 3 To dcCount
k = k + 1
Result(i, j) = Data(k, 2)
Next j
Next i
End If
' Write the remainder of values in Data Array to Result Array.
If Remainder > 0 Then
k = k + 1
For j = 1 To 2
Result(i, j) = Data(k, j)
Next j
If Remainder > 1 Then
For j = 3 To 1 + Remainder
k = k + 1
Result(i, j) = Data(k, 2)
Next j
End If
End If
' Write values from Result Array to Destination Range.
With wb.Worksheets(dstName).Range(dstFirstCell).Resize(, dcCount)
.Resize(.Worksheet.Rows.Count - .Row + 1).ClearContents
.Resize(drCount).Value = Result
End With
End Sub

Please do not try this code without taking precautions.
Sub Macro1()
Dim Rng As Range
Dim Rstart As Long ' start of range row
Dim Rend As Long ' end of range row
Dim Target As Range ' destination of copy action
Dim DelRng As Range ' range to delete
Rstart = 6
Rend = 1577
Application.ScreenUpdating = False
Do
Set Rng = Range(Cells(Rstart, "C"), Cells(Rend, "C"))
Set Target = Cells(Rstart - 1, "D")
Set DelRng = Range(Cells(Rstart, "B"), Cells(Rend, "C"))
Rng.Copy
TargetPasteSpecial Paste:=xlPasteAll, _
Operation:=xlNone, _
SkipBlanks:=False, _
Transpose:=True
DelRng.Delete Shift:=xlUp
Rstart = Rstart + 1
Loop Until Rstart = Rend
With Application
.CutCopyMode = False
.ScreenUpdating = False
End With
End Sub
Primary precaution to take is to understand the code. Start and End rows are specified at the top. They are used to set 3 ranges in columns C, D and B:C. Initially, they are precisely your 3 ranges Range("C6:C1577"), Range("D5"), Range("B6:C1577"). The code copies from the first range to the second and then deletes the third. It does so without selecting anything because once VBA is told where a range is it doesn't need to select it.
You will notice that there is no equivalent of your Range("C7").Activate. That's because C7 is within the range Range("B6:C1577") which is earmarked for destruction. It makes no difference which cell in it is active. However, I have some doubt about the deletion and whether you actually meant to delete only this cell (which your code doesn't do). Please check my code against your intentions in this regard.
Now the critical part. That's the loop. On each iteration the 3 ranges are moved one row down. The line of code requiring your attention is this one.
Loop Until Rng.Row = Rend
The loop will continue until the first row of the first range is equal to Rend. That means that there will be 1571 loops - probably enough time to have a coffee and a chat even while screen updating is turned off. But even this fearsome number is definitely wrong. Your question gives no clue as to your needs but I guess it should be like Loop Until Rng.Row = (411278 - Rend), give or take 1. I didn't want to enter into an argument about this final, last row (which tends to be omitted in loops) while not believing that your number of rows is invariable. I think it should be replaced with a formula that finds the last existing row in a particular column.
However, your question was how to advance the ranges. My code does that beautifully. Enjoy the show.

Related

Adding 1 to each row without using for loop in VBA

Looking for an easy way to add a certain number to each row in a column. Something like range("b1:b9")=range("a1:a9")+1
From this:
to this:
You could use Evaluate, seems quite quick.
Sub Add1()
With Range("A1:A10000")
.Value = Evaluate(.Address & "+1")
End With
End Sub
Looking for a "time-efficient" solution and avoiding loops are not the same thing.
If you were to loop over the range itself, then yes, it would be slow. Copying the range data to a Variant array, looping that, then copying the result back to the range is fast.
Here is a demo
Sub Demo()
Dim rng As Range
Dim dat As Variant
Dim i As Long
Dim t1 As Single
t1 = Timer() ' just for reportingh the run time
' Get a reference to your range by whatever means you choose.
' Here I'm specifying 1,000,000 rows as a demo
Set rng = Range("A1:A1000000")
dat = rng.Value2
For i = 1 To UBound(dat, 1)
dat(i, 1) = dat(i, 1) + 1
Next
rng.Value2 = dat
Debug.Print "Added 1 to " & UBound(dat, 1) & " rows in " & Timer() - t1; " seconds"
End Sub
On my hardware, this runs in about 1.3 seconds
FYI, the PasteSpecial, Add technique is faster still
Start the macro recorder.
type a 1 into an empty cell
copy that cell
select the cells that you want to add that value to
open the Paste Special dialog
select "Add" and OK
Stop the macro recorder. Use that code as is or work it into your other code.
Range("C1").Value = 1
Range("C1").Select
Application.CutCopyMode = False
Selection.Copy
Range("A1:A5").Select
Selection.PasteSpecial Paste:=xlPasteAll, Operation:=xlAdd, SkipBlanks:= False, Transpose:=False
Increase Range Values
In the following examples, the Add solution took 2.4s while the Array solution took 8.7s (on my machine) to process 5 columns.
In the Array solution, the selection is never changed, it just writes the result to the range.
The Add solution is kind of mimicking this behavior, by setting all selections as they initially were. Hence the complications.
Option Explicit
' Add Solution
Sub increaseRangeValuesTEST()
increaseRangeValues Sheet1.Range("A:E"), 1 ' 2.4s
End Sub
Sub increaseRangeValues( _
ByVal rg As Range, _
ByVal Addend As Double)
Application.ScreenUpdating = False
Dim isNotAW As Boolean: isNotAW = Not rg.Worksheet.Parent Is ActiveWorkbook
Dim iwb As Workbook
If isNotAW Then Set iwb = ActiveWorkbook: rg.Worksheet.Parent.Activate
Dim isNotAS As Boolean: isNotAS = Not rg.Worksheet Is ActiveSheet
Dim iws As Worksheet
If isNotAS Then Set iws = ActiveSheet: rg.Worksheet.Activate
Dim cSel As Variant: Set cSel = Selection
Dim aCell As Range: Set aCell = ActiveCell
Dim sCell As Range: Set sCell = rg.Cells(rg.Rows.Count, rg.Columns.Count)
Dim sValue As Double: sValue = sCell.Value + Addend
sCell.Value = Addend
sCell.Copy
rg.PasteSpecial xlPasteAll, xlPasteSpecialOperationAdd ' 95%
Application.CutCopyMode = False
sCell.Value = sValue
aCell.Activate
cSel.Select
If isNotAS Then iws.Activate
If isNotAW Then iwb.Activate
Application.ScreenUpdating = True
End Sub
' Array Solution
Sub increaseRangeValuesArrayTEST()
increaseRangeValuesArray Sheet1.Range("A:E"), 1 ' 8.7s
End Sub
Sub increaseRangeValuesArray( _
ByVal rg As Range, _
ByVal Addend As Double)
With rg
Dim rCount As Long: rCount = .Rows.Count
Dim cCount As Long: cCount = .Columns.Count
Dim Data As Variant
If rCount > 1 Or cCount > 1 Then
Data = .Value
Else
ReDim Data(1 To 1, 1 To 1): Data = .Value
End If
Dim r As Long, c As Long
For r = 1 To rCount
For c = 1 To cCount
Data(r, c) = Data(r, c) + Addend
Next c
Next r
.Value = Data ' 80%
End With
End Sub
#1. Disable your autocalculations
Application.Calculation = xlCalculationManual
#2. Disable your screenupdating
Application.ScreenUpdating = False
#3. As long as your row entries aren't more than ~56000, but your dataset is substantial then its quicker to read into an array, do the manipulations in an array, then output that array in one go.
array1 = Range(cells(3,2), cells(12,2)).value
for i = 1 to ubound(array1, 1)
array1(i, 1) = array(i, 1) + 1
next i
range(cells(3,10), cells(12,10)) = array1
Note that array1 will be 2D, and you'll be addressing (1,1) through to (10,1) in the example above
Then after pasting back in, reenable your autocalcs, THEN your screenupdate
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True

Macro to copy and paste (transpose) data from column to row - Scalable

I am looking to create a macro which would allow me to copy and paste data from one column and then transpose that data over 2 columns in the right order
I have recorded a macro while doing the process manually
Range("G3").Select
Application.CutCopyMode = False
Selection.Copy
Range("G2:G7").Select ' (The column range I want to copy)
Application.CutCopyMode = False
Selection.Copy
Range("I1").Select ' (Row where the range of G2:G7) is now transposed)
Selection.PasteSpecial Paste:=xlPasteAll, Operation:=xlNone, SkipBlanks:= _
False, Transpose:=True
Range("H2:H7").Select ' (The second column range I want to copy)
Application.CutCopyMode = False
Selection.Copy
Range("I2").Select ' (Second Row where the range of H2:H7) is now transposed)
Selection.PasteSpecial Paste:=xlPasteAll, Operation:=xlNone, SkipBlanks:= _
False, Transpose:=True
Range("H8:H13").Select ' (The third column range I want to copy)
Application.CutCopyMode = FalseSelection.Copy
Range("I3").Select' ( Third Row where the range of H8:H13) is now transposed)
Selection.PasteSpecial Paste:=xlPasteAll, Operation:=xlNone, SkipBlanks:= _
False, Transpose:=True
The problem is that this code only works up to certain number of rows (up till H13 for example), but if I want to this repeat this process up to row H600 (range of H600:H605) and pasting to I31 for example without copying and pasting this code hundreds of times, is there a way I can do this?
This is what I mean by example
Column H
Star
Greenwood
Titon
Humford
converted to
Column I | Column J**
Star | Greenwood
titon | Humford
Here's an alternative to Copy/Paste - using Variant Arrays. This will be much faster for large data sets.
Sub Demo()
Dim rng As Range
Dim Src As Variant
Dim Dst As Variant
Dim GroupSize As Long
Dim Groups As Long
Dim iRow As Long
Dim iCol As Long
Dim iDst As Long
Dim SrcStartRow As Long
Dim SrcColumn As Long
Dim DstStartRow As Long
Dim DstColumn As Long
' Set up Parameters
GroupSize = 2
SrcStartRow = 2
SrcColumn = 8 'H
DstStartRow = 1
DstColumn = 9 'I
With ActiveSheet 'or specify a specific sheet
' Get Reference to source data
Set rng = .Range(.Cells(SrcStartRow, SrcColumn), .Cells(.Rows.Count, SrcColumn).End(xlUp))
' Account for possibility there is uneven amount of data
Groups = Application.RoundUp(rng.Rows.Count / GroupSize, 0)
If rng.Rows.Count <> Groups * GroupSize Then
Set rng = rng.Resize(Groups * GroupSize, 1)
End If
'Copy data to Variant Array
Src = rng.Value2
'Size the Destination Array
ReDim Dst(1 To UBound(Src, 1) / GroupSize, 1 To GroupSize)
'Loop the Source data and split into Destination Array
iDst = 0
For iRow = 1 To UBound(Src, 1) Step GroupSize
iDst = iDst + 1
For iCol = 1 To GroupSize
Dst(iDst, iCol) = Src(iRow + iCol - 1, 1)
Next
Next
' Move result to sheet
.Cells(DstStartRow, DstColumn).Resize(UBound(Dst, 1), UBound(Dst, 2)).Value = Dst
End With
End Sub
Before
Well, you are not really transposing, but I would use this method. I start at 2 to leave the first in place, then basically move the next one over and delete all the empty spaces at the end.
Sub MakeTwoColumns()
Dim x As Long
For x = 2 To 500 Step 2
Cells(x, 6) = Cells(x, 5)
Cells(x, 5).ClearContents
Next x
Columns(5).SpecialCells(xlCellTypeBlanks).Delete
Columns(6).SpecialCells(xlCellTypeBlanks).Delete
End Sub
After

Speeding Up a Loop in VBA

I am trying to speed up a loop in VBA with over 25,000 line items
I have code that is stepping down through a spread sheet with over 25,000 lines in it. Right now the code loops thought each cell to see if the Previous cell values match the current cell values. If they do not match it inserts a new blank line. Right now the code take over 5 hours to complete on a pretty fast computer. Is there any way I can speed this up?
With ActiveSheet
BottomRow4 = .Cells(.Rows.Count, "E").End(xlUp).Row
End With
Do
Cells(ActiveCell.Row, 5).Select
Do
ActiveCell.Offset(1, 0).Select
'Determines if previous cells is the same as current cells
Loop Until (ActiveCell.Offset(0, -1) & ActiveCell <>
ActiveCell.Offset(1, -1) & ActiveCell.Offset(1, 0))
'Insert Blank Row if previous cells do not match current cells...
Rows(ActiveCell.Offset(1, 0).Row & ":" & ActiveCell.Offset(1,
0).Row).Select
Selection.Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
BottomRow4 = BottomRow4 + 1
Loop Until ActiveCell.Row >= BottomRow4
Similarly to when deleting rows, you can save your inserts until you're done looping.
Run after selecting a cell at the top of the column you want to insert on (but not on row 1):
Sub Tester()
Dim c As Range, rngIns As Range, sht As Worksheet
Dim offSet As Long, cInsert As Range
Set sht = ActiveSheet
For Each c In sht.Range(Selection, _
sht.Cells(sht.Rows.Count, Selection.Column).End(xlUp)).Cells
offSet = IIf(offSet = 0, 1, 0) '<< toggle offset
If c.offSet(-1, 0).Value <> c.Value Then
'This is a workaround to prevent two adjacent cells from merging in
' the rngInsert range being built up...
Set cInsert = c.offSet(0, offSet)
If rngIns Is Nothing Then
Set rngIns = cInsert
Else
Set rngIns = Application.Union(cInsert, rngIns)
End If
End If
Next c
If Not rngIns Is Nothing Then
rngIns.EntireRow.Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
End If
End Sub
Edit: runs in 3 secs on 25k rows populated using ="Val_" & ROUND(RAND()*1000), converted to values, then sorted.
Insert If Not Equal
Sub InsertIfNotEqual()
Const cSheet As Variant = 1 ' Worksheet Name/Index
Const cFirstR As Long = 5 ' First Row
Const cCol As Variant = "E" ' Last-Row-Column Letter/Number
Dim rng As Range ' Last Cell Range, Union Range
Dim vntS As Variant ' Source Array
Dim vntT As Variant ' Target Array
Dim i As Long ' Source Array Row Counter
Dim j As Long ' Target Array Row Counter
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
On Error GoTo ProcedureExit
' In Worksheet
With ThisWorkbook.Worksheets(cSheet)
' Determine the last used cell in Last-Row-Column.
Set rng = .Columns(cCol).Find("*", , xlFormulas, , , xlPrevious)
' Copy Column Range to Source Array.
vntS = .Cells(cFirstR, cCol).Resize(rng.Row - cFirstR + 1)
End With
' In Arrays
' Resize 1D Target Array to the first dimension of 2D Source Array.
ReDim vntT(1 To UBound(vntS)) As Long
' Loop through rows of Source Array.
For i = 2 To UBound(vntS)
' Check if current value is equal to previous value.
If vntS(i, 1) <> vntS(i - 1, 1) Then
' Increase row of Target Array.
j = j + 1
' Write Source Range Next Row Number to Target Array.
vntT(j) = i + cFirstR
End If
Next
' If no non-equal data was found.
If j = 0 Then Exit Sub
' Resize Target Array to found "non-equal data count".
ReDim Preserve vntT(1 To j) As Long
' In Worksheet
With ThisWorkbook.Worksheets(cSheet)
' Set Union range to first cell of row in Target Array.
Set rng = .Cells(vntT(1), 2)
' Check if there are more rows in Target Array.
If UBound(vntT) > 1 Then
' Loop through the rest of the rows (other than 1) in Target Array.
For i = 2 To UBound(vntT)
' Add corresponding cells to Union Range. To prevent the
' creation of "consecutive" ranges by Union, the resulting
' cells to be added are alternating between column A and B
' (1 and 2) using the Mod operator against the Target Array
' Row Counter divided by 2.
Set rng = Union(rng, .Cells(vntT(i), 1 + i Mod 2))
Next
End If
' Insert blank rows in one go.
rng.EntireRow.Insert
End With
ProcedureExit:
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
End Sub
Edited: Added two options: didn't test for speed. I thought test2() would have been faster but I'm not certain depending on number of rows.
Untested, but just something I thought of quickly. If I'll remember I'll come back to this later because I think there are faster ways
Sub Test1()
Dim wsSheet As Worksheet
Dim arrSheet() As Variant
Dim collectRows As New Collection
Dim rowNext As Long
Application.ScreenUpdating = False
Application.EnableEvents = False
Const ColCheck As Integer = 6
Set wsSheet = ActiveSheet
arrSheet = wsSheet.Range("A1").CurrentRegion
For rowNext = UBound(arrSheet, 1) To LBound(arrSheet, 1) + 1 Step -1
If arrSheet(rowNext, ColCheck) <> arrSheet(rowNext - 1, ColCheck) Then collectRows.Add rowNext
Next rowNext
For rowNext = 1 To collectRows.Count
wsSheet.Cells(collectRows(rowNext), 1).EntireRow.Insert
Next rowNext
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
Second Option inserting all at once:
I used a string here because union would change rows next to each other into one larger range. Instead of Range("1:1", "2:2") it would create ("1:2") and that won't insert the way you need. I don't know of a cleaner way, but there probably is.
Sub Test2()
Dim wsSheet As Worksheet
Dim arrSheet() As Variant
Dim collectRows As New Collection
Dim rowNext As Long
Dim strRange As String
Dim cntRanges As Integer
Dim rngAdd As Range
Application.ScreenUpdating = False
Application.EnableEvents = False
Const ColCheck As Integer = 6
Set wsSheet = ActiveSheet
arrSheet = wsSheet.Range("A1").CurrentRegion
For rowNext = UBound(arrSheet, 1) To LBound(arrSheet, 1) + 1 Step -1
If arrSheet(rowNext, ColCheck) <> arrSheet(rowNext - 1, ColCheck) Then
strRange = wsSheet.Cells(rowNext, 1).EntireRow.Address & "," & strRange
cntRanges = cntRanges + 1
If cntRanges > 10 Then
collectRows.Add Left(strRange, Len(strRange) - 1)
strRange = vbNullString
cntRanges = 0
End If
End If
Next rowNext
If collectRows.Count > 0 Then
Dim i As Long
For i = 1 To collectRows.Count
Set rngAdd = Range(collectRows(i))
rngAdd.Insert
Next i
End If
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub

Copy data from a variable table

I have been trying to do a report and creating a macro to copy the data from one file to another.
I cannot figure out how to copy the data since the table I need to get my data from varies.
Example one:
What I need to copy is what is below the Alarm text.
But in, example one, I have no critical alarms but there are files that may have. Same applies to major/minor/warning.
The max of lines below the Alarm text are 3, but I can have 1/2/3 or even none.
In example 2, I have no data.
Here I have 2 critical and 3 on all other categories.
I know this may be a weird question, but I have no idea in how to find the these values, since they may vary so much.
All help is appreciated
Here is the code i have, but i am missing the important part,
Sub Copy()
Dim wbOpen As Workbook
Dim wbMe As Workbook
Dim vals As Variant
Set wbMe = ThisWorkbook
Set wbOpen = Workbooks.Open("C:\XXX\Core")
'MSS
vals = wbOpen.Sheets("MSS02NZF").Range("A2:B260").Copy
wbMe.Sheets("MSS02NZF").Range("B5").PasteSpecial Paste:=xlPasteValuesAndNumberFormats, _
Operation:=xlNone, SkipBlanks:=False, Transpose:=False
' wbOpen.Sheets(1).Range("A2:B260").Copy
' wbMe.Sheets(1).Range("B5").PasteSpecial Paste:=xlPasteValuesAndNumberFormats, _
Operation:=xlNone, SkipBlanks:=False, Transpose:=False
'MME
vals = wbOpen.Sheets("MME01NZF").Range("A2:H260").Copy
wbMe.Sheets("MME01NZF").Range("B5").PasteSpecial Paste:=xlPasteValuesAndNumberFormats, _
Operation:=xlNone, SkipBlanks:=False, Transpose:=False
'CSCF
vals = wbOpen.Sheets("CSCF").Range("A2:H2060").Copy
wbMe.Sheets("CSCF").Range("B5").PasteSpecial Paste:=xlPasteValuesAndNumberFormats, _
Operation:=xlNone, SkipBlanks:=False, Transpose:=False
Modify to your own need; code is using A:B as your source, and pastes the results in C:D.
Sub test()
Dim lrow As Long, alarmRow() As Long, alarmEnd() As Long
Dim count As Long, count2 As Long, rowcount As Long
ReDim alarmRow(1 To Application.CountIf(Range("A:A"), "Alarm"))
ReDim alarmEnd(1 To UBound(alarmRow))
With Worksheets("Sheet4") 'Change this to the Sheetname of your source.
lrow = .Cells(Rows.count, 1).End(xlUp).Row
For x = 1 To lrow Step 1
If .Range("A" & x).Value = "Alarm" Then 'Change "A" column to where your source data is.
count = count + 1
alarmRow(count) = x + 1
ElseIf .Range("A" & x).Value = "" Then 'Change "A" column to where your source data is.
count2 = count2 + 1
alarmEnd(count2) = x
End If
alarmEnd(UBound(alarmEnd)) = lrow
Next
For x = 1 To UBound(alarmRow) Step 1
lrow = .Cells(Rows.count, 3).End(xlUp).Row + 1
rowcount = alarmEnd(x) - alarmRow(x)
.Range("C" & lrow & ":D" & lrow + rowcount).Value = .Range("A" & alarmRow(x) & ":B" & alarmEnd(x)).Value ' Change A/B to where your source data is, and C/D to where you want to put the list.
Next
End With
End Sub
It's a bit of a mess, but here's how it works:
It'll look at the list where the word "Alarm" is. Once it finds it, the row number the word is in is registered to an Array. The row of the blank space is also taken to another array. This will serve as the range when copying the data.
Array of Arrays feat. 3-dimensional Jagged Arrays
Option Explicit
'*******************************************************************************
' Purpose: If not open, opens a specified workbook and pastes specific data
' found in two columns from several worksheets into a range specified
' by a cell in worksheets with the same name in this workbook.
'*******************************************************************************
Sub CopyPasteArray()
'***************************************
' List of Worksheet Names in Both Workbooks
Const cStrWsName As String = "MSS02NZF,MME01NZF,CSCF"
' Separator in List of Names of Worksheets in Both Workbooks
Const cStrSplit As String = ","
' Path of Workbook to Be Copied From
Const cStrSourcePath As String = "C:\XXX"
' Name of Workbook to Be Copied From
Const cStrSourceName As String = "Core.xls"
' Address of First Row Range to Be Copied From
Const cStrSourceFirst As String = "A2:B2"
' Target Top Cell Address to Be Pasted Into
Const cStrTopCell As String = "B5"
' Search String
Const cStrSearch As String = "Alarm"
' Target Columns
Const cIntTargetCols As Integer = 2 ' Change to 3 to include Type of Error.
'***************************************
Dim objWbSource As Workbook ' Source Workbook
Dim vntWsName As Variant ' Worksheet Names Array
Dim vntSourceAA As Variant ' Source Array of Arrays
Dim vntTargetAA As Variant ' Target Array of Arrays
Dim vntTargetRows As Variant ' Each Target Array Rows Array
Dim vntTarget As Variant ' Each Target Array
Dim blnFound As Boolean ' Source Workbook Open Checker
Dim lngRow As Long ' Source Array Arrays Rows Counter
Dim intCol As Integer ' Source Array Arrays Columns Counter
Dim intArr As Integer ' Worksheets and Arrays Counter
Dim lngCount As Long ' Critical Data Counter
Dim lngCount2 As Long ' Critical Data Next Row Counter
Dim strPasteCell As String
'***************************************
' Paste list of worksheets names into Worksheet Names Array.
vntWsName = Split(cStrWsName, cStrSplit)
'***************************************
' Check if Source Workbook is open.
For Each objWbSource In Workbooks
If objWbSource.Name = cStrSourceName Then
Set objWbSource = Workbooks(cStrSourceName)
blnFound = True ' Workbook is open.
Exit For ' Stop checking.
End If
Next
' If Source Workbook is not open, open it.
If blnFound = False Then
Set objWbSource = Workbooks.Open(cStrSourcePath & "\" & cStrSourceName)
End If
'***************************************
' Paste data from Source Workbook into Source Array of Arrays.
ReDim vntSourceAA(UBound(vntWsName))
For intArr = 0 To UBound(vntWsName)
With objWbSource.Worksheets(vntWsName(intArr))
vntSourceAA(intArr) = _
.Range( _
.Range(cStrSourceFirst).Cells(1, 1) _
, .Cells( _
.Range( _
.Cells(1, .Range(cStrSourceFirst).Column) _
, .Cells(Rows.Count, .Range(cStrSourceFirst).Column _
+ .Range(cStrSourceFirst).Columns.Count - 1)) _
.Find(What:="*", _
After:=.Range(cStrSourceFirst).Cells(1, 1), _
LookIn:=xlFormulas, Lookat:=xlWhole, _
SearchOrder:=xlByRows, SearchDirection:=xlPrevious _
).Row _
, .Range(cStrSourceFirst).Column _
+ .Range(cStrSourceFirst).Columns.Count - 1 _
) _
).Value2
End With
Next
' The Source Array of Arrays is a 3-dimensional (jagged) array containing
' a 0-based 1-dimensional array containing an 'UBound(vntWsName)' number of
' 1-based 2-dimensional arrays.
'***************************************
' Count the number of critical data rows to determine size
' of each Target Array.
ReDim vntTargetRows(UBound(vntWsName))
For intArr = 0 To UBound(vntSourceAA)
For lngRow = 1 To UBound(vntSourceAA(intArr), 1)
If vntSourceAA(intArr)(lngRow, 1) = cStrSearch Then
For lngCount2 = lngRow + 1 To UBound(vntSourceAA(intArr), 1)
If vntSourceAA(intArr)(lngCount2, 1) <> "" Then
' Debug.Print vntSourceAA(intArr)(lngCount2, 1)
lngCount = lngCount + 1
lngRow = lngRow + 1
Else
Exit For
End If
Next
End If
Next
vntTargetRows(intArr) = lngCount
lngCount = 0
Next
'***************************************
' Copy critical data into each Target Array and paste it into
' Target Array of Arrays.
ReDim vntTargetAA(UBound(vntWsName))
For intArr = 0 To UBound(vntSourceAA)
ReDim vntTarget(1 To vntTargetRows(intArr), 1 To cIntTargetCols)
For lngRow = 1 To UBound(vntSourceAA(intArr), 1)
If vntSourceAA(intArr)(lngRow, 1) = cStrSearch Then
If cIntTargetCols = 3 Then
lngCount = lngCount + 1
vntTarget(lngCount, 1) = vntSourceAA(intArr)(lngRow - 1, 1)
lngCount = lngCount - 1
End If
For lngCount2 = lngRow + 1 To UBound(vntSourceAA(intArr), 1)
If vntSourceAA(intArr)(lngCount2, 1) <> "" Then
' Debug.Print vntSourceAA(intArr)(lngCount2, 1)
lngCount = lngCount + 1
vntTarget(lngCount, cIntTargetCols - 1) _
= vntSourceAA(intArr)(lngCount2, 1)
vntTarget(lngCount, cIntTargetCols) _
= vntSourceAA(intArr)(lngCount2, 2)
lngRow = lngRow + 1
Else
Exit For
End If
Next
End If
Next
vntTargetAA(intArr) = vntTarget
lngCount = 0
Next
'***************************************
' Clean up
Erase vntTarget
Erase vntTargetRows
Erase vntSourceAA
'***************************************
' Paste each Target Array into each of this workbook's worksheet's ranges,
' which are starting at the specified cell (cStrTopCell) if no data is below,
' or else at the first empty cell found searching from the bottom.
For intArr = 0 To UBound(vntWsName)
With ThisWorkbook.Worksheets(vntWsName(intArr))
If .Cells(Rows.Count, .Range(cStrTopCell).Column + cIntTargetCols - 2) _
.End(xlUp).Row = 1 Then
' No data in column
strPasteCell = cStrTopCell
Else
' Find first empty cell searching from bottom.
strPasteCell = _
.Cells( _
.Range( _
.Cells(1, .Range(cStrTopCell).Column) _
, .Cells(Rows.Count, .Range(cStrTopCell).Column _
+ cIntTargetCols - 1)) _
.Find(What:="*", _
After:=.Range(cStrTopCell).Cells(1, 1), _
LookIn:=xlFormulas, Lookat:=xlWhole, _
SearchOrder:=xlByRows, SearchDirection:=xlPrevious _
).Row + 1 _
, .Range(cStrTopCell).Column _
).Address
' First empty cell is above Target Top Cell Address.
If Range(strPasteCell).Row < Range(cStrTopCell).Row Then _
strPasteCell = cStrTopCell
End If
' Paste into range.
.Range(strPasteCell).Resize( _
UBound(vntTargetAA(intArr)) _
, _
UBound(vntTargetAA(intArr), 2) _
) = vntTargetAA(intArr)
End With
Next
'***************************************
' Clean up
Erase vntTargetAA
Erase vntWsName
Set objWbSource = Nothing
End Sub
'*******************************************************************************

VBA to copy specified random data from different workbook

Sub getdata()
'CTRL+J
Windows("sample rnd.xlsm").Activate
Range("A1:L5215").Select
Range("A2").Activate
Selection.Copy
Application.CutCopyMode = False
Selection.Copy
Application.CutCopyMode = False
Selection.Copy
Windows("rnd sample draft.xlsm").Activate
Sheets("Random Sample").Select
Sheets("Random Sample").Name = "Random Sample"
Range("A1").Select
ActiveSheet.Paste
Application.CutCopyMode = False
ActiveWorkbook.Save
End Sub
Above is my code so far. It is just copying data from another another workbook and pasting it to my specified worksheet.
What I want is to get random data (rows) without duplicates and I always want to include the first row since it contains the header.
Also, I want to have a text box where I can input number so that I can specify how many data to get from the other workbook. Quite new to vba. need help.
I attached a screenshot.
One solution would be to load the rows in an array, shuffle the rows and write the array to the target:
Sub CopyRandomRows()
Dim source As Range, target As Range, randCount&, data(), value, r&, rr&, c&
' define the source to take the data
Set source = Workbooks("CheckSum3.xlsm").Worksheets("Sheet17").Range("$A$1:$B$10")
' define the target to paste the data
Set target = Workbooks("rnd sample draft.xlsm").Worksheets("Random Sample").Range("A1")
' define the number of rows to generate
randCount = 5
' load the data in an array
data = source.value
'shuffle the rows
For r = 1 To randCount
rr = 1 + Math.Round(VBA.Rnd * (UBound(data) - 1))
For c = 1 To UBound(data, 2)
value = data(r, c)
data(r, c) = data(rr, c)
data(rr, c) = value
Next
Next
' write the data to the target
target.Resize(randCount, UBound(data, 2)) = data
End Sub
a "not so smart" way to do it would be something like this:
Sub Macro1(numRows As Long)
Dim a As Long, i As Long, rng As Range
Windows("sample rnd.xlsm").Activate
a = Int(Rnd() * 5213) + 2
Set rng = Range("A1:L1")
For i = 1 To numRows
While Not Intersect(Cells(a, 1), rng) Is Nothing
a = Int(Rnd() * 5213) + 2
Wend
Set rng = Union(rng, Range("A1:L5215").Rows(a))
Next
rng.Copy
Sheets("Random Sample").Range("A1").Select
ActiveSheet.Paste
End Sub
if you are not going for a huge amount of lines... you also could put all lines in a collection and then delete one random item in it till the count reaches the number of lines you want like this (also not so smart solution):
Sub Macro2(numRows As Long)
Dim a As Long, myCol As New Collection, rng As Range
Windows("sample rnd.xlsm").Activate
For a = 2 To 5215
myCol.Add a
Next
While myCol.Count > numRows
myCol.Remove Int(Rnd() * myCol.Count) + 1
Wend
Set rng = Range("A1:L1")
For a = 1 To myCol.Count
Set rng = Union(rng, Range("A1:L5215").Rows(myCol(a)))
Next
rng.Copy
Sheets("Random Sample").Range("A1").Select
ActiveSheet.Paste
End Sub
if you still have questions, just ask ;)

Resources