I need to check if values (approx 10.000 insecure passwords) stored in a one-dimensional array occur in a TextBox and display a warning if necessary.
But due to the constant checking after each keystroke the input into the TextBox is very delayed.
Does anyone know a faster way to do this?
Greetings Ronny
'ForbiddenPasswords = one-dimensional array
Private Sub txtAdminPassword_Change()
Dim VarDat As Variant
For Each VarDat In ForbiddenPasswords
If InStr(1, LCase(txtAdminPassword.Text), VarDat) > 0 Then
lblInsecureWarning.Visible = True
Exit For
End If
Next
End Sub
Rather than loading the list of banned passwords in an array and searching through it to find if it exists, consider using a table to store the (lower case) passwords. You can then open a recordset based on the text entered, and see if it returns any records. Something like:
Function fCheckBannedPWD(strPWD As String) As Boolean
' returns true if the entered password is banned, otherwise false
On Error GoTo E_Handle
Dim db As DAO.Database
Dim rsPWD As DAO.Recordset
Dim strSQL As String
Set db = CurrentDb
strSQL = "SELECT BannedPWD FROM tblPWD WHERE InStr('" & LCase(strPWD) & "',BannedPWD)>0;"
Set rsPWD = db.OpenRecordset(strSQL)
If Not (rsPWD.BOF And rsPWD.EOF) Then
fCheckBannedPWD = True
End If
fExit:
On Error Resume Next
rsPWD.Close
Set rsPWD = Nothing
Set db = Nothing
Exit Function
E_Handle:
MsgBox Err.Description & vbCrLf & vbCrLf & "fCheckBannedPWD", vbOKOnly + vbCritical, "Error: " & Err.Number
Resume fExit
End Function
Regards,
Arrays want to be iterated with For...Next; Object collections want to be iterated with For Each...Next. Using For Each to iterate a large array is going to be slower than it needs to be.
Converting 10K strings to lowercase is also taking a toll on performance.
Using InStr is verifying whether the password contains the forbidden password, not whether it is one of them: assuming you mean to check if the user's password exists in the forbidden passwords list, you don't need InStr.
If you want to stick with an array, use For...Next to iterate it, and specify Option Compare Text to have the = operator treat "string" the same as "STRING" (default is Option Compare Database in Access, which matches your database settings - in all other hosts it's Option Compare Binary, which is case-sensitive).
But you shouldn't need to iterate any array. Assuming the 10K strings are all unique, you can have them added to a keyed Collection, and then you can instantly know if the user's password is in the collection by trying to add the user's password to the collection:
Private Function IsForbiddenPassword(ByVal Value As String) As Boolean
'assuming a ForbiddenPasswords global collection exists
On Error Resume Next
ForbiddenPasswords.Add Value, Value 'if the key already exists, an error is raised
If Err.Number <> 0 Then
'key already exists: password is forbidden
IsForbiddenPassword = True
Else
'.Add was successful, password is not forbidden (gotta remove it now!)
ForbiddenPasswords.Remove Value
End If
On Error GoTo 0
End Function
Alternatively, use a Scripting.Dictionary and its Exists method, which returns True if a given key already exists:
Private Sub txtAdminPassword_Change()
'where ForbiddenPasswords is a Dictionary instance keyed with the passwords:
lblInsecureWarning.Visible = ForbiddenPasswords.Exists(Value)
End Sub
Try the next way, please:
Create a Private variable on top of the module (in the declarations area):
Private objExcel As Object
Copy the next function code:
Function passExists(strPass As String, arr) As Boolean
Dim lPath As String
lPath = UCase(strPass)
passExists = Not IsError(objExcel.Application.match(lPath, arr, 0))
End Function
It can be checked in the next way:
Sub checkPasswordInArray()
Dim ForbiddenPasswords, boolMatch As Boolean, strPass As String
If objExcel Is Nothing Then Set objExcel = CreateObject("Excel.Application")
ForbiddenPasswords = Split("pass1,pass2,pass3", ",")
strPass = "Pass2" 'txtAdminPassword.Text
Debug.Print passExists(strPass, ForbiddenPasswords)
End Sub
In order to be very fast, objExcel must not be created each time you need to use the function, but you must take care to quit after using it...
Just to be complete, 2 additions to the propositions found.
txtAdminPassword.Text LIKE vardat & "*" is about 6x faster than
InStr(1, LCase(txtAdminPassword.Text), VarDat) > 0
for even better perf with your array, you can use Filter:
Dim strSubNames As Variant
strSubNames = Filter(forbiddenPasswords, txtAdminPassword.Text)
Related
I am trying to merge several bits of data from different sheets. After a few questions and attempts (at arrays, thanks to Stackoverflow before for help with this), i think a dictionary may be best. The final outcome is a populated table that holds all data for each individual entry. (depending on the entry the data in a column in raw data could be in different locations)
The data can include multiple entries for one person. But the data for each entry is different depending on the stage of entry. For example, if the data in column 3 would be in column 5 of the final table if a condition was stage 1, however if condition was stage 2, the same data that was in column 3 could be column 10 of the final table.
https://www.youtube.com/watch?v=o8fSY_4p93s
Following this video tutorial ondictionaires, i think i could o through the dtaa and find each individual entry and then add the corresponding variables for the case. E.g. find data for Steve Smith, if steve smith exists then if stage 1, add data to variable stagedate1, if stage2 add data to stage2date and so on. If not found add entry and find the stage.
Similar to the video, where he finds the corresponding data for each customer, and adds sales and volumes, i could do the same if an if function is round before to identify which datastage and to then put the value in correct variable.
I know there will be a million way to do it, but this seems simple and effective.
Sub Dictionary()
Dim dict As Dictionary
Set dict = ReadData()
Call WriteDict(dict)
End Sub
Function ReadData()
Dim dict As New Dictionary
Dim DataWs As Worksheet: Set DataWs = ThisWorkbook.Sheets("DATA")
Dim PoolOfWeekWs As Worksheet: Set PoolOfWeekWs = ThisWorkbook.Sheets("Pool of the week")
Dim LastrowData As Long: LastrowData = DataWs.range("A" & Rows.Count).End(xlUp).Row
Dim LastColData As Long: LastColData = DataWs.Cells(1 & DataWs.Columns.Count).End(xlToLeft).Column
Dim LastColDataString As String: LastColDataString = Split(Cells(1, LastColData).Address, "$")(1)
Dim DataRange As range: Set DataRange = DataWs.range("A1:" & LastColDataString & LastrowData)
Dim DataArr As Variant: DataArr = DataWs.range("A1:AO" & LastrowData)
Dim range As range: Set range = DataWs.range("A1").CurrentRegion
Dim i As Long
Dim CandidateProcessID As String, CandidateName As String, FirstName As String, ProcessStatus As String, PQLDate As Date, oCandidate As ClsCandidate
For i = 2 To range.Rows.Count
CandidateProcessID = range.Cells(i, 10).Value
CandidateName = range.Cells(i, 16).Value
FirstName = range.Cells(i, 17).Value
ProcessStatus = range.Cells(i, 9).Value
If dict.Exists(CandidateProcessID) = True Then
Set oCandidate = dict(CandidateProcessID) 'CODE ERRORS HERE AFTER A FEW ROWS (Comes across a
Else an entry that is already in the dictionary)
Set oCandidate = New ClsCandidate
dict.Add CandidateProcessID, oCustomer
End If
oCandidate.CandidateName = oCandidate.CandidateName
oCandidate.FirstName = oCandidate.FirstName
oCandidate.ProcessStatus = oCandidate.ProcessStatus
oCandidate.PQLDate = oCandidate.PQLDate
Next i
Set ReadData = dict
End Function
Sub WriteDict(dict As Dictionary)
Dim key As Variant, oCandidate As ClsCandidate
For Each key In dict
Set oCandidate = dict(key)
Debug.Print key, oCandidate.CandidateName, oCandidate.FirstName, oCandidate.ProcessStatus, oCandidate.PQLDate
Next key
End Sub
I believe the error is object error. It stops and debugs.
That would be "Object Required", and it's absolutely consistent with a very common trap: add Option Explicit at the top of every module, and now VBA won't let you run the code until it knows what oCustomer is.
Option Explicit being missing has allowed the code to run with oCustomer undeclared: simply, a new Variant/Empty pointer is created on-the-spot for that local identifier ("oCustomer"), such that every iteration that "works" is just preparing the ground to make the ones that don't, blow up:
If dict.Exists(CandidateProcessID) = True Then
Set oCandidate = dict(CandidateProcessID) '<~ Variant/Empty is retrieved here: "object required"
Else
Set oCandidate = New ClsCandidate
dict.Add CandidateProcessID, oCustomer '<~ Variant/Empty is added here
End If
The Variant/Empty is successfully retrieved from the dictionary: the problem is that the Set oCandidate instruction on the left side of that = operator says the right-hand side of the operation must be an object reference. Variant/Empty fails to satisfy this requirement, and an "object required" run-time error is raised.
The bug isn't with the retrieval: it's with the storage!
You can easily find bugs like this with static code analysis tooling, such as Rubberduck (disclosure: that's my website).
I have a function that is used to find the information in a Excel worksheet knowing that:
- The Key can be in a variable column
- Variable fields can be searched
Sheets usually have less than a hundred column, but can have anything from a few hundred to 100 000 rows to search. In our biggest files, the function I'm trying to optimize can be used about a million times.
After reading
https://fastexcel.wordpress.com/2011/10/26/match-vs-find-vs-variant-array-vba-performance-shootout/
... and finding our function used Find (3 times), I tried using arrays.
This is the code I wrote
Function getInfo(Key As String, NameField As String, NameKey As String, WksName As String) As Variant
On Error GoTo Error
Dim iColumnKEY As Integer
Dim iColumnFIELD As Integer
Dim i As Integer
Dim ListFields, ListKeys As Variant
ListFields = Worksheets(WksName).Range("A1:ZZ1")
i = LBound(ListFields, 2)
'To identify which column contains the Key and which one contains the
'information we are searching for
Do While iColumnKEY=0 Or iColumnFIELD=0
If i > UBound(ListFields, 2) Then
getInfo = "//error\\"
ElseIf ListFields(1, i) = NameKey Then
iColumnKEY = i
ElseIf ListFields(1, i) = NameField Then
iColumnFIELD = i
End If
i = i + 1
Loop
Dim iROW As Integer
ListKeys = Worksheets(WksName).Columns(iColumnFIELD)
i = LBound(ListKeys, 1)
Do While iROW=0
If i > UBound(ListKeys,1) Then
getInfo = "//error\\"
ElseIf ListKeys(i,1) = Key Then
iROW = i
End If
i = i + 1
Loop
getInfo = Worksheets(WksName).Cells(iROW, iColumnFIELD)
Exit Function
Error:
getInfo = "//error\\"
End Function
The code works, but is very slow. What am I doing that is slowing things down?
It is not in the code right now, but I did try turning the screen update down, as well as automatic calculation down. I didn't see any difference in speed, which indicates me that the basic algorithm is the main issue.
Also, the article was in 2011. Are arrays still a lot faster than Match/Find?
As a side note: eventually, I'll suggest having a macro that search for a range of Keys in a batch, instead of calling the function for every single key. This would means the first Do... While loop would be done only once for a macro, and only the Do_While for Rows would be run for every key. However, this is not an option in the very short term.
Thanks. Any help or advice would be greatly appreciated.
To make sure I understood you correctly, you have a sheet that has a random column that contains unique keys.
you want to search for one of these keys and return related info (like row no, etc) many times
Approach:
Find the column in which the keys are listed.
Load that column in a dictionary(Indexed).
Use GetInfo function to return info about a specific key if it exists.
Dependencies:
Microsoft scripting runtime (Tools > refrences > Microsoft scripting runtime)
code:
Option Explicit
Private KeyDictionary As Scripting.Dictionary
Sub PopulateDictionary(ByRef WS As Worksheet, ByVal FieldName As Variant)
Dim i As Long, LastRow As Long, iColumnFIELD As Long
Dim ListKeys As Variant
iColumnFIELD = WS.Range("A1:ZZ1").Find(FieldName).Column
With WS 'Finds the last row in the sheet
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
LastRow = .Cells.Find(What:="*", _
After:=.Range("A1"), _
LookAt:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
End If
Set KeyDictionary = New Scripting.Dictionary
For i = 1 To LastRow 'populates Dictionary with Key, Row number pair
If Not (KeyDictionary.Exists(.Cells(i, iColumnFIELD))) Then 'Make sure the key doesn't already exist(Key values should be unique)
KeyDictionary.Add .Cells(i, iColumnFIELD).Value, .Cells(i, iColumnFIELD).Row
End If
Next i
End With
End Sub
Function getInfo(ByVal key) As Variant
If KeyDictionary.Exists(key) Then
getInfo = KeyDictionary(key) 'if the key exist return row number (or whatever you want to)
Else
getInfo = "Null" 'Else return whatever you want like a msgbox "not Found" , etc
End If
End Function
usage
'populates and returns the row number of key 9500
Sub TestSearch()
PopulateDictionary ThisWorkbook.Worksheets("Sheet1"), "Key"
Debug.Print getInfo(9500)
End Sub
Notes:
-almost always Use long instead of integer , not much performance difference, but can save you from overflow pitfalls.
-you can add a reference to the range containing the key instead of the row number that would more flexible
-Passing a sheet by reference (Full Ref) is better than passing just its name and avoids a lot of possible problems like the case of multiple workbooks with the same sheet name and makes your code more reusable.
References:
Dictionary object
Edit:
I misunderstood your request , thought you wanted to know the best method available.
here's a performance comparison of the four methods:
it takes 1325 ms (Or 1.3 seconds) to populate the Dictionary with the unique key list the first time (100,000 Row)
it takes 1.79646327708265E-02 ms aka 0.02 ms To search for an item at the end of list (row 99863) using the dictionary object
it takes around 10.5 ms To search for the same item with WorksheetFunction.Match
it takes around 50 ms To search for the same item with the array method
it takes around 80 ms To search for the same item with the Range.find
Result:
Dictionary Method is faster than match -the second-best method of the Four- by over 500 Times!
The reason is that the keys are indexed inside the dictionary, unlike the other methods.
notes:
Office 2016 was used on a 2 cores (3.20 GHz) machine with 8 gigs or ram (Dictionary took about extra 8 Megabytes ram)
All of these searches were done on the same data set
(the search was done on only 1 column with 100,000 unique keys ,with the searched for value at the bottom of the list)
The break-even point on whether you should use Match or Dictionary is around 120 searches.
if the code will search for more than 120 values then it's better to use the dictionary approach.
Windows API QueryPerformanceCounter was used for High resolution timer.
Code line used to search for Values(not gonna put the full sub)
'Match
WorksheetFunction.Match(Key, ThisWorkbook.Worksheets(1).Range("CW:CW"), 0)
'Find
ThisWorkbook.Worksheets(1).Range("CW:CW").Find(Key).Row
'Array
'Loops through the column till it finds a match
In your code you never use iColumnKEY
I think this is what you are actually after:
Function getInfo(key As String, NameField As String, NameKey As String, WksName As String) As Variant
Dim keyCol As Variant, fieldCol As Variant, keyRow As Variant
Dim errMsg As String
getInfo = "//error\\"
With Worksheets(WksName)
With Intersect(.UsedRange, .Columns("A:ZZ")) ' <--| reference a range in passed worksheet cells belonging to columns "A" to "ZZ" from worksheet first used row to last used one and from worksheet first used column to last used one
MsgBox .Address
fieldCol = Application.Match(NameField, .Rows(1), 0) '<--| look for passed 'NameField' in referenced range
If IsError(fieldCol) Then
errMsg = " :field column '" & NameField & "' not found"
Else
keyCol = Application.Match(NameKey, .Rows(1), 0) '<--| look for passed 'NameKey' in referenced range
If IsError(keyCol) Then
errMsg = " :key column '" & NameKey & "' not found"
Else
MsgBox .Columns(keyCol).Address
keyRow = Application.Match(key, .Columns(keyCol)) '<--| look for passed 'key' in referenced range 'NameKey' column
If IsError(keyRow) Then
errMsg = " :key '" & key & "' not found in column '" & NameKey & "'"
Else
getInfo = .Cells(keyRow, fieldCol) '<--| get referenced range "item"
End If
End If
End If
If errMsg <> "" Then getInfo = getInfo & errMsg
End With
End With
End Function
I see that in your loop you are doing a UBound() evaluation every time. This is not needed.
The following should be faster than a Do While loop. Notice that the array returned by Range().Value has always a lower bound of one. No need to call LBound()
Also, find where the last data exists in the column and restrict the loop to that range. I do this with .End(xlUp)
Dim ListKeys() as Variant
Dim iROW As Long, nRows as Long
nRows = Worksheets(WksName).Cells(Worksheets(WksName).Rows.Count, iColumnFIELD).End(xlUp).Row
ListKeys = Worksheets(WksName).Cell(1, iColumnFIELD).Resize(nRows,1).Value
For i=1 To nRows
If ListKeys(i,1) = Key Then
iROW = i
Exit For
End If
Next i
not an answer but a radically different approach, since im from data-science background i use these methods for fast searching any data in a database which are few GB in size, in your case excel for example. this approach can be parallelized based on number of CPUs in your system. and uses python framework Pandas, which has very big community incase you need support, VB has limited community.
also read this before judging this answer https://corporatefinanceinstitute.com/resources/knowledge/other/transitioning-from-excel-to-python/
i expect criticism for this answer , OP asked this but you are giving this blah. but if you want faster development times for ever changing business needs you need something fast, and easy to maintain. python makes it easy, pandas makes it fast.
to get started read this.https://towardsdatascience.com/read-excel-files-with-python-1000x-faster-407d07ad0ed8
i will mention the pipeline here however. see very few lines of code!!! finish work faster, go home early.
import the excel file as csv
import pandas as pd
dataframe=pd.read_excel("file.xlsx")
item=dataframe[dataframe["Order ID"]==886714971] #condition based searching in excel
note "Order ID" is just any arbitary column and you can use SQL like logic here which resembles match/find in VBA.
for speed reference iterating 1,000,000 rows took 0.03 seconds, which means a transaction speed of 30 TPS. use https://modin.readthedocs.io/en/latest/ to scale up that speed linearly with number of cores in cpu.
To find out what parts of the code are the slowest, you can use Timer:
Dim t as Single
t = Timer
' part of the code
Debug.Print CDbl(Timer - t) ' CDbl to avoid scientific notation
Using .Value2 instead of .Value should help a bit:
ListFields = Worksheets(WksName).Range("A1:ZZ1").Value2
Searching for the key and field in two separate loops should be a bit faster because there will be less comparisons. Also, I am not sure if it will be a bit slower or faster, but you can iterate even multi-dimensional arrays:
Dim i As Long, v ' As Variant
i = 1
For Each v in ListFields
If v = NameKey Then
iColumnKEY = i
Exit For
End If
i = i + 1
Next
I'm writing code in VBscript that opens a recordset object and then loads an array with objects containing data from each record. My recordset type doesn't support the rs.RecordCount property, so I either need to ReDim Preserve the array while looping through the recordset or I need to reopen the recordset after doing a counting loop, since using rs.MoveFirst after the counting loop doesn't seem to work... Which would be faster? There is only at most 7 records in the recordset object, so at most I would need to ReDim that many times.
This is one way I attempted, but rs.MoveFirst doesn't seem to work correctly, see comments:
Function LoadData(filter_val)
Dim arr
Dim rs
'Calls function that opens the rs and returns it
Set rs = GetRS(filter_val)
Dim counter
counter = 0
Do Until rs.EOF
counter = counter + 1
rs.MoveNext
Loop
ReDim arr(counter)
rs.MoveFirst
For i = 0 To counter
Set arr(i) = New obj
'attempt to load values into the object from the recordset, but get an
'error saying 'either BOF or EOF is true, or the current record has been deleted'
'I tried adding If statements with MsgBox print outs checking for rs.EOF or rs.BOF
'being true right after rs.MoveFirst, but neither evaluates to true...
Next
End Function
This method works, but I have to continuously ReDim the array:
Function LoadData(filter_val)
Dim arr
Dim rs
Set rs = GetRS(filter_val)
Dim counter
counter = 0
ReDim arr(counter)
Do Until rs.EOF
Set arr(counter) = New obj
'load data from rs into object
rs.MoveNext
If Not rs.EOF
counter = counter + 1
ReDim Preserve arr(counter)
End If
Loop
End Function
Re-dimensioning arrays performs surprisingly well, so I'd go with ReDim. The generic way to increase the size of an array is to initialize it as an empty array:
ReDim arr(-1)
and then increase the upper boundary by one with every iteration before adding something:
Do Until rs.EOF
ReDim Preserve arr(UBound(arr)+1)
Set arr(UBound(arr)) = New obj
'load data from rs into object
rs.MoveNext
Loop
That way you don't need a counter variable for keeping track of the array size.
This appears easier, from MSDN
GetRows Method (ADO)
Retrieves multiple records of a Recordset object into an array.
Syntax
array = recordset.GetRows(Rows, Start, Fields )
Return Value
Returns a Variant whose value is a two-dimensional array.
Parameters
Rows
Optional. A GetRowsOptionEnum value that indicates the number of records to retrieve. The default is adGetRowsRest.
Start
Optional. A String value or Variant that evaluates to the bookmark for the record from which the GetRows operation should begin. You can also use a BookmarkEnum value.
Fields
Optional. A Variant that represents a single field name or ordinal position, or an array of field names or ordinal position numbers. ADO returns only the data in these fields.
Also just dim the array to 7 if the max is 7.
I'm trying to switch a user between three different screens depending on what a stored procedure returns in a BtnView_Click procedure in asp.net VB. The SP would return a "0, 1, or NULL". Currently it's only returning a "1" and not the others. I'm having trouble with the Reader.Read area with the IF statement and i'm wondering if there's a simple fix to this so it directs everything accuratley.
This is what I currently have (updated)
Sub BtnView_Click(ByVal sender As Object, ByVal e As CommandEventArgs)
Session.Add("SvarRecord", e.CommandArgument)
Dim sb As New System.Text.StringBuilder()
Dim connectionString As String = ConfigurationManager.ConnectionStrings("CS_Connection").ConnectionString
Using connection As New SqlConnection(connectionString)
Dim myCommand As New SqlCommand("View", connection)
myCommand.CommandType = CommandType.StoredProcedure
Dim sqlRecord As SqlParameter = New SqlParameter("#Name", SqlDbType.VarChar)
sqlRecord.Value = Session("SvarRecord")
myCommand.Parameters.Add(sqlRecord)
connection.Open()
Using reader As SqlClient.SqlDataReader = myCommand.ExecuteReader
REM Read() returns True if data can be read
If reader.Read() Then
REM IsDbNull checks if given column (by ordinal) contains DbNull.
REM You need it because you can not convert DbNull to a number. As alternative
REM you may read it as object and compare by yourself.
If reader.IsDBNull(0) Then
Response.Redirect("Entry.Aspx")
REM We are sure it is not DbNull and we can assume it is an integer
ElseIf reader.GetInt32(0) = 0 Then
Response.Redirect("Negatives.Aspx")
ElseIf reader.GetInt32(0) = 1 Then
Response.Redirect("PrevEntry.Aspx")
End If
End If
reader.Close()
End Using
connection.Close()
connection.Dispose()
End Using
You're comparing HasRows property (a Boolean that indicates if recordset is empty or not), not value returned from your stored procedure.
Change your code to:
Using reader As SqlClient.SqlDataReader = myCommand.ExecuteReader
Rem Read() returns True if data can be read
If reader.Read() Then
Rem IsDbNull checks if given column (by ordinal) contains DbNull.
Rem You need it because you can not convert DbNull to a number.
Rem As alternative you may read it as object and compare by yourself.
If reader.IsDbNull(0) Then
Response.Redirect("Entry.Aspx")
Rem We are sure it is not DbNull and we can assume it is an integer
ElseIf reader.GetInt32(0) = 0 Then
Response.Redirect("Negatives.Aspx")
ElseIf reader.GetInt32(0) = 1 Then
Response.Redirect("PrevEntry.Aspx")
End If
End If
End Using
Here I assume your stored procedure returns an integer value. If it's not you can get/compare with right value or convert it to integer. First case (same for the other If):
ElseIf reader.GetString(0) = "0" Then
Second case:
ElseIf Convert.ToInt32(reader.GetObject(0)) = 0 Then
Last note about your code, as suggested by Jhon in his comment your code may fail for an unlimited number of reasons, you'd better to always wrap disposable objects in a Using statement like this:
Dim connectionString As String =
ConfigurationManager.ConnectionStrings("CS_Connection").ConnectionString)
Using connection As New SqlConnection(connectionString)
Rem Put here all code that uses connection
End Using
This will ensure connection and other shared (and limited!) resources will always be released even in case of error. Edit: compiler won't complain about comparison of a Boolean and a String because you didn't set OPTION STRICT to ON (see #Tim's answer for more details).
First, use the Using-statement to dispose/close the connection and anything else implementing IDisposable even on error. Second, you should really set OPTION STRICT to on globally, then this will not compile which is a good thing:
If reader.HasRows = "0" Then
The problem with that code is that HasRows is a Boolean but you are comparing it with a String. That would result in a compiler error normaly, but OPTION STRICT off allows it. The Boolean will be converted to a String implicitely. So this comparison seems to work but it does not.
Actually you have to read the field, you can use the Get... methods:
If reader.HasRows Then
If reader.IsDBNull(0) Then
Response.Redirect("Entry.Aspx")
ElseIf reader.GetInt32(0) = 1 Then
Response.Redirect("PrevEntry.Aspx")
ElseIf reader.GetInt32(0) = 0 Then
Response.Redirect("Negatives.Aspx")
End If
End If
Having two tables (the source and target) intend to copy only the records from the source table that do not exist in the target table (making the comparison with the value of a specific cell in each record). I thought to do it using arrays, but as I am new in this area, needed help.
Examples:
Source Table
ID Date Description
115 01-Ago Description1
120 05-Ago Description2
130 03-Ago Description5
110 08-Ago Description4
105 06-Ago Description6
Destination Table
ID Date Description
130 03-Ago Description5
110 08-Ago Description4
I want to add in the target table records from the source table that do not exist in the target table (ID's 115,120,105 in this example). Thank you!
I'm almost there. After consulting some other questions, I need something like this:
Sub Tests()
Dim MyArray() As String
Dim tgtLastRow, srcLastRow As Integer
Dim rngTarget, rngSource, cel As Range
Dim Delim As String
Delim = "#"
tgtLastRow = Range("H1").End(xlDown).Row
srcLastRow = Range("A1").End(xlDown).Row
Set rngTarget = Range("H2:H" & tgtLastRow)
Set rngSource = Range("A2:A" & srcLastRow)
MyArray = rngTarget.Value
strg = Join(MyArray, Delim)
strg = Delim & strg
For Each cel In rngSource
If InStr(1, strg, Delim & cel.Value & Delim, vbTextCompare) Then
Else
'Copy the row or range here
End If
Next cel
End Sub
But now, I have one of two problems:
If I declare MyArray as string type I have problems loading values to array
If I declare MyArray as variant type I have problems in the Join
Can anyone help-me please??
All you need is to use Either Collection object, or Dictionary Object. These objects help a lot when you try to find the unique records.
Let us take an example, We have two sheets: Source and Target.
You need to loop through Column A in both sheets and move the data from Source Worksheet to target Worksheet. Following is the code, not tested, but it should do the trick. I have added comments so you can easily understand and fit this in your situation easily
Dim ids As Collection
Sub MoveData()
On Error GoTo MoveData_Error
Set ids = New Collection
Dim sourceRange As Range
Dim idRange As Range
Dim cell As Range
Set sourceRange = Range("A1:A100") 'assign your source range here. Code will try to pick ID in this range, and check in ID Range
Set idRange = Range("A1:A100") ' assign your target range here. Code will find the ID in this range
'load all ids from target range in the collection.
On Error Resume Next ' suppressing the error if any duplicate value is found
For Each cell In idRange.Cells
ids.Add cell.Value, cell.Value ' adding in collection to maintain a unique collection
Err.Clear
Next cell
On Error GoTo MoveData_Error
'now I have information about all the availabe IDs in id collection. Now I will loop through to check
For Each cell In sourceRange
If ShouldCopy(cell) Then
'write your code to copy
End If
Next cell
On Error GoTo 0
Exit Sub
MoveData_Error:
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure MoveData of VBA Document Sheet1"
End Sub
Public Function ShouldCopy(cell As Range) As Boolean
On Error GoTo ShouldCopy_Error
If cell.Value = "" Or IsEmpty(cell.Value) Then Exit Function
ids.Add cell.Value, cell.Value ' if error occurs here, then it means the id has been already moved or found in the ID worksheet range
ShouldCopy = True
On Error GoTo 0
Exit Function
ShouldCopy_Error:
ShouldCopy = False
End Function
If you face any issues in understanding and need any help, please let me know.
Thanks,
V
Add a lookup to your source data flagging each record as either present or absent and then bounce your macro off of that column (i.e only move it into target if the lookup = absent).