Using an Excel macro (VBA) I'm inserting the following formula into a worksheet. Later in the code I paste over the formulas as values.
Firstrow = 2
Lastrow = .Cells(.Rows.Count, "D").End(xlUp).Row
With .Range(.Cells(Firstrow, "A"), .Cells(Lastrow, "A"))
.Formula = "=IF(ISERROR(VLOOKUP(D2,Codes!$A$1:$A$14,1,FALSE))=TRUE,""YES"",""NO"")"
End With
Is there a better way to just have the answers Yes or No entered into the cells in column A. I would like the lookup list (Codes!$A$1:$A$14) to be inside of the macro instead of in one of the worksheets. Thanks in advance for any help you might be able to send my way! Jordan.
Fill the values array in with the appropriate values from Codes!$A$1:$A$14.
Code without comments
Sub UpdateLookups()
Dim data, values As Variant
Dim Target As Range
Dim x As Long
values = Array("Tom", "Henry", "Frank", "Richard", "Rodger", "ect...")
With Worksheets("Sheet1")
Set Target = .Range("D2", .Range("D" & .Rows.Count).End(xlUp))
End With
data = Target.Value
For x = 1 To UBound(data, 1)
data(x, 1) = IIf(IsError(Application.Match(data(x, 1), values, 0)), "YES", "NO")
Next
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Target.Offset(0, -3).Value = data
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = False
End Sub
Code with comments
Sub UpdateLookups()
Dim data, values As Variant
Dim Target As Range
Dim x As Long
'values: Array of values that will be searched
values = Array("Tom", "Henry", "Frank", "Richard", "Rodger", "ect...")
'With Worksheets allows use to easily 'qualify' ranges
'The term fully qualified means that there is no ambiguity about the reference
'For instance this referenece Range("A1") changes depending on the ActiveSheet
'Worksheet("Sheet1").Range("A1") is considered a qualified reference.
'Of course Workbooks("Book1.xlsm").Worksheet("Sheet1").Range("A1") is fully qualified but it is usually overkill
With Worksheets("Sheet1")
'Sets a refernce to a Range that starts at "D2" extends to the last used cell in Column D
Set Target = .Range("D2", .Range("D" & .Rows.Count).End(xlUp))
End With
' Assigns the values of the Target Cells to an array
data = Target.Value
'Iterate over each value of the array changing it's value based on our formula
For x = 1 To UBound(data, 1)
data(x, 1) = IIf(IsError(Application.Match(data(x, 1), values, 0)), "YES", "NO")
Next
Application.ScreenUpdating = False 'Speeds up write operations (value assignments) and formatting
Application.Calculation = xlCalculationManual 'Speeds up write operations (value assignments)
'Here we assign the data array back to the Worksheet
'But we assign them 3 Columns to the left of the original Target Range
Target.Offset(0, -3).Value = data
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = False
'Loading the data into an Array allows us to write the data back to the worksheet in one operation
'So if there was 100K cells in the Target range we would have
'reduced the number of write operations from 100K to 1
End Sub
Untested as no sample data but it would look something like this:
Firstrow = 2
Lastrow = .Cells(.Rows.Count, "D").End(xlUp).Row
With .Range(.Cells(Firstrow, "A"), .Cells(Lastrow, "A"))
If IsError(Application.WorksheetFunction.VLookup(ThisWorkbook.Sheet(1).Range("D2"), ThisWorkbook.Sheet(Codes).Range("$A$1:$A$14"), 1, False)) Then
.Value2 = "YES"
Else
.Value2 = "NO"
End If
End With
Please note that I have not scoped your range D2 properly as I don't know the structure of your Workbook or what the worksheet name is. Please adapt to your needs. Cheers,
an Autofilter() approach, with no loops
Option Explicit
Sub main()
Dim arr As Variant
arr = Array("a", "b", "c") '<--| set your lookup list
With Worksheets("MyData") '<--| change "MyData" to your actual worksheet with data name
With .Range("D2", .Cells(.Rows.Count, "D").End(xlUp)) '<--| reference its column "D" cells from row 2 down to last not empty one
.Offset(, -3).Value = "YES" '<--| write "YES" in all corresponding cells in column "A" ("NO"s will be written after subsequent filtering)
.AutoFilter field:=1, Criteria1:=arr, Operator:=xlFilterValues '<--| filter referenced cells with lookup list
If Application.WorksheetFunction.Subtotal(103, .Cells) > 1 Then .SpecialCells(xlCellTypeVisible).Offset(, -3).Value = "NO" '<--| if any filtered cell then write "NO" in their corresponding column "A" ones
End With
.AutoFilterMode = False
End With
End Sub
Related
I am aiming to lock entire rows where the word "Done" appears in a specific column. My code below achieves what I seek but it takes 18 seconds to compute (too long). Is there a faster/more efficient coding alternative?
There is an existing question on StackOverflow similar to this (found here) but my data does not exist in defined tables (this won't change), so I don't know how to adapt the suggestion there.
Private Sub Lock_Rows(ByVal Target As Range)
Dim DestSh As Worksheet
Dim lastrow As Long
Dim i As Long ' Integer
Set DestSh = Worksheets(8) 'Data tab for Item 1
With DestSh
'finds the last row with data on B column, B column has dates
lastrow = .Range("B" & .Rows.Count).End(xlUp).Row
'parse all rows
For i = 26 To lastrow 'rows of data begin at row 26
'if your conditions are met
If .Cells(i, "Z").Value = "Done" Then
.Cells(i, "Z").EntireRow.Cells.Locked = True 'lock the row
Else
.Cells(i, "Z").EntireRow.Cells.Locked = False 'leave rows unlocked
End If
Next i
.Protect UserInterfaceOnly:=True
End With
Set DestSh = Nothing
End Sub
The Lock / Unlock operations om individual rows are quite slow. Better to build a range reference to Lock / Unlock and do that operation in on go at the end.
Something like
Private Sub Lock_Rows(ByVal Target As Range)
Dim DestSh As Worksheet
Dim lastrow As Long
Dim i As Long ' Integer
Dim rLock As Range, rUnlock As Range
Set DestSh = Worksheets(8) 'Data tab for Item 1
With DestSh
'finds the last row with data on B column, B column has dates
lastrow = .Range("B" & .Rows.Count).End(xlUp).Row
'parse all rows
For i = 26 To lastrow 'rows of data begin at row 26
'if your conditions are met
If .Cells(i, "Z").Value = "Done" Then
If rLock Is Nothing Then
Set rLock = .Cells(i, "Z").EntireRow
Else
Set rLock = Application.Union(rLock, .Cells(i, "Z").EntireRow)
End If
Else
If rUnlock Is Nothing Then
Set rUnlock = .Cells(i, "Z").EntireRow
Else
Set rUnlock = Application.Union(rUnlock, .Cells(i, "Z").EntireRow)
End If
End If
Next i
If Not rLock Is Nothing Then rLock.Locked = True
If Not rUnlock Is Nothing Then rUnlock.Locked = False
.Protect UserInterfaceOnly:=True
End With
Set DestSh = Nothing
End Sub
It will be faster still you could incorporate use of Variant Arrays on the loop
On my hardware it takes about 6 s to process 500,000 rows
Try with this solution which seems to be much faster than original one:
Private Sub Lock_Rows_new(ByVal Target As Range)
Debug.Print "s:" & Timer
Dim DestSh As Worksheet
Dim lastrow As Long
Dim i As Long ' Integer
Set DestSh = Worksheets(8) 'Data tab for Item 1
With DestSh
'range to search
Dim firstRNGRow As Variant '!! important
firstRNGRow = 26
Dim firstRNG As Range
Set firstRNG = .Cells(firstRNGRow, "Z")
Dim lastRNG As Range
Set lastRNG = .Cells(.Range("B" & .Rows.Count).End(xlUp).Row, "Z")
'unlock all
Range(firstRNG, lastRNG).EntireRow.Cells.Locked = False
'search for first done
firstRNGRow = Application.Match("Done", Range(firstRNG, lastRNG), 0)
Do While (Not IsError(firstRNGRow))
Set firstRNG = .Cells(firstRNG.Row + firstRNGRow, "Z")
firstRNG.Offset(-1, 0).EntireRow.Cells.Locked = True 'lock the row
If firstRNG.Row > lastRNG.Row Then Exit Do
firstRNGRow = Application.Match("Done", Range(firstRNG, lastRNG), 0)
Loop
.Protect UserInterfaceOnly:=True
End With
Set DestSh = Nothing
Debug.Print "e:" & Timer
End Sub
Edited to add a faster solution combining Sort() and AutoFilter()
AutoFilter() can make things fast:
Private Sub Lock_Rows(ByVal Target As Range)
With Worksheets(8)
If IsEmpty(.Range("Z25")) Then .Range("Z25").Value = "xxx" ' be sure you have a column "header" for data in column Z from row 26 downwards
With .Range("Z25:Z" & .Cells(.Rows.Count, "B").End(xlUp).Row)
.EntireRow.Locked = False ' unlock all cells
.AutoFilter field:=1, Criteria1:="Done"
With Intersect(ActiveSheet.UsedRange, .EntireColumn).Resize(.Rows.Count - 1, .Columns.Count).Offset(1, 0)
If CBool(Application.Subtotal(103, .Cells)) Then .SpecialCells(xlCellTypeVisible).EntireRow.Locked = True ' lock only filtered range rows
End With
End With
If .Range("Z25").Value = "xxx" Then .Range("Z25").ClearContents ' remove any "not original" column header
End With
End Sub
if you know that cell Z25 content is always not empty then you can omit the lines:
If IsEmpty(.Range("Z25")) Then .Range("Z25").Value = "xxx"
If .Range("Z25").Value = "xxx" Then .Range("Z25").ClearContents
and if you Sort things, it's even faster:
Option Explicit
Private Sub Lock_Rows(ByVal Target As Range)
Dim dataRange As Range, sortRange As Range, lockRange As Range
With Worksheets("8") ' reference wanted sheet
Set dataRange = .Range("Z25", .Cells(.Rows.Count, "B").End(xlUp))
Set lockRange = Intersect(.Columns("Z"), dataRange)
Set sortRange = Intersect(dataRange.EntireRow, .UsedRange.Columns(.UsedRange.Columns.Count + 1)) ' reference the range in same rows as referenced one but in first "not used" column
Set dataRange = .Range(dataRange, sortRange)
End With
With sortRange
.Formula = "=ROW()" ' write rows indexes in referenced range. this will be used to sort things back
.Value = .Value ' get rid of formulas
End With
dataRange.Sort key1:=lockRange.Resize(1), order1:=xlAscending, Header:=xlYes ' sort data on columns with possible "Done" values
If IsEmpty(lockRange(1, 1)) Then lockRange(1, 1).Value = "xxx" ' remove any "not original" column header
With dataRange ' reference referenced sheet column B range in
.AutoFilter field:=lockRange.Column - Columns(1).Column, Criteria1:="Done"
With .Resize(.Rows.Count - 1, .Columns.Count).Offset(1, 0)
If CBool(Application.Subtotal(103, .Cells)) Then .SpecialCells(xlCellTypeVisible).EntireRow.Locked = True ' lock only filtered range rows
End With
.Parent.AutoFilterMode = False
.Sort key1:=sortRange.Resize(1), order1:=xlAscending, Header:=xlYes ' sort things back
sortRange.ClearContents ' delete rows index, not needed anymore
End With
If lockRange(1, 1).Value = "xxx" Then lockRange(1, 1).ClearContents ' remove any "not original" column header
End Sub
again, if you know that cell Z25 content is always not empty then you can omit the lines:
If IsEmpty(lockRange(1, 1)) Then lockRange(1, 1).Value = "xxx" ' remove any "not original" column header
If lockRange(1, 1).Value = "xxx" Then lockRange(1, 1).ClearContents ' remove any "not original" column header
I've been working on a spreadsheet to help with reporting and I'm stumped on the final element. Essentially, if column G of a worksheet contains a certain text string, I want to copy the appropriate row to another worksheet under the existing data in that sheet.
After two hours of googling I've tried various solutions but haven't been able to configure one to do what I want it to. Currently I'm working with the below:
Dim x As Integer
Dim Thisvalue As String
Dim NextRow As Range
Sheets("Week 4").Select
' Find the last row of data
FinalRow = Cells(Rows.Count, 1).End(xlUp).Row
' Loop through each row
For x = 2 To FinalRow
' Decide if to copy based on column D
Thisvalue = Cells(x, 7).Value
If Thisvalue = "Customer placed on hold" Then
Cells(x, 1).Resize(1, 33).Copy
Sheets("Retained data").Select
NextRow = Cells(Rows.Count, 1).End(xlUp).Row + 1
Cells(NextRow, 1).Select
ActiveSheet.Paste
Sheets("Week 4").Select
End If
Next x
End Sub
However, I think I'm on the wrong track and in all honesty I've forgotten so much about VBA that I'm essentially starting from scratch again as far as my knowledge goes. Any help would be greatly appreciated!
The code below will loop throug all cells in Column G (until FinalRow), and check for value "Customer placed on hold". When it finds, it copies the entire row to the next avaialble row at "Retained data" worksheet.
Note: it's better to avoid using Select and ActiveSheet as they might change according to your current ActiveSheet. Instead it's better to use referenced Sheet objects, .Cells and Ranges.
Code
Option Explicit
Sub CopyRow()
Dim x As Long
Dim Thisvalue As String
Dim NextRow As Long
Dim FinalRow As Long
With Sheets("Week 4")
' Find the last row of data in Column A
FinalRow = .Cells(.Rows.Count, 1).End(xlUp).Row
' Loop through each row
For x = 2 To FinalRow
' Decide if to copy based on column G
If .Cells(x, 7).Value = "Customer placed on hold" Then
' Find the last row of data
NextRow = Sheets("Retained data").Cells(Sheets("Retained data").Rows.Count, 1).End(xlUp).Row
' copy > paste in 1 line
.Cells(x, 7).EntireRow.Copy Sheets("Retained data").Range("A" & NextRow + 1)
End If
Next x
End With
End Sub
Try this one:
Sub Makro2()
Dim x As Integer
Dim Thisvalue As String
Sheets("Week 4").Select
' Find the last row of data
FinalRow = Cells(Rows.Count, 1).End(xlUp).Row
' Loop through each row
For x = 2 To FinalRow
' Decide if to copy based on column D
Thisvalue = Cells(x, 7).Value
If Thisvalue = "Customer placed on hold" Then
Range(Cells(x, 1), Cells(x, 33)).Copy
With Sheets("Retained data")
.Cells(.Cells(Rows.Count, 1).End(xlUp).Row + 1, 1).PasteSpecial xlPasteAll
End With
End If
Next x
End Sub
since you want to check column "G" values against a string ("Customer placed on hold") then you want to avoid looping through column "A" cells and loop through "string" cells of columns "G" only
then you can avoid looping through all cells and just Find() the wanted ones:
Sub CopyRow()
Dim firstAddress As String
Dim f As Range
With Worksheets("Week 4") '<--| reference your relevant worksheet
With .Range("G2", .Cells(.Rows.COUNT, "G").End(xlUp)).SpecialCells(xlCellTypeConstants, xlTextValues) '<--| loop through its column G "string" values only
Set f = .Find(what:="Customer placed on hold", lookat:=xlWhole, LookIn:=xlValues, after:=.Areas(.Areas.COUNT).Cells(.Areas(.Areas.COUNT).COUNT)) '<--| search for wanted string in referenced range, starting from the cell after its last cell (i.e.: the first cell)
If Not f Is Nothing Then '<--| if found
firstAddress = f.Address '<--| store its address to stop 'Find()' loop at its wrapping back to the first found cell
Do
With Worksheets("Retained data") '<--| reference target sheet
f.EntireRow.Copy .Cells(.Rows.COUNT, 1).End(xlUp).Offset(1) '<--| copy found cell entire row into last referenced worksheet first not empty cell
End With
Set f = .FindNext(f) '<--| find next cell matching wanted value
Loop While f.Address <> firstAddress '<--| exit loop when it wraps back to first found cell
End If
End With
End With
End Sub
should your column "G" data extend beyond actual range of column "A" data, and you be interested in limiting the range to this latter, then you just have to change:
With .Range("G2", .Cells(.Rows.COUNT, "G").End(xlUp)).SpecialCells(xlCellTypeConstants, xlTextValues) '<--| loop through its column G "string" values only
to
With Intersect(.Range("A2", .Cells(.Rows.COUNT, "A").End(xlUp)).EntireRow, .Range("G2", .Cells(.Rows.COUNT, "G").End(xlUp)).SpecialCells(xlCellTypeConstants, xlTextValues)) '<--| loop through its column G "string" values only down to its column "A" last not empty row
I'm trying to copy data from columns C,D,J,P when the value in column I is "O"
I'm very new at VBA and the best approach I could think of what to use an IF statement, but I haven't been able to paste more than two consecutive columns.
sub firsttry
Dim bodata As Worksheet
Dim bopresentation As Worksheet
Set bodata = Worksheets("BO Data")
Set bopresentation = Worksheets("BO presentation")
bodata.Activate
Dim i As Integer
i = 1
For i = 1 To 20
If bodata.Cells(i, 9).Value = "O" Then
bodata.Range(Cells(i, 3), Cells(i, 4)).Copy
bopresentation.Range("b20").End(xlUp).Offset(1, 0).PasteSpecial (xlPasteAll)
Else
End if
Next
end sub
Range doesn't work like that: try this:
For i = 1 To 20
If bodata.Cells(i, 9).Value = "O" Then
Application.Union(bodata.Cells(i, 3), bodata.Cells(i, 4), _
bodata.Cells(i, 10), bodata.Cells(i, 16)).Copy
bopresentation.Range("b20").End(xlUp).Offset(1, 0).PasteSpecial (xlPasteAll)
End if
Next
Your approach itself is exactly right. Looping over a range of cells is usually the most efficient way of handling data. In your current code you'd simply need to repeat the copy for the cells missing inside the loop.
In VBA, however, "pasting" in a seperate line of code if rarely necessary. It is also good coding practise to define all your variables at the top of your code.
Here is different approach using the For each-loop and summing up the wanted range with the Union method:
Dim rCell as Range
Dim rSource as Range
'Define the source range as the entire column C
With ThisWorkbook.Worksheets("BO Data")
Set rSource = .Range("C1", .Cells(Rows.Count, 3).End(xlUp))
End with
'Loop over each cell in the source range and check for its value
For each rCell in rSource
If rCell.Value = "0" Then
'If requirement met copy defined cells to target location
With ThisWorkbook.Worksheets("BO Data")
Application.Union(.Cells(rCell.Row, 3), _
.Cells(rCell.Row, 4), _
.Cells(rCell.Row, 10), _
.Cells(rCell.Row, 16) _
).Copy Destination = ThisWorkbook.Worksheets("BO Presentation").Range("B2")
End With
Next rCell
This, of course, loops over all cells in Column C. If you want to limit the range, you can simply adjust rSource to your needs.
I am new to VBA as a language, and I'm having issues sorting a large spreadsheet. The sheet is roughly 400,000 rows by 8 columns. The relevant data begins on row 5. In Column C, I changed the format of the date and rounded it down to give a single integer representing the day.
The goal is to find where the data changes days, and cut and paste all of that day's data to a seperate tab. The code I have written successfully does this for the first 2 days, but the 3rd iteration and beyond will not work properly. I have used a color code (blue) to represent the last row for each day, and I'm using this color change as my loop condition. The 3rd loop ignores the 1st color change and instead cuts and pastes 2 day's worth of data, and the 4th loop moves 3 days.
Would there be a more efficient way to move each day's data to a new tab? Each day represents 28800 rows by 6 columns. It should be noted that an additional macro is run before this in order to simply organize the raw data. The portion of the code giving me issues are the loops following the "Sort the data by date" comment.
Any help would be greatly appreciated! Thanks in advance. Attached is my code and a sample of the data
Sub HOBO_Split_v2()
'Before this code can be run, you must run "Hobo_Organize" 1 time. Press 'Ctrl + Shift + O' to do this
'The purpose of this code is to separate the hobo data by day. Weekends and evenings will be removed.
'This will create smaller data sets, which allows for easier data manipulation
Application.ScreenUpdating = False
'Find the last row
Lastrow = Range("C" & Rows.Count).End(xlUp).Row
'Set the known parameters
Dim days As Range
Set days = Worksheets("Full Data Set").Range("C5:C" & Lastrow)
Dim daychanges As String
daychanges = 0
'Maximum of 3 weeks of data, 21 different sheets
Dim sheetnum(1 To 21) As Integer
For i = 1 To 21
sheetnum(i) = i
Next i
'Loop through the day index (Col C), counting the number of day changes
For Each cell In days
If cell.Value <> cell.Offset(1).Value Then
cell.Interior.ColorIndex = 37
daychanges = daychanges + 1
End If
Next cell
'Add new sheets for each day and rename the sheets
Sheets.Add after:=ActiveSheet
ActiveSheet.Name = "Day 1"
For i = 2 To daychanges
Sheets.Add Before:=ActiveSheet
ActiveSheet.Name = "Day " & sheetnum(i)
Next i
Sheets("Full Data Set").Select
'Sort the data by date
For Each cell In days
If cell.Interior.ColorIndex = 37 Then
cell.Select
Range(Selection, Selection.End(xlUp)).Select
Range(Selection, Selection.End(xlToRight)).Select
Selection.Cut
Worksheets(Worksheets.Count).Select
ActiveSheet.Range("B2").Select
ActiveSheet.Paste
Application.CutCopyMode = False
ActiveSheet.Move Before:=Sheets("Full Data Set")
Sheets("Full Data Set").Select
Range("C4").Select
Selection.End(xlDown).Select
Range(Selection, Selection.End(xlDown)).Select
Set days = Selection
End If
Next cell
Application.ScreenUpdating = True
End Sub
Example of the data
I'd not pass through any cell coloring and use RemoveDuplicates() method of Range object as like follows:
Option Explicit
Sub HOBO_Split_v2()
Dim datesRng As Range, dataRng As Range, cell As Range
Dim iDay As Long
Application.ScreenUpdating = False
With ThisWorkbook.Worksheets("Full Data Set")
Set datesRng = .Range("C5", .Cells(.Rows.Count, 3).End(xlUp)) '<--| set dates range
Set dataRng = datesRng.offset(-1).Resize(datesRng.Rows.Count + 1, 6) '<--| set data range as dates one extended to next 5 columns
With datesRng.offset(, .UsedRange.Columns.Count) '<--| use a helper column out of current used range
.value = datesRng.value '<--| copy dates value in helper column
.RemoveDuplicates Columns:=Array(1) '<--| remove duplicates and have only unique values in helper column
For Each cell In .Cells.SpecialCells(xlCellTypeConstants, xlNumbers) '<--| iterate through remaining (unique) day values in helper column
iDay = iDay + 1 '<--| update "current day" counter
dataRng.AutoFilter Field:=1, Criteria1:=Format(cell, "#.00") '<--| filter data by "current day", format the criteria as the actual column C cells format
dataRng.offset(1).Resize(dataRng.Rows.Count - 1).SpecialCells(xlCellTypeVisible).Copy Destination:=SetWorkSheet(ThisWorkbook, "Day " & iDay).Range("B2") '<--| copy filtered data and paste the into "current day" corresponding sheet
Next cell
.Parent.AutoFilterMode = False '<--| remove autofilter
.Clear '<--| clear helper column
End With
End With
Application.ScreenUpdating = True
End Sub
Function SetWorkSheet(wb As Workbook, SheetName As String) As Worksheet
On Error Resume Next
Set SetWorkSheet = wb.Worksheets(SheetName)
On Error GoTo 0
If SetWorkSheet Is Nothing Then
Set SetWorkSheet = wb.Worksheets.Add(after:=wb.Worksheets(wb.Worksheets.Count))
SetWorkSheet.Name = SheetName
Else
SetWorkSheet.UsedRange.Clear '<--| clear preceeding values in already existent sheet
End If
End Function
There is no need to iterate over the list twice. GetWorkSheet create the new worksheets for you if they don't exist and handle any errors.
Sub HOBO_Split_v2()
Application.ScreenUpdating = False
Dim cell As Range, days As Range
Dim lFirstRow As Long, Lastrow As Long
Dim SheetName As String
Dim ws As Worksheet
With Sheets("Full Data Set")
Lastrow = Range("C" & Rows.Count).End(xlUp).Row
Set days = .Range("C5:C" & Lastrow)
For Each cell In days
If c.Text <> SheetName Or c.Row = Lastrow Then
If lFirstRow > 0 Then
Set ws = getWorkSheet(SheetName)
.Range("A" & lFirstRow, "A" & cell.Row).EntireRow.Copy ws.Range("A1")
End If
SheetName = c.Text
lFirstRow = i
End If
Next
End With
Application.ScreenUpdating = True
End Sub
Function getWorkSheet(SheetName As String) As Worksheet
Dim ws As Worksheet
On Error Resume Next
Set ws = Worksheets(SheetName)
If ws Is Nothing Then
Set ws = Worksheets.Add(after:=ActiveSheet)
ws.Name = SheetName
End If
On Error GoTo 0
Set getWorkSheet = ws
End Function
I'm writing a macro in Excel 2010 for a problem that is as follows:
I have two columns, one with a Key string value and one with a uuid. The idea is that every key should have only one uuid but as the table is now, key cell could be merged cells or single cells.
The macro needs to recognize which cells are merged and which are not, so, I have two options:
If cell is merged, check all its adjacent cells, pick first uuid value and copy/paste it to other adjacent cells, that is to say, cell below(Could be with an Offset())
If cell is not merged , but key value is repeated in multiple cells, copy/paste uuid value to adjacent cells.
So basically is to check merged cells MergeArea but I don't know if I need to iterate through its addresses or check cells in the range with an offset of Offset(0,1) or what.
With my code I can know if the cells are merged but now, how con I iterate through it's adjacent cells values?
Code as is now:
Sub CopyUUID()
Dim lRow As Long
Dim rng As Range
Dim ws As Worksheet
Dim rMerged As Range
Dim value As Variant
Set ws = Sheets(ActiveSheet.Name)
On Error GoTo ExitProgram 'If an error happens within the execution, skips it and continue in next step
Application.DisplayAlerts = False 'We can cancel the procedure without errors
With ws
lRow = .Range("F" & .Rows.count).End(xlUp).row
Set rng = .Range(.Cells(3, 6), .Cells(lRow, 6))
rng.Select
For Each cell In rng
If cell.MergeCells Then
'Code for merged cells
Else
'Code to use for single cells
End If
Next cell
End With
ExitProgram:
Exit Sub
End Sub
Option Explicit
Sub CopyUUID()
Const UUID As Long = 31 'col AE
Dim lRow As Long, cel As Range, isM As Boolean, copyID As Boolean, kCol As Long
With ActiveSheet
kCol = -25 'col F
lRow = .Cells(.Rows.Count, UUID + kCol).End(xlUp).Row
For Each cel In .Range(.Cells(3, UUID), .Cells(lRow, UUID))
isM = cel.Offset(0, kCol).MergeCells
copyID = isM And Len(cel.Offset(0, kCol)) = 0
copyID = copyID Or (Not isM And cel.Offset(0, kCol) = cel.Offset(-1, kCol))
If copyID Then cel = cel.Offset(-1)
Next
End With
End Sub
Try the following code. Note that this is going to overwrite the current contents of UUID, so make a backup copy before testing. If you don't want the UUID column modified, you can modify this to suit your needs.
Sub CopyUUID()
Dim lRow As Long
Dim rng As Range
Dim c As Range
Dim ws As Worksheet
Dim rMerged As Range
Dim value As Variant
Set ws = Sheets(ActiveSheet.Name)
On Error GoTo ExitProgram 'If an error happens within the execution, skips it and continue in next step
' Application.DisplayAlerts = False 'We can cancel the procedure without errors
With ws
lRow = .Range("F" & .Rows.Count).End(xlUp).Row
Set rng = .Range(.Cells(3, 6), .Cells(lRow, 6))
' rng.Select
For Each c In rng
If c.MergeCells Then
'Code for merged cells
c.Offset(0, 1).Formula = c.MergeArea.Cells(1, 1).Offset(0, 1).Formula
Else
'Code to use for single cells
If c.Formula = c.Offset(-1, 0).Formula Then
c.Offset(0, 1).Formula = c.Offset(-1, 1).Formula
End If
End If
Next c
End With
ExitProgram:
Exit Sub
End Sub
When in a MergedCell, it makes the UUID the same as the UUID of the first cell in the merged area. When not in a MergedCell, it copies UUID from the row above if Key is the same as the row above.
I changed your variable cell to c (I don't like to use variable names that can be confused with built-ins) and commented out a couple of lines.
Hope this helps
I adopt a simple approach to this problem as illustrated through steps taken by me.
sample sheet showing data with merged cells and unmerged cells.
Run the program code to unmerge the cells. Output of the program is appended below.
If this structure of data matches your case then addition of 2 lines of code for column B will leave the data as per following image.
Program code is as follows:
'Without column deletion:
Sub UnMergeRanges()
Dim cl As Range
Dim rMerged As Range
Dim v As Variant
For Each cl In ActiveSheet.UsedRange
If cl.MergeCells Then
Set rMerged = cl.MergeArea
v = rMerged.Cells(1, 1)
rMerged.MergeCells = False
rMerged = v
End If
Next
End Sub
'With coumn deletion
Sub UnMergeRangesB()
Dim cl As Range
Dim rMerged As Range
Dim v As Variant
For Each cl In ActiveSheet.UsedRange
If cl.MergeCells Then
Set rMerged = cl.MergeArea
v = rMerged.Cells(1, 1)
rMerged.MergeCells = False
rMerged = v
End If
Next
Columns("B:B").Select
Selection.Delete Shift:=xlToLeft
End Sub