MS Access: Compare two columns for Matches - Names in Different Order - ms-access

I'm trying to compare two different columns in Access 2016. Both contain people's names, but not in the same order in both.
Name
Harris, Keisha
Banaghan, John
Garcia Silveira, Ana
NameFormat2
Keisha Melinda Harris
John C. Banaghan
Ana Lucia Garcia Silveira
Currently, when comparing these two columns, they all flag, as they are not exact matches.
The names in the first column are always shorter than the FULL name that lives in the second column.
Is there a way, be it a query or VBA or any other option, to say that if all of the names in the first column are contained in the second column, then it's a match and to move on?
Any help would be greatly appreciated!
Thanks,
Robert

Not sure if this is the best way, but you could create a VBA function that does the check by splitting the two fields into arrays and looping them. Something like this seems to work:
Function fCompareNames(strName1 As String, strName2 As String) As Boolean
On Error GoTo E_Handle
Dim astrName1() As String
Dim astrName2() As String
Dim intLoop1 As Integer
Dim intLoop2 As Integer
Dim intNames As Integer
Dim intMatch As Integer
strName1 = Replace(strName1, ",", "")
strName2 = Replace(strName2, ",", "")
astrName1 = Split(strName1, " ")
astrName2 = Split(strName2, " ")
intNames = UBound(astrName1) - LBound(astrName1) + 1
For intLoop1 = LBound(astrName1) To UBound(astrName1)
For intLoop2 = LBound(astrName2) To UBound(astrName2)
If astrName1(intLoop1) = astrName2(intLoop2) Then
intMatch = intMatch + 1
Exit For
End If
Next intLoop2
Next intLoop1
If intMatch = intNames Then fCompareNames = True
fExit:
On Error Resume Next
Exit Function
E_Handle:
MsgBox Err.Description & vbCrLf & vbCrLf & "fCompareNames", vbOKCancel + vbCritical, "Error: " & Err.Number
Resume fExit
End Function
You may need to add some extra Replaces to deal with things like "." in the name.
In the examples given, it returns true for the first two (Keisha Harris and John Banaghan) and false for the last (Ana Silveira) as there appears to be a spelling mistake in the second instance of her surname (Silbeira).
Regards,

Related

Lookup function in Access to insert a result based on a text string

I have a database that I am creating based on a loosely created spreadsheet of sales at a Print on Demand site. The details of the product that was sold is like this:
Sale: 2009 Saturn Sky Red Line Coupe 120 - Face Mask - Adult Large - Quantity: 1
and the next field could be:
Sale: Hudson Hornet 4 door Sedan Twin H Power painted - Men's T-Shirt (Regular Fit) - Charcoal - 2XLarge - Quantity: 1
There are many different types of product and some are quite repetitive - ie iPhone 6S case, iPhone 8 case, iPhone 10 case and so on.
I wanted to summarize what products have sold but to group them at a more consistent level - ie all iPhone cases would simply say "iPhone Case". All T-Shirts would say "T-Shirt".
So my thought was that I can do a search within these fields for iPhone and if the search is successful then update a new Product field with "iPhone case". If I find T-Shirt then I look that up and put "T-Shirt" in my new field.
I thought of putting these alternatives in a new table with the phrase to be searched in one column and the chosen result in the next column, but there are no real keys between those tables.
A very long If, then else, statement might do it as well, but much less elegantly!
Steve
When you look at the two pieces of data supplied, they appear to be multiple pieces of data that have been combined with " - ", with data including product, product type, colour (optional), size (may be optional) and quantity.
What I'd do in this situation is to write some VBA code to process the data and to normalize it. You may need to do this in stages, dealing with the product type first, then the colour, size and product. Finally, you would add to a Sales table all of the foreign keys from these attribute tables.
The code below is what I'd start with for the product type, assuming that the data has been imported into Access in a table called tblSalesData in a single field, and that there is another table called tblProductType that has two fields, ProductTypeID and ProductType.
Sub sSplitSalesData()
On Error GoTo E_Handle
Dim db As DAO.Database
Dim rsSales As DAO.Recordset
Dim rsProductType As DAO.Recordset
Dim astrData() As String
Dim intLoop1 As Integer
Dim strSQL As String
Set db = CurrentDb
db.Execute "DELETE * FROM tblProductType;"
strSQL = "SELECT DISTINCT SalesData FROM tblSalesData;"
Set rsSales = db.OpenRecordset(strSQL)
If Not (rsSales.BOF And rsSales.EOF) Then
Set rsProductType = db.OpenRecordset("SELECT * FROM tblProductType WHERE 1=2;")
Do
If Left(rsSales!SalesData, 4) = "Sale" Then
astrData = Split(Mid(rsSales!SalesData, 6), " - ")
If IsNull(DLookup("ProductTypeID", "tblProductType", "Replace(ProductType,Chr(39),'')='" & Replace(astrData(1), "'", "") & "'")) Then
With rsProductType
.AddNew
!ProductType = astrData(1)
.Update
End With
End If
Select Case UBound(astrData)
Case 3 ' has product name/type/size/qty
Case 4 ' has product name/type/colour/size/qty
Case Else
Debug.Print "Unknown number of elements: " & rsSales!SalesData
End Select
Else
Debug.Print "Odd data: " & rsSales!SalesData
End If
rsSales.MoveNext
Loop Until rsSales.EOF
End If
sExit:
On Error Resume Next
rsSales.Close
rsProductType.Close
Set rsSales = Nothing
Set rsProductType = Nothing
Set db = Nothing
Exit Sub
E_Handle:
MsgBox Err.Description & vbCrLf & vbCrLf & "sSplitSalesData", vbOKOnly + vbCritical, "Error: " & Err.Number
Resume sExit
End Sub
Regards,

Join two worksheets; first contains a list of ranges, second contains data that may fall within in range of the first

I'm working on combining two excel worksheets. Before I start, I'd like to mention that I also have mysql workbench, so I'm open to working on this issue in either sql or vba (I should learn both). I'm working with .bed files, which are lists of genomic coordinates. In short, the data is indexed by chromosome number (ie:chr2) and then has a numerical start and stop location on the chromosome. These numerical locations can span a large range (ie:100-10,000) or be a single position (ie: 999-1000). I have a list of coordinates that cover a large range, and in a separate file I have a list of single positions.
Example of a file with ranges:
chromosome start stop
chr1 4561 6321
chr3 9842 11253
Example of file with single positions:
chromosome start stop
chr1 5213 5214
chr3 10254 10255
I would like to combine these worksheets such that if a location in my list of single positions is found within the range in my list of ranges, the locations for both are listed in the same row. The lists are 1000s of locations long, so I'd also like this program to loop through every row. Using the example data listed above, I'd like my output to look like the following:
Example of desired output:
chromosome start stop chromosome start stop
chr1 4561 6321 chr1 5213 5214
chr3 9842 11253 chr3 10254 10255
There is a high probability that multiple single positions will fall within a single range, and I would like these to be listed as separate rows.
I appreciate any help I can get! Thank you in advance. I am eager to learn!
Here's a basic outline which queries two tables on sheets named "Ranges" and "Positions", and outputs the results on a sheet named"Results"
The input tables should have headers, and start in the top-left cell (A1)
Sub SqlJoin()
Dim oConn As New ADODB.Connection
Dim oRS As New ADODB.Recordset
Dim sPath
Dim sSQL As String, wb As Workbook
Set wb = ThisWorkbook
sSQL = " select a.chromosome, a.start, a stop," & _
" b.chromosome, b.start, b.stop " & _
" from <ranges_table> a, <positions_table> b" & _
" where b.start >= a.start and b.stop <= a.stop"
sSQL = Replace(sSQL, "<ranges_table>", _
Rangename(wb.Worksheets("Ranges").Range("A1").CurrentRegion))
sSQL = Replace(sSQL, "<positions_table>", _
Rangename(wb.Worksheets("Positions").Range("A1").CurrentRegion))
If wb.Path <> "" Then
sPath = wb.FullName
Else
MsgBox "The workbook must be saved first!"
Exit Sub
End If
oConn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source='" & sPath & "';" & _
"Extended Properties='Excel 12.0;HDR=Yes;IMEX=1';"
oRS.Open sSQL, oConn
If Not oRS.EOF Then
wb.Worksheets("Results").Range("A2").CopyFromRecordset oRS
Else
MsgBox "No records found"
End If
oRS.Close
oConn.Close
End Sub
Function Rangename(r As Range) As String
Rangename = "[" & r.Parent.Name & "$" & _
r.Address(False, False) & "]"
End Function

Concatenating Null values in DCount query that compare to corresponding concatenated table fields

Using Access 2007.
I have to compare several data entry fields with their corresponding tables fields. If the fields match, do not add a new record. If not, add a new record with the values.
AnimalInfo Table fields
WHno (Wildlife Health Number)
Species
LETClr1 (Left Ear Tag Color 1)
LETNo1 (Left Ear Tag Number 1)
LETClr2 (Left Ear Tag Color 2)
LETNo2 (Left Ear Tag Number 2)
RETClr1 (Right Ear Tag Color 1)
RETNo1 (Right Ear Tag Number 1)
RETClr2 (Right Ear Tag Color 2)
RETNo2 (Right Ear Tag Number 2)
Form F_HotelForm unbound fields
txtSpecies
txtLETClr1
txtLETNo1
txtLETClr2
txtLETNo2
txtRETClr1
txtRETNo1
txtRETClr2
txtRETNo2
I am trying to create a DCount to check and see if there are any matching records. The animal's uniqueness is determined by its species and ear tag information. It can have one ear tag number and color, or four. (Some of the older data has none but I can't do anything about that! In those cases, a new record, i.e. new Wildlife Health Number will be generated)
This is what I want to accomplish with this form:
If there are no matching fields (DCount = 0) add a new record and update fields from the form into table.
If there is 1 matching record, then the animal's wildlife health number is displayed (in a new form eventually)
If there are multiple records, then these are displayed in another form and the user needs to pick the correct animal.
LETClr1 and LETNo1 are paired.
LETClr2 and LETNo2 are paired.
RETClr1 and RETNo1 are paired.
RETClr2 and RETNo2 are paired.
Any and all of these fields could have values or not. Left ear tag numbers and colors could have been entered as either LETClr1 or LETClr2 so I have to compare both LETClr1 and LETClr2 with the txtLETClr1 data entry. (This holds true for all paired fields)
Below is a sample of the script so far. It is very rudimentary as I am very new to this and am just trying to see what works.
Private Sub GenerateWHno_Click()
Dim rs As DAO.Recordset
If IsNull(Forms!F_HotelEntry!txtSpecies) Or (Forms!F_HotelEntry!txtSpecies) = "" Then
MsgBox "Species is a required field. Please enter a species"
Exit Sub
End If
MsgBox txtSpecies
SpeciesCount = DCount("[Species]", "AnimalInfo", "[Species]= '" & txtSpecies & "'AND [L_ET_Color1]='" & txtL_ET_Color1 & "' AND [L_ET_No1]='" & txtL_ET_No1 & "'")
If SpeciesCount > 1 Then
MsgBox SpeciesCount & " Greater than 1"
ElseIf SpeciesCount = 0 Then
MsgBox "You need a new WHno"
WHno = Nz(DMax("WHno", "AnimalInfo")) + 1
MsgBox WHno
Set rs = CurrentDb.OpenRecordset("AnimalInfo")
rs.AddNew
rs!WHno = WHno
rs!Species = txtSpecies
rs!L_ET_Color1 = txtL_ET_Color1
rs!L_ET_No1 = txtL_ET_No1
rs!R_ET_Color2 = txtR_ET_Color2
rs.Update
rs.Close
Else
End If
Forms!F_HotelEntry!txtSpecies = ""
Forms!F_HotelEntry!txtL_ET_Color1 = ""
Forms!F_HotelEntry!txtL_ET_No1 = ""
End Sub
So the problem is that I cannot concatenate NULL fields. The DCount only works if there is Non Null data in the form/table.
Any ideas as to how I can work around this?
Many thanks.
My comments are getting garbled so I am putting below original posting.
I copied the suggested code into module and rewrote query a couple of different way but still got error message: Run-time error 424. Object required
SpeciesCount = DCount("[Species]", "AnimalInfo", "[Species] = txtSpecies AND (is_null([L_ET_Color1],"""") = is_null(txtL_ET_Color1,""""))")
SpeciesCount = DCount("[Species]", "AnimalInfo", "[Species] = '" & txtSpecies & "'AND is_null([L_ET_Color1],"") ='" & is_null(txtL_ET_Color1, "") & "' AND [L_ET_No1]='" & txtL_ET_No1 & "'")
I have been tinkering with this for 3 hours and am no closer to a solution. What am I doing wrong?
In a module create the following function:
public function is_null(ctl as variant, nullreplace as string) as variant
if ctl is null then
is_null = nullreplace
else
is_null = ctl
end if
end function
In your query change your reference to a given field, yourField to is_null(yourField, "").
In a string enclosed in double quotes, this will need to be is_null(yourField, """") (it needs a double-quote to escape a double-quote, so 4 is 2 internally)

How would I make a form which searches for values in all tables of a database in access

I am trying to make a form which searches for the value inside all of the tables in the database (there are more than 1 table). The result will be displayed as the name of the table which this appears in. If someone can help me that will be nice.
In short, I have a form with a textbox and button. I enter the search string (for example 183939) and click on the button. It searches the value (183939) inside all the fields in the tables in the database, and if the value is found, then it displays the name of the table that it appears in. Thanks for the help.
I think this is a bad idea because it could take a very long time, and provide confusing results due to also searching system tables... but the following function will return an array of all table names containing the search term or nothing if it wasn't found. Calling example is such: theTables = containingTable("hello") where theTables is a variant. A limitation is that this will fail for multi-valued fields.
Function containingTables(term As String)
Dim db As Database
Dim tds As TableDefs
Dim td As TableDef
Set db = CurrentDb
Set tds = db.TableDefs
For Each td In tds
For Each f In td.Fields
On Error Resume Next
If DCount("[" & f.Name & "]", "[" & td.Name & "]", "[" & f.Name & "] LIKE '*" & term & "*'") Then
If Err.Number <> 0 Then
Debug.Print Err.Number, Err.Description
Err.Clear
On Error GoTo 0
Else
containingTables = containingTables & td.Name & ","
Exit For
End If
End If
Next
Next
Set tds = Nothing
Set db = Nothing
'Alternate Version
if Len(containgingTables) then containingTables = Left(containingTables, Len(containingTables) - 1)
'Original Version
'if Len(containgingTables) then containingTables = Split(Left(containingTables, Len(containingTables) - 1), ",")
End Function
To display the results with the alternate version, just use: Msgbox(containingTables(searchTerm)) where searchTerm is whatever you are searching.
Me as well i don't know why you would want to do something like that...
I think the solution posted by Daniel Cook is correct, i just took a slightly different approach. Do you need to match the exact value like I do? Anyway, here's my code:
Function searchTables(term as String)
Dim T As TableDef
Dim Rs As Recordset
Dim Result() As String
Dim Counter
Counter = 0
For Each T In CurrentDb.TableDefs
If (Left(T.Name, 4) <> "USys") And (T.Attributes = 0) Then
Set Rs = T.OpenRecordset
While Not Rs.EOF
For Each Field In Rs.Fields
If Rs(Field.Name) = term Then
Counter = Counter + 1
ReDim Preserve Result(Counter)
Result(Counter) = T.Name & "," & Field.Name
End If
Next
Rs.MoveNext
Wend
Rs.Close
End If
Next
If Counter = 0 Then
searchTables = Null
Else
searchTables = Result
End If
End Function
You should filter out duplicated values, in case the function matches multiple times the same filed in the same table.

Repetition of query to produce report

I am creating a bill of materials program.
There are two main tables named Products and Sub_Products.
In the Products table, the fields are (Product_Name, Code).
In the Sub_Products table, the fields are (Code, Sub_Name).
The tables are linked with code, i.e.: one product is made up of many sub_products, each sub_product is a product as well, making it have many sub_products.
I have created a query that reads a product and gets its sub_products. I need a query to compare Sub_Name with Product_Name and then check more sub_products,
continuing until no more sub_products are found.
Any ideas?
I guess you will have to use a script rather than SQL query to loop through them. Assuming that the products can be nested more than 3 levels.
I've been working on this exact problem in an ASP.NET MVC application. A function that gathered all the subproducts for each product and recursed on each subproduct worked well. We have some BOMs that are 15 levels deep.
I realize this question was asked a long time ago, but I had a very similar question and finally figured out a good answer. So I am posting it here in case anyone needs to know how to create a Bill of Materials.
In my example there is a table called "Part_Item_Table" which lists parent items and all of their childeren. Those childeren can also be parents to other childeren. The difficulty was that the BOM could be 3 levels deep all the way up to 30 levels deep or more. My "Part_Item_Table" also lists whether items are "Make" items or not. Only "Make" items will have childeren. The table you are querying may not have that feature, but the code below will probably still be helpful to get the idea.
This set of code uses several things that were new to me such as recursive code, calling a query I had already created and passing in a variable using the querydef methods, and using recordsets to get large information sets in and out of functions. I also used a sequence field in my BOM Table so I could sort by it and view the BOM in the order it is meant to be (Showing visually which level 3 items roll up into which level 2 items). If there is something that can be improved I am open to suggestions. This does work for my needs right now and hopefully it is helpful to someone else.
Option Compare Database
Public stFirstPart As String
Private Const BOMTable As String = "BOM_Table" 'Set this variable to the name of the table
Private Const ComponentQ As String = "GetComponentsQ" 'Set to the name of the query in the database
Function BOM()
Dim stQuery As String 'Used to make a query
Dim i As Integer 'Used to create the sequence number
Dim iLevel As Integer 'Used to show BOM level
Dim rsParent, rsBOMTable As DAO.Recordset 'Used to hold query results
'Make sure there is a part number in the form
If IsNull(Forms![Entry Form]![Part_Number]) Then
Debug.Print "There is no part number entered in the form"
MsgBox "There is no part number in the form.", vbOKOnly, "Can't fool me."
Exit Function
End If
stFirstPart = Forms![Entry Form]![Part_Number] 'Get the top part number from the form
'Make sure this is a Make item. Only make items will have childeren
stQuery = "SELECT ITEM.ITEM_NO, ITEM.MAKE_BUY_FLAG, ITEM.CURRENT_FLAG " & _
" FROM PART_ITEM_TABLE AS ITEM " & _
" WHERE (((ITEM.ITEM_NO)='" & stFirstPart & "') AND ((ITEM.MAKE_BUY_FLAG)='M') AND ((ITEM.CURRENT_FLAG)='Y'));"
Set rsParent = CurrentDb.OpenRecordset(stQuery)
If rsParent.EOF And rsParent.BOF Then
Debug.Print "This is not a make item"
MsgBox "This is not a Make item.", vbOKOnly, "I tried."
Exit Function
End If
'Clear the BOM table and load this first part number
DoCmd.SetWarnings False
DoCmd.RunSQL "Delete from " & BOMTable & ""
Set rsBOMTable = CurrentDb.OpenRecordset(BOMTable, dbOpenDynaset)
i = 1
iLevel = 1
rsParent.MoveFirst
With rsBOMTable
.AddNew
!Sequence = i
!Level = iLevel
!Item_Number = stFirstPart
!Make_Buy = "M"
.Update
End With
rsParent.Close
Set rsParent = Nothing
rsBOMTable.Close
Set rsBOMTable = Nothing
'-----------------------------------------------------------------------------------------------------------------------------------
'Start going down levels
'-----------------------------------------------------------------------------------------------------------------------------------
iLevel = 2
Call RecursiveLevels(stFirstPart, iLevel, i)
DoCmd.SetWarnings True
End Function
Function RecursiveLevels(PartNumber As String, iLevel As Integer, i As Integer)
Dim rsLevels As DAO.Recordset
Dim stPart As String
Set rsLevels = GetComponents(PartNumber)
If rsLevels.BOF And rsLevels.EOF Then
Debug.Print "This was a Make item with no children. That shouldn't happen. "; PartNumber
GoTo ExitPoint
End If
rsLevels.MoveFirst
Do While Not rsLevels.EOF
If rsLevels!Make_Buy <> "M" Then ' Anything that is not a Make item is written to the BOM table one line at a time.
i = i + 1
Call WriteToBOMTable(iLevel, i, rsLevels!Parent_Number, rsLevels!Component_Number, rsLevels!Make_Buy)
Else 'The Make item is written to the table, then we query for all of its children
stPart = rsLevels!Component_Number
i = i + 1
Call WriteToBOMTable(iLevel, i, rsLevels!Parent_Number, rsLevels!Component_Number, rsLevels!Make_Buy)
If stPart = stFirstPart Then 'Check to make sure this recursive thing doesn't go on forever.
Debug.Print "This part number is the same as the first part number. Circ Reference. "; stPart
GoTo ExitPoint
End If
iLevel = iLevel + 1 ' get ready to go one level deeper
Call RecursiveLevels(stPart, iLevel, i)
End If
rsLevels.MoveNext
Loop
ExitPoint:
iLevel = iLevel - 1 'Done with this level. Come back up a level.
rsLevels.Close
Set rsLevels = Nothing
End Function
Function WriteToBOMTable(Level As Integer, i As Integer, ParentNumber As String, ComponentNumber As String, MakeBuy As String)
Dim rsBOMTable As DAO.Recordset
Set rsBOMTable = CurrentDb.OpenRecordset(BOMTable, dbOpenDynaset)
With rsBOMTable
.AddNew
!Parent_Number = ParentNumber
!Item_Number = ComponentNumber
!Level = Level
!Make_Buy = MakeBuy
!Sequence = i
.Update
End With
Debug.Print "Level: "; Level; "Component: "; ComponentNumber
rsBOMTable.Close
Set rsBOMTable = Nothing
End Function
Function GetComponents(PartNumber As String) As DAO.Recordset
Dim qdf As QueryDef
Set qdf = CurrentDb.QueryDefs(ComponentQ)
qdf.Parameters("PartNumber") = PartNumber
Set GetComponents = qdf.OpenRecordset
End Function