Excel VBA - struggling to create macros which takes action on cell values by column - vba

Very new to Excel VBA, and struggling.
I'm a junior C# developer, but am finding that writing simple statements in VBA is very tricky for me. Can anyone tell me how to write VBA code for the following pseudocode requirements please?
Insert True into Column E only WHERE there is a specific string value of X in column A
Insert False into Column E WHERE there is text (ie: something/anything) in column D AND no text (ie: nothing) in Column A
Delete X wherever there is a specific string value X in any cell in Column A.
Any help at all would be so greatly appreciated.

Public Sub Text_Process()
Dim lngLastRow As Long
Dim lngRow As Long
Dim strColA As String
Dim strColD As String
lngLastRow = Sheet1.UsedRange.Rows.Count
For lngRow = 1 To lngLastRow ' change 1 to 2 if you have headings in row 1
strColA = Sheet1.Cells(lngRow, 1).Value ' store value in column A
strColD = Sheet1.Cells(lngRow, 4).Value ' store value of column D
Sheet1.Cells(lngRow, 5).Clear ' clear column E
If strColA = "X" Then ' or whatever you are looking for
Sheet1.Cells(lngRow, 5).Value = True
ElseIf strColA = "" And strColD <> "" Then
Sheet1.Cells(lngRow, 5).Value = False
End If
If strColA = "X" Then ' or whatever you are looking for
Sheet1.Cells(lngRow, 1).Clear ' clear out the value in column A, is this is what is requried?
End If
Next lngRow
End Sub

It's basic stuff do to. VBA has basically the same syntax of VB 6.
You can read the language reference or hitting F1 on VBA editor for help.
Just for example, check this code. Besides your requirements are a bit confusing, pay attention in the code structure and the functions used, like Range.Cells, IsEmpty
Sub Macro1()
Dim limit, index As Integer
' Iterate limit, just for test
limit = 20
' Iterate your worksheet until the limit
For i = 1 To limit Step 1
' Range param is a string, you can do any string contatenation you wish
' Value property is what inside the cell
If (Not IsEmpty(Range("D" & i).Value) And IsEmpty(Range("A" & i).Value)) Then
' Requirement 2
Range("E" & i).Value = False
ElseIf (Range("A" & i).Value = "X") Then
' Requiriment 1
Range("E" & i).Value = True
' Requiriment 3
Range("A" & i).Value = Empty
End If
Next
End Sub
Like Siddharth said, recording a macro and study the generated code is a great way to learn some VBA tricks.

Related

VBA-Excel Look for column names, return their number and use column letters in function

I'm quite new at VBA. I've used it in excel for a couple macros, but this one is way above my head.
I'm looking to create a macro that will find the appropriate column, then based on the value in this columns, changes the values in three other columns. I already have a static macro:
Sub AdjustForNoIntent()
'Adjusts columns Role(U) (to C-MEM), REV Profile Follow-up Date(AJ) (to N/A) and deletes Follow-up Date(Y) when column Survey: Intent to Participate = No
Dim lastrow As Long
Dim i As Long
lastrow = Range("AE" & Rows.Count).End(xlUp).Row
For i = 2 To lastrow
If Not IsError(Range("AE" & i).Value) Then
If Range("AE" & i).Value = "No" And Range("U" & i).Value = "MEM" Then
Range("U" & i).Value = "C-MEM"
Range("Y" & i).ClearContents
Range("AJ" & i).Value = "N/A"
ElseIf Range("AE" & i).Value = "No" And Range("U" & i).Value = "VCH" Then
Range("U" & i).Value = "C-VCH"
Range("Y" & i).ClearContents
Range("AJ" & i).Value = "N/A"
End If
End If
Next i
End Sub
But this is a shared workbook, so people are adding columns randomly and every time I need to go back to the code and modify the columns refereces. What I want is, for instance, to look for column with "Role" header in row A3 and to insert it where the macro looks for column "U". That way other users can add/delete columns but I won't have to modify the macro every time.
In other macros, I manage to have this thing working:
Function fnColumnNumberToLetter(ByVal ColumnNumber As Integer)
fnColumnNumberToLetter = Replace(Replace(Cells(1,ColumnNumber).Address, "1", ""), "$", "")
End Function
Dim rngColumn As Range
Dim ColNumber As Integer
Dim ColName As String
ColName = "Email Address"
Sheets("Tracking").Select
Set rngColumn = Range("3:3").Find(ColName)
ColNumber = Sheets("Tracking").Range(rngColumn, rngColumn).Column
Sheets("Combined").Range(ActiveCell, "W2").FormulaLocal = "=IF(ISERROR(INDEX(Tracking!$A:$A,MATCH(O:O,Tracking!" & fnColumnNumberToLetter(ColNumber) & ":" & fnColumnNumberToLetter(ColNumber) & ",0))), INDEX(Tracking!$A:$A,MATCH(U:U,Tracking!" & fnColumnNumberToLetter(ColNumber) & ":" & fnColumnNumberToLetter(ColNumber) & ",0)), INDEX(Tracking!$A:$A,MATCH(O:O,Tracking!" & fnColumnNumberToLetter(ColNumber) & ":" & fnColumnNumberToLetter(ColNumber) & ",0)))"
However, I am unable to link the latter to the first and much less to get it to find multiple columns. Any help is appreciated.
EDIT:
Following suggestions, here is the new code. Doesn't return an error, but doesn't do anything either. It loops through the c loop ok, but jumps from For i =2 ... line to End Sub.
Sub Adjust()
Dim lastrow As Long
Dim i As Long
Dim headers As Dictionary
Dim c As Long
Set headers = New Scripting.Dictionary
For c = 1 To Cells(3, Columns.Count).End(xlToLeft).Column
headers.Add Cells(3, c).Value, c
Next c
lastrow = Cells(headers.Item("Survey: Interest to Participate") & Rows.Count).End(xlUp).Row
For i = 2 To lastrow
If Not IsError(Cells(i, headers.Item("Survey: Interest to Participate")).Value) Then
If Cells(i, headers.Item("Survey: Interest to Participate")).Value = "No" And Cells(i, headers.Item("Role")).Value = "MEM" Then
Cells(i, headers.Item("Role")).Value = "C-MEM"
Cells(i, headers.Ittem(" Follow-up date")).ClearContents
Cells(i, headers.Item("REV profile follow-up date")).Value = "N/A"
ElseIf Cells(i, headers.Item("Survey: Interest to Participate")).Value = "No" And Cells(i, headers.Item("Role")).Value = "VCH" Then
Cells(i, headers.Item("Role")).Value = "C-VCH"
Cells(i, headers.Ittem(" Follow-up date")).ClearContents
Cells(i, headers.Item("REV profile follow-up date")).Value = "N/A"
End If
End If
Next i
End Sub
The way I'd go about this would be to create a Dictionary with header names as keys and column numbers as values:
Dim headers As Dictionary
Set headers = New Scripting.Dictionary
Dim c As Long
'Assuming headers are in row 1 for sake of example...
For c = 1 To Cells(1, Columns.Count).End(xlToLeft).Column
headers.Add Cells(1, c).Value, c
Next
Then, instead of using hard-code column letters with the Range, use the Cells collection and index it by column number using the Dictionary to look it up based on the header. For example, if your code expects column "U" to be under that header "Role" here:
Range("U" & i).Value = "C-MEM"
You can replace it with a column lookup like this using the Dictionary like this:
Cells(i, headers.Item("Role")).Value = "C-MEM"
Note that this requires a reference to the Microsoft Scripting Runtime (Tools->References... then check the box).
But this is a shared workbook, so people are adding columns randomly and every time I need to go back to the code and modify the columns refereces.
Protect the workbook to prevent this undesired behavior?
I would personally prefer to use Named Ranges, which will adjust with insertions and re-sorting of the data columns.
From Formulas ribbon, define a new name:
Then, confirm that you can move, insert, etc., with a simple procedure like:
Const ROLE As String = "Role"
Sub foo()
Dim rng As Range
Set rng = Range(ROLE)
' This will display $B$1
MsgBox rng.Address, vbInformation, ROLE & " located:"
rng.Offset(0, -1).Insert Shift:=xlToRight
' This will display $C$1
MsgBox rng.Address, vbInformation, ROLE & " located:"
rng.Cut
Application.GoTo Range("A100")
ActiveSheet.Paste
' This will display $A$100
MsgBox rng.Address, vbInformation, ROLE & " located:"
End Sub
So, I would define a Named Range for each of your columns (presently assumed to be AE, U, Y & AJ). The Named Range can span the entire column, which will minimize changes to the rest of your code.
Given 4 named ranges like:
Role, representing column U:U
RevProfile, representing column AJ:AJ
FollowUp, representing column Y:Y
Intent, representing column AE:AE
(NOTE: If you anticipate that users may insert rows above your header rows, then I would change the Named range assignments to only the header cells, e.g., "$AE$1", "$U$1", etc. -- this should require no additional changes to the code below)
You could do like this:
'Constant strings representing named ranges in this worksheet
Public Const ROLE As String = "Role"
Public Const REVPROFILE As String = "RevProfile"
Public Const FOLLOWUP As String = "FollowUp"
Public Const INTENT As String = "Intent"
Sub AdjustForNoIntent()
'Adjusts columns Role(U) (to C-MEM), REV Profile Follow-up Date(AJ) (to N/A) and deletes Follow-up Date(Y) when column Survey: Intent to Participate = No
Dim lastrow As Long
Dim i As Long
lastrow = Range(INTENT).End(xlUp).Row
For i = 2 To lastrow
If Not IsError(Range(INTENT).Cells(i).Value) Then
If Range(INTENT).Cells(i).Value = "No" And Range(ROLE).Cells(i).Value = "MEM" Then
Range(ROLE).Cells(i).Value = "C-MEM"
Range(FOLLOWUP).ClearContents
Range(REVPROFILE).Cells(i).Value = "N/A"
ElseIf Range(INTENT).Cells(i).Value = "No" And Range(ROLE).Cells(i).Value = "VCH" Then
Range(ROLE).Cells(i).Value = "C-VCH"
Range(FOLLOWUP).Cells(i).ClearContents
Range(REVPROFILE).Value = "N/A"
End If
End If
Next
End Sub
I would go with David Zemens answer but you could also use Range().Find to get the correct columns.
Here I refactored you code to find and set references to your column headers. Everything is based relative to these references.
Here I set a reference to Row 3 of the Survey column where your column header is:
Set rSurvey = .Rows(3).Find(What:="Survey", MatchCase:=False, Lookat:=xlWhole)
Because everything is relative to rSurvey the last row is = the actual last row - rSurvey's row
lastrow = rSurvey(.Rows.Count - rSurvey.Row).End(xlUp).Row - rSurvey.Row
Since rSurvey is a range we know that rSurvey.Cells(1, 1) is our column header. What isn't apparent is that since rSurvey is a range rSurvey(1, 1) is also our column header and since column and row indices are optional rSurvey(1) is also the column header cell.
Know all of that we can iterate over the cells in each column like this
For i = 2 To lastrow
rSurvey( i )
Sub AdjustForNoIntent()
'Adjusts columns Role(U) (to C-MEM), REV Profile Follow-up Date(AJ) (to N/A) and deletes Follow-up Date(Y) when column Survey: Intent to Participate = No
Dim lastrow As Long
Dim i As Long
Dim rRev As Range 'AJ
Dim rRole As Range 'U
Dim rFollowUp As Range 'Y
Dim rSurvey As Range 'AE
With Worksheets("Tracking")
Set rRev = .Rows(3).Find(What:="REV", MatchCase:=False, Lookat:=xlWhole)
Set rRole = .Rows(3).Find(What:="Role", MatchCase:=False, Lookat:=xlWhole)
Set rFollowUp = .Rows(3).Find(What:="Follow-up", MatchCase:=False, Lookat:=xlWhole)
Set rSurvey = .Rows(3).Find(What:="Survey", MatchCase:=False, Lookat:=xlWhole)
lastrow = rSurvey(.Rows.Count - rSurvey.Row).End(xlUp).Row - rSurvey.Row
End With
For i = 2 To lastrow
If Not IsError(rSurvey(i).value) Then
If rSurvey(i).value = "No" And rRole(i).value = "MEM" Then
rRole(i).value = "C-MEM"
rFollowUp(i).ClearContents
rRev(i).value = "N/A"
ElseIf rSurvey(i).value = "No" And rRole(i).value = "VCH" Then
rRole(i).value = "C-VCH"
rFollowUp(i).ClearContents
rRev(i).value = "N/A"
End If
End If
Next i
End Sub

Compare and copy matching data from adjacent cells

I was having some trouble with a macro I have been writing. I am trying to find a match in column A and column D. When I detect a match I want to copy the adjacent cells of each I.E copy the contents of B of the line of the first match to E where the match occurs in D. Whenever I do this I never get the right copy. It will copy the values that match but put them in the completely wrong space. I only encounter a problem when the order is mixed up or there is a white space. Any suggestions would be helpful.
Thanks
Nick.
Note: In this version of my code I was using input boxes to pick what two columns of data the user wants to compare and the one he wants to copy from and paste too. It should not make a big difference.
Sub Copy()
Dim column1 As String
Dim column2 As String
Dim from As String
Dim too As String
numrows = Sheet1.Range("A1").Offset(Sheet1.Rows.Count - 1, 0).End(xlUp).Row
'MsgBox numrows
column1 = InputBox("which column do you want to select from")
column2 = InputBox("which column do you want to compare to ")
from = InputBox("which column do you want to copy data from")
too = InputBox("which column do you want to copy data to")
Dim lngLastRow As Long
Dim lngLoopCtr As Long
Dim i As Long
Dim j As Long
Dim value As String
lngLastRow = Range(column1 & Rows.Count).End(xlUp).Row
lngLastRow2 = Range(column2 & Rows.Count).End(xlUp).Row
'lngLastRow = Sheet1.Range("A1").Offset(Sheet1.Rows.Count - 1, 0).End(xlUp).Row
Dim temp As String
For i = 1 To lngLastRow Step 1
temp = Cells(i, column1).value
value = Cells(i, from).value
'MsgBox "temp"
'MsgBox (temp)
If Cells(i, column1).value <> "" Then
For j = 1 To lngLastRow2 Step 1
' MsgBox "cell"
' MsgBox (Cells(j, column2).value)
If Cells(j, column2).value = "" Then
Cells(j, column2).Offset(1, 0).Select
End If
If Cells(j, column2).value <> "" Then
If temp = Cells(j, column2).value Then
'MsgBox "equal"
'MsgBox "i"
'MsgBox i
'MsgBox "j"
'MsgBox j
'value = Cells(j, from).value
'MsgBox Cells(i, too).value
'Cells(i, too).value = Cells(j, from).value
'Dim num As Integer
'On Error Resume Next
'num = Application.WorksheetFunction.VLookup(temp, Sheet1.Range("A0:M13"), 3, False)
Cells(i, too).value = Cells(j, from).value
'MsgBox j
' MsgBox (Cells(i, column1).value)
' MsgBox "="
' MsgBox (Cells(j, column2).value)
End If
End If
Next j
End If
Next i
End Sub
I have studied your text and your macro and think the macro below does what you want.
If this macro does what you want, your problem was caused by your use of meaningless variable names such as: column1, column2, i and j. This meant you did not notice you were using the wrong variables in the statement that copied values.
I have renamed all your variables. I am not asking you to like my naming convention but I am recommending you have a naming convention. I can look at macros I wrote years ago and know what all the variables are because I developed my convention in my early days of VBA programming and have used it every since. This makes my life much easier when I need to update old macros.
I have added Option Explicit at the top of the module. Without this statement, a misspelt variable name becomes a declaration:
Dim Count As Long
Lots of statements
Count = Conut + 1
This causes Conut to be declared with a value of zero. Such errors can be a nightmare to find.
I have used a With Statement to make explicit which worksheet I am using.
You checked both cells to not be empty. I only check the first because it is not necessary to check the second since, if the second is empty, it will not match the first.
Your code did not stop working down the Compare column if it found a match so my code does the same. This is correct if values can repeat in the Compare column. If they cannot repeat, you may wish to add Exit For to exit the inner loop after a match has been processed.
I believe the above explains all the changes I hve made.
Option Explicit
Sub Copy()
Dim ColCompare As String
Dim ColCopyFrom As String
Dim ColCopyTo As String
Dim ColSelect As String
Dim RowCrntCompare As Long
Dim RowCrntSelect As Long
Dim RowLastColCompare As Long
Dim RowLastColSelect As Long
Dim SelectValue As String
With Sheet1
ColSelect = InputBox("which column do you want to select ColCopyFrom")
ColCompare = InputBox("which column do you want to compare to ")
ColCopyFrom = InputBox("which column do you want to copy data ColCopyFrom")
ColCopyTo = InputBox("which column do you want to copy data to")
RowLastColSelect = .Range(ColSelect & .Rows.Count).End(xlUp).Row
RowLastColCompare = .Range(ColCompare & .Rows.Count).End(xlUp).Row
For RowCrntSelect = 1 To RowLastColSelect Step 1
SelectValue = .Cells(RowCrntSelect, ColSelect).value
If SelectValue <> "" Then
For RowCrntCompare = 1 To RowLastColCompare Step 1
If SelectValue = Cells(RowCrntCompare, ColCompare).value Then
.Cells(RowCrntCompare, ColCopyTo).value = _
.Cells(RowCrntSelect, ColCopyFrom).value
End If
Next RowCrntCompare
End If
Next RowCrntSelect
End With
End Sub

Change a cell's format to boldface if the value is over 500

I am using Excel 2010 and trying to add a bunch of rows placing the sum of columns A and B in column C. If the sum is over 500 I would then like to boldface the number in column C. My code below works works mathematically but will not do the bold formatting. Can someone tell me what I am doing wrong? Thank you.
Public Sub addMyRows()
Dim row As Integer 'creates a variable called 'row'
row = 2 'sets row to 2 b/c first row is a title
Do
Cells(row, 3).Formula = "=A" & row & "+B" & row 'the 3 stands for column C.
If ActiveCell.Value > 500 Then Selection.Font.Bold = True
row = row + 1
'loops until it encounters an empty row
Loop Until Len(Cells(row, 1)) = 0
End Sub
Pure VBA approach:
Public Sub AddMyRows()
Dim LRow As Long
Dim Rng As Range, Cell As Range
LRow = Range("A" & Rows.Count).End(xlUp).Row
Set Rng = Range("C2:C" & LRow)
Rng.Formula = "=A2+B2"
For Each Cell In Rng
Cell.Font.Bold = (Cell.Value > 500)
Next Cell
End Sub
Screenshot:
An alternative is conditional formatting.
Hope this helps.
Note: The formula in the block has been edited to reflect #simoco's comment regarding a re-run of the code. This makes the code safer for the times when you need to re-run it. :)

Resizing Cell in excel macro

I'm trying to link data from an Excel sheet, copy them to another sheet, and then copy onto another workbook. The data is non-contiguous, and the amount of iterations I need is unknown.
A portion of the code that I have now is below:
Sub GetCells()
Dim i As Integer, x As Integer, c As Integer
Dim test As Boolean
x = 0
i = 0
test = False
Do Until test = True
Windows("Room Checksums.xls").Activate
'This block gets the room name
Sheets("Sheet1").Activate
Range("B6").Select
ActiveCell.Offset(i, 0).Select
Selection.Copy
Sheets("Sheet2").Activate
Range("A1").Activate
ActiveCell.Offset(x, 0).Select
ActiveSheet.Paste Link:=True
'This block gets the area
Sheets("Sheet1").Activate
Range("AN99").Select
ActiveCell.Offset(i, 0).Select
Selection.Copy
Sheets("Sheet2").Activate
Range("B1").Activate
ActiveCell.Offset(x, 0).Select
ActiveSheet.Paste Link:=True
i = i + 108
x = x + 1
Sheets("Sheet1").Activate
Range("B6").Activate
ActiveCell.Offset(i, 0).Select
test = ActiveCell.Value = ""
Loop
Sheets("Sheet2").Activate
ActiveSheet.Range(Cells(1, 1), Cells(x, 12)).Select
Application.CutCopyMode = False
Selection.Copy
Windows("GetReference.xlsm").Activate
Range("A8").Select
ActiveSheet.Paste Link:=True
End Sub
The problem is that it is copying and pasting each cell one by one, flipping between sheets in the process. What I'd like to do is select a number of scattered cells, offset by 108 cells, and select the next number of scattered cells (re-sizing).
What would be the best way to do so?
I have been studying the end result of your macro. My objective is to identify a better approach to achieving that result rather than tidying your existing approach.
You name your two workbooks: "Room Checksums.xls" and "GetReference.xlsm". "xls" is the extension of an Excel 2003 workbook. "xlsm" is the extension of a post-2003 workbook that contains macros. Perhaps you are using these extensions correctly but you should check.
I use Excel 2003 so all my workbooks have an extension of "xls". I suspect you will need to change this.
I have created three workbooks: "Room Checksums.xls", "GetReference.xls" and "Macros.xls". "Room Checksums.xls" and "GetReference.xls" contain nothing but data. The macros are in "Macros.xls". I use this division when only privileged users can run the macros and I do not wish ordinary users to be bothered by or have access to those macros. My macro below can be placed without changes within "GetReference.xls" if you prefer.
The image below shows worksheet “Sheet1” of "Room Checksums.xls". I have hidden most of the rows and columns because they contain nothing relevant to your macro. I have set the cell values to their addresses for my convenience but there is no other significance to these values.
I ran your macro. “Sheet2” of "Room Checksums.xls" became:
Note: the formula bar shows cell A1 as =Sheet1!$B$6. That is, this is a link not a value.
The active worksheet of "GetReference.xls” became:
Note 1: the zeros in columns C to L are because you move 12 columns. I assume there is other data in these columns of “Sheet2” of your "Room Checksums.xls" that you want.
Note 2: the formula bar shows cell A8 as ='[Room Checksums.xls]Sheet2'!A1.
My macro achieves the same result as yours but in a somewhat different manner. However, there are a number of features to my macro which I need to explain. They are not strictly necessary but I believe they represent good practice.
Your macro contains a lot of what I call magic numbers. For example: B6, AN99, 108 and A8. It is possible that these values are meaningful to your company but I suspect they are accidents of the current workbooks. You use the value 108 several times. If this value were to change to 109, you would have to search your code for 108 and replace it by 109. The number 108 is sufficiently unusual for it to be unlikely that it occurs in your code for other reasons but other numbers may not be so unusual making replacement a painstaking task. At the moment you may know what this number means. Will you remember when you return to amend this macro in 12 months?
I have defined 108 as a constant:
Const Offset1 As Long = 108
I would prefer a better name but I do not know what this number is. You could replace all occurrences of “Offset1” with a more meaningful name. Alternatively, you could add comments explaining what it is. If the value becomes 109, one change to this statement fixes the problem. I think most of my names should be replaced with something more meaningful.
You assume "Room Checksums.xls" and "GetReference.xlsm" are open. If one of both of them were not open, the macro would stop on the relevant activate statement. Perhaps an earlier macro has opened these workbooks but I have added code to check that they are open.
My macro does not paste anything. It has three phases:
Work down worksheet “Sheet1” of "Room Checksums.xls" to identify last non-empty cell in the sequence: B6, B114, B222, B330, B438, ... .
Create links to these entries (and the AN99 series) in worksheet “Sheet2” of "Room Checksums.xls". Formulae are just strings which start with the symbol “=” and they can be created like any other string.
Create links in worksheet “Xxxxxx” of "GetReference.xls” to the table in “Sheet2” of "Room Checksums.xls". I do not like relying on the correct worksheet being active. You will have to replace “Xxxxxx” with the correct value.
In my macro I have attempted to explain what I am doing but I have not said much about the syntax of the statements I am using. You should have little difficulty finding explanations of the syntax but do ask if necessary.
I think you will find some of my statements confusing. For example:
.Cells(RowSrc2Crnt, Col1Src2).Value = "=" & WshtSrc1Name & "!$" & Col1Src1 & _
"$" & Row1Src1Start + OffsetCrnt
None of the names are as meaningful as I would like because I do not understand the purpose of the worksheets, columns and offset. Instead of copying and pasting, I am building a formula such as “=Sheet1!$B$6”. If you work through the expression you should be able to relate each term with an element of the formula:
"=" =
WshtSrc1Name Sheet1
"!$" !$
Col1Src1 B
"$" $
Row1Src1Start + OffsetCrnt 6
This macro is not quite as I would have coded it for myself since I prefer to use arrays rather than access worksheets directly. I decided that I was introducing more than enough concepts without the addition of arrays.
Even without arrays this macro is more difficult for a newbie to understand than I had expected when I started coding it. It is divided into three separate phases each with a separate purpose which should help a little. If you study it, I hope you can see why it would be easier to maintain if the format of the workbooks changed. If you have large volumes of data, this macro would be substantially faster than yours.
Option Explicit
Const ColDestStart As Long = 1
Const Col1Src1 As String = "B"
Const Col2Src1 As String = "AN"
Const Col1Src2 As String = "A"
Const Col2Src2 As String = "B"
Const ColSrc2Start As Long = 1
Const ColSrc2End As Long = 12
Const Offset1 As Long = 108
Const RowDestStart As Long = 8
Const Row1Src1Start As Long = 6
Const Row2Src1Start As Long = 99
Const RowSrc2Start As Long = 1
Const WbookDestName As String = "GetReference.xls"
Const WbookSrcName As String = "Room Checksums.xls"
Const WshtDestName As String = "Xxxxxx"
Const WshtSrc1Name As String = "Sheet1"
Const WshtSrc2Name As String = "Sheet2"
Sub GetCellsRevised()
Dim ColDestCrnt As Long
Dim ColSrc2Crnt As Long
Dim InxEntryCrnt As Long
Dim InxEntryMax As Long
Dim InxWbookCrnt As Long
Dim OffsetCrnt As Long
Dim OffsetMax As Long
Dim RowDestCrnt As Long
Dim RowSrc2Crnt As Long
Dim WbookDest As Workbook
Dim WbookSrc As Workbook
' Check the source and destination workbooks are open and create references to them.
Set WbookDest = Nothing
Set WbookSrc = Nothing
For InxWbookCrnt = 1 To Workbooks.Count
If Workbooks(InxWbookCrnt).Name = WbookDestName Then
Set WbookDest = Workbooks(InxWbookCrnt)
ElseIf Workbooks(InxWbookCrnt).Name = WbookSrcName Then
Set WbookSrc = Workbooks(InxWbookCrnt)
End If
Next
If WbookDest Is Nothing Then
Call MsgBox("I need workbook """ & WbookDestName & """ to be open", vbOKOnly)
Exit Sub
End If
If WbookSrc Is Nothing Then
Call MsgBox("I need workbook """ & WbookSrcName & """ to be open", vbOKOnly)
Exit Sub
End If
' Phase 1. Locate the last non-empty cell in the sequence: B6, B114, B222, ...
' within source worksheet 1
OffsetCrnt = 0
With WbookSrc.Worksheets(WshtSrc1Name)
Do While True
If .Cells(Row1Src1Start + OffsetCrnt, Col1Src1).Value = "" Then
Exit Do
End If
OffsetCrnt = OffsetCrnt + Offset1
Loop
End With
If OffsetCrnt = 0 Then
Call MsgBox("There is no data to reference", vbOKOnly)
Exit Sub
End If
OffsetMax = OffsetCrnt - Offset1
' Phase 2. Build table in source worksheet 2
RowSrc2Crnt = RowSrc2Start
With WbookSrc.Worksheets(WshtSrc2Name)
For OffsetCrnt = 0 To OffsetMax Step Offset1
.Cells(RowSrc2Crnt, Col1Src2).Value = "=" & WshtSrc1Name & "!$" & Col1Src1 & _
"$" & Row1Src1Start + OffsetCrnt
.Cells(RowSrc2Crnt, Col2Src2).Value = "=" & WshtSrc1Name & "!$" & Col2Src1 & _
"$" & Row2Src1Start + OffsetCrnt
RowSrc2Crnt = RowSrc2Crnt + 1
Next
End With
' Phase 3. Build table in destination worksheet
RowSrc2Crnt = RowSrc2Start
RowDestCrnt = RowDestStart
With WbookDest.Worksheets(WshtDestName)
For OffsetCrnt = 0 To OffsetMax Step Offset1
ColDestCrnt = ColDestStart
For ColSrc2Crnt = ColSrc2Start To ColSrc2End
.Cells(RowDestCrnt, ColDestCrnt).Value = _
"='[" & WbookSrcName & "]" & WshtSrc2Name & "'!" & _
ColNumToCode(ColSrc2Crnt) & RowSrc2Crnt
ColDestCrnt = ColDestCrnt + 1
Next
RowSrc2Crnt = RowSrc2Crnt + 1
RowDestCrnt = RowDestCrnt + 1
Next
End With
End Sub
Function ColNumToCode(ByVal ColNum As Long) As String
Dim Code As String
Dim PartNum As Long
' Last updated 3 Feb 12. Adapted to handle three character codes.
If ColNum = 0 Then
ColNumToCode = "0"
Else
Code = ""
Do While ColNum > 0
PartNum = (ColNum - 1) Mod 26
Code = Chr(65 + PartNum) & Code
ColNum = (ColNum - PartNum - 1) \ 26
Loop
End If
ColNumToCode = Code
End Function

Merge cells and delete duplicate data

I have a list of companies and each has a scope of work, address and phone number. Some of the companies have multiple scopes of work. It looks something like this:
I want to get rid of the second copy of the stuff like the address (and in my case phone numbers and such) while copying the unique data in the second line and putting it in the first line and then getting rid of the second line.
I have very little experience of coding. I looked up how to do this step by step but something is wrong within the code or the syntax:
I found code for going down a column for a blank space.
I looked up how I would copy a cell to the right of the active blank cell.
I found code for merging the info into the cell one above and one to the right of the active cell.
I found code that deletes the row with the active cell.
I want it to loop until there are no more blank company cells.
So this is how I put it together:
Public Sub SelectFirstBlankCell()
Dim sourceCol As Integer, rowCount As Integer, currentRow As Integer
Dim currentRowValue As String
Do
sourceCol = 6 'column F has a value of 6
rowCount = Cells(Rows.Count, sourceCol).End(xlUp).Row
'for every row, find the first blank cell and select it
For currentRow = 1 To rowCount
currentRowValue = Cells(currentRow, sourceCol).Value
If IsEmpty(currentRowValue) Or currentRowValue = "" Then
Cells(currentRow, sourceCol).Select
End If
Next
Loop Until A647
End Sub
.
Sub mergeIt()
Range(ActiveCell.Offset(0, 1), ActiveCell.Offset(1, 1)).Merge
ActiveCell.Select
End Sub
.
Sub DeleteRow()
RowNo = ActiveCell.Row
If RowNo < 7 Then Exit Sub
Range("A" & ActiveCell.Row).EntireRow.Delete
Sheets("Summary").Select
Range("A4:O4").Select
Selection.Copy
LastRow = Range("A65536").End(xlUp).Offset(1, 0).Row
End Sub
Please never post code as an image since someone who wants to try it out must type it. You can edit your question and add a new section including revised code if necessary.
My copy of your code (plus line numbers) is:
1 Public Sub SelectFirstBlankCell()
2 Dim sourceCol As Integer, rowCount As Integer, currentRow As Integer
3 Dim currentRowValue As String
4 sourceCol = 1 'column F has a value of 6
5 rowCount = Cells(Rows.Count, sourceCol).End(xlUp).Row
6 'for every row, find the first blank cell and select it
7 For currentRow = 1 To rowCount
8 currentRowValue = Cells(currentRow, sourceCol).Value
9 If IsEmpty(currentRowValue) Or currentRowValue = "" Then
10 Cells(currentRow, sourceCol).Select
11 End If
12 Range(ActiveCell.Offset(0, 0), ActiveCell.Offset(1, 1)).Merge
13 ActiveCell.Select
14 If IsEmpty(currentRowValue) Or currentRowValue = "" Then
15 Cells(Range("sourceCol:21")).Delete
16 End If
17 Next
18 End Sub
I am sure we all started selecting cells and accessing the ActiveCell because the macro recorder does this. However, selecting cells is slow and it is very easy to lose track of what is selected. I believe this is your main problem.
Problem 1 The end value for a For-Loop is fixed at the start; Any attempt to reduce rowCount when you delete something will have no effect on the For-Loop.
Problem 2 I suspect you mean the range in line 15 to be sourceCol & ":" & currentRow.
Problem 3 In line 10 you select a cell if it is blank. In line 12 you merge the active cell whether or not you have just selected it. This means your code attempts a merge for every row.
Problem 4 Column 1 is the column that might be blank. Suppose row 1000 is the last row with a supplier's name but row 1005 is the last row with a product. Your code would not process rows 1001 to 1005.
Problem 5 Function IsEmpty() only returns sensible values for Variants. A Variant is either a cell or a variable that can hold different types of value.
I have not tried your code so there may be more mistakes. Do get dispirited. To the best of my knowledge, problem 1 is not documented. I had to discover this "feature" for myself by attempting code similar to yours. The specification for Function IsEmpty() states its limitations but, unless you fully understand Variants, the significance is not obvious. The other problems are easy errors to make and only practice will reduce their frequency.
Below is my solution to your problem. It is not how I would code it for myself but I think I have introduced enough new concepts for one solution.
I do not say much about the syntax of the VBA statements I use since it is usually easy to look up a statement once you know it exists. Ask if necessary but please try to understand the code before asking.
I do not like deleting in situ; it is slow and, if your code is faulty, you have to load the previous version of the worksheet and start again. I have a source (Src) and a Destination (Dest) worksheet.
I use constants for values that might change but not during a single run of your macro.
You assume the address and other details for Jan's Supply on rows 2 and 3 match. I am paranoid and never make assumptions like this. If my code would discard important information if rows 2 and 3 did not match, I check they match. I also allow for rows like this because I have encountered them:
John's supply Cookies 555 Main Street CA
Cakes Littleville CA
This will become:
John's supply Cookies & Cakes 555 Main Street Littleville CA
Some of the comments explain my choice of VBA statement but most do not. When you have to update a macro you wrote 12 months ago for new requirements, the few minutes you spent adding comments can save you hours finding your way around the code.
You may not like my system of naming variables. Fine; develop your own. When you return to this macro in 12 months, an immediate understanding of the variables will save more time.
Option Explicit
Const WkshtSrcName As String = "Sheet1" ' \ Replace "Sheet1" and "Sheet2"
Const WkshtDestName As String = "Sheet2" ' / with the names of your worksheets
Const ColSupplier As String = "A" ' \ In Cells(R, C), C can be a
Const ColProduct As String = "B" ' / number or a column identifier
Const RowDataFirst As Long = 1
Sub MergeRowsForSameSupplier()
Dim ColCrnt As Long ' \ Columns in source and destination are the
Dim ColMax As Long ' / same so single variables are adequate.
Dim RowDestCrnt As Long ' \ Rows in source and destination
Dim RowSrcCrnt As Long ' | worksheets are different
Dim RowSrcMax As Long ' / so need separate variables.
Dim ProductCrnt As String
Dim Join As String
Dim SupplierCrnt As String
Dim WkshtSrc As Worksheet
Dim WkshtDest As Worksheet
Set WkshtSrc = Worksheets(WkshtSrcName)
Set WkshtDest = Worksheets(WkshtDestName)
With WkshtSrc
' I consider this to be the easiest technique of identifying the last used
' row and column in a worksheet. Note: the used range includes trailing
' rows and columns that are formatted but otherwise unused or were used but
' aren't now so other techniques can better match what the user or the
' programmer usually mean by "used".
ColMax = .UsedRange.Columns.Count
RowSrcMax = .UsedRange.Rows.Count
End With
With WkshtDest
.Cells.EntireRow.Delete ' Delete any existing contents
End With
RowDestCrnt = RowDataFirst
For RowSrcCrnt = RowDataFirst To RowSrcMax
With WkshtSrc
SupplierCrnt = .Cells(RowSrcCrnt, ColSupplier).Value
ProductCrnt = .Cells(RowSrcCrnt, ColProduct).Value
End With
If SupplierCrnt <> "" Then
' This is the first or only row for a supplier.
' Copy it to Destination worksheet.
With WkshtSrc
.Range(.Cells(RowSrcCrnt, 1), .Cells(RowSrcCrnt, ColMax)).Copy _
Destination:=WkshtDest.Cells(RowDestCrnt, 1)
End With
RowDestCrnt = RowDestCrnt + 1
ElseIf ProductCrnt = "" Then
' Both Supplier and Product cells are empty.
With WkshtSrc
If .Cells(RowSrcCrnt, Columns.Count).End(xlToLeft).Column = 1 And _
.Cells(RowSrcCrnt, 1).Value = "" And _
.Cells(RowSrcCrnt, Columns.Count).Value = "" Then
' If you do not understand why I have so many tests,
' experiment with Ctrl+Left
' Row empty so ignore it
Else
' Don't know what to do with this error so give up
Call MsgBox("Cells " & ColSupplier & RowSrcCrnt & " and " & _
ColProduct & RowSrcCrnt & " of worksheet " & _
WkshtSrcName & _
" are blank but the entire row is not blank", _
vbOKOnly + vbCritical, "Merge rows for same supplier")
Exit Sub
End If
End With
Else
' Supplier cell is empty. Product cell is not.
' Row RowDestCrnt-1 of the Destination worksheet contains the first row
' for this supplier or the result of merging previous rows for this
' supplier.
If WkshtSrc.Cells(RowSrcCrnt + 1, ColSupplier).Value = "" And _
WkshtSrc.Cells(RowSrcCrnt + 1, ColProduct).Value <> "" Then
' The next row is for the same supplier but is not a blank row
Join = ","
Else
' This is last row for this supplier
Join = " &"
End If
' Add to list of products
With WkshtDest
.Cells(RowDestCrnt - 1, ColProduct).Value = _
.Cells(RowDestCrnt - 1, ColProduct).Value & Join & " " & _
ProductCrnt
End With
For ColCrnt = 1 To ColMax
If ColCrnt = Cells(1, ColSupplier).Column Or _
ColCrnt = Cells(1, ColProduct).Column Then
' You may think (and you may be right) that the supplier and product
' will always be in the first two columns. But have seen the
' weirdest arrangements and make no assumptions
' Ignore this column
Else
If WkshtSrc.Cells(RowSrcCrnt, ColCrnt).Value = "" Then
' The most likely arrangement: the subsequent row has no
' value in this column. Nothing to do.
ElseIf WkshtDest.Cells(RowDestCrnt - 1, ColCrnt).Value = "" Then
' This source row has a value in this column but [the] previous
' row[s] did not.
' Note: I use the copy statement because it copies formatting as
' well as the value which may be useful.
WkshtSrc.Cells(RowSrcCrnt, ColCrnt).Copy _
Destination:=WkshtDest.Cells(RowDestCrnt - 1, ColCrnt)
ElseIf WkshtSrc.Cells(RowSrcCrnt, ColCrnt).Value = _
WkshtDest.Cells(RowDestCrnt - 1, ColCrnt).Value Then
' Values match. Nothing to do.
Else
' Values do not match.
' Don't know what to do with this error so give up.
Call MsgBox("The value in cell " & ColNumToCode(ColCrnt) & _
RowSrcCrnt & " of worksheet " & WkshtSrcName & _
" does not match a value in an earlier row " & _
"for the same supplier", _
vbOKOnly + vbCritical, "Merge rows for same supplier")
Exit Sub
End If
End If
Next
End If
Next
With WkshtDest
.Cells.Columns.AutoFit
End With
End Sub
Function ColNumToCode(ByVal ColNum As Long) As String
' Convert a column identifier (A, AA, etc.) to its number
Dim Code As String
Dim PartNum As Long
' Last updated 3 Feb 12. Adapted to handle three character codes.
If ColNum = 0 Then
ColNumToCode = "0"
Else
Code = ""
Do While ColNum > 0
PartNum = (ColNum - 1) Mod 26
Code = Chr(65 + PartNum) & Code
ColNum = (ColNum - PartNum - 1) \ 26
Loop
End If
ColNumToCode = Code
End Function