Shifting data down in a table with both forward and reverse loops - excel

Please see an abstracted example in the image below
Warning! I'm very new to VBA and still learning, so there might be some obvious mistakes in my code.
I have a very large table of data containing several rows and columns. The objective is to loop through a column containing a bunch of IDs and detect duplicates in a specific segment of the string. As soon as there is a mismatch in this segment, the row and new value is stored before a reverse loop begins that shifts everything below down the last duplicate down by four spaces.
The result is three blank rows after all duplicates (see image).
There's a few conditions that I have to meet for this code to be compatible with the software that secures this sheet:
Inserting whole rows needs to be avoided, insert and shift down is okay
Avoiding select is ideal
No application enable/disable can be used
The fewer individual cell changes the better
The idea is to loop through each of the columns to shift all corresponding values in that row down once I have perfected the first column. It would be great to avoid having to do so if there's a way to shift the whole range down instead of individual cells.
The second, reverse loop seems to be the problem.
I've tried several ways of looping using integer loops, range for loops, do while, and do until.
Please let me know if you need clarification! Thank you so much for your help.
Sub shiftValues()
Dim ws1 As Worksheet
Set ws1 = Worksheets("Tab1=Raw Data")
Dim lastRow As Variant
lastRow = ws1.Range("A" & ws1.Rows.count).End(xlUp).Row
Dim cell As Range
Dim rng As Range
Set rng = ws1.Range("A16:A" & lastRow)
Dim oldString As String
Dim newString As String
newString = "newString"
Dim oldRow As Integer
oldRow = 15 'Start of table
Dim beforeEqual() As String
beforeEqual = Split(ws1.Range("A15").Value, "=")
Dim tar As Long
For Each cell In rng
oldString = Right(beforeEqual(0), 2)
If cell.Value <> vbNullString And Len(cell.Value) > 6 Then
beforeEqual = Split(cell.Value, "=")
newString = Right(beforeEqual(0), 2)
If newString <> oldString And cell.Row > 15 Then
oldString = newString
oldRow = cell.Row
tar = lastRow
Do Until tar = oldRow
Range("A" & tar + 4).Value = Range("A" & tar).Value
Range("A" & tar).ClearContents
tar = tar - 1
Loop
End If
End If
Next cell
End Sub

This may do what you want:
j = 0
For i = Range("A" & Rows.Count).End(xlUp).Row To 3 Step -1
If Range("A" & i).Value <> Range("A" & i - 1).Value And Range("A" & i - 1).Value <> Range("A" & i - 2).Value Then
If j = 0 Then
j = i
Else
End If
Else
If j > 0 Then
Range("A" & i & ":A" & i + 2).Insert Shift:=xlDown
j = 0
End If
End If

Related

VBA Looping cells and Copy based on criteria

[Copy A2 to E2 till the end of row of the table and check if the cell is within the same month](https://i.stack.imgur.com/Q7YAx.png)
Hi,
I would like to loop through rows from a sheet table from column A2 to E2 to A3 to E3... till the end of the table Ai to Ei by defining a variable and counting the last row of the table.
As the second step, I would like to copy the cells into another sheet and fill it the corresponding months.
[Desired Output--> it will copy the data and return to another sheet in the corresponding month] (https://i.stack.imgur.com/zhgYh.png)
Instead, I've changed the data type into a number format and have set up two condition to loop through.
eg. 1/1/2017 change to 42736
28/2/2017 change to 42794
Sub Mike_Copy_cell()
Dim i As Long 'for looping inside each cell
Dim myvalue As Variant
Dim Lastrow As Long
Const StartRow As Byte = 2
Dim LastMonth As Long
("Mike Filter").Select
Lastrow = Range("A" & StartRow).End(xlDown).Row
For i = StartRow To Lastrow
myvalue = Range("H" & i).Value
If myvalue \< Sheets("Automate Report").Range("A" & i).Value \_
'First data Feb Data 42794 \< Jan Category 42736
Then Sheets("Automate Report").Range("B" & i).Value = ""
'leave the cells in blanks and loop through next cell
If myvalue > Sheets("Automate Report").Range("A" & i).Value _
'First data Feb Data 42794 > Jan Category 42736
Then Range("A" & i, "E" & i).Copy Sheets("Automate Report").Range("B" & i, "F" & i)
'Copy the cells into corresponding category
Next i
End sub()
In my output, it is able to loop through and copy all the cells. However, I am wondering the reason why VBA output is not able leave any blank cells when the first condition is met ?
**I am expecting some blanks in the table if it is not data is not within the same month or in my case is less than criteria I have set. **
The output of my code
If myvalue < Sheets("Automate Report").Range("A" & i).Value _
Then Sheets("Automate Report").Range("B" & i).Value = ""
Greatly appreciate if you can advise the flaws in my code. Massive Thanks.
Best regards,
Kenneth
I'll try to help. But before, may I give you two suggestions that might help you?
First, for me the best way to find the last row is, instead of using xldown from the first row, using xlup from the very last row of excel. This way, if there is a blank in any middle row, the code still gives you the last row with value.
Second, I found that referring to any cells with the "range" method may limit you sometimes when using variables in this reference. I think using the "cells(row, column)" method is more useful.
Why not trying this?
Lastrow = Cells(Rows.Count, 1).End(xlUp).Row
Sorry for the suggestions, It's just that I wish someone had taught them to me sooner.
Back to the topic, I think the problem is how you structure the "if" statement. Allow me to change it a bit:
Lastrow = Cells(Rows.Count, 1).End(xlUp).Row
For i = StartRow To Lastrow
myvalue = cells(i, 8).Value
'if myvalue date is equal or previous to the one found in Ai...
If myvalue <= Sheets("Automate Report").cells(i, 1).Value then
Sheets("Automate Report").cells(i, 2).Value = ""
'but if myvalue is later than Ai...
else
sheets("Automate Report").select
range(cells(i, 1), cells(i, 5).select
selection.copy
cells(i, 2).select
activesheet.paste
end if
Next i
Hope this helps. Best regards,
Mike
I'm not sure what your code is doing but consider using an array(12) of row numbers, one for each month. Copy lines into corresponding month and increment the row number for that month. For example ;
Option Explicit
Sub Mike_Copy_cell()
Const LINES_MTH = 5 ' lines per month
Dim wb As Workbook
Dim wsIn As Worksheet, wsOut As Worksheet
Dim lastrow As Long, rIn As Long, rOut(12) As Long
Dim uid As String, prevuid As String
Dim dAVD As Date, m As Long, n As Long
Set wb = ThisWorkbook
Set wsIn = wb.Sheets("Mike Filter")
Set wsOut = wb.Sheets("Automate Report")
' space out months
For n = 0 To 11
rOut(n + 1) = 2 + n * LINES_MTH
wsOut.Cells(rOut(n + 1), "A").Value2 = MonthName(n + 1)
Next
n = 0
With wsIn
lastrow = .Cells(.Rows.Count, "A").End(xlUp).Row
For rIn = 2 To lastrow
dAVD = .Cells(rIn, "D")
' create a unique ID to skip duplicates
uid = .Cells(rIn, "A") & Format(.Cells(rIn, "D"), "YYYY-MM-DD")
If uid <> prevuid Then
m = Month(dAVD)
.Cells(rIn, "A").Resize(, 5).Copy wsOut.Cells(rOut(m), "B")
rOut(m) = rOut(m) + 1
n = n + 1
End If
prevuid = uid
Next
End With
MsgBox n & " lines copied to " & wsOut.Name, vbInformation
End Sub

Using a cell in a loop to define a range in vba. I want to basically delete the row of that cell and the next 3 ones

I'm basically writing a clean up program to make it more straight forward to access data. Anywho, I ran into possibly a nomenclature error. I want to use the "current" cell in a "for" loop to delete that row and the next 3 rows. Code looks something like this:
For Each SingleCell In SingleSheet1.Range("a1:a40")
If SingleCell.Value = "S" Or SingleCell.Value = "B" Then
Range(SingleCell.Range, SingleCell.Range.Offset(4, 0)).EntireRow.Delete Shift:=xlUp
Else
End If
Next
I tried to define the range to delete as specified in the code but it gave me a runtime error
Delete backwards looping trough row number:
Sub EXAMPLE_1()
Dim i As Long
For i = 40 To 1 Step 1
If Range("A" & i).Value = "S" Or Range("A" & i).Value = "B" Then Range("A" & i & ":A" & i + 3).EntireRow.Delete Shift:=xlUp
Next i
End Sub
Sub EXAMPLE_2()
Dim i As Long
Dim LR As Long 'in case last row is not always number 40, adapt it dinamically
LR = Range("A" & Rows.Count).End(xlUp).Row
For i = LR To 1 Step 1
If Range("A" & i).Value = "S" Or Range("A" & i).Value = "B" Then Range("A" & i & ":A" & i + 3).EntireRow.Delete Shift:=xlUp
Next i
End Sub
Your code looses the reference for the deleted rows and you should iterate backwards, if you like iteration between cells (which is slow), but a better/faster solution will be to build a Union range and delete all rows at the code end, at once:
Sub testDeleteOffset()
Dim sh As Worksheet, Urng As Range, i As Long
Set sh = ActiveSheet
For i = 1 To 40
If sh.Range("A" & i).Value = "S" Or sh.Range("A" & i).Value = "B" Then
addToRange Urng, sh.Range("A" & i, "A" & i + 3)
i = i + 4
End If
Next i
If Not Urng Is Nothing Then Urng.EntireRow.Delete xlUp
End Sub
If the involved range is huge, a better solution will be to place some markers for the necessary rows (after last existing column), sort on that marker column and delete the (consecutive marked) rows. Another column with the initial order would be necessary to re-sort according to it at the end... The idea is that building a Union range having more than 1000 areas may become slow.

VBA Excel - Take range of few columns

I want to read data from columns by for loop. It should starts on B2 and ends on G2. I want to read B2 value as a string, and put another value to G2 cell. I use cellValue variable as Range type, and deviceName to take out this value of cell (cells(1) because first cell in this cellValue range).
However I have got error like this:
"Invalid procedure call or argument"
For Row = 2 To rows
cellValue = ActiveSheet.Cells("B" & CStr(Row) & ":" & "G" & CStr(Row))
deviceName = cellValue.Cells(1)
// my code...
Next Row
Couldn’t find any obvious flaws above, but I changed it up a bit. This should work. I’m using Cells and only numbers for the iteration, rather than casting with Cstr.
Dim ws as worksheet
Dim fRow as Long
Set ws = ThisWorkbook.Worksheets(“Sheet1”)
With ws
fRow = .Cells(.Rows.Count,6).end(xlUp).Row
End with
For row = 2 to fRow
cellValue = ws.Range(Cells(2,row),Cells(6,row)).Value
deviceName = CellValue
Next row
Post an example for a range.
Sub test()
Dim cellValue As Range, vDB As Variant
Dim i As Long, r As Long
Dim deviceName
r = ActiveSheet.UsedRange.Rows.Count 'Set r to suit your situation.
'Row and rows are similar to reserved words in visual basic, so we don't recommend them.
For i = 2 To r
'Because range is an object, you must use the set statement.
Set cellValue = ActiveSheet.Range("B" & CStr(i) & ":" & "G" & CStr(i))
'same code above
Set cellValue = ActiveSheet.Range("B" & i & ":" & "G" & i) '<~~ When it is made by connecting letters and numbers, it becomes a letter.
Set cellValue = ActiveSheet.Range("B" & i, "G" & i) '<~~ The first part of the comma is the first cell in the region, and the last part is the last cell in the region.
Set cellValue = ActiveSheet.Range("B" & i).Resize(1, 6) '<~~ 1 is rows.count, 6 is columns.count ; Change the size of the range to the size of the resize statement.
'If the set statement is not used, it is an array of variants.
vDB = ActiveSheet.Range("B" & i & ":" & "G" & i) 'vDB is 2D array
deviceName = cellValue.Cells(1)
'same above code
deviceName = cellValue.Range("a1")
deviceName = cellValue(1)
deviceName = cellValue.Cells(1).Offset(0, 0)
'If you use arrays, vDB
deviceName = vDB(i, 1)
Next i
End Sub

Find duplicate values within the same row (Excel)

I have a list of addresses and parts of the addresses for some records have been duplicated. e.g. some records contain "London" in both column D and column E.
I want to find and highlight any duplicate values across all columns, but within the same row.
So far I have written the code below, but I want it to work through every column containing values and not just the two columns I have named.
Dim Lastrow As Long
Dim i As Long
Lastrow = Range("D" & Rows.Count).End(xlUp).Row
For i = 2 To Lastrow
If Range("D" & i).Value = Range("E" & i).Value Then
Range("E" & i).Interior.ColorIndex = 6
End If
Next i
I have tried to search for an answer, but I have only been able to find ways of highlighting entire duplicate rows or duplicate values in different columns and rows.
Thank you for taking the time to read this and for any help you can give.
If you really want a VBA Solution this does the trick:
Sub JustCall()
Call DuplicatedInRangeByRow(Range("A1:D5"))
End Sub
Sub DuplicatedInRangeByRow(RangeToLook As Range)
Const ColorHighlight = vbYellow
Dim ItemRange As Range
Dim TotalRows As Long: TotalRows = IIf(RangeToLook.Row > 1, RangeToLook.Rows.Count + RangeToLook.Row - 1, RangeToLook.Rows.Count)
Dim TotalCols As Long: TotalCols = IIf(RangeToLook.Column > 1, RangeToLook.Columns.Count + RangeToLook.Column - 1, RangeToLook.Columns.Count)
Dim CounterCols As Long
Dim CounterRows As Long
Dim StartCol As Long
Dim SheetForRange As Worksheet: Set SheetForRange = RangeToLook.Parent
For CounterRows = RangeToLook.Row To TotalRows
For CounterCols = RangeToLook.Column To TotalCols
StartCol = IIf(StartCol = 0, CounterCols, StartCol)
With SheetForRange
If CStr(.Cells(CounterRows, StartCol).Value) = CStr(.Cells(CounterRows, CounterCols).Value) And StartCol <> CounterCols Then .Cells(CounterRows, StartCol).Interior.Color = ColorHighlight: .Cells(CounterRows, CounterCols).Interior.Color = ColorHighlight
End With
Next CounterCols
StartCol = 0
Application.StatusBar = "Progress: " & CounterRows & " out of " & TotalRows & " Rows analyzed " & Format(CounterRows / TotalRows, "Percent")
Next CounterRows
End Sub
for conditional formatting you would use the following formula:
=COUNTIF($A1:$J1,A1)>1
Where $A1 and A1 refers to the most upper left cell in the range to which the formatting is being applied. And the $J1 is the upper right cell of the range.
Pay close attention to what is absolut and what is relative.

Merge empty cells with previous value

I have an Excel file with around 100,000 records. I have 6+ columns, the first five of which are:
Required Format:
So far I have :
Sub Main()
Dim i As Long
Dim j As Long
Dim sameRows As Boolean
sameRows = True
For i = 1 To Range("A" & Rows.Count).End(xlUp).Row
For j = 1 To 4
If StrComp(Cells(i, j), Cells(i + 1, j), vbTextCompare) Then
sameRows = False
End If
Next j
If sameRows Then
Range(Cells(i, 4), Cells(i + 1, 4)).merge
End If
sameRows = True
Next i
End Sub
I am able to get below by running the macro by changing value in Range from 4 to 1/2/3/4 and running macro four times.
Please help me get the data in required format. I still need to merge the empty fields with the previous non empty field.
Pratik, listen carefully to Jeeped. Working with large data in Excel isn't ideal, and working with raw data in merged cells is staring into the abyss - it's a dark, dark place where Range referencing and things like Offset functions will show you a dimension of despair you never knew existed.
If you have this data in another format, say XML, that you've imported into Excel then use VBA to read the data, query it, etc. in its original format. If it exists in a database, then, again, use VBA to access that database and manipulate the recordsets as you wish. If this is your only source of data, then why not write it into an XML document or into VBA's own data storage options (like Collection or arrays).
If you have to use Excel then don't confuse raw data with data display. Yes, the merged cells might be easier to read for the human eye, but I'd just pose the question: is that your primary objective in conducting the merge?
If you must take that leap into the abyss - and you can see that at least two of us would advise against - then at least speed things up by reading from an array and merging rows at a time:
Sub OpenDoorsToHades()
Dim dataSheet As Worksheet
Dim v As Variant
Dim mergeCells As Range
Dim mergeAreas As Range
Dim i As Long
Dim blankStart As Long
Dim blankEnd As Long
Dim doMerge As Boolean
Dim c As Integer
Set dataSheet = ThisWorkbook.Worksheets("data") 'rename to your sheet
'Read values into array of variants
With dataSheet
v = .Range("A1", .Cells(.Rows.Count, "A").End(xlUp)).Value2
End With
'Check for blanks
For i = 1 To UBound(v, 1)
If IsEmpty(v(i, 1)) Then
If Not doMerge Then
blankStart = i - 1
doMerge = True
End If
Else
If doMerge Then
blankEnd = i - 1
For c = 1 To 4
With dataSheet
Set mergeCells = .Range( _
.Cells(blankStart, c), _
.Cells(blankEnd, c))
If mergeAreas Is Nothing Then
Set mergeAreas = mergeCells
Else
Set mergeAreas = .Range(mergeAreas.Address & _
"," & mergeCells.Address)
End If
End With
Next
mergeAreas.Merge
Set mergeAreas = Nothing
doMerge = False
End If
End If
Next
'Format the sheet
dataSheet.Cells.VerticalAlignment = xlCenter
Application.ScreenUpdating = True
End Sub
How about just populating the empty cells with the values above, so the values on the far right are associated with the same values that would've been in the merged cell. For example, if 19 is in cell A2, you can recreate the table starting in G2 with =IF(A2<>"",A2,G1), and this way all empty cells will be populated with the value above, pairing the values at the far right with the exact same values.
I tackled the same problem myself this week. Ambie's solution seemed overly complex, so I wrote something pretty simple to merge rows:
Sub MergeRows()
Sheets("Sheet1").Select
Dim lngStart As Long
Dim lngEnd As Long
Dim myRow As Long
'Disable popup alerts that appear when merging rows like this
Application.DisplayAlerts = False
lngStart = 2
lngEnd = 2
For myRow = 2 To Range("A" & Rows.Count).End(xlUp).Row 'last row
If Range("A" & (myRow + 1)).value = "" Then
'include row below in next merge
lngEnd = myRow + 1
Else
'merge if 2+ rows are included
If lngEnd - lngStart > 0 Then
Range("A" & lngStart & ":A" & lngEnd).Merge
Range("B" & lngStart & ":B" & lngEnd).Merge
Range("C" & lngStart & ":C" & lngEnd).Merge
Range("D" & lngStart & ":D" & lngEnd).Merge
End If
'reset included rows
lngStart = myRow + 1
lngEnd = myRow + 1
End If
Next myRow
Application.DisplayAlerts = True
End Sub

Resources