Row index variable used with +1 in ListObject - excel

I run the following:
For r = 2 To lastRowSan
useSan.Activate
searchCATID = Cells(r, LONameSan.ListColumns("CATID").Range.Column)
useOfCat = Cells(r, LONameSan.ListColumns("Total 6 months").Range.Column)
compList.Activate
searchVal = WorksheetFunction.XLookup(searchCATID, compList.ListObjects("completeList").ListColumns("CATID").Range, compList.ListObjects("completeList").ListColumns("CATID").Range, "NOK")
If searchVal <> "NOK" Then
compList.Activate
Set rowCatSearch = [completeList].Rows(WorksheetFunction.Match(searchCATID, [completeList[CATID]], 0))
rowCatID = rowCatSearch.Row
colNameUSe = "Number of uses - " & market
compList.ListObjects("completeList").ListColumns(colNameUSe).DataBodyRange(rowCatID) = useOfCat
End If
Next r
where useSan and compList are worksheets, LONameSan is a ListObject defined dynamically.
When I run the script, the matching values are found at the correct row index (the rowCatID = rowCatSearch.row is correct (I check step by step in Degug mode).
However, the line compList.ListObjects("completeList").ListColumns(colNameUSe).DataBodyRange(rowCatID) = useOfCat will put the useOfCat valmue in the next row, ie. in rowCatID + 1^
I have a second similar loop after this one which gives the exact same result.
Is this normal and due to the fact that I put the value in the databodyrange and that in this case, the row 1 is the first row of the databodyrange and does not count the real row 1 which is the Header?

Edit:
My answer is mostly valid, upon further inspection, the reason that your Match is providing the Worksheet Row is not because of the Match, but because of the range provided by the Table:
Set rowCatSearch = [completeList].Rows(WorksheetFunction.Match(searchCATID, [completeList[CATID]], 0))
The Match is actually giving the proper Row number you want, however because you have the match enclosed with the Table.Rows, it changes the Row.
Instead if you use:
rowCatID = WorksheetFunction.Match(searchCATID, [completeList[CATID]], 0)
that should give out the proper Row # you are looking for. Notice that I jumped directly to rowCatID, becasue the Set rowCatSearch is no longer needed by only using the Match function with the Table Column in it.
Original Answer:
This is because you are trying to assign a value using the "worksheet row" returned by the MATCH function. However the DataBodyRange is only requesting the row number of the DATABODY, in this case, Row 1 in the DataBodyRange is the First Row of the Data and not the first row of the Worksheet.
Example: in a table that starts with Headers on A1 and data from A2 to A5, Worksheet Row 5 is actually Row 4 in the DataBodyRange, but your MATCH Function is giving you the worksheet row because that's where it found the value. As long as the Table starts at Row 1 you can do a -1 on rowCatID and it should work.

Related

Specify non equality in string comparison

Within a procedure I am writing, I check all values of different columns of a table to be equal (or not equal) to a value.
Thing is I do not know in advance how many columns I will check and what values I will check and does not want to have to ask for another array of value (like 0 and 1) to specify if the value given in the first array is to be equal or NOT equal to the table's column's value.
Here is an example:
With Workbooks(path).Worksheets(sheetName)
Set tbl = .ListObjects(tableName)
i = 1
For Each col In fColumn
colRange(i) = tblSource.ListColumns(col).DataBodyRange
i = i + 1
Next
Dim flag As Boolean
For i = 1 To .ListRows.Count
flag = True
For j = 1 To fColumn.UBound
If colRange(i).Value <> fColumnVal(i) Then ' check if stored value is equal to specific value
flag = False
Exit For
End If
Next j
If flag = True Then
' do something
End If
Next i
So fColumn is an array of string which contains the name of the column to be checked, colRange is the respective data range, fColumnVal is the value to be tested.
How I approached my problem is for each row of the table, I test the cell value of the different columns to be tested (names in fColumn) against the value of fColumnVal.
In theory that would work w/o problem. However, it could be possible that someone would like to find values which are different of the value in fColumnVal such as:
<<Find every cell which are different of value "Hello">>
My question is, is it possible to encompass the idea of check if not equal in the value of fColumnVal and not in the line of code of the condition. Something similar to the use of "<>" in Excel's formula which is equivalent <<to different of "">>.
If that does not exist I will simply add another required variable to the sub which indicates how to test the value.
Thanks.

Lookup 2 values in a table and write to a 3rd cell in the table

I have a table that has duplicate text values in each column that I need to write a value to.
I am using a form with 3 input boxes that are used to set the criteria to lookup and write a new value to.
I want to find the matching row based on the values on the form and write the new value to a cell.
My problem is, I can't figure out how to find the matching row using vba.
I've tried different things (index, match, vlookup) but just can't seem to get it figured out.
The picture below shows what I have now.
I need to update the QTY column in the table with the form's Coin Quantity value based off of the Exchange and Currency selected on the form.
So, in this case, I need to change Binance ETH Qty from 7 to 6.5
Form And Table
Thanks for any help!
---I've adjusted Courtney's answer below with the following:
Dim ws As Worksheet
Set ws = Worksheets("Data")
For Row = 2 To 50
Col = 14
If (ws.Cells(Row, Col) = ExchangeInputValue) And (ws.Cells(Row, Col + 1) = CurrencyInputValue) Then
'UpdateQty
'UpdateQty = boxQty.Value
'Qty = boxQty.Value
'UpdateQty.Value = boxQty.Value
'ws.Cells(Col + 2).Value = boxQty.Value
End If
Next Row
But nothing selects the Qty column based on the form inputs.
You would have to do a nested Vlookup or Index if you wanted to go those routes. Or you can just loop through each row with an if statement:
For row = start to finish
If (ws.cells(row,col) = ExchangeInputValue) and (ws.cells(row,col+1) = CurrencyInputValue) then
UpdateQTY
End if
Next row

How to set value in a cell based on another cell value from another column using formula or VBA?

I have two sheets:
Sheet 1 : consist of three columns (Status, Cost, Version)
Sheet 2 : Used as an Action Page with two columns (Formula, Version Number).
Question:
I want to set a version number in column two in sheet 2 and let the formula or the Button using VBA code to do the following:
The User set value = 5 in column 2 [Version Number] sheet 2
The System takes the value from [Version Number] cell
The System search for all rows containing the [Version Number] value = 5 , in sheet 1 column [Version]
The System Replace each cell in column [Status] having [Version] value= 5 , with Status Value = "Delivered".
Any help please either throw formula or using the VBA Code.
Regards
If that's all you want to achieve, and if you can use a formula in Sheet1, in the [Status] field, then probably all you want to do is to use the following formula:
=IF(C2 = 5, "Delivered", "Pending")
This is basically looking at your Version column and checks if your version number is 5, then change the text to Delivered, else use Pending (or whatever you need).
I would like to thank you all for your advice , after searching for the key code to know how to return the number of rows filled in a specific table , I found the solution and now I am able to do my logic. Below is my solution for the above question.
Steps:
Create Variable to hold the values
The below variable will hold the inserted value need to be updated
The below variable will hold the Item Version Column number in the table
The below variable will hold the value of each row under the column [Get_Version_Item_Column] to be used when comparing the values of each row
The below variable will hold the Item Progress Column number in the table
The below variable will hold the value of each row under the column [Get_Item_Progress_Column] to be used when comparing the values or each row
The below variable used to get the total number of used rows in the table
The below variable used as a counter to go throw a loop for all the rows
Create a Function that will do the below in Order:
Get Column Number from table 1 Sheet1 where header name = [Fixed in Version]
Get Column Number from table 1 Sheet1 where header name = [Item Progress]
Select Sheet 2 to get the value inserted in the Cell B1
Get Inserted value from the cell located inside the Sheet2
Set Counter value = 1 to start the loop from the first row after the header in table 1 Sheet 1
Open while Loop and set a condition if counter <= Total number of rows do the below
Start moving row by row in the table using the Cells Method : Cells(counter, Get_Version_Item_Column) ' in the cells method we set the [Row]=> counter variable which starts with value = 1 , [Column]=> Get_Version_Item_Column '
Get the value of the second column we want to put into the condition with the same way shown in Step 7
Start setting the condition usinf IF Else
If Get_Fixed_In_Version_Value = Get_Version_Inserted_Value And Get_Item_Progress_Value = "Approved" Then 'IF CORRECT NUMBER ' DO something Example [Replace the value in this row , this column , this cell with value = "" ]
Else ' Do something Else
Add 1 to the counter
Close the While Loop
Code Solution:
Public Get_Version_Inserted_Value As String
Public Get_Version_Item_Column As Integer
Public Get_Fixed_In_Version_Value As String
Public Get_Item_Progress_Column As Integer
Public Get_Item_Progress_Value As String
Public QATotal_Items_Row As Integer
Public counter As Integer
Function Change_Version_Item_Progress() As String
Get_Version_Inserted_Value = ThisWorkbook.Worksheets("Sheet2").Range("B1").Value2
Get_Version_Item_Column = Application.Match("Fixed in Version", Sheets("Sheet1").Rows(1), 0)
Get_Item_Progress_Column = Application.Match("Item Progress", Sheets("Sheet1").Rows(1), 0)
Sheets(Sheet1).Select
QATotal_Items_Row = WorksheetFunction.CountA(Range("B:B")) - 1
counter = 1
While counter <= QATotal_Items_Row
Get_Fixed_In_Version_Value = ThisWorkbook.Worksheets("Sheet1").Range("Table1").Cells(counter, Get_Version_Item_Column).Value2
Get_Item_Progress_Value = ThisWorkbook.Worksheets("Sheet1").Range("Table1").Cells(counter, Get_Item_Progress_Column).Value2
If Get_Fixed_In_Version_Value = Get_Version_Inserted_Value And Get_Item_Progress_Value = "Approved" Then
Get_Item_Progress_Value = ThisWorkbook.Worksheets("Sheet1").Range("Table1").Cells(counter, Get_Item_Progress_Column).Value = "Delivered"
Else
Get_Item_Progress_Value = ThisWorkbook.Worksheets("Sheet1").Range("Table1").Cells(counter, Get_Item_Progress_Column).Value = Get_Item_Progress_Value
End If
counter = counter + 1
Wend
End Function
Hope it will help.
Regards

Excel - finding unmatched data in order

I have 2 tabs of data with a unique identifier. The identifier is not in any particular order. I need my vlookup / index / match to show me all the identifiers that are not present in tab 2.
Reason: I am working where the systems they used failed a data transfer. I have to see what data there was compared to what data is currently on the system. Any data that is missing, i will need to add to the new system.
Example;
Tab1 Column A:
123456,
654321,
789456,
456789.
Tab2 Column B:
654321,
123456,
456789.
In Tab 3, I want excel to tell me that 789456 is not present in Tab 2.
As you can see in the above example, the unique identifier could be in any order, therefore i cannot put both columns side by side and ask to do a match between the 2 - i need it to look through the whole column.
All the tutorials i have seen assume that column A matches in order of column B
I have 70,000 rows to go through.
Any help would be appreciated.
Thanks in advance.
To do it with a formula you will want a helper column in the First tab.
In an empty column, I used column B, put the following in the second row:
=IF(ISERROR(VLOOKUP(A2,Sheet2!B:B,1,FALSE)),MAX($B$1:B1)+1,"")
This will create a column of numbers that increment on the ones not found in sheet two.
At this point you can simply filter on the new column for anything that in non blank and get your list.
If you want to do it with a formula in the Third tab then use this formula that refers to the helper column on the first tab:
=IFERROR(INDEX(Sheet1!A:A,MATCH(ROW(1:1),Sheet1!B:B,0)),"")
Then copy/drag down sufficient to get blanks.
With 70,000 items I would avoid array formulas as it will slow the calculation down and may even crash excel.
You could try using something like this:
=IFERROR(VLOOKUP(<value cell>, 'Tab2'!B:B, 1, FALSE), FALSE)<>FALSE
Copy all the values from tab 1 column A into tab 3 column A. In tab 3 column B, paste the above formula in every row where there is a value in column A, using referencing the cell from column A and the same row as the value cell. The formula will attempt to look up the value from tab 1 in tab 2. If it is missing, it will generate an error which is caught by the IFERROR function, which will return FALSE instead of letting the error escape. Finally, that FALSE is negated to return TRUE if the value is present in tab 2, and FALSE if the value is missing in tab 2.
From this point you can use a column filter in tab 3 to only see those rows with a TRUE value, that will only show you values that are present in both tab 1 and tab 2.
Soulution for this is COUNTIF() the formula would be:
=COUNTIF(Sheet1!A:A,Sheet2!A1)
After applying that for all rows, just filter those that have value 0.
This macro will produce a compact list in Sheet3:
Sub WhatsMissing()
Dim s1 As Worksheet, s2 As Worksheet, s3 As Worksheet
Dim r1 As Range, N As Long, K As Long, i As Long
Dim v As Variant
Set s1 = Sheets("Sheet1")
Set s2 = Sheets("Sheet2")
Set s3 = Sheets("Sheet3")
Set r2 = s2.Range("B:B")
K = 1
N = s1.Cells(Rows.Count, "A").End(xlUp).Row
With Application.WorksheetFunction
For i = 1 To N
v = s1.Cells(i, "A").Value
If .CountIf(r2, v) = 0 Then
s3.Cells(K, "A").Value = v
K = K + 1
End If
Next i
End With
End Sub

Sort values in an Excel spreadsheet

I've got a spreadsheet full of names and peoples' roles, like the one below:
Role Name Change
1 A Yes
2 A No
5 A N/Ap
1 B Yes
3 B No
2 C Yes
4 C No
I have to come up with a spreadsheet like the one below:
1 2 3 4 5 6
A Yes
B
C
Basically, it should retrieve the information from the first spreadsheet and be layed out clearly on the second one.
There are way too many names and roles to do it manually. VLMOVE won't work and I've tried MATCH and INDEX.
Alternative to #RocketDonkey (but thanks for more complete desired result!) could be to string together Role and Name (say in a column inserted between B & C in Sheet1 [because I think OP wants a separate sheet for the results]):
C2=A1&B2 copied down as required
then use a lookup in Sheet2!B2:
=IFERROR(VLOOKUP(B$1&$A2,Sheet1!$C$2:$D$8,2,FALSE),"")
copied across and down as required.
This assumes the grid for the results (as in question) has been constructed (and that there are 7 rows with data - adjust $8 as necessary otherwise.)
Agree with #Melanie that if you can force your data into a structure that can be interpreted as numbers (1 being yes, 0 being false, for example), Pivot tables are far and away the easiest way (since they will display numbers as the values - not the text). *(see below)
If you want to display arbitrary text, you could try this:
=IF(
SUMPRODUCT(--($A$2:$A$8=F$1),--($B$2:$B$8=$E2),ROW($A$2:$A$8))=0,"",
INDEX(
$A$1:$C$8,
SUMPRODUCT(--($A$2:$A$8=F$1),--($B$2:$B$8=$E2),ROW($A$2:$A$8)),
3))
This checks to see if the SUMPRODUCT of the three columns totals 0 (which will happen when no combo of x/y is matched (like Name: C, Role: 5, for instance), and if so, it returns "". Otherwise, it returns the value in column Value.
*A ‘pivot table option’ would be to represent the Change as a number (eg as formula in D2 copied down). Then create a pivot table from (in the example) A1:D8, with fields as shown. Copy the pivot table to a different sheet with Paste Special/Values (though shown in F11:K15 of same sheet in example). Then in that other sheet select row starting with Name A and as far down as required, Replace -1 with No, 1 with Yes and 0 with N/Ap.
AMENDED
You can use array formulas to reorganize your table, without having to change the its structure. Assuming the data is in the range A2:C8 on Sheet1 and the result table is to be in range A1:G4 on Sheet2, the following formula would be the first entry (role 1 and name A) in the result table.
=IFERROR(INDEX(Sheet1!$A$2:$C$8,MATCH(B$1&$A2,Sheet1!$A$2:$A$8&Sheet1!$B$2:$B$8,0),3),"-")
The MATCH formula returns the row number in which the role/name combination 1A occurs. The INDEX function returns the contents of the cell at the row number found by the MATCH formula and the column number 3, i.e., the Change column of your data table. The IFERROR returns "-" if the role/name combination is not in the data table.
Be sure to enter the formula using the Control-Shift-Enter key combination. Then copy the formula to the remaining cells of the result table.
The data table on Sheet1:
The result table on Sheet2:
Well since there's Excel-VBA tag, thought it would complete the solutions types by adding one in VBA :) The following code is not elegant, in any case you need to use code base, give it a try :)
Code:
Option Explicit
Public Sub sortAndPivot()
Dim d As Object
Dim ws As Worksheet
Dim sourceArray As Variant, pvtArray As Variant, v As Variant
Dim maxRole As Long
Dim i, j, k, m As Integer
Set d = CreateObject("Scripting.Dictionary")
Set ws = Worksheets("Sheet3") '-- set according to your sheet
'-- you could enhance by using an input box to select the range
sourceArray = Application.WorksheetFunction.Transpose(ws.Range("B3:D9").Value)
'-- max role number
maxRole = Application.WorksheetFunction.Max(ws.Range("B3:B9"))
'-- find unique name list
For i = LBound(sourceArray, 2) To UBound(sourceArray, 2)
If Not d.exists(sourceArray(2, i)) Then
d.Add sourceArray(2, i), i
End If
Next i
ReDim pvtArray(d.Count, maxRole)
pvtArray(0, 0) = "Name"
'-- add unique names from dictionary
j = 1
For Each v In d.keys
pvtArray(j, 0) = v
j = j + 1
Next
'-- add unique Role number list
For i = UBound(pvtArray, 2) To LBound(pvtArray) + 1 Step -1
pvtArray(0, i) = i
Next i
'-- sort into the correct positions
For k = LBound(pvtArray, 1) + 1 To UBound(pvtArray, 1)
For m = LBound(pvtArray, 2) + 1 To UBound(pvtArray, 2)
For i = LBound(sourceArray, 2) To UBound(sourceArray, 2)
If pvtArray(k, 0) = sourceArray(2, i) Then
If pvtArray(0, m) = sourceArray(1, i) Then
pvtArray(k, m) = sourceArray(3, i)
End If
End If
Next i
Next m
Next k
'Output the processed array into the Sheet in pivot view.
Range("F2").Resize(UBound(pvtArray) + 1, _
UBound(Application.Transpose(pvtArray))) = pvtArray
Set d = Nothing
End Sub
Results:
There is another way to go about it without VBA. If you create another column that concatenates the first two in the first spreadsheet, like so:
Role Name Change CheckColumn
1 A Yes 1A
2 A No 2A
5 A N/Ap 5A
1 B Yes 1B
3 B No 3B
2 C Yes 2C
4 C No 4C
Then you can use the Offset and Match functions together to find the change in the 2nd sheet. So assuming your data is laid from cell A1, the formula in cell B2 would be:
=iferror(offset(Sheet1!$A$1,match(B$1&$A2,sheet1!$D:$D,0),2),"")
Alternatively, if you put the concatenated column in sheet1 before the role column, you can use vlookup in sheet2, with the formula being:
=iferror(vlookup(B$1&$A2,sheet1!$A:$D,4,false),"")

Resources