I am currently making an search loop in VBA for my database. In this database I Have 2 Tables, one with the customers records called Main and another one with search words called Sparr. The idea is to use a filter to filter out the customers that match any of the search words in the Sparr table. Any customer that does not match any of the search words gets added to another table called filteredCustomerT.
For example:
table "Main"
Field "Mail"
mike#coolmail.com
john#hotmail.com
dave#mail.com
jonny#mailx.com
table "Sparr"
Field "sparrord"
hotmail
jonny
table "Testtable"
Field "testMail"
mike#coolmail.com
dave#mail.com
So if I run this VBA code I want john#hotmail.com and jonny#mailx.com to be filtered out. The Main table contains 200k records and the Sparr table contains 2k search words. I have wrote some VBA code that should loop through the Main table. For every record in the Main table a have another nested loop that loops through the Sparr table so see if there is any match. If there is not a match the VBA code copies the entry to anther table called Testtable. I Use the inStr function to do the matching.
Below I have posted the VBA code that does not seem to work. Can anyone help me ant maybe point out a fault in the code. I am very new to VBA programming.
Option Compare Database
Option Explicit
Sub filter()
Dim mainMail As Recordset
Dim sparrSokord As Recordset
Dim testtableTestmail As Recordset
Dim mainTemp As String
Dim sparrTemp As String
Dim match As Integer
Set mainMail = CurrentDb.OpenRecordset("Main")
Set sparrSokord = CurrentDb.OpenRecordset("Sparr")
Set testtableTestmail = CurrentDb.OpenRecordset("Testtable")
Do Until mainMail.EOF
mainTemp = mainMail![Mail]
match = 0
sparrSokord.MoveFirst
Do Until sparrSokord.EOF
sparrTemp = sparrSokord![sparrord]
If (InStr(mainTemp, sparrTemp) <> 0) Then
match = 1
Exit Do
End If
sparrSokord.MoveNext
Loop
If (match = 0) Then
testtableTestmail.AddNew
testtableTestmail![testMail] = mainTemp
testtableTestmail.Update
End If
mainMail.MoveNext
Loop
End Sub
InStr can operate in unexpected ways is you have nulls/empty strings/etc, involved.
I've noticed that you have nothing resetting the position of SearchwordWord back to the beginning of the record set once you reach the end.
Naturally you would do something like SearchwordWord.MoveFirst before the Do Until SearchwordWord.EOF. It doesn't hurt to do one before the Do Until customerMail.EOF either
I'll also note the Do Until always executes the contents of the loop, and then checks the condition at the end (which could be giving you unexpected results, especially with SearchwordWord being at EOF after the first successful pass of the loop.
You probably want to use a while/wend instead for both do untils (I practically never use them as it is). This is probably the biggest cause of your grief.
The problem is now solved. I just had to close some programs and try again with the updated code above. Worked just fine!
Related
I am fairly new to access and I am working on a database to manage Bills of Materials (BOMs) of several products. I would like to create a form where the user can input a list of component names to see where these components are being used.
To run a query based on one word is simple but I am struggling to extend it to more than one word.
I would like the user to input the components separated by new lines like this:
Item1
Item2
Item3
...
If I use the text field as is then the query won't find anything because it takes the text field as a whole and not line by line.
I have tried to process the text field into a ListBox because I thought that it would be handled like an array but it does not have a <value> therefore it will not return any search results.
My next try was to use a second text field where I can format the information to the format "Item1";"Item2";"Item3" so that I can use it in an in statement.
If I directly put:
in ("Item1";"Item2";"Item3")
In the query criteria then it will run as expected, however if I try to reference the HelpText (which contains: "Item1";"Item2";"Item3") like so:
In ([Forms]![Search_mult_component]![HelpText])
Then I get no results. I have also tried formatting the text to include the parenthesis like so ("Item1";"Item2";"Item3")
As I mentinoed I am just getting to know Access therefore I am not sure if this is a good practice or if I am trying to force something which can be done in a simple way with a slightly different approach.
Thank you for the support in advance!
You can't. Just like table and field names, the value string of an IN statement must be present before the query runs.
Your only option is to write the SQL string dynamically and, say, create a temporary query to which you pass the SQL.
Using a list box to represent the selection criteria is a good approach as it will allow the user to select & manipulate multiple items as distinct elements, without having to worry about delimiters and such.
However, rather than attempting to access the value of the list box, I would suggest using the list box to populate an underlying table, which you can then join to the relevant field in your query, causing the results to be automatically filtered without the need for a where clause.
I have managed to solve it after #Lee Mac provided the spark!
I created a Table TempTable to store the input values and I was able to use this table in the query later. It still took some code to get there though!
In the form I used a textbox with the multiline data as defined in the question and a button to run the Query. The steps therefore are somewhat separated between the Textbox itself and the button.
The Textbox has the following code for the AfterUpdate event:
Private Sub search_text_AfterUpdate()
Dim TempTable As DAO.Recordset
Dim i As Integer
Dim allNums() As String
' ======= Open table =======
Set TempTable = CurrentDb.OpenRecordset("SELECT * FROM [TempTable]")
' ======= Clear table =======
DoCmd.SetWarnings False 'Disable warnings. If not disabled the user will be prompted to confirm the delete
DoCmd.RunSQL "DELETE * FROM TempTable" 'SQL statement used to delete all entries from a table
DoCmd.SetWarnings True 'Enable warnings
' ======= Process multiline text =======
allNums = Split(search_text.Value, vbNewLine) 'Split the multiline text
For i = LBound(allNums) To UBound(allNums) 'process line by line
If (StrComp(Trim(allNums(i)), "", vbTextCompare) > 0) Then 'Check if current text is empty string
TempTable.AddNew 'Add new Record to table
TempTable![ToSearch] = (Trim(allNums(i))) 'Fill in the text, trim whitespaces
TempTable.Update 'Update Table. Seems to be necessary for changes to take effect
End If
Next i
' ======= Clean up and close table =======
TempTable.Close 'Close the table
Set TempTable = Nothing 'No idea why this is needed :D
' ======= Refresh table =======
'DoCmd.SelectObject acTable, "TempTable"
'DoCmd.Requery
'DoCmd.GoToRecord acDataTable, "TempTable", acLast
End Sub
This code takes care of clearing the table TempTableand filling it with the new input. TempTable has two columns ID and ToSearch where ID is the primary key for the table and ToSearch will be populated with the input from the textbox. I have seen during the testing that when the entries from the table get deleted the new items will still receive new keys when added which worries me because the keys might run out at some point. This is a problem for the future but if you have any advice on this (for example removing the key from the table or sthing) then please let me know.
The code runs after the user hits an enter on the text field but needs a Requery of the table to take effect! This is included in the Button macro:
First the table oject is selected and a Requery is run. After this I close the table and open the actual Query which also needs to be requeried to show the correct updated values.
With this solution I was able to do what I intended to do without having to delve into the depths of SQL.
As always, I appreciate your comments!
Okay, friends, I'm leaving my job in a week and a half, and I'm trying to make what I've done easier for my boss to do. He has no access knowledge, so I'm trying to create a form that will automate the reports I've been generating. Rather than create a different form for all the different reports, I'm trying to automate it from a table of parameters. Here's what I'm going for:
I have a table, which I have created, which is comprised of 5 fields. I'd like to use these fields to fill parameter fields in a standard form template. The five fields in my table are as follows:
The type of query being run (the result spit out)
The queries that generate this report, separated by a comma and no space. "QRYNAMEA,QRYNAMEB"
The Table which these queries generate, which will be used by transferspreadsheet
The destination excel file, which already has a pivot table set up to feed of the data.
The input sheet of this excel file. Currently, all of these sheets are called "Input". (that isn't important)
My issue comes with having no idea where to go after I've made my combo box. I know enough visual basic to automate my queries, but not enough to populate the form with the information in 3,4 and 5 (so far, I've been manually changing these for different queries). I have no idea how to look up the record in the table from the choice in the 'choosebox', and then select individual fields from that in my automation.
I'm pretty confident in my ability to parse #2 and automate the queries, and to put the values into the fields I'm looking at, but I don't know how to actually pull those values from the table, before I can do these things. I also can't seem to describe this well enough for google to help me.
Has anyone done something like this before? I'm assuming I just lack knowledge of one of the VBA libraries, but I've not had any luck finding out which.
edit:
my inclination at this point is to create a query for this table, which will return a single field depending on the input I give. I can imagine doing this in SQL, but I still don't know how to populate the forms, nor extract the field object from the table once I get it.
I have to head out for the day, but I'll be back on Friday to keep working on this, and I'll post my solution, once I find it. This seems like a unique conundrum, and it would be nice to give an answer to it.
Final edit: code is polished (does not have much in the way of error handling):
The first method, which pulls the fields from the table and populates the form, is activated by choosing a new entry in the combo box and looks like this:
Private Sub QuerySelect_Change()
Dim db As Database
Dim rec As Recordset
Set db = CurrentDb
Set rec = db.OpenRecordset("SELECT [Queries to Run], [Source Table], [Destination Spreadsheet], [Destination Sheet Name] FROM TBL_QRY_SETTINGS WHERE TBL_QRY_SETTINGS.[Query Type] Like '" & [Forms]![QuerySelector]![QuerySelect] & "';")
[Forms]![QuerySelector]![QueriesToRun].Value = rec("Queries to Run")
[Forms]![QuerySelector]![SourceTable].Value = rec("Source Table")
[Forms]![QuerySelector]![FileDest].Value = rec("Destination Spreadsheet")
[Forms]![QuerySelector]![SheetName].Value = rec("Destination Sheet Name")
Set rec = Nothing
Set db = Nothing
End Sub
The second code pulls that data to run the query. I like how this turned out. It runs when a button near the combobox is clicked.
Private Sub DynamicQuery_Click()
Dim qryArray As Variant
Dim i As Integer
qryArray = Split([Forms]![QuerySelector]![QueriesToRun], ",")
DoCmd.SetWarnings False
For i = LBound(qryArray) To UBound(qryArray)
Debug.Print qryArray(i)
DoCmd.OpenQuery (qryArray(i))
Next
DoCmd.SetWarnings True
DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel12Xml, [Forms]![QuerySelector]![SourceTable], _
[Forms]![QuerySelector]![FileDest], _
True, [Forms]![QuerySelector]![SheetName]
End Sub
Note that the final code for part (1) is almost the same as the selected answer, except that I am grabbing more than one field. This works because I know that I have unique "Query Types", and my recordset will only contain one record.
Anyway, I hope some people stumble upon this and find it useful. Send me a message if you do. As far as I can tell from brief googling, this sort of automation work has not been done in access. It should make it easier for access-illiterate to run their own queries, and be simple for designers to add to, if they want all their queries available after a few clicks.
Someone could conceivably use this to automate a variety of reports in sequence, by iterating through a table like the one I reference.
I may be massively misunderstanding what you're doing, but I think it's as easy as creating a new form using the form wizard. It will let you choose the table that contains the data, and it will let you choose which fields you want to add.
You can later change any of the textboxes to combo boxes which will allow you to limit the choices available to fill in.
Am I understanding that correctly?
EDIT: This will fill a variable (MyRandomField) with the contents of a field in a table
Dim db as Database
Dim rec as Recordset
set db = CurrentDB
set rec = db.OpenRecordSet("Select SomeField from SomeTable Where Something = 'SomethingElse'")
MyRandomField = rec("SomeFieldName")
set rec = Nothing
set db = Nothing
I'm currently working with an Access(2010) Query that is taking multiple order items and GROUPING BY the order number. For example, a customer may have ordered 5 items under order number 123 but my query groups all of these individual items into one line with each item in it's own column. The objective is each individual order number is contained on one line because the query will be exported in a .csv format to 3rd party software.
Since I'm combining multiple records/items I'm running into issues when the description field is getting truncated based on 255 characters. Since working in a query I don't see the option of changing the text field to a memo field, so that won't resolve the issue and the GROUP BY clause is capping all the text fields at 255 chars anyway.
So, my question is instead of using the description fields from the current query is there a way that I can use an additional table to lookup an items description based on the part number ordered? Proposed new table would be something very simple like:
PART | DESC
123 Widget Z_Langs_AUS_INT<br>
567 Widget K_Langs_DOM_CAN<br>
890 Widget B_Langs_SM
So the ideal statement in the query would be something like IIF TABLE1.PART#ORDERED(from current query) = NEWTABLE.PART(from new table), then obtain NEWTABLE.DESC(from new table) otherwise ""
This would return the description of a specific part number if the part number fields matched otherwise leave the field blank because it likely doesn't contain a part number.
I'd appreciate any suggestions at this point. I may be going about this all wrong when trying to resolve this issue so fresh input is welcome.
Don't write that as a query. It's too complex to bother with, and is extremely simple to do with VBA and direct text access. Add a reference to the Microsoft Scripting Runtime (scrun.dll), use internal query objects for your SQL data, and use code similar to what's below.
Sub makeText()
Dim rs1 As Recordset, rs2 As Recordset
Dim oFSO As Scripting.FileSystemObject, oFile As Scripting.TextStream
Dim txtLine As String
Set oFSO = New Scripting.FileSystemObject
oFile = oFSO.CreateTextFile("FileName.csv")
Set rs1 = CurrentDb.OpenRecordset("baseQuery")
Do Until rs1.EOF
txtLine = rs1!OrderNumber.Value
Set rs2 = CurrentDb.OpenRecordset("innerQuery")
Do Until rs2.EOF
txtLine = txtLine & "," & rs2!itemDesc.Value
rs2.MoveNext
Loop
oFile.WriteLine txtLine
rs1.MoveNext
Loop
oFile.Close
End Sub
Below is a picture from an example database we used to use. It has our parts with all the info you could need, and then the part that matches up with our vendor. The query used will display each and every row where our part matches the vendor part.
You can also just put a [AskMeForMyPart] or whatever in square brackets like that to make the query pull based on that specific part.
here's my problem. I have 2 tables
tvehicle and tPMCL
the tVehicle table has a list of our vehicles, and the tPMCL holds when preventitive maint. is done.
tvhhicle.VehicleTagnumber holds the actual plate number, and tPMCL.Tag holds only a Index of a look up of that number from when it was entered, I wish it had the tag number so when i do loops through my data comparing it would be able to match up, as it is:
it's comparing something along the lines of "XPE 269" to 1 and that's not working so well.
Any ideas? the answer may not be a VBA answer it may be a diferent way to do the lookup in the first place. But I just can't find another way to do the lookup and actually store the plate number and not an index of it.
It appears that what you think of as Index is actually a foreign key. This is a good thing. This means that if VehicalTagNumber where to change (e.g. a bad input) the referring table would not need to be updated.
If you need to loop through the tPMCL and you need the corresponding Tag Number you can do one of two things.
You could use Dlookup to get it on each loop. e.g.
Dim strTag As String
strTag = DLookup("[VehicleTagnumber]", "tvhhicle","[Id] = 1")
However this will be slow for large numbers of records.
Instead just base your record set on a SQL statement that joins the two table instead of opening a table directly.
Dim dbVehicle As Object
Dim rstVehicle As Object
Dim fldEnumerator As Object
Dim fldColumns As Object
Dim strSQL as String
Set dbVehicle = CurrentDb
Set rstVehicle = dbVehicle.OpenRecordset("tVehicle")
Set fldColumns = rstVehicle.Fields
strSQL = "SELECT * FROM tMPCL m INNER JOIN tVehicle v ON m.Tag = v.ID"
Set rsttPMCL = dbVehicle.OpenRecordset(strSQL)
I am using Access 2007 and writing a macro in VBA.
"Analyze" subroutine is passed a recordset rev_rec. This recordset has two fields "PeriodNo" (Integer) and "Revenue"(double).
Minrev is a variable where I have to store the minimum value of "Revenue" field. The code that I am using is below.
Public Sub Analyze(rev_rec As DAO.Recordset)
Dim Minrev As Double
rev_rec.MoveFirst
Minrev = rev_rec("Revenue")
While Not rev_rec.EOF
If rev_rec("Revenue") < Minrev Then
Minrev = rev_rec("Revenue")
End If
rev_rec.MoveNext
Wend
rev_rec.MoveFirst
.
.
.
End Sub
I tried using DMin() with recordset but am not able to find a way to do that. Is there a way to do this without using any kind of loop?
I think your best bet is to build a new recordset using a SQL statement that only retrieves one record, the one with the minimum for the desired period. Another option is, you could open this particular recordset with the Order By on the Revenue column Ascending. This way you would know that the smallest value will be in the first record.
Andy Brown's suggestion of using DMin should also work. It's actually an idea very similar to my first suggestion.
The problem is that you're passing a recordset. If you just had the table or query name (or SQL statement), you could just use DMIN. Eg:
MinRev = DMIN("Revenue","TableOrQueryNameInQuotes","")
The third argument can be used to set some criteria. For example:
MinRev = DMIN("Revenue","TableOrQueryNameInQuotes","PeriodNo > 5")
Be warned, however, that the functions starting with D (DMIN, DLOOKUP, DSUM) run very slowly, although if you've got less than about 10,000 records you won't notice this.