I'm trying to store data from Excel sheet into an array.
The data looks like this:
The code I use:
Sub StoreData()
Dim Data() As String
'Count number of Line in Sheet1
Sheet1_size = Worksheets("Sheet1").Range("A1").CurrentRegion.Rows.Count
'Array to store data
ReDim Data(1 To Sheet1_size - 1, 1 To 6) As String
'storing data into array
For i = 1 To Sheet1_size - 1
With Worksheets("Sheet1")
Data(i, 1) = .Cells(i + 1, Application.Match("Name", .Rows(1), 0))
Data(i, 2) = .Cells(i + 1, Application.Match("Sex", .Rows(1), 0))
Data(i, 3) = .Cells(i + 1, Application.Match("Age", .Rows(1), 0))
Data(i, 4) = .Cells(i + 1, Application.Match("Nationality", .Rows(1), 0))
Data(i, 5) = .Cells(i + 1, Application.Match("License", .Rows(1), 0))
Data(i, 6) = .Cells(i + 1, Application.Match("Hand", .Rows(1), 0))
End With
Next i
End Sub
Everything works perfectly when the sheet1 looks like above.
However, order and number of columns may differ each time in sheet1. For example it might be: Name Age Nationality or Name License Hand Sex Age Nationality or Nationality Age and etc. This table is filled in by people so they may forget to include some variables.
If any column is missing I get an error below:
What I'd like to to is to show message/message box with the name of column which is missing instead of this error. If there are several missing columns I'd like to message all the missing names.
Disabling errors is not a solution because there are no messages delivered on screen. Any solutions?
Here is the solution I'd propose:
Option Explicit
Option Compare Text
Public Sub StoreData()
Dim ws As Worksheet
Dim Data As Variant
Dim LastRow As Long, LastColumn As Long
Dim nColumn As Long, RequirementCount As Long, CheckCount As Long
Dim RequirementList() As String, ErrorMessage As String
'Determine the range
Set ws = ThisWorkbook.Worksheets("Sheet1")
LastRow = ws.Cells.Find(What:="*", LookIn:=xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
LastColumn = ws.Cells.Find(What:="*", LookIn:=xlFormulas, LookAt:=xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Row
'Range to array
Data = ws.Range(ws.Cells(1, 1), ws.Cells(LastRow, LastColumn)).Value2
'Set requirements
RequirementList = Split("Name|Nationality|Age|License|Hand|Sex", "|")
'Compare all available columns against the requirements
For nColumn = 1 To UBound(Data, 2)
For RequirementCount = LBound(RequirementList) To UBound(RequirementList)
If Data(1, nColumn) = RequirementList(RequirementCount) Then
RequirementList(RequirementCount) = vbNullString
CheckCount = CheckCount + 1
End If
Next RequirementCount
Next nColumn
'If less then the required 6 columns were found then pass a message box to the user telling him/her about it
If CheckCount <> 6 Then
ErrorMessage = "The following columns are missing:" & Chr(10)
For RequirementCount = LBound(RequirementList) To UBound(RequirementList)
ErrorMessage = ErrorMessage & IIf(RequirementList(RequirementCount) = vbNullString, "", " -" & RequirementList(RequirementCount) & Chr(10))
Next RequirementCount
MsgBox ErrorMessage
Else
MsgBox "All columns are accounted for and ready for import."
End If
End Sub
Check out the comments in the code for more information. Also, note the importance of Option Compare Text at the top of the code to ensure that Age = age = aGe, etc.
Related
I've got a problem. I' m trying to match specific values by item_id using hlookup function. But this function does not return specified value.
Here is the code of my macro :
Sub create_report()
Dim itemWs As Worksheet, offerWs As Worksheet, testWs As Worksheet
Dim itemLastRow As Long, offerLastRow As Long
Dim offerLastCol As Long, itemLastCol As Long
Dim dataRng As Range
Set itemWs = ThisWorkbook.Worksheets("nn_rfx_compare_per_lot")
Set offerWs = ThisWorkbook.Worksheets("Offers")
Set testWs = ThisWorkbook.Worksheets("Testowy")
itemLastRow = itemWs.Range("A" & Rows.Count).End(xlUp).Row
offerLastRow = offerWs.Range("A" & Rows.Count).End(xlUp).Row
offerLastCol = offerWs.Cells(1, Columns.Count).End(xlToLeft).Column
itemLastCol = itemWs.Cells(1, Columns.Count).End(xlToLeft).Column
Set dataRng = testWs.Range("I3:AF" & 4)
'For x = 2 To 7
'On Error Resume Next
'itemWs.Range("I" & x).Value = Application.WorksheetFunction.VLookup(itemWs.Range("C" & x).Value & itemWs.Range("B" & x).Value, dataRng, 3, 0)
'Next x
Sheets("Testowy").Range(Sheets("Testowy").Cells(offerLastCol - 1, 1), Sheets("Testowy").Cells(itemLastRow + 4, itemLastCol)) = _
Sheets("nn_rfx_compare_per_lot").Range(Sheets("nn_rfx_compare_per_lot").Cells(1, 1), Sheets("nn_rfx_compare_per_lot").Cells(itemLastRow, itemLastCol)).Value
Sheets("Testowy").Range(Sheets("Testowy").Cells(1, itemLastCol), Sheets("Testowy").Cells(offerLastCol - 2, offerLastRow - 2)) = _
WorksheetFunction.Transpose(Sheets("Offers").Range(Sheets("Offers").Cells(1, 2), Sheets("Offers").Cells(offerLastRow, offerLastCol - 1)))
Dim lastTestCol As Long
lastTestCol = testWs.Cells(1, Columns.Count).End(xlToLeft).Column
Dim ColumnLetter As String
For Row = 6 To 11
For Col = 9 To lastTestCol
On Error Resume Next
testWs.Cells(Row, Col).Value = Application.WorksheetFunction.Index(testWs.Range( _
"I4:AF4"), WorksheetFunction.Match(testWs.Cells(Row, 3).Value, testWs.Cells(3, Col), 0))
'Match(testWs.Cells(Row, 3), dataRng, 1)
'HLookup(testWs.Cells(Row, 3), dataRng, 2, 0)
Next Col
Next Row
End Sub
In this link there is shown a report which I'd like to organise
enter image description here
The task and conditions are not completely clear (what to do with duplicates, whether they can occur, whether item_id is unique and so on).
If, for example, you need to select sup_id corresponding to item_id, it can be done by the following code:
Set item_id_rng = testWS.Range("I3:AF3")
For Row = 6 To 11
' search `item_id` in Range("I3:AF3")
find_col = Application.Match(testWS.Cells(Row, 3).Value, item_id_rng, 0)
If IsNumeric(find_col) Then ' if found, get correspondent value from correspondent row
'output to 9 column (empty area), for example
testWS.Cells(Row, 9).Value = item_id_rng(1).Offset(-1, find_col - 1)
End If
Next Row
As for the task as a whole, it would be good if you formulated the conditions of the task and placed an image of the result
My code seems to not be working, and I'm not sure why?
Sub Concat()
'Formula to combine the member AC# and Parish Name
Sheets("Risk Partner Data").Select
Dim ACParish As String, i As String
Dim rng As Range
Set rng = Range("A" & Rows.Count).End(x1Up)
ACParish = rng.Row
For i = 2 To ACParish
AcrtiveWorkbook.Sheets("Calc Data").Cells(i, 1) = Cells(i, 1) & Cells(1, 2)
Next i
End Sub
Says that Compile error, Type mismatch and highlights the "i" in For i = 2
My objective:
In another sheet (Risk Partner Data) I have Columns F & E, these are a mixture of text and numbers. I want it to run for all of the active cells in the columns.
I'm new to vba.
i is being used as an integer in the For ... Next but you've declared it as a string; it should be a Long. Same for ACParish.
There is a typo in AcrtiveWorkbook.
You don't need to .Select a worksheet in order mto access it's values.
Should ... = .Cells(i, 1) & .Cells(1, 2) be ... = .Cells(i, 1) & .Cells(i, 2)?
Sub Concat()
'Formula to combine the member AC# and Parish Name
Dim ACParish As long, i As long
with workSheets("Risk Partner Data")
ACParish = .Range("A" & Rows.Count).End(xlUp).row
For i = 2 To ACParish
.parent.workSheets("Calc Data").Cells(i, 1) = .Cells(i, 1) & .Cells(1, 2)
Next i
end with
End Sub
I am trying to set the first 7 characters of the first cell in a range to be a named ranges name IF the cell begins with the word "kit".
Here is what I have so far:
Sub DefineRanges()
Dim rngStart As Range
Set rngStart = Range("A1")
Dim LastRow As Integer
Dim RangeName As String
For Each cell In Range("A2:A7")
If LCase(Left(cell.Value, 3)) = "kit" Then
RangeName = LCase(Left(cell.Value, 7))
ActiveWorkbook.Names.Add _
Name:=RangeName, _
RefersToLocal:=Range(rngStart.Address & ":C" & cell.Row - 1)
Set rngStart = Range("A" & cell.Row)
End If
LastRow = cell.Row
Next
RangeName = LCase(Left(cell.Value, 7))
ActiveWorkbook.Names.Add _
Name:=RangeName, _
RefersToLocal:=Range(rngStart.Address & ":C" & LastRow)
End Sub
Essentially I want it to look through my overall Range, find any cells that begin with the word "kit", create a named range that goes from that cell until the next cell that begins with "kit", and assign the first 7 characters of that cell to be the ranges name. So far I am able to get it to create the ranges, but I run into issues when I try to pass the contents of the cell into the range name. Any ideas?
This assumes that you data is similar to your last question.
It uses Match to find each "Kit..." saving a few iterations:
Sub DefineRanges()
Dim rngStart As Long
Dim RangeName As String
Dim col As Long
Dim PreFx As String
col = 1 'change to the column number you need
PreFx = "kat" 'change to the prefix you are looking for
With Worksheets("Sheet7") 'change to your sheet
On Error Resume Next
rngStart = Application.WorksheetFunction.Match(PreFx & "*", .Columns(col), 0)
On Error GoTo 0
If rngStart > 0 Then
Do
i = 0
On Error Resume Next
i = Application.WorksheetFunction.Match(PreFx & "*", .Range(.Cells(rngStart + 1, col), .Cells(.Rows.Count, col)), 0) + rngStart
On Error GoTo 0
If i > 0 Then
RangeName = LCase(Left(.Cells(rngStart, col).Value, 7))
ActiveWorkbook.names.Add name:=RangeName, RefersToLocal:=.Range(.Cells(rngStart, col), .Cells(i - 1, col + 2))
rngStart = i
Else 'no more "kit..." so find the last row with data and use that
i = Application.WorksheetFunction.Match("zzz", .Columns(col))
RangeName = LCase(Left(.Cells(rngStart, 1).Value, 7))
ActiveWorkbook.names.Add name:=RangeName, RefersToLocal:=.Range(.Cells(rngStart, col), .Cells(i - 1, col + 2))
End If
Loop While i < Application.WorksheetFunction.Match("zzz", .Columns(col))
End If
End With
End Sub
I am trying to improve some code I did not write, mostly to make it easier for other people to understand (I found it to be really weirdly written). I attempted to rewrite it, and it does basically the same thing, using roughly the same processes.
However the old one takes 2 minutes to run with some data I recorded. The other takes well over an hour to work on the same data. What on earth is going on?
The data format they work on is:
Edit: I should add, I would use dictionaries, as I think they would be fastest, except the number of "TextX" is basically random. Sometimes only Text1 appears. Sometimes it goes all the way to Text20, or more.
(sorry for impending wall of code, and sorry if I got the image link format wrong - never done that before)
Old code:
sub DivideSheet()
Application.ScreenUpdating = False
Dim name As String
Dim point_name As String
Dim SheetCount, sheetNumber As Integer
Dim RowCount, RowStart As Long
Dim Exist, RowEmpty As Boolean
Sheets("RAW DATA").Columns("A:A").TextToColumns Destination:=Range("A1"), DataType:=xlFixedWidth, _
FieldInfo:=Array(Array(0, 1), Array(12, 1), Array(13, 1), Array(22, 1), Array(23, 1), Array(27, 1), _
Array(31, 1), Array(36, 1)), TrailingMinusNumbers:=True
Exist = False
RowCount = 1
RowStart = 1
SheetCount = 1
point_name = Worksheets(1).Cells(1, 3)
For RowCount = 1 To 1048576 'loop through all the rows in the sheet
If CStr(Sheets(1).Cells(RowCount, 3)) = "" Then 'If the cell isnt a new transmition
'do nothing
ElseIf CStr(Sheets(1).Cells(RowCount, 3)) = point_name Then 'If the new transmition is from the same node
'Do nothing
Else 'If its a new node
For SheetCount = 1 To Sheets.Count 'loop through sheets
If Worksheets(SheetCount).name = point_name Then 'If the sheet name matches point_name
Exist = True 'set flag to true
sheetNumber = SheetCount 'Record Sheet number
Exit For 'Exit the for loop
End If
Next SheetCount
If Exist = False Then 'If the Node didnt have a sheet
sheetNumber = SheetCount
Worksheets.Add After:=Worksheets(SheetCount - 1) 'Create a sheet
Worksheets(Sheets.Count).name = point_name 'Name it for the RTU
End If
Call CopyLine(sheetNumber, RowStart, RowCount - 1) 'Call the Copying function to copy the chunk of data
RowStart = RowCount 'Set a new start point for the next chunk of data
point_name = Worksheets(1).Cells(RowCount, 3) 'Set the Node the chunk of data will belong to
SheetCount = 1 'Reset Variable
Exist = False 'Reset Variable
End If
Next RowCount
SheetCount = 1 'Resets the SheetCount Variable
For SheetCount = 1 To Sheets.Count 'Loops through Sheets
Sheets(SheetCount).Columns("H:H").EntireColumn.AutoFit 'Autofits the H column
Next SheetCount
End Sub
Public Sub CopyLine(sheetNumber As Integer, RowStart As Long, rowNumber As Long)
If Sheets(sheetNumber).Range("A1").Value = "" Then 'If its the first data to enter the sheet
'Copy the chunk of data
Sheets(1).Range("a" & RowStart, "h" & rowNumber).Copy _
Destination:=Sheets(sheetNumber).Range("A1", Cells(rowNumber - RowStart + 1, "H"))
ElseIf Sheets(sheetNumber).Range("H2").Value = "" Then 'If its the Second data to enter the sheet
'Copy the chunk of data
Sheets(1).Range("a" & RowStart, "h" & rowNumber).Copy _
Destination:=Sheets(sheetNumber).Range("A2", Cells(rowNumber - RowStart + 2, "H"))
Else 'otherwise
'Copy the chunk of data
Sheets(1).Range("a" & RowStart, "h" & rowNumber).Copy _
Destination:=Sheets(sheetNumber).Range("h1048576").End(xlUp).Offset(1, -7)
End If
End Sub
and my code...
sub DivideSheet()
Application.DisplayAlerts = False
Application.ScreenUpdating = False
Dim Point As String
Set wb1 = ThisWorkbook
wb1.Sheets("RAW DATA").Columns("A:A").TextToColumns Destination:=Range("A1"), DataType:=xlFixedWidth, _
FieldInfo:=Array(Array(0, 1), Array(12, 1), Array(13, 1), Array(22, 1), Array(23, 1), Array(27, 1), _
Array(31, 1), Array(36, 1)), TrailingMinusNumbers:=True
'loop through all the rows in the sheet
For i = 1 To wb1.Sheets(1).UsedRange.Rows.Count
Point = wb1.Sheets(1).Cells(i, 3)
If wb1.Sheets(1).Cells(i, 3) <> "" Then
a = sheetexists(Point)
If a Then
Call CopyLine(a, i, Point)
Else
wb1.Sheets.Add after:=wb1.Sheets(Sheets.Count)
wb1.Sheets(Sheets.Count).name = Point
Call CopyLine(wb1.Sheets.Count, i, Point)
wb1.Sheets(Sheets.Count).Rows(1).Delete
End If
End If
Next i
For Each wsh In wb1.Sheets 'Loops through Sheets
wsh.Columns.AutoFit 'Autofits everything
Next wsh
End Sub
Public Sub CopyLine(a, i, Point)
Set wb1 = ThisWorkbook
j = i + 1
While (wb1.Sheets(1).Cells(j, 3) = "" Or wb1.Sheets(1).Cells(j, 3) = Point) And j <= wb1.Sheets(1).UsedRange.Rows.Count 'WorksheetFunction.CountA(wb1.Sheets(1).Rows(i)) = 0
j = j + 1
Wend
wb1.Sheets(1).Range(wb1.Sheets(1).Cells(i, 1), wb1.Sheets(1).Cells(j - 1, 8)).Copy Destination:=wb1.Sheets(a).Cells(wb1.Sheets(a).UsedRange.Rows.Count, 1).Offset(1, 0)
i = j - 1
End Sub
Function sheetexists(Point)
Dim a As Integer
Set wb1 = ThisWorkbook
sheetexists = 0
For a = 1 To wb1.Sheets.Count
If wb1.Sheets(a).name = Point Then
sheetexists = a
Exit Function
End If
Next a
End Function
I'm not crazy, right? There is no fundamental difference between these two, other than mine being fewer lines, less circuitous, and generally not being wasteful? So why is my new code taking so much longer?
I need to compare 1 worksheet (Sheet1) to another similar worksheet (Sheet2)
Sheet2 contains up to date information,which needs to be transferred to Sheet1.
However, I've run into a couple of problems:
There are some rows in Sheet1 that are not Sheet2. These need to be ignored/skipped over
There are some rows in Sheet2 that are not Sheet1. These need to be appended to the end of Sheet1
If a row exists in both Sheets, the information from the row sheet 2 needs to be transferred to the corresponding row in Sheet1
For what its worth, they have same number of columns and the column titles are exactly the same.
I've tried using a dictionary object to accomplish this but am still having all sorts of trouble.
Here's the code I have tried thus far:
Sub createDictionary()
Dim dict1, dict2 As Object
Set dict1 = CreateObject("Scripting.Dictionary")
Set dict2 = CreateObject("Scripting.Dictionary")
Dim maxRows1, maxRows2 As Long
Dim i, ii, j, k As Integer
maxRows1 = Worksheets("Sheet1").Range("A65000").End(xlUp).Row
For i = 2 To maxRows1
Dim cell1 As String
cell1 = Worksheets("Sheet1").cells(i, 2).Text & " " & Worksheets("Sheet1").cells(i, 11).Text
If Not dict1.Exists(cell1) Then
dict1.Add cell1, cell1
End If
Next i
maxRows2 = Worksheets("Sheet2").Range("A65000").End(xlUp).Row
For ii = 2 To maxRows2
Dim cell2 As String
cell2 = Worksheets("Sheet2").cells(ii, 11).Text
If Not dict2.Exists(cell2) Then
dict2.Add cell2, cell2
End If
Next ii
Dim rngSearch1, rngFound1, rngSearch2, rngFound2 As Range
For j = 2 To maxRows1
Dim Sheet1Str, Sheet2Str As String
Sheet1Str = Worksheets("Sheet1").cells(j, 2).Text & " " & Worksheets("Sheet1").cells(j, 11).Text
Sheet2Str = Worksheets("Sheet2").cells(j, 11).Text
If dict2.Exists(Sheet1Str) = False Then
'ElseIf Not dict1.Exists(Sheet2) Then
'
' Worksheets("Sheet2").Range("A" & j & ":" & "Z" & j).Copy
' Worksheets("Sheet1").Range("A" & maxRows1 + 1).Insert
' Worksheets("Sheet1").Range("A" & maxRows1 + 1).Interior.Color = RGB(255, 255, 0)
' Worksheets("Sheet1").Range("U" & maxRows1 + 1) = "INCH"
' Worksheets("Sheet1").Range("Q" & maxRows1 + 1) = "FPM"
' Worksheets("Sheet1").Range("S" & maxRows1 + 1) = "INCHES WIDE"
' Worksheets("Sheet2").Range("K" & j) = Replace(Worksheets("Sheet2").Range("K" & j), Worksheets("Sheet2").Range("B" & j), "")
' Worksheets("Sheet1").Range("K" & maxRows1 + 1) = Trim(Worksheets("Sheet2").Range("K" & j))
Else
For k = 3 To 6
If Not k = 11 Then
If Not UCase(Worksheets("Sheet1").cells(j, k).Value) = UCase(Worksheets("Sheet2").cells(j, k).Value) Then
Worksheets("Sheet1").cells(j, k).Value = Worksheets("Sheet2").cells(j, k).Value
End If
End If
Next k
End If
Next j
End Sub
Cool question, and the "does row order matter" question above lends itself nicely to using Excel's built in Range.RemoveDuplicates method. Let's get into it...
Suppose Sheet1 looks like this:
Let's say Sheet2 looks like this:
All the conditions that are described in your original question are met here. Namely:
There are rows on Sheet1 that are not on Sheet2 (row 2, for example). These will be left alone.
There are rows on Sheet2 that are not on Sheet1 (row 2, for example). These will be added to Sheet1.
There are rows that are the same on Sheet2 and Sheet1, save for a single update. (Row 7 on Sheet2, for example.) These rows will be updated on Sheet1. Of course, your situation will be different -- perhaps more columns might be updated, or they might not be in column E like my example -- you'll need to do a bit of customization here.
The following heavily-commented script walks through copying data from Sheet2 to Sheet1, then letting Excel's built-in Range.RemoveDuplicates method kill all of the rows that have been updated in column E. The script also makes use of a couple handy functions: LastRowNum and LastColNum.
Option Explicit
Sub MergeSheetTwoIntoSheetOne()
Dim Range1 As Range, Range2 As Range
Dim LastRow1 As Long, LastRow2 As Long, _
LastCol As Long
'setup - set references up-front
LastRow2 = LastRowNum(Sheet2)
LastRow1 = LastRowNum(Sheet1)
LastCol = LastColNum(Sheet1) '<~ last col the same on both sheets
'setup - identify the data block on sheet 2
With Sheet2
Set Range2 = .Range(.Cells(2, 1), .Cells(LastRow2, LastCol))
End With
'setup - identify the data block on sheet 1
With Sheet1
Set Range1 = .Range(.Cells(2, 1), .Cells(LastRow1, LastCol))
End With
'step 1 - move the data block on sheet 1 down the sheet
' to allow room for the data block from sheet 2
Range1.Cut Destination:=Sheet1.Cells(LastRow2 + 1, 1)
'step 2 - move the data block from sheet 2 into the recently-
' cleared space on sheet 1
Range2.Copy Destination:=Sheet1.Cells(2, 1)
'step 3 - find the NEW last row on sheet 1
LastRow1 = LastRowNum(Sheet1)
'step 4 - use excel's built-in duplicate removal to
' kill all dupes on every column EXCEPT for those
' that might have been updated on sheet 2...
' in this example, Column E is where updates take place
With Sheet1
Set Range1 = .Range(.Cells(2, 1), .Cells(LastRow1, LastCol))
Range1.RemoveDuplicates Columns:=Array(1, 2, 3, 4), Header:=xlYes
End With
End Sub
'this handy function allows us to find the last row with a one-liner
Public Function LastRowNum(Sheet As Worksheet) As Long
If Application.WorksheetFunction.CountA(Sheet.Cells) <> 0 Then
LastRowNum = Sheet.Cells.Find(What:="*", _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious).Row
Else
LastRowNum = 1
End If
End Function
'this handy function allows us to find the last column with a one-liner
Public Function LastColNum(Sheet As Worksheet) As Long
If Application.WorksheetFunction.CountA(Sheet.Cells) <> 0 Then
LastColNum = Sheet.Cells.Find(What:="*", _
LookIn:=xlFormulas, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious).Column
Else
LastColNum = 1
End If
End Function
Running this script results in the following: