scanning for serial numbers - ms-access

I have some VBA code in Access that scans the body of an email for a specific serial number. The serial number previously started with a 2 followed by three letters, and then 5 Numbers. I was using something like the posted code to search for the most common prefixes. Now they have changed the serial numbers to eliminate the 2 and with my current method this makes grabbing incorrect text much more likely. EMText is a string containing the email body.
The new format is ABC12345D1234 this can pretty much be any combination of letters or numbers, but the letters are always letters and the numbers are always numbers. Is there a quick way to search for something with this specfic length and number of letters and numbers or the specific format. I am having trouble coming up with something that is not overly complicated on my own and can't track down an example that matches what I am trying to do.
Function GetUnitNumber(ByVal EMText) As String
unit = ""
If InStr(1, EMText, "2ABC") Then
vItem = Split(EMText, "2ABC")
unit = "2ABC" & Left(vItem(1), 5)
ElseIf (InStr(1, EMText, "2CA")) Then
vItem = Split(EMText, "2CA")
unit = "2CA" & Left(vItem(1), 6)
ElseIf (InStr(1, EMText, "2DFS")) Then.......

This is a typical scenario for Regular Expressions.
Link to Microsoft VBScrpt Regular Expressions
And use this:
Function GetUnitNumber(ByVal EMText)
Dim regEx As New RegExp
With regEx
.Global = True
.MultiLine = True
.IgnoreCase = True
'This matches the pattern: i.e. ABC12345D1234
.Pattern = "[A-Z]{3}[0-9]{5}[A-Z]{1}[0-9]{4}"
End With
If regEx.Test(EMText) Then
GetUnitNumber = regEx.Execute(EMText)(0)
End If
End Function

Related

Why Newtonsoft.Json.Linq.Item behaves weird

Just look at this one line code
jobject.parse("{""volume"": 5074364.34541878}").item("volume").tostring
The result is 5074364,34541878
That is with . instead of comma. How can that be? This has caused lots of bugs.
The value of
Threading.Thread.CurrentThread.CurrentUICulture.NumberFormat.CurrencyDecimalSeparator
is "."
Recently I found something weird in my computer.
For some reasons many dots becomes comma. For some reasons, some program like firefox think that the decimal separator is comma instead of dot.
That is even though my international settings is set to US (I am actually in Indonesia).
Then the program I created (that should have been independent of this) start behaving erratically.
For example, look at this code
Dim valueInString = grabform.Item(frontstr).ToString
The value of grabform.toString is
{
"key": "tTRXUSD",
"bid": 0.060322,
"ask": 0.060323,
"last": 0.060289,
"volume": 5074364.34541878,
"high": 0.061143,
"low": 0.060192,
"base123": "USD",
"quote123": "TRX"
}
The type of grabform is JObject
in this function
Private Shared Function grabValueOfActualQuote(grabform As JObject, frontstr As String) As Decimal
Dim valueInString = grabform.Item(frontstr).ToString
If valueInString = "" Then
Return 0D
End If
If valueInString.Length > 15 Then
Dim b = 1
End If
Dim left1 = Strings.Left(valueInString, 20)
Dim output = Decimal.Parse(left1, System.Globalization.NumberStyles.Any)
Return output
End Function
Now the result of valueInstring is 5074364,34541878 (notice the comma)
How can that be?
The value of
It seems that somehow there is a hidden settings in my computer that makes my computer think that the decimal mark is comma instead of dot. Obviously my program should have used dot irrelevant of computer settings. Not to mention I know where the setting is.
Newtonsoft.Json.Linq. shouldn't change behavior no matter what international setting is.
What should I do?
Look at this piece of code:
using System.Globalization;
var xyz = 5074364.34541878;
Console.WriteLine($"Current culture: {CultureInfo.CurrentCulture.Name}");
Console.WriteLine(xyz);
// https://stackoverflow.com/a/5263650/724039
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture;
Thread.CurrentThread.CurrentUICulture.NumberFormat.CurrencyGroupSeparator = "A";
Thread.CurrentThread.CurrentUICulture.NumberFormat.CurrencyDecimalSeparator = "B";
Thread.CurrentThread.CurrentUICulture.NumberFormat.NumberDecimalSeparator = "C";
Thread.CurrentThread.CurrentUICulture.NumberFormat.NumberGroupSeparator = "D";
Console.WriteLine(xyz.GetType().ToString());
Console.WriteLine(xyz);
Console.WriteLine("Change NumberDecimalSeparator to '.'");
Thread.CurrentThread.CurrentUICulture.NumberFormat.NumberDecimalSeparator = ".";
Console.WriteLine(xyz);
It's output is:
Current culture: nl-NL
5074364,34541878
System.Double
5074364C34541878
Change NumberDecimalSeparator to '.'
5074364.34541878
Conclusion: The value that you read from JSON is a System.Double, and C# applies the NumberDecimalSeparator when using this variable.

IIf query decimal removal

Trying to attempt the following in MS Access.
Convert data in one field to an 18 digit number starting with 01 in another field.
There are also some conditions that have to be met:
the first dash should become double zeros
the second dash should be removed
the third and fourth dash should be a single zero
the decimal must also be replaced with a zero
My query works fine until the decimal is the 15th character in the data.
Here is the query:
SELECT MasterVacant.ParcelIdNumber,
"01" + Mid([ParcelIdNumber],1,2) + "00" + Mid([ParcelIdNumber],4,2) + Mid([ParcelIdNumber],7,1)
+ IIf(Mid([ParcelIDNumber],11,1) = "", "0"+Mid([ParcelIDNumber],9,2), Mid([ParcelIDNumber],9,3))
+ IIf(Mid([ParcelIDNumber],14,1) = ".", "0"+Mid([ParcelIDNumber],12,2), Mid([ParcelIDNumber],12,3))
+ Mid([ParcelIDNumber],15,3) AS ParcelNumber
FROM MasterVacant;
Here is a start and finish example...
'12-06-1-00-50.000-RR' should become '011200061000050000'
'12-06-3-07-09.000-RR' should become '011200063007009000'
'13-35-1-01-129.000-RR' should become '011300035100112900'
However, instead of getting `0113000351001129000' I get '013000351001129.00'.
The issue is how do I remove the decimal when the decimal is the 15th character like in the third set of example?
I receive the data as a single column. Some of it is below....
1. 13-35-1-07-29.000-RR
2. 13-35-1-01-112.000-RR (Removing the decimal when the data is like this is the issue)
3. 13-35-4-01-01.000-RR
4. 13-35-4-02-04.000-RR
5. 13-35-1-13-17.000-RR
The output for the above data should be
1. 011300351007029000
2. 011300351001112000
3. 011300354001001000
4. 011300354002004000
5. 011300351013017000
Use a custom function:
Public Function Make18(ByVal Value As String) As String
Const Head As String = "01"
Const Tail As String = "000"
Const Lead As String = "00"
Dim Parts As Variant
Dim Part As Integer
Dim Result As String
Parts = Split(Split(Value, ".")(0), "-")
For Part = LBound(Parts) To UBound(Parts)
Select Case Part
Case 0
Parts(Part) = Head & Parts(Part)
Case 1
Parts(Part) = Lead & Parts(Part)
Case 3, 4
Parts(Part) = Right(Lead & Parts(Part), 3)
End Select
Next
Result = Join(Parts, "") & Tail
Make18 = Result
End Function
and your query becomes:
SELECT
MasterVacant.ParcelIdNumber,
Make18([ParcelIdNumber]) AS ParcelNumber
FROM
MasterVacant;
I am assuming you meant the opposite where:
12-06-1-00-50.000-RR should become 011200061000050000
12-06-3-07-09.000-RR should become 011200063007009000
13-35-1-01-129.000-RR should become 0113000351001129.00
I would recommend the REPLACE() in MSACCESS to strip the dashes out. Once you have the dashes out you can MID()
Unfortunately your attempted code does something different with the 3rd row because 3 zeros are being put in when there should be only two in my opinion.
Try in a text box:
=Replace("13-35-1-01-129.000-RR","-","")
will return 1335101129.000RR
and see if that assists you in making your code.
Maybe go one step further and put it in a function.

Using .Find in a Recursive Function

I am trying to find the row number in a sheet using the .Find function in a recursive function.
I set an object called Found = .Find.... and it works great... for a little bit. I set it when I'm 1 level of recursion deep, then set it again when I'm 2 levels deep. Then, my code finds the end of the path and starts backing up until it gets back to 1 level deep, but not my Found object has been re-declared and kept its values from the 2nd level. My other variables (ThisRow etc...) keep the value of the level that they are in, and that's what I would like to do with the object Found. Is there a way that I can declare Found locally so that it's value doesn't extend to the next function, and can't be overwritten in a deeper level? You can find my code below for reference.
Here is my current code - irrelevant parts cut out:
Public Function FindChildren()
ThisRow = AnswerRow 'Also declared before function call
BeenHereCell = Cells(ThisRow, "O").Address
If Range(BeenHereCell).Value = "Yes" Then
Exit Function 'That means we've already been there
End If
Range(BeenHereCell).Value = "Yes"
With Worksheets("MasterScore").Range("j1:j50000")
Set Found = .Find(NextQuestionID, LookIn:=xlValues)
If Not Found Is Nothing Then
firstAddress = Found.Address
NextCell = Found.Address
Do
AnswerRow = Range(NextCell).Row
FindChildren 'This is where it's recursive.
Set Found = .FindNext(Found)
NextCell = Found.Address
Loop While Not Found Is Nothing And Found.Address <> firstAddress
End If
End With
End Function
Now I have gotten around it by activating cells, but it makes my code a lot slower. Currently I am using this:
Set Found = Worksheets("MasterScore").Range("j1:j50000").Find(NextQuestionID, LookIn:=xlValues)
If Not Found Is Nothing Then
Count = 1
Do
Columns("J:J").Select
FirstFoundRow = Selection.Find(What:=NextQuestionID, After:=ActiveCell, LookIn:= _
xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:= _
xlNext, MatchCase:=False, SearchFormat:=False).Row
For i = 1 To Count
Selection.FindNext(After:=ActiveCell).Activate
Next i
AnswerRow = ActiveCell.Row
If AnswerRow = FirstFoundRow And Count <> 1 Then Exit Do
FindChildren
Count = Count + 1
Loop
End If
This way, I don't have to set the value of the object again, but I have to iterate through it.FindNext quite a few times and each time it runs that line its also activating the row. I really just want something like.
AnswerRow = .Find(nth instance of NextQuestionID).Row
(I have about 50k rows and the count goes to about 20 pretty often so it really takes a while).
I'd appreciate any ideas! Currently my code is working, but it's going to take a good part of the day to complete, and I'll need to run this again at some point!
I ended up finding a way to speed it up a little bit. I think this could help someone so I will share what I've found.
It's not the best solution (I would have preferred to just declare the object locally so my other functions wouldn't change it's value), but at least with this I'm not looping through 20 or so finds every iteration of the Do Loop.
Set Found = Worksheets("MasterScore").Range("j1:j50000").Find(NextQuestionID, LookIn:=xlValues)
If Not Found Is Nothing Then
NextAnswerRange = "j" & 1 & ":" & "j50000" 'The first search will be from the beginning
Do
Set Found = Worksheets("MasterScore").Range(NextAnswerRange).Find(NextQuestionID, LookIn:=xlValues)
NextCell = Found.Address
AnswerRow = Range(NextCell).Row
NextAnswerRange = "j" & AnswerRow & ":" & "j50000"
If LastAnswerRange = NextAnswerRange Then Exit Function 'This would mean we've reached the end.
LastAnswerRange = NextAnswerRange
FindChildren
Loop
End If
End Function
So we know we've already covered our bases with previous ranges since it always finds the immediate next. We just change the range of the search each time and it will find the next value.
A weird thing about this solution is that if you are looking for a value among range 70 -> 50,000 and you have the answer your looking for on row 70, it will actually find the next row (it skips that first one). But, if there aren't any rows past 70 that have the answer, it will actually take the value from row 70. That meant that I couldn't do
NextAnswerRange = "j" & AnswerRow + 1 & ":" & "j50000"
because it would miss some values. Doing it without the + 1 meant at the end of the document I would end up searching the same last value over and over (it would never go back to Found Is Nothing) so I had to put in the check to see if the LastAnswerRange = NextAnswerRange.
I hope this helps someone. I don't think it's the most elegant solution, but it's a lot faster than what I had.

Global Date Variable Works Only for Entered Values

I'm currently working on what should be a relatively simple database, which is very close to being at its end, until I hit the inevitable problem.
I'm using Global Variables and a Form to collect parameters to pass into the Criteria portion of a Query, which works just fine for the first two, which are basic strings and integers.
Then comes the dates, which work, so long as you chose a date from the DatePicker that is entered into the query.
For example, if the query field holds 6/1/2014, 6/3/2014, and 6/8/2014, and the date 6/5/2014 is picked, the form will crash and go blank, though if you pick 6/8/2014, it'll go on as it should.
I had tried a variety of different forms of the code, but in the most basic form I simple have:
Between Get_Global('GBL_Start_Date_ID') AND Get_Global('GBL_End_Date_ID')
I'm not sure if I should be limiting the DatePicker based on the values entered in the query, or if there's a more robust way of going about this, or maybe I just completely missed a simple checkbox.
EDIT
My code for the Global Variables looks like this:
Option Compare Database
Global GBL_Start_Date_ID As Date
Global GBL_End_Date_ID As Date
Global GBL_Customer_ID As Long
Global GBL_Engineer_ID As Long
Public Function Init_Globals()
GBL_Start_Date_ID = #6/1/2014#
GBL_End_Date_ID = #6/30/2014#
GBL_Customer_ID = 1
GBL_Engineer_ID = 1
End Function
Public Function Get_Global(gbl_parm)
Select Case gbl_parm
Case "GBL_Customer_ID"
Get_Global = GBL_Customer_ID
Case "GBL_Engineer_ID"
Get_Global = GBL_Engineer_ID
Case "GBL_Start_Date_ID"
Get_Global = GBL_Start_Date_ID
Case "GBL_End_Date_ID"
Get_Global = GBL_End_Date_ID
End Select
End Function
And I just add a simple line to the AfterUpdate event of the ComboBoxes and TextBoxes to assign the variable:
GBL_Engineer_ID = Me.EngineerSelection
Thanks in advance,
Aaron
You can do two things. Fix the query as it is written or make it much more robust.
To fix it as written
Between "#" & Get_Global('GBL_Start_Date_ID') & "#" AND "#" & Get_Global('GBL_End_Date_ID') & "#"
OR
change your Get_Global function
Public Function Get_Global(gbl_parm)
Select Case gbl_parm
Case "GBL_Customer_ID"
Get_Global = GBL_Customer_ID
Case "GBL_Engineer_ID"
Get_Global = GBL_Engineer_ID
Case "GBL_Start_Date_ID"
Get_Global = "#" & GBL_Start_Date_ID & "#"
Case "GBL_End_Date_ID"
Get_Global = "#" & GBL_End_Date_ID & "#"
End Select
End Function
You are correctly specifying that the values of GBL_Start_Date_ID and GBL_End_Date_ID are dates by using #s when you create them however when you use them in your query they appear without them. You can prove to yourself this is what is happening by typing ?#1/1/2014# into the immediate window. The date is printed as 1/1/2014 which when used in your query, makes Between 6/1/2014 AND 6/30/2014 which is a syntax error.
To make things all that much better you need to parameterize this part of your query. Change this
Between Get_Global('GBL_Start_Date_ID') AND Get_Global('GBL_End_Date_ID')
to this
Between pStartDate AND pEndDate
Before you call your query you need to do your usual checks: are either of these null? is pStartDate < pEndDate?
By performing these checks and parameterizing this you ensure that you never end up with a query like this. Calling your query means you need to need to populate the parameters with DAO or ADO.
Between AND

access fields same but not equal?

i'm using the following code to compare two recordsets:
For i = 1 To (recordsetA.Fields.Count - 1)
If recordsetA.Fields(i).Value <> recordsetB.Fields(i).Value Then
stringFieldList = stringFieldList & ", " & recordsetA.Fields(i).Name
End If
Next i
However in the stringFieldList there are a couple of fields which have the same values (like 1339.5). Why?
Since it sounds like your dealing with double datatypes, the proper way for the test would be to set a limit, then test the absolute difference. You will also need to think about handling a null value.
Const epsilon as double = 0.00001
If Abs(recordsetA.Fields(i).Value - recordsetB.Fields(i).Value) < epsilon Then
'do stuff here
End If