I have written a VBA code to select any row where a special value appears in a chosen column.
`Sub test()
vonZeile = 4 ' first row
bisZeile = Cells(vonZeile, 7).End(xlDown).Row
Spalte = 7 ' column G
Markierung = False
For Zeile = bisZeile To vonZeile Step -1
If (Cells(Zeile, Spalte).Value = "Werkstatt") Then
If Markierung Then
Union(Selection, Rows(Zeile)).Select
Else
Rows(Zeile).Select
Markierung = True
End If
End If
Next Zeile
If Zeilen > "" Then Selection.Delete Shift:=xlUp
End Sub`
This might not be the prettiest but it works pretty well and very fast.
Now I would like to change this code so that the rows with the specific value are not only selected but cut out or hidden.
I couldn't figure out how to change this code to get this.
I have a different code that does delete all these rows but it lats an eternity. But it should be much faster when all the rows with the specific value would be deleted at once.
Shouldn't there be a way to just change the .Select part in the code to maybe Hidden or Delete?
This is just a guessing as I am not very familiar with VBA coding.
Very happy to get some advice on this matter.
Thanks
Here's the fastest way I've found to do this: create an array the size of your original data, loop through the rows adding the keepers to the array, then clear all of the data from the worksheet(far less time consuming than deleting) and then lastly write the array of stored data to the sheet.
Option Explicit
Sub test()
Dim ws As Worksheet
Dim firstRow As Integer, lastRow As Integer
Dim lastCol As Integer, criteriaCol As Integer
Dim criteriaValue As Variant
Dim arr As Variant
Dim iRow As Integer, iCol As Integer, iCounter As Integer
'Set this to the worksheet you want to perform this procedure on
Set ws = ActiveSheet
'Set your first row, last row, criteria column, and last column
firstRow = 4
lastRow = Cells(firstRow, 7).End(xlDown).Row
lastCol = 7
criteriaCol = 7
criteriaValue = "Werkstatt"
'Resize the array to fit the length of your sheet
ReDim arr(1 To (lastRow - firstRow), 1 To lastCol)
'iCounter is used to track the position of the first dimension in arr
iCounter = 1
'For each row, if the value you are looking for matches then loop through each column and write it to the array
For iRow = firstRow To lastRow
If ws.Cells(iRow, criteriaCol).Value = criteriaValue Then
For iCol = 1 To lastCol
arr(iCounter, iCol) = ws.Cells(iRow, iCol)
Next
iCounter = iCounter + 1
End If
Next iRow
'Clear the specific rows on the sheet
ws.Rows(firstRow & ":" & lastRow).Cells.Clear
'Resize the range to fit the array and write it the worksheet
ws.Cells(firstRow, 1).Resize(firstRow + iCounter - 1, lastCol) = arr
End Sub
I now found the answer to my problem. It is just a change of one single line. I deleted the last line in my code If Zeilen > "" Then Selection.Delete Shift:=xlUp and replaced it by the following line Selection.EntireRow.Delete. This solves the problem and it also works fast which was very important to me. Thanks everyone for the help!
Related
In VBA I try to run a loop with an if statement. The loop is set to run a row at a time for a range (wks "Data", B7:J25).
For each row if the value at column C7:C25 is 1, I would like to copy that row (e.g. B7:J7) and insert it at the worksheet "temp" one at a time.
I have tried various codes, for example:
Sub start()
Dim i As Integer
Dim wsData, wsCalcAndOutput, wsTemp As Worksheet
For i = 1 To 25
If Cells((7 + i), 3) = "1" Then
Worksheets("Data").Range("B7:J7").Copy _
Worksheets("temp").Range("B7:J7")
End If
Next
End Sub
But then I can only copy and paste the first row of the range. Alternatively, I found this procedure at stackoverflow, but I can't seem to be able to paste what I copy at each iteration:
Dim wsData, wsCalcAndOutput As Worksheet
Dim rSPX, rSX5E, rNKY, rUKX, rSMI, rEEMUP, testData As Range
Sub start()
Dim i As Integer
For i = 1 To 25
If Cells((7 + i), 3) = "1" Then
With ActiveSheet
.Range(.Cells((7 + i), 2), .Cells((7 + i), 10)).Copy
End With
End If
Next
End Sub
Is this the right way to do so or is there a more efficient way?
Also - in the dataset the criteria for the if statement is actually a string called either "TRUE" or "FALSE". Can an if statement use a string as a signal instead of "1"?
All the best,
Christoffer
As BigBen says, using AutoFilter would be quicker but here is one way of doing this with a loop. Have added a few comments which hopefully explain the basics.
One problem with your code was that you weren't changing the destination cells so they would continually be overwritten.
Sub start()
Dim i As Long 'better than integer
Dim n As Long: n = 7
Dim wsData As Worksheet, wsCalcAndOutput As Worksheet, wsTemp As Worksheet 'specify each type
With Worksheets("Data")
For i = 7 To 25 'change as appropriate
If .Cells(i, 3) = 1 Then 'no need for quotes
Range(.Cells(i, "B"), Cells(i, "J")).Copy _
Worksheets("temp").Cells(n, "B") 'start at row 7?
n = n + 1 'update so that we don't overwrite next time
End If
Next
End With
End Sub
I just populated a table via an algorithm with data from an Autodesk file. A good many of the fields are empty, and, in fact, a number of columns are empty but for a few rows. There are roughly 1300 columns, and I'm trying to trim that down.
Can anyone help me out with the VBA to remove a column if there are more than 1000 rows where the column is empty? I don't really know any VBA, so I don't know where to begin...
I'm trying something like this:
Sub ClearColumns()
'
' ClearColumns Macro
' Check to see if more than 1000 rows of a column are empty. If so, delete the column.
Dim iColumn As Long
Dim iRow As Long
Dim iColumnMax As Long
Dim iRowMax As Long
Dim iEmptyCount
iColumnMax = 13000
iRowMax = 20000
For iColumn = 1 To iColumnMax
For iRow = 1 To iRowMax
If (Cells(iRow, iColumn) = "") Then iEmptyCount.Add (1)
End If
If (iEmptyCount > 999) Then Columns(iColumn).Delete
Step 1
End If
Step 1
Application.Goto Reference:="ClearColumns"
End Sub
But again, I don't know what I'm doing. Any help/feedback is appreciated
Sub DeleteColumns()
Dim ColNumber as Long
For ColNumber = 1300 to 1 Step -1
If WorksheetFunction.CountBlank(Cells(1, ColNumber).Resize(20000,1)) > 1000 Then
Cells(1,ColNumber).EntireColumn.Delete
End If
Next
End Sub
Here is a code that removes a completely empty column.
The last column is allocated on the first line, that is, the stub cell in the first line must be filled with any sign.
Sub DeleteColumns()
Dim i As Long, lc As Long, lr As Long
lc = Cells(1, Columns.Count).End(xlToLeft).Column
For i = 1 To lc
lr = Cells(Rows.Count, i).End(xlUp).Row
If IsEmpty(Cells(lr, i)) = True Then
Cells(lr, i).EntireColumn.Delete
i = i - 1
lc = Cells(1, Columns.Count).End(xlToLeft).Column
If lc = i Then Exit For
End If
Next i
End Sub
This is credited to #Jeeped from a 2015 solution. It will remove all empty cells in your used range.
With Worksheets("name of your worksheet")
.UsedRange.Cells.SpecialCells(xlCellTypeBlanks).Delete Shift:=xlUp
End With
First post here so bear with me. It's possible something similar to what I am going to ask has been posted but my technical illiteracy might have prevented me from finding it.
I have a column of data ~45,000 cells.
Within these cells lie descending data of individuals identified by an ID#, followed by anywhere from 1-8 additional cells with criteria relevant to the preceding ID#.
What I'm trying to do it convert this large column to a row for each of the ~5,500 IDs.
Here is an example of what I'm trying to achieve
I come from a beginner level SAS background and have only used Excel previously in a very brief manner, and have been trying to figure this out off and on for a week or two now. I've started transposing them manually but that is going to take forever and I hope there's an easier way.
My best guess would be, from what I've seen so far, that a VBA code could be written, but I don't know where to start with that. I'm also open to any other ideas on how to achieve the result I'm trying to get.
Thanks in advance!
Sub TransposeData()
Dim Data, TData
Dim x As Long, x1 As Long, y As Long
With Worksheets("Sheet1")
Data = .Range("A1", .Range("A" & .Rows.Count).End(xlUp))
End With
ReDim TData(1 To UBound(Data, 1), 1 To 8)
For x = 1 To UBound(Data, 1)
'If the Data macthes the ID pattern (7 Digits) then
'increment the TData Row Counter
If Data(x, 1) Like "#######" Then
x1 = x1 + 1
y = 0
End If
'Increment the TData Column Counter
y = y + 1
TData(x1, y) = Data(x, 1)
Next
With Worksheets("Sheet2")
With .Range("A" & .Rows.Count).End(xlUp)
If .Value = "" Then 'If there is no data, start on row 1
.Resize(x1, 8).Value = TData 'Resize the range to fit the used elements in TData
Else ' Start on the next empty row
.Offset(1).Resize(x1, 8).Value = TData
End If
End With
End With
End Sub
If I correctly understand your problem the following code should solve it;
Sub ColToRow()
Dim inCol As Range
Set inCol = Application.InputBox(Prompt:="Please Select Range", Title:="Range Select", Type:=8) 'Get the input column as a range
Dim outCol As Range
Set outCol = inCol.Offset(0, 2) 'Set the output column as a range
Dim index As Long 'Current row
Dim cell As Range 'Current cell
Dim lastRow As Long 'The last row
Dim currRow As Long 'Current output row
Dim currCol As Long 'Current output column
lastRow = inCol.SpecialCells(xlCellTypeLastCell).Row
currRow = inCol.Row - 1
currCol = 0
For index = inCol.Row To lastRow
Set cell = ActiveSheet.Cells(index, inCol.Column) 'Set the cell range to the current cell
If Application.WorksheetFunction.IsNumber(cell) And Len(cell.Value) = 7 Then 'If numeric then we assume it is the ID, else we assume it is the
currRow = currRow + 1 'Advance to next output row
currCol = 0 'Reset column offset
cell.Copy Destination:=ActiveSheet.Cells(currRow, outCol.Column + currCol) 'Copy ID
ElseIf currRow > 0 Then 'Ensure we are within the row bounds and not at 0 or below
currCol = currCol + 1 'Advance the column
cell.Copy Destination:=ActiveSheet.Cells(currRow, outCol.Column + currCol) 'Copy Text Values until we get the next numeric value
End If
Next index 'Advance the row
End Sub
The code simply goes (in order) down the column and does the following;
- If the cell has a numeric value then we assume it is the ID and create a new row.
- If the cell has a text value we just add it to the next column in the current row, it'll continue to do this with however many string values until a new ID is reached.
Hope it helps.
-Regards
Mark
Another possible solution, based on ID being 7 digits numbers and all other numbers being not
Option Explicit
Sub main()
Dim area As Range
Dim iArea As Long
With ThisWorkbook.Worksheets("Transpose") '<--| reference relevant worksheet (change "Transpose" to your actual sheet name)
With .Range("A1", .Cells(.Rows.COUNT, 1).End(xlUp).Offset(1))
.Cells(.Rows.COUNT, 1).Value = 1111111 '<--| add a "dummy" ID to end data
.AutoFilter Field:=1, Criteria1:=">=1000000", Operator:=xlAnd, Criteria2:="<=9999999" '<--| filter its "JobCol_Master" named range on textbox ID
.Cells(.Rows.COUNT, 1).ClearContents '<--| remove "dummy" ID
With .SpecialCells(xlCellTypeVisible)
.Parent.AutoFilterMode = False
For iArea = 1 To .Areas.COUNT - 1
Set area = .Parent.Range(.Areas(iArea), .Areas(iArea + 1).Offset(-1))
.Parent.Cells(.Parent.Cells.Rows.COUNT, 3).End(xlUp).Offset(1).Resize(, area.Rows.COUNT).Value = Application.Transpose(area.Value)
Next iArea
End With
End With
End With
End Sub
I need some help with this macro. I have a workbook that is formatted pretty poorly, but consistently every time I open it. Among other things, the goal is to find the non-blank cells in column B and delete the entire 2 rows below and the 1st row above each of those populated B cells.
The first loop I have in the code works just the way I want it to, but the second loop seems to only work on the 1st instance of a populated B cell, but then it deletes everything else above it, like 500 cells worth of data.
Can someone explain to me why this is happening, and if you could find a way to combine both of those for loops into 1, that would be nice too.
Sub test()
Dim currentSht As Worksheet
Dim startCell As Range
Dim lastRow As Long, lastCol As Long
Dim colNames As Variant
Dim i As Integer, j As Integer
Set currentSht = ActiveWorkbook.Sheets(1)
Set startCell = currentSht.Range("A1")
lastRow = startCell.SpecialCells(xlCellTypeLastCell).Row
lastCol = startCell.SpecialCells(xlCellTypeLastCell).Column
For i = lastRow To 1 Step -1
If currentSht.Cells(i, "B").Value <> "" Then
currentSht.Cells(i, "B").Offset(1).EntireRow.Delete
End If
Next i
Range("D3").Select
Range(Selection, Selection.End(xlToRight)).Select
Selection.Delete Shift:=xlUp
currentSht.Rows("1:1").EntireRow.Delete
currentSht.Range("c:d, f:g, i:k").EntireColumn.Delete
currentSht.Range("A:D").Columns.AutoFit
For j = lastRow To 2 Step -1
If currentSht.Cells(j, "B").Value <> "" Then
currentSht.Range(Cells(j, "B").Offset(-1), Cells(j, "B").Offset(-3)).EntireRow.Delete
End If
Next j
End Sub
Thank you
The second loop deletes everything because upon deletion of the lines above the found value, said value gets moved up and will be found again, triggering another deletion. To fix this, the quickest way would be to skip the next two lines by modifying j:
For j = lastRow To 2 Step -1
If currentSht.Cells(j, "B").Value <> "" Then
currentSht.Range(Cells(j, "B").Offset(-1), Cells(j, "B").Offset(-3)).EntireRow.Delete
j = j - 2
End If
Next j
It really doesn't matter much if you are looping from top to bottom or vice versa. The only difference would be if there are two entries in column B near each other. In that case, the search order would determine which one is deleted. But is deletion really what you want? Maybe you could .Clear the contents of the rows instead of deleting them.
edit: here's the new code a bit cleaned up
Sub test()
Dim currentSht As Worksheet
Dim startCell As Range
Dim lastRow As Long, lastCol As Long
Dim colNames As Variant
Dim i As Integer, j As Integer
Set currentSht = ActiveWorkbook.Sheets(1)
Set startCell = currentSht.Range("A1")
lastRow = startCell.SpecialCells(xlCellTypeLastCell).Row
lastCol = startCell.SpecialCells(xlCellTypeLastCell).Column
For i = lastRow To 1 Step -1
If currentSht.Cells(i, "B").value <> "" Then
'reference the row directly
currentSht.Rows(i + 1).Delete
End If
Next i
'Do not use selection if you can avoid it
Range("D3", Range("D3").End(xlToRight)).Delete Shift:=xlUp
currentSht.Rows(1).Delete
currentSht.Range("C:D, F:G, I:K").Delete
currentSht.Range("A:D").Columns.AutoFit
For j = lastRow To 2 Step -1
If currentSht.Cells(j, "B").value <> "" Then
currentSht.Rows(j - 1).Delete
currentSht.Rows(j - 2).Delete
j = j - 2
End If
Next j
End Sub
If you want to combine the loops the behavior of the macro will change because of the deletions that happen between the loops.
I am running this vba code in Excel, it copies a columns from sheet 1, pastes it into sheet two. It then compares it to a column in sheet two before deleting any duplicates.
Private Sub CommandButton1_Click()
Dim MasterList As New Dictionary
Dim iListCount As Integer
Dim x As Variant
Dim iCtr As Integer
Dim v As Variant
Dim counter As Integer, i As Integer
counter = 0
Sheets("Sheet2").Select
Sheets("Sheet2").Range("M:M").Select
Selection.ClearContents
Sheets("Sheet1").Select
Sheets("Sheet1").Range("C:C").Select
Selection.Copy
Sheets("Sheet2").Select
Sheets("Sheet2").Range("M1").Select
ActiveSheet.Paste
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
' Get count of records in master list
iListCount = Sheets("sheet2").Cells(Rows.Count, "A").End(xlUp).Row
'Load Dictionary:
For iCtr = 1 To iListCount
v = Sheets("sheet2").Cells(iCtr, "A").value
If Not MasterList.Exists(v) Then MasterList.Add v, ""
Next iCtr
'Get count of records in list to be deleted
iListCount = Sheets("sheet2").Cells(Rows.Count, "M").End(xlUp).Row
'Loop through the "delete" list.
For iCtr = iListCount To 1 Step -1
If MasterList.Exists(Sheets("Sheet2").Cells(iCtr, "M").value) Then
Sheets("Sheet2").Cells(iCtr, "M").Delete shift:=xlUp
End If
Next iCtr
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
MsgBox "Done!"
End Sub
There is just under 30,000 rows that it has to compare so I know that it is always going to take some time, but I was wondering if there was any way to speed it up or even just make my code more streamline and efficient.
Don't copy and paste from sheet 1 to sheet 2. Store the values from both sheets in arrays:
Dim v1 as variant, v2 as variant
v1 = Sheet1.Range("C:C").Value
v2 = Sheet2.Range("A1").Resize(iListCount,1).Value
Then read the values in v1 into a dictionary, loop through the values in v2 and check if each of them exists in the dictionary or not. If they exist, remove the item from the dictionary.
This will make it a bit more efficient
Dim MasterList As New Dictionary
Dim iListCount As Integer
Dim x As Variant
Dim iCtr As Integer
Dim v As Variant
Dim counter As Integer, i As Integer
counter = 0
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
With Sheets("Sheet2")
.Range("M:M").ClearContents
Sheets("Sheet1").Range("C:C").Copy
.Range("M1").Paste
' Get count of records in master list
iListCount = .Cells(Rows.Count, "A").End(xlUp).Row
'Load Dictionary:
For iCtr = 1 To iListCount
v = .Cells(iCtr, "A").Value
If Not MasterList.Exists(v) Then MasterList.Add v, ""
Next iCtr
'Get count of records in list to be deleted
iListCount = .Cells(Rows.Count, "M").End(xlUp).Row
' Loop through the "delete" list.
For iCtr = iListCount To 1 Step -1
If MasterList.Exists(.Cells(iCtr, "M").Value) Then
.Cells(iCtr, "M").Delete shift:=xlUp
End If
Next iCtr
End With
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
MsgBox "Done!"
If you really wanted to make it more effceint I would change below
' Loop through the "delete" list.
For iCtr = iListCount To 1 Step -1
If MasterList.Exists(.Cells(iCtr, "M").Value) Then
.Cells(iCtr, "M").Delete shift:=xlUp
End If
Next iCtr
So that you miss the sheet. e.g. delete them out of the dictionary and then clear the list and then output the dictionary in one line of code. Accessing the sheet is the costly part in terms of CPU use, limit how many times you access the sheet for much much faster code. you could also try to remove the loop for reading entries in and try and do that in one line of code too
Slow parts to consider
.Cells(iCtr, "A").Value
and probably causing most of the time below
.Cells(iCtr, "M").Delete shift:=xlUp
Here is my version of optimized code.
Comments about the concepts used are put in the code.
Private Sub CommandButton1_Click()
Dim MasterList As New Dictionary
Dim data As Variant
Dim dataSize As Long
Dim lastRow As Long
Dim row As Long
Dim value As Variant
Dim comparisonData As Variant
Dim finalResult() As Variant
Dim itemsAdded As Long
'-----------------------------------------------------------------
'First load data from column C of [Sheet1] into array (processing
'data from array is much more faster than processing data
'directly from worksheets).
'Also, there is no point to paste the data to column M of Sheet2 right now
'and then remove some of them. We will first remove unnecessary items
'and then paste the final set of data into column M of [Sheet2].
'It will reduce time because we can skip deleting rows and this operation
'was the most time consuming in your original code.
With Sheets("Sheet1")
lastRow = .Range("C" & .Rows.Count).End(xlUp).row
data = .Range("C1:C" & lastRow)
End With
'We can leave this but we don't gain much with it right now,
'since all the operations will be calculated in VBA memory.
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
'We make the same operation to load data from column A of Sheet2
'into another array - [comparisonData].
'It can seem as wasting time - first load into array instead
'of directly iterating through data, but in fact it will allow us
'to save a lot of time - since iterating through array is much more
'faster than through Excel range.
With Sheets("Sheet2")
lastRow = .Range("A" & .Rows.Count).End(xlUp).row
comparisonData = .Range("A1:A" & lastRow)
End With
'Iterate through all the items in array [comparisonData] and load them
'into dictionary.
For row = LBound(comparisonData, 1) To UBound(comparisonData, 1)
value = comparisonData(row, 1)
If Not MasterList.Exists(value) Then
Call MasterList.Add(value, "")
End If
Next row
'Change the size of [finalResult] array to make the place for all items
'assuming no data will be removed. It will save some time because we
'won't need to redim array with each iteration.
'Some items of this array will remain empty, but it doesn't matter
'since we only want to paste it into worksheet.
'We create 2-dimensional array to avoid transposing later and save
'even some more time.
dataSize = UBound(data, 1) - LBound(data, 1)
ReDim finalResult(1 To dataSize, 1 To 1)
'Now iterate through all the items in array [data] and compare them
'to dictionary [MasterList]. All the items that are found in
'[MasterDict] are added to finalResult array.
For row = LBound(data, 1) To UBound(data, 1)
value = data(row, 1)
If MasterList.Exists(value) Then
itemsAdded = itemsAdded + 1
finalResult(itemsAdded, 1) = value
End If
Next row
'Now the finalResult array is ready and we can print it into worksheet:
Dim rng As Range
With Sheets("Sheet2")
Call .Range("M:M").ClearContents
.Range("M1").Resize(dataSize, 1) = finalResult
End With
'Restore previous settings.
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
MsgBox "Done!"
End Sub