Excel VBA insert array variable into single field in access - sql

In my project I'm trying to save indefinite entry in excel sheet into the database (access). I'm trying to use vba to do so. However, because I don't know how long the entries will be, and I can't create so many fields in the database to fit the unknown amount of entries. And I can't INSERT an array variable into one field in the table. How can I do this?
Say I have below.
Dim SN(100) As String
SN(0)=123 'value of each entry
SN(1)=2412
'so on and so many entries
Then in the SQL statement,
"INSERT INTO [TableName] ([FieldName]) Values (SN)"
It will say RunTime Error "13" Type mismatch.
Is there any way the database field can accept a list or array as data type? So when I call the data out I can use the index to call the variable inside the list?

MS Access does not have such a data type, more primitive types like string, boolean, long. But consider joining all items in array using Join into a single string and save that string to database table. Then use Split to pull back into array as needed:
Sub ArrayIntoSQL()
Dim SN(100) As String, TN() As String
Dim SNstr As String,
SN(0) = 123 'value of each entry'
SN(1) = 2412
SNstr = Join(SN, "|") ' PIPE DELIMITER '
'123|2412|||||||||||||||||||||||||||||||||||||||||||||_
||||||||||||||||||||||||||||||||||||||||||||||||||||||'
strSQL = "INSERT INTO [TableName] ([FieldName]) Values ('" & SNstr & "')"
...
End Sub
Sub ArrayOutofSQL()
Dim SN() As String
Dim SNstr As String
strSQL = "SELECT [FieldName] FROM [TableName]"
...
Do While Not rst.EOF
SN() = Split(rst!FieldName, "|")
rst.MoveNext
Loop
...
End Sub
Because the array items may exceed Access' 255 character limit for short text data type, use the long text type (previously called memo) which does not have a declared limit.
However for best practices, wide tables (many columns) and stored objects like arrays in fields are more expensive than long tables (many rows) and linked objects. So simply save data iteratively:
For Each item in SN
strSQL = "INSERT INTO [TableName] ([FieldName]) Values (" & item & ")"
...
Next item
Of course, save values with an identifier like ID, Person, Date!

Related

MS Access Extract Multiple Matching Text Strings from Long Text Field compared to Table List

Issue: Query is not able to pull all of the restricted words found in a Long Text Field. It is getting the restricted words from a Table Column of ~100 values.
Sample Data
Table: RecipeTable with Long Text Field: RecipeText
Example Contents of RecipeText Field: Add the rutabaga, leeks, carrots and cabbage to the Instant Pot®. Seal and cook on high pressure for 4 minutes. Quick release the steam. Thinly slice the brisket across the grain and transfer to a serving platter. Arrange the vegetables around the meat, sprinkle with the parsley and serve with the sour cream, horseradish and mustard on the side.
Desired Result:
Want to Compare RecipeText Field against every value in this Short Text Field RestrictedItem in Table: RestrictedTable.
RestrictedTable.RestrictedItem contains 100 values. Let's say it contains 6 for this exercise: milk, bake, spoon, carrots, mustard and steam.
Query would find these matched words in no particular order for a single record: carrots mustard steam
I've tried this: How to find words in a memo field with microsoft access
Result: Finds only 1 of many matches within the Long Text field.
Desired Result: Find ALL matched words extracted within the Long Text string. Duplicates & wildcards are fine. Case sensitive is bad.
Example Tried:
SELECT a.Adjectives, b.Content
FROM A, B
WHERE b.Content Like "*" & a.[adjectives] & "*"
LIKE and after is where I believe the issue is. I've tried using %, parentheses, spaces, etc to no avail.
Mine became this:
SELECT RecipeTable.RecipeText, RestrictedTable.RestrictedItem
FROM RecipeTable, RestrictedTable
WHERE RecipeTable.RecipeText LIKE "*" & RestrictedTable.RestrictedItem & "*";
Notes:
I can find lots of advice to find single words, but not comparing whole table columns to one field.
And, lots of advice to find the first substring or nth position, but I want all of the substrings that match. Not the position & I'm afraid that applying trimming, etc, will slow things down on searching 100 words & trimming for each one.
I am fine making this a calculated field on my form that holds the RecipeText field.
Also fine with making a button that would launch a query to compare the RecipeText field with the RestrictedTable.RestrictedItem List & fill in an empty field RestrictedFound on the same form.
The code below are two approaches to find all restricted words that are in a memo field. While this could all be done programmatically without staging/work tables I would recommend using a temporary or permanent table to extract the words from the memo field via the split function in VBA (after accounting for punctuation and other data scrubbing).
After splitting the words from the memo field into an array they could then be inserted into a separate table with a foreign key reference to RecipeTable. This could be a temporary table or permanent if needed and could be part of the workflow process. A field like PendingReview could be added to RecipeTable for processing new records then marked as false afterwards so they won't be processed again.
After the words were added to the other table it could be joined to RecipeTable
by foreign key and you should have all matches of restricted words.
Once you have the information you could store the stats and discard the work record from your temporary table or delete the work records until the process is run again.
You could do it all in VBA with a dictionary lookup of the restricted words, i.e., query restricted words table, add to a dictionary then loop through matching each word in the memo field with lower case or case insensitive comparison, but it may take a while.
First Code Snippet Below
(If you want compile time checks then you must Reference the Microsoft Scripting Runtime my path is C:\Windows\SysWOW64\scrrun.dll)
Dim dic as Dictionary
Dim memoField as string
Dim words() as String
Dim matchCnt as Integer
'Other variables I didnt declare
'Code to populate dictionary
'Do Until rstRestricted.EOF
' dic.add LCase$(rst("restrictedWord")), 0
' rstRestricted.MoveNext
'Loop
'rstRestricted.Close
'Set rstRestricted = Nothing
Set rst = New adodb.Recordset
rst.Open "SELECT [MemoField] FROM RecipeTable;"
lngRowCnt = CLng(rst.RecordCount) - 1
For x = 0 to lngRowCnt
memoField = LCase$(Nz(rst("MemoField")))
'Replace punctuation like commas, periods
'memoField = Replace(memoField, ",","")
'Now split after data scrubbed
words = Split(memoField, " ")
intWordCnt = UBound(words)
For z = 0 to intWordCnt
If LenB(words(z)) <> 0 Then
If dic.Exists(words(z) = True Then
matchCnt = dic(words(z))
dic(words(z)) = matchCnt + 1
End If
End If
Next z
Next x
Dim WordKeys() as Variant
Dim y as Integer
Dim restrictedWord as string
Dim wordCnt as Integer
WordKeys = dic.Keys
For y = 0 to UBound(WordKeys) '-1
restrictedWord = CStr(WordKeys(y))
wordCnt = CInt(WordKeys(restrictedWord))
'code to save or display stats
Next y
rst.Close
Set rst = Nothing
Set conn = Nothing
I would just do the split of all words into a working table with the word field indexed then do an aggregate with counts of restricted words.
Second Code Snippet
'Option Explicit
Dim sql as String
Dim memoDelimitedData() as String
'Other variables declared
'Code to open Recordset table for recipe and also code to open
'Work table with adOpenDynamic (SELECT * from WorkTable)
'loop through records to be processed
'Split Field (May need variant instead of array. My Access VBA is rusty)
words = Split(memoField, " ")
intWordCnt = UBound(words)
For x = 0 to intWordCnt
With rstWorkTable
.AddNew
!Word = words(x)
!ForeignKeyIdToRecipeTable = intForeignKeyId
.Update
End With
Next x
Then when you have the work table records added you can join to the RecipeTable and the RestrictedTable.
So build a WorkTable of delimited Words from the memo field. Have the foreign key reference to the recipe table then join the RestrictedTable to the WorkTable by the RestrictedItem.
If needed this could be a query for a make table or a staging table permanent table. etc.
So something like this would then give you matches, of any words in your restricted table:
SELECT RecipeTable.RecipeText, RestrictedTable.RestrictedItem
FROM RecipeTable
INNER JOIN WorkTable ON
RecipeTable.Id = WorkTable.RecipeTableId
INNER JOIN RestrictedTable ON
WorkTable.ForeignKeyIdToRecipeTable = RestrictedTable.RestrictedItem
MS Access Split Function
At that point you could do counts, sums, and other data.
I'm sorry I thought I had example code, but I couldn't find it. I had to do something like this in college many moons ago using VBA and Access (Word Count/Ranking assignment), but I can't find it. Nowadays I'd do this kind of stuff with SQL Server with numbers tables, XML/JSON functionality or the Full Text Searching capability.
Hopefully this may help point you in the right direction if you need to limit your work inside MS Access.
If you're not comfortable with working with ADODB or DAO recordsets you could build a CSV delimited file with the foreign key and the word then import that file into a work table.

Run list of SQL queries using conditions from range of cell values

I'm trying to run a list of SQL queries where a condition exists for "code" and the values sit in a range of cells on another sheet (from cells A2 to A385).
I have the code below, however, I get an invalid object name for SQLQueries!$A2:A385
So, I understand the syntax is not correct but I'm struggling to find the correct one regardless of reading numerous articles.
Sub RunSQLQueries()
'Select SQLQueries sheet
Sheets("SQLQueries").Activate
'Initializes variables
Dim cnn As New ADODB.Connection
Dim rst As New ADODB.Recordset
Dim ConnectionString As String
Dim StrQuery As String
'Setup the connection string for accessing MS SQL database
ConnectionString = "Provider=SQLOLEDB; Data Source=HOSTNAME; Initial Catalog=DBNAME; UID=domain\user; Integrated Security=SSPI"
'Opens connection to the database
cnn.Open ConnectionString
'Timeout
cnn.CommandTimeout = 900
'Build SQK queries
StrQuery = "SELECT * FROM table WHERE code IN (SELECT * FROM [SQLQueries!$A2:A385])"
'Performs the queries
rst.Open StrQuery, cnn
'Select Results sheet
Sheets("Results").Activate
'Dumps all the results from the StrQuery into cell A2 of the active sheet
Range("A2").CopyFromRecordset rst
End Sub
The result I'm expecting is for a SQL query to be run using each condition from the range of values with the results being populated on the "Results" sheet from cells A2 down
The query string is literally sent to the database server, and since your sql attempts to refer to an excel list that the server cannot access it returns an error. The server is looking for a table named [SQLQueries!$A2:A385]
To stick with your current plan, you will need to pass the the IN () clause literally or by vba variable that is formatted as such:
IN ( 'item1', 'item2' ...)
Note:You can remove single quotes if the items are numeric
I advise rethinking the plan by either
1) if it is possible to adjust things in the database side: Can you create a new reference table to join to the actual table or create a view that only returns desired rows? Then you would need a job that tweaks the filtering view/ table before running the query. The idea being you would not need to adjust the query each time bc a constant sql string would return the rows you need
Or
2) if the source table has say 100k rows or less, and data is not too wide, just select all the rows into excel in a new sheet, then filter that sheet (add a new column in excel that returns true using vlookup against your reference sheet) or use vlookup on your reference sheet to pull the desired columns
Here's a suggestion:
StrQuery = "SELECT * FROM table WHERE code IN (" & _
InList(Sheets("SQLQueries").Range("A2:A385"),True) & ")"
Function to create a SQL "in" list given a range:
Function InList(rng As Range, quoted As Boolean)
Dim qt, a, r As Long, c As Long, rv As String, v, sep As String
a = rng.Value
qt = IIf(quoted, "'", "")
For r = 1 To UBound(a, 1)
For c = 1 To UBound(a, 2)
v = Trim(a(r, c))
If Len(v) > 0 Then
rv = rv & sep & qt & v & qt
sep = ","
End If
Next c
Next r
InList = rv
End Function
Notes:
Pass False as the second argument if you have numeric values
I wouldn't use this for very large lists
Be very certain you're not at risk from possible SQL injection issues: parameterized queries are always preferable but do not work with "in" lists

formatting combobox elements

In my database serial-numbers are stored as an Integer.
I want to represent these numbers in a Access-frontend combobox with a different formation:
"121212345" --> "12.12.12345"
When a number is chosen it should be stored as an Integer again.
I already tried to populate the combobox by myself with some VBA code:
use a query to select all numbers
iterate all numbers and convert each to the new formation
put each converted number in the combobobox
When a selection is made i use the afterUpdate-Event to convert it back to an integer value and store it in my table.
This approach works very well but populating the combobox takes very long (4 sec for 20.000 numbers).
Is there a faster way of doing it?
When i just use the plain integer-field as recordsource, the box is populated in no time.
Edit:
'populate Combobobox:
Set db = CurrentDb
strSQL = "SELECT intSerialNumber FROM tblXXXX"
Set rs = db.OpenRecordset(strSQL)
Do While Not rs.EOF
strCurrentSerNum = rs.Fields(0).Value
Dim strSerNum As String
'xxxxxxxxx to xx.xx.xxxxx when possible
If (Len(strCurrentSerNum)) = 9 Then
strSerNum = Left(strCurrentSerNum, 2) & "." &
Mid(strCurrentSerNum, 3, 2) & "." & Right(strCurrentSerNum, 5)
Else
strSerNum = strCurrentSerNum
End If
cboSerNum.AddItem (strSerNum)
rs.MoveNext
Loop
You should use the Input Mask property for this.
A valid input mask to achieve exactly what you want is the following:
##.##.#####;;

VBA MS Access Compare values in a column to a value using Instr

I have an Access database which contains a main table and a reference table. I'm trying to change a column in the table based on if it contains values in an array (pulled from the reference table) so the logic is something like this
Compare the value of the array to see if its contained within a column of the main table
If it is contained in the main table column change the group column to the value in the array
I have the code below which simply compares the value in the array against the table and prints the value. Im getting the error "Type mismatch". Does anyone know how i can compare the records?
Set rstTableName = CurrentDb.OpenRecordset("MAIN_TABLE")
For Each vItem In MyArray
Do While Not rstTableName.EOF
If InStr(1, [rstTableName!NAME], vItem) Then
Debug.Print (rstTableName!NAME)
End If
rstTableName.MoveNext
Loop
Next
You should be able to do this with just an update query.
For example:
Main_Table contains these fields - ID (AutoNum) and FieldToChange (Text)
ReferenceTable contains these fields - ID (AutoNum), ALookUpValue (Text), ChangeTo (Text)
If the value held in ALookUpValue appears anywhere in FieldToChange then replace the field value with whatevers in ChangeTo.
So, if FieldToChange contains ABCDEFG, ALookUpValue contains A and ChangeTo contains 'blah blah' then ABCDEFG will update to say 'blah blah'
UPDATE MainTable INNER JOIN ReferenceTable ON MainTable.FieldToChange LIKE '*' & ReferenceTable.ALookUpValue & '*'
SET MainTable.FieldToChange = ReferenceTable.ChangeTo
Hope that's understandable - end of the day. :)
Edit - test on a copy of your database - it will update values!
Several things:
Instr() returns a number (specifically variant type of Long) which is the first position of the lookup value. You have it compared to a boolean (T/F). But in VBA this is still legal as False is zero value and True non-zero value. However, it is advised never to rely on the equivalent numeric values of these types.
Brackets should not be wrapped around your entire recordset item but only around the field name:
Check the array item types. How it is declared and populated may invoke a Type Mismatch error. You do not include how array, MyArray is initialized and defined. For your needs, you need either String or Variant type array.
Try the following adjustment:
Set rstTableName = CurrentDb.OpenRecordset("MAIN_TABLE")
For Each vItem In MyArray
Do While Not rstTableName.EOF
If InStr(1, rstTableName![NAME], vItem) > 0 Then
Debug.Print (rstTableName![NAME])
End If
rstTableName.MoveNext
Loop
Next vItem
Alternatively, use the Like operator
Set rstTableName = CurrentDb.OpenRecordset("MAIN_TABLE")
For Each vItem In MyArray
Do While Not rstTableName.EOF
If rstTableName![NAME] Like "*" & vItem & "*" Then
Debug.Print (rstTableName![NAME])
End If
rstTableName.MoveNext
Loop
Next vItem

How to read an Access database column into an array with VB?

I apologize in advance if this has been answered before, but I couldn't seem to find exactly what I was looking for when I searched.
I'm not too familiar with VB. I was wondering if it was possible to read an entire column of a table in an Access database and put the data into an array using VB?
If you are using Access VBA you can use the Recordset.GetRows method.
This creates a two-dimensional array which matches the design of your recordset, and it takes a single parameter which is the number of rows to retrieve. To retrieve all rows, either get the .RecordCount before populating the array, or put in a number which you know is larger than required.
For example:
Sub ReadIntoArray()
Dim rstName As Recordset
Dim varName As Variant
Set rstName = CurrentDb.OpenRecordset("SELECT FirstName, LastName FROM tblContact")
varName = rstFirstName.GetRows(1000) ' Gets the first 1000 records
' Retrieve the 16th value from the 1st column
Debug.Print varName(0, 15)
' Get the 100th value from the 2nd column
Debug.Print varName(1, 99)
End Sub