I have the first cell in the row saved as a range and I just wanted to know how I would go about bringing in the whole row so I would be able to compare the two. Any help would be greatly appreciated.
Sub crossUpdate()
Dim rng1 As Range, rng2 As Range, N As Long, C As Long
N = Cells(Rows.Count, "A").End(xlUp).row
Set rng1 = Sheet1.Cells.Range("A2:A" & N)
C1 = rng1.Rows.Count
Set rng1 = Sheet2.Cells.Range("A2:A" & N)
C2 = rng2.Rows.Count
For i = 2 To C
End Sub
I notice a typo in your code, as indicated in the comments you're double-assigning to the rng1 variable, change the second to Set rng2 = ...
You have a loop For i = 2 to C but you have never assigned anything to the variable C, so that will not cause an error, but it will fail to do what you hope it will do.
Option Explicit 'use this to force variable declaration, avoids some errors/typos
Sub crossUpdate()
'Declare each variable separately, it is easier to read this way and won't raise problems
' in some cases if you need to pass to other subs/functions
Dim rng1 As Range
Dim rng2 As Range
Dim N As Long
'Add these declarations
Dim C As Long
Dim R as Long
'I deleted declarations for i (replaced with R), C, N which won't be needed. And also C1, C2
'I'm going to declare some additional range variables, these will be easier to work with
Dim rng1Row as Range
Dim rng2Row as Range
Dim cl as Range
N = Cells(Rows.Count, "A").End(xlUp).row
Set rng1 = Sheet1.Cells.Range("A2:A" & N)
Set rng2 = Sheet2.Cells.Range("A2:A" & N) 'This line was incorrect before
'Now to compare the cells in each row
For R = 2 to rng1.Rows.Count
Set rng1Row = rng1.Cells(R,1).EntireRow
Set rng2Row = rng2.Cells(R,1).EntireRow
For C = 1 to rng1.Columns.Count
If rng1Row.Cells(R,C).Value <> rng2Row.Cells(R,C).Value Then
'Do something if they are NOT equal
Else
'Do something if they ARE equal
End If
Next
End Sub
There are actually some simpler ways to do this, probably, but for purpose of demonstration it is easier for me to explain by breaking it down like this. But for example, range's aren't limited by the number of cells they contain. Consider this:
Debug.Print Range("A1").Cells(,2).Address
Should this raise an error? After all, [A1] is a single cell. It won't raise an error, and instead it will correctly print: $B$1.
So you could probably simplify to this, and avoid using the rng1Row and rng2Row variables:
For R = 2 to rng1.Rows.Count
For C = 1 to rng1.Columns.Count
If rng1.Cells(R,C).Value <> rng2.Cells(R,C).Value Then
'Do something if they are NOT equal
Else
'Do something if they ARE equal
End If
Next
End Sub
Related
I would like to ask whats the logic that I should use in order to search for Range A value in Range D. If Range A value is not equal to any of Range D, would like to add it under Range D.
I had came out with this code. However for the Range1, is it possible to set to last row in range A which contain value?
Sub find()
Dim range1 As Range
Set range1 = Range("A1:A9")
Dim range2 As Range
Set range2 = Range("D:D")
Dim rgvalue1 As Variant
Dim rgvalue2 As Variant
Dim rgfound As Variant
For Each rgvalue1 In range1
Set rgfound = range2.find(rgvalue1)
If rgfound Is Nothing Then
Range("D1").End(xlDown).Offset(1, 0) = rgvalue1
End If
Next rgvalue1
Dim i As Integer
i = 2
Do Until Cells(i, 4) = ""
Cells(i, 5).FormulaR1C1 = "=INDEX(C[-3],MATCH(RC[-1],C[-4],0),0)"
i = i + 1
Loop
End Sub
Never use a name for your procedure that is already used by VBA. You name your procedure Sub find() but find is already the name of a VBA method. This will soon or later cause strange issues and is a very bad practice.
Instead use meaningful names like Sub FindMissingProductsAndCopyThem().
I suggest the following code:
Option Explicit
Public Sub FindMissingProductsAndCopyThem()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1") 'define which worksheet
Dim lRowSource As Long
lRowSource = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row 'find last row in column A
Dim lRowDestination As Long
lRowDestination = ws.Cells(ws.Rows.Count, "D").End(xlUp).Row 'find last row in column D
Dim FoundRow As Long
Dim iRow As Long
For iRow = 2 To lRowSource 'loop throug data of column A
FoundRow = 0 'initialize/reset
On Error Resume Next 'next line throws an error if not matched, catch that error
'try to find/match the data of column A in column D
FoundRow = Application.WorksheetFunction.Match(ws.Cells(iRow, "A"), ws.Columns("D"), 0)
On Error GoTo 0 're-activate error reporting
'if Match threw an error, then FoundRow is still 0
If FoundRow = 0 Then 'product was not found, so add it
lRowDestination = lRowDestination + 1
ws.Cells(lRowDestination, "D").Value = ws.Cells(iRow, "A")
End If
Next iRow
End Sub
The logic would be:
Set a pointer to first cell in A
Start Loop
Find the value of pointer in D. (either using WorksheetFunction.MATCH or FIND)
If not found then
Find Next Blank Row in D
Copy pointer value to that row, column D
(optionally, blank cell in A)
Advance pointer to next cell
If you've reached the last cell in A then stop, else loop back to start of loop
Now you can translate that to VBA code
I have made an array for M2:Tx - The x will vary dependent code prior to this.
What I would like to do is have a 'clear contents' function and 'sort' function for this array (m2:tx).
I am just struggling to make the array DYNAMIC. If anyone could help.
I believe it will be something along these lines:
k = sh.Range("A3").End(xlDown).Row 'This will give me the length.
Dim TaskDates As Range
Dim StartCell As Range 'First part of Array
Dim EndCell As Range 'End of Array
Set EndCell = Range("T" & 2 + k) 'maybe 2 or 3
Set StartCell = Range("M2")
Set TaskDates = Range(StartCell, EndCell) 'Dynamic Range
This will work i beleive, but is there a more succinct methodology.
I don't really understand the logic by which you are setting your range.
If you want the last row in the range that starts with M2 to be the same as the last occupied row in Column A, then. in general
LastRow = Cells(Rows.Count, "A").End(xlUp).Row
So to set your range M2:Tx where x = LastRow
Set TaskDates = Range("M2", "T" & Cells(Rows.Count, "A").End(xlUp).Row)
To sort the range, decide how you want it sorted, then record a macro.
Here is one way:
Sub MakeRange()
Dim x As Long, MyDynamicRange As Range
x = 123
Set MyDynamicRange = Range("M2:T" & x)
MsgBox MyDynamicRange.Address(0, 0)
End Sub
I have two worksheets in different workbooks. Each sheet can have only a few lines to thousands of lines. They never have the same number of lines.
In Column E of the Capital worksheet, I want to find any and all cells that contain ITS#### where #### are numeric characters. When a cell is identified, I want to go to column A of that row and identify that value. I then want to find the value I just identified (Column A) in column J of the Trans worksheet which is in a different workbook. If a match is found, I want the value of column I in the Trans workbook to be changed to "Cost of Goods Sold/Expense.
I have searched the Internet for weeks and have tried many different solutions to similar problems, but have found nothing that works. I believe I could figure it out if someone could get me past the indicated line. I keep getting a
Run-time error 1004 Method Range of object _worksheet failed.
The following code is one that I was working on, but I was just tying to get past the error so it doesn't even try to tackle the entire problem.
Thank you for any help you may provide.
Sub ITSTRANSCOM()
'
' ITSTRANSCOM Macro
'
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim i As Variant
Dim C As Variant
Dim Lrow As Variant
Dim Lastrow As Variant
Set ws1 = Worksheets("Capital")
Set ws2 = Worksheets("Trans")
Lrow = ws1.Cells(ws1.Rows.Count, "A:A").End(xlUp).Row
Lastrow = ws2.Cells(ws2.Rows.Count, "j:j").End(xlUp).Row
'Run-time error occurs on next row.
For Each i In ws1.Range("A:A", Lrow)
For Each C In ws2.Range("J:J", Lastrow)
If i.Cells.Value = C.Cells.Value Then
If C.Cells.Value = "ITS####" Then
i.Cells.Interior.ColorIndex = xlNone
End If
End If
Next C
Next i
End Sub
Try something like this:
Dim I as Integer, C as Integer
Dim Tmp_Val as Variant
For I = 1 to LRow
If Left(UCase(Ws1.Range(“E” & I).Value),3) = “ITS” then
Tmp_Val = Ws1.Range(“A” & I).Value
For C = 1 to LastRow
If Ws2.Range(“J” & C).Value = Tmp_Val then
Ws2.Range(“I” & C).Value = “Cost of Goods Sold/Expense”
Exit For
End if
Next C
End if
Next I
Your solution looks like it is on the way there...just try replacing the line:
If C.Cells.Value = "ITS####" Then
With the line:
If UCase(Left(C.Cells.Value,3)) = "ITS" Then
I think that will allow you to identify the cells you want, and you seem like you should be capable of developing the code to shift those values between your sheets (based on your other code).
Try changing
For Each i In ws1.Range("A:A", Lrow)
to
For Each i In ws1.Range("A1", "A" & Lrow)
Same with For Each C In ws2.Range("J:J", Lastrow):
For Each C In ws2.Range("J1", "J" & Lastrow)
Looks simple. Perhaps I did not get your explanations right ?
Dim c As Range, r As Range
For Each c In ws1.Range("A:A").SpecialCells(xlCellTypeConstants, xlTextValues).Cells
If c.Value Like "ITS[0-9][0-9][0-9][0-9]" Then
Set r = ws2.Range("J:J").Find(c.Value)
If Not r Is Nothing Then
r.Offset(0, -1) = "Cost of Goods Sold/Expense"
Else
Debug.Print c.Value, " not found"
End If
End If
Next c
Debug.Print "Done"
I'm trying to combine the values in a range of cells. I created a range as follows:
Dim rng As Range
Set rng = Application.Union(Range("A1:A3"), Range("C1:E2"))
For Each Address In rng
Debug.Print Address.Address
Next
I want to read the cells in horizontal order: A1,C1,D1,E1,A2,C2... etc.
But instead they're being read A1,A2,A3,C1,C2,.. etc
How can I read them horizontally?
Thanks
You have to deal with the Range.Areas within the Range object created with the Union method.
Sub laterally()
Dim r As Long, c As Long, a As Long
Dim mnRW As Long, mxRW As Long, mnCL As Long, mxCL As Long
Dim rng As Range
With Worksheets("Sheet1") '<~~ ALWAYS set the worksheet!
Set rng = Union(.Range("A1:A3"), .Range("C1:E2"))
Debug.Print rng.Address(0, 0)
mnRW = Rows.Count: mxRW = 0
mnCL = Columns.Count: mxCL = 0
With rng
For a = 1 To .Areas.Count
With .Areas(a)
mnRW = Application.Min(mnRW, .Rows(1).Row)
mxRW = Application.Max(mxRW, .Rows(.Rows.Count).Row)
mnCL = Application.Min(mnCL, .Columns(1).Column)
mxCL = Application.Max(mxCL, .Columns(.Columns.Count).Column)
End With
Next a
For r = mnRW To mxRW
For c = mnCL To mxCL
If Not Intersect(.Cells, .Parent.Cells(r, c)) Is Nothing Then _
Debug.Print .Parent.Cells(r, c).Address(0, 0)
Next c
Next r
End With
End With
End Sub
After collecting the extents of the unioned range, each possible cell is looped through and the Intersect method is used to determine whether it belongs in the union or not.
A union of ranges doesn't care in which order the cells are added - whether it's by row or by column. So your original loop to unify the ranges is fine.
If you're concerned about the order in which you read the data, simply read by row or by column accordingly, with an inner and an outer loop. So for example, after you build the range, do as follows:
Dim Col as Variant, Rw as Variant
For Each Col in rng.Columns
For each Rw in Col.Rows
Debug.Print Rw.Address
Next Rw
Next Col
Just thought this might be an interesting addition to Chris' solution?
(I changed the range slightly to highlight some of the advantages)
Dim Rng As Range, Col As Variant, Rw As Variant
Set Rng = Application.Union(Range("A3:A5"), Range("C2:E4"))
For Each Rw In ActiveSheet.UsedRange.Rows
For Each Col In Rw.Columns
If Not Intersect(Col, Rng) Is Nothing Then Debug.Print Intersect(Col, Rng).Address
Next Col
Next Rw
Short version of the question:
The code here
Dim rng As Range
Set rng = Selection
Set rng = rng.Columns(1)
For Each cl In rng
cl.Select ' <-- Break #2
gives me this in the immediate window when the selection is A1:B37
? rng.address(External:=True)
[Book2]Sheet1!$A$1:$A$37
? cl.Address(External:=True)
[Book2]Sheet1!$A$1:$A$37
Anyone can help me understanding why cl -> A1:A37 instead of cl -> A1?
Note that I imagine rewriting code to get the intended results. But I would like to know what is the problem, and probably learn something new along. This is what the question is about.
Long version of the question (as originally posted):
I have a subroutine, which works on the selected (rectangular) range rng. The code of relevance here is shown below. It branches depending on the number ncols of columns of rng.
When ncols=1, it loops through each cell cl in rng, selecting cl and performing some actions.
When the starting selection is A1:A37, this works ok, as shown by the output in the immediate window right after entering the loop at Break #1 (see code below)
? rng.address(External:=True)
[Book2]Sheet1!$A$1:$A$37
? cl.Address(External:=True)
[Book2]Sheet1!$A$1
When ncols<>1, I want to loop through each cell cl in the first column of rng, doing the same as before.
Now when the starting selection is A1:B37, this does not work, as shown by the output in the immediate window at Break #2
? rng.address(External:=True)
[Book2]Sheet1!$A$1:$A$37
? cl.Address(External:=True)
[Book2]Sheet1!$A$1:$A$37
Anyone can help me understanding why here cl -> A1:A37 instead of cl -> A1 (as in Break #1)?
Note that I imagine rewriting code to get the intended results. But I would like to know what is the problem, and probably learn something new along. This is what the question is about.
Dim rng As Range
Set rng = Selection
Dim ncols As Long
ncols = rng.Columns.Count
Dim cl As Range
' 1- If only one column is selected, ...
If (ncols = 1) Then
For Each cl In rng
cl.Select ' <-- Break #1
...
Next cl
' 2- If more than one column is selected, ...
Else
Set rng = rng.Columns(1)
For Each cl In rng
cl.Select ' <-- Break #2
Dim rng2 As Range
Set rng2 = Range(cl, cl.Offset(0, ncols - 1))
rng2.Select
...
Next cl
End If
I have not had a chance to test your code yet but you may simply be suffering from lack of explicity: cl is a Range, so is a Column and a Row and an Area and any other sort of range-type object. You can use a range iterator like cl : For each cl in Rng.Rows or ...in rng.Columns, or in ...rng.Cells, etc.
In other words, while you may be expecting cl to be a cell range, that may not be the case unless you make it explicit, like:
For each cl in rng.Cells
Or, since you are defining it as a single-column, this would be equivalent:
For Each cl in rng.Rows
(technically, cl represents a row range in that rng, but since it's a single column range, each "row" is a single cell, too).
Your code can acutally be quite streamlined:
Sub f()
Dim rng As Range
Dim cl As Range
Dim rng2 As Range
Set rng = Range(Selection.Address).Resize(, 1)
ncols = Range(Selection.Address).Columns.Count
For Each cl In rng.Cells
cl.Select ' <-- Break #2
If nCols > 1 Then
Set rng2 = Range(cl, cl.Offset(0, ncols - 1))
rng2.Select
'...
End If
Next cl
End Sub