Good evening!
At this moment I'm learning to work in MS Access for my job purposes. I gained some understanding of the program's basics, such as creating tables or making easy forms (though not yet working ideally), and by now I've got stuck in solving the following task.
I have a database BooksDatabase, which consists of three tables: Books, Authors and AuthorsInfo. First one contains information about books (name, genre, country, release year etc.), third one is about authors (first name, last name etc.) and the second one links ever book with its author(s). The task is to import data from text file to those tables, so that it would be almost automatic. I understand how to import files to MS Access (at least, the ones of *.txt extension) and I do this into the table BooksToImport, but I have some difficulties with inserting imported data. Here is the code of my function ImportBooks(), which I execute from macros of the same name:
' Procedure which imports data about books from the table BooksToImport
Function ImportBooks()
Dim dbBooks As Database
Dim rstImBooks, rstBooks, rstAuthors, rstBALink As DAO.Recordset
Dim codeI, codeB, codeA, codeL As Variant
'initializing database
Set dbBooks = CurrentDb
Set rstImBooks = dbBooks.OpenRecordset("Query_BooksToImport",dbOpenDynaset) 'receiving data from query
'checking if the query has any records
If rstImBooks.RecordCount = 0 Then
MsgBox "There are no records for importing!", vbInformation, "Attention!"
rstImBooks.Close
Set dbBooks = Nothing
Exit Function
End If
'if it's OK, we're making a loop on query's records
rstBooks = dbBooks.OpenRecordset("Books",dbOpenDynaset)
rstAuthors = dbBooks.OpenRecordset("AuthorsInfo",dbOpenDynaset)
rstBALink = dbBoks.OpenRecordset("Authors",dbOpenDynaset)
rstImBooks.MoveLast
rstImBooks.MoveFirst
Do While rstImBooks.EOF = False
'checking if there is a book in out database with the same name as in imported data
codeB = DLookup("[ID]","[Books]","[BookName] = '" & rstImBooks![BookName] & "'")
If IsNull(codeB) Then
'inserting new record
With rstBooks
.AddNew
![BookName] = rstImBooks![BookName]
.Update
.Bookmark = .LastModified
codeB = ![ID]
End With
End If
'in much the same way we're treating the data about authors and making the links
rstImBooks.MoveNext
Loop
rstImBooks.Close
rstBooks.Close
rstAuthors.Close
rstBALink.Close
Set dbBooks = Nothing
End Function
I have two problems with this function:
method .AddNew for rstBooks is not working — MS Access shows me a message with error 438 ("Object doesn't support this property or method");
also I cannot assign variable rstBALink to the recordset because compiler says "Invalid use of property".
So my question is this: how should I solve these two problems? What do I do wrong that my function is not working properly?
A few issues with your code that I see. These may or may not fix your problem.
Your declarations are implicit, meaning you aren't being specific with your code about what your recordset objects are. Instead of using:
Dim rstImBooks, rstBooks, rstAuthors, rstBALink As DAO.Recordset
Try:
Dim rstImBooks As DAO.Recordset
Dim rstBooks As DAO.Recordset
Dim rstAuthors As DAO.Recordset
Dim rstBALink As DAO.Recordset
You can put them all on one line separated by commas, but you still need to declare the type for each or Access will assume it's a variant.
Secondly, recordset objects need to be created using the Set keyword, not by using an = alone.
This was done correctly in the top portion of your code, but is incorrect here:
rstBooks = dbBooks.OpenRecordset("Books",dbOpenDynaset)
rstAuthors = dbBooks.OpenRecordset("AuthorsInfo",dbOpenDynaset)
rstBALink = dbBoks.OpenRecordset("Authors",dbOpenDynaset)
Should be:
Set rstBooks = dbBooks.OpenRecordset("Books",dbOpenDynaset)
Set rstAuthors = dbBooks.OpenRecordset("AuthorsInfo",dbOpenDynaset)
Set rstBALink = dbBooks.OpenRecordset("Authors",dbOpenDynaset)
I think that will solve your issues, but I didn't review every line of your code admittedly. Let me know if you still have problems.
EDIT:
Found a typo:
rstBALink = dbBoks.OpenRecordset("Authors",dbOpenDynaset)
Should be:
Set rstBALink = dbBooks.OpenRecordset("Authors",dbOpenDynaset)
(missed an 'o' in dbBooks)
Related
I have an excel document that I want to link to an SQL query. In the excel document I have a list of item numbers. Whenever an item number gets changed I want the sql query to query that list of item numbers and return an output. Basically I want the excel sheet to use the Item Number as a parameter for the database item numbers ? The excel item numbers are updated daily.
Keep in mind that this is a mock example for what you are trying to do. With no knowledge of your database or spreadsheet, I can't guarantee that any of this will even work. At the very least, it will require you to make some adjustments before you can use it.
With that in mind, I have commented on various parts of the code to let you know what is going on there. The sections that have a *** are areas that you may want to change. The sections with ### are areas that you will HAVE to change for it to work for you.
This code assumes that you have a list of item numbers in column A of sheet 1, that each item number will only return one record, and that there are no blank cells in your list of item numbers.
Sub GrabItemInfo()
Dim objADO As New ADODB.Connection
Dim objRecSet As New ADODB.Recordset
Dim objCmd As New ADODB.Command
Dim strConn As String
Dim strSQL As String
Dim RowNum As Long
Dim errNum As Long
'open a connection to the database
'### change the properties for the connection to suit your needs
strConn = "DSN=DSNName; DBQ=Database; UID=Username; PWD=Password"
objADO.Open strConn
objCmd.ActiveConnection = objADO
objCmd.CommandType = adCmdText
'errNum is the row that the error log will start on
'***change errNum to change which row it starts on
errNum = 1
'***changeRowNum here to change which row to start on
RowNum = 1
'start the loop
Do Until ThisWorkbook.Sheets(1).Cells(RowNum, 1) = ""
On Error Resume Next
'### change the sql to whatever you need
'*** change the cells section if you're not using the first column
strSQL = "SELECT [field] FROM [table] WHERE ItemNum = " & ThisWorkbook.Sheets(1).Cells(RowNum, 1).Value
objCmd.CommandText = strSQL
Set objRecSet = objCmd.Execute
'pastes results from query into the cell next to the item number
'***change the cells section if you want to use a different column
ThisWorkbook.Sheets(1).Cells(RowNum, 2).CopyFromRecordset objRecSet
'clear out the recordset before the loops starts again
Set objRecSet = Nothing
'put the item number, error number, and error description on the second sheet of the work book
'***change the sheet number to put it on another sheet if you're already using the second
If Err > 0 Then
ThisWorkbook.Sheets(2).Cells(errNum, 1).Value = ThisWorkbook.Sheets(1).Cells(RowNum, 1).Value
ThisWorkbook.Sheets(2).Cells(errNum, 2).Value = Err.Number
ThisWorkbook.Sheets(2).Cells(errNum, 3).Value = Err.Description
On Error GoTo 0
End If
'raise the value for the row for the next iteration
RowNum = RowNum + 1
Loop
'clear out the connection
Set objADO = Nothing
Set objRecSet = Nothing
Set objCmd = Nothing
End Sub
For more information on connection strings, I recommend http://www.connectionstrings.com
It's a great resource to use for figuring out what kind of connection string you need. Connections strings can be...tricky...sometimes, and this really helps.
If you need any resources for SQL, I would recommend http://www.w3schools.com/sql
They have a good introduction to it there. Past that, get a good reference book, find a mentor, join forums(or Q&A sites like this one), etc. If you look into the SQL tag on this site, there is more information, along with some recommended resources as well.
Good luck.
I have a simple_table with 4 fields:
a,b,x,P
I am trying to update the field p based on the output of a function that uses the other fields as input parameters. In this case the function is an excel function.
I was using SQL server but really need to access some statistical functions. So yesterday I opened access for the first time. Eeek. I've spent the last day trying to learn vba and following various tutorials on recordsets.
The bit I'm struggling with is how to I update a the P field based on the other fields? In a loop?
Thanks very much.
Dim objExcel As Excel.Application
Set objExcel = CreateObject("Excel.Application")
'Test it works
MsgBox objExcel.Application.BetaDist(0.4, 2, 5)
'OK, that works :)
'set up the ADO stuff
Dim cnn1 As ADODB.Connection
Dim MyRecordSet As New ADODB.Recordset
Set cnn1 = CurrentProject.Connection
MyRecordSet.ActiveConnection = cnn1
'Load data into MyRecordSet
MySQLcmd = "SELECT * FROM simple_table"
MyRecordSet.Open MySQLcmd
'HELP WITH THE NEXT BIT PLEASE!
'Some kind of loop to go through the recordset to set the field P
' equal to the result of the excel function betadist(x,a,b)
'I imagine looping through something like the following semi pseudo code ???
myRecordSet.Fields(“P”).Value = objExcel.Application.BetaDist(myRecordSet.Fields(“x”).Value, myRecordSet.Fields(“a”).Value, myRecordSet.Fields(“b”).Value)
'end of the loop
objExcel.Quit
Set objExcel = Nothing
MyRecordSet.Close
cnn1.Close
Set MyRecordSet = Nothing
Set cnn1 = Nothing
Since your code works with "Dim objExcel As Excel.Application", that means you have a reference set for the Excel object library. In that case, you don't need a full Excel application instance in order to use the BetaDist function. You can set an object variable to Excel.WorksheetFunction and call the function as a method of that object. However, I don't know whether that makes a significant difference. I didn't test the CreateObject("Excel.Application") alternative.
In this sample, I used a DAO recordset instead of ADO. The reason is I've found DAO can be significantly faster with native Access (Jet/ACE) data sources. You can switch to ADO if you prefer, but I don't see an advantage.
Notice I opened the table directly rather than via a query. The DAO dbOpenTable option can also benefit performance.
With those details out of the way, it's just a simple matter of looping through the recordset, calling the function with values from the current row, and storing the function's result in the P field ... pretty much what you outlined in your pseudo-code. :-)
Dim objWFunction As Object ' Excel.WorksheetFunction
Dim MyRecordSet As DAO.Recordset
Dim db As DAO.database
Set objWFunction = Excel.WorksheetFunction ' Excel reference required
Set db = CurrentDb
Set MyRecordSet = db.OpenRecordset("simple_table", dbOpenTable)
With MyRecordSet
Do While Not .EOF
'Debug.Print objWFunction.BetaDist(!x, !a, !b)
.Edit
!p = objWFunction.BetaDist(!x, !a, !b)
.Update
.MoveNext
Loop
.Close
End With
Set MyRecordSet = Nothing
Set db = Nothing
Set objWFunction = Nothing
I have a timesheet system in excel with 3 rows (standard time, overtime, double time) for each of our (100+) employees, and one column for each cost code on the site. This ends up being a giant matrix, most of which is empty. My solution is to basically create an employee datatype which stores the employee information and hours for a single cost code.
Public Type Employee
Name As String
Trade(1 To 3) As String
EmpNum As Long
Comment As String
AddOns(1 To 3) As Single
Allowance(1 To 3) As Single
Contract As Long
CostCode As Long
STHours As Single
OTHours As Single
DTHours As Single
WorkDate As Date
End Type
I can process the spreadsheet and organize the information in excel as an array of employee-type objects, but I'm not familiar with how to export this into Access, and most questions relate to exporting from excel cells to Access. I can obviously put these objects into cells on another worksheet and do it that way, but it seems like there should be a better way.
Currently my best guess is something like this:
Insert data form Excel to Access 2010 using VBA
but then I'd be making 100+ updates to the table for each export.
Is there an efficient way to create a table object in VBA, populate it with the array information, and then append it to the end of my table in Access in a single update?
Thanks.
-Sean
The easiest way is to create a table link in Access. Table links look like tables in the rest of Access, but the data is stored externally. The data could be inside another Access database, or inside a SQL Server database, or what have you.
In particular, the data can be in an Excel spreadsheet. Define a table in Excel that contains the data in the format that's right for your Access application. Then build a table link in Access that links back to the table you defined in Excel.
When you update the Excel table, the updated results will automatically appear the next time you reference the table link in Access.
thanks for the help from everyone ... I just wanted to share what I came up with for a solution. I ended up building a function to insert one object into the database ... copied and modified from the interwebs. Code below, cheers!
Public Function InsertTimeRecord(EmpData As Employee) As Boolean
Dim SaveTime As Date
Dim db As DAO.Database
Dim rs As DAO.Recordset
'//Database Location
Const DB_LOCATION = "C:\access\KMP Tracker.mdb"
'//If errors occur the function will exit with a return value of false (insertion failed)
On Error GoTo ErrHandler:
'//Table has a datecreated/datemodified timestamp for each record
SaveTime = Now
'//Open Database
If db Is Nothing Then
Set db = DAO.Workspaces(0).OpenDatabase("C:\access\KMP Tracker.mdb") 'Removed DB_LOCATION
End If
'//Open Table
If rs Is Nothing Then
Set rs = db.OpenRecordset("Timesheet Data", dbOpenDynaset)
End If
'//Create a new record
With rs
.AddNew
![EmpName] = EmpData.Name
![Trade1] = EmpData.Trade(1)
![Trade2] = EmpData.Trade(2)
![Trade3] = EmpData.Trade(3)
![EmpNum] = EmpData.EmpNum
![Comment] = EmpData.Comment
![AddOns1] = EmpData.AddOns(1)
![AddOns2] = EmpData.AddOns(2)
![AddOns3] = EmpData.AddOns(3)
![Allowance1] = EmpData.Allowance(1)
![Allowance2] = EmpData.Allowance(2)
![Allowance3] = EmpData.Allowance(3)
![Contract] = EmpData.Contract
![CostCode] = EmpData.CostCode
![STHours] = EmpData.STHours
![OTHours] = EmpData.OTHours
![DTHours] = EmpData.DTHours
![WorkDate] = EmpData.WorkDate
![DateSubmitted] = SaveTime
'//Insert Record into Database
.Update
InsertMachineHoursRecord = True '//SUCCESSFUL INSERTION
End With
'//Note that we use recordset in this example, but equally effective
'// is to create an update query command text and simply run the update query:
'// (INSERT INTO Table (Field1, Field2) VALUES (Value1, Value2);
'//Make sure we have closed the database
My_Exit:
rs.Close
Set rs = Nothing
db.Close
Set db = Nothing
Exit Function
ErrHandler:
MsgBox Err.Description
Resume My_Exit
End Function
I am attempting to round-robin names into a "TASK" column depending on which type of job is assigned in the "JOB" column. My table looks like this for example:
my code is as follows:
Sub macro2()
Dim Rst As DAO.Recordset
Set Rst = CurrentDb.OpenRecordset("table1")
Dim employee(2) As String
employee(0) = "empname1"
employee(1) = "empname2"
Dim i As Integer
With Rst
i = 0
Rst.MoveFirst
Do While Not .EOF
If Rst.Fields("JOB") = "LETTER" Then
Rst.Edit
Rst.Fields("Task").value = employee(i)
Rst.Update
End If
.MoveNext
i = i + 1
If i > 2 Then i = 0
Loop
End With
DoCmd.Requery
End Sub
The problem is, sometimes it "misses" an assignment, and I am not sure why.
It should have kept looping those 2 names into the column, but it wont. However, sometimes after running it a couple of times it will do it. Upon opening the DB fresh, it will not, and will appear as above after completing. Any ideas?
This piece of the code allows i to have a value of 2.
i = i + 1
If i > 2 Then i = 0
But UBound(employee) is 1, which means employee(i) should throw a "subscript out of range" error when i is 2. But you didn't report getting that error, so I don't understand what's going on.
Also your first screenshot shows "Letter" and "Change" in the Job column, but the second screenshot shows all as "Letter". That's another puzzler.
Maybe you need to load the recordset with a query rather than the full table.
Set Rst = CurrentDb.OpenRecordset("SELECT Job, Task FROM table1 " & _
"WHERE Job = 'Letter'")
Then as you move through the recordset rows, toggle i between 0 and 1.
i = IIf(i = 1, 0, 1)
It looks to me like those changes might allow your code to work as you intend. However consider an approach which is more flexible and easier to maintain. Store the employee names in a table and open a second recordset, rsEmployees, to hold those employee names. As you move through the first recordset, Rst, also advance the second: rsEmployees.MoveNext If you reach rsEmployees.EOF, do rsEmployees.MoveFirst That should "round robin" the assignments.
That approach would allow you to add/remove employees from the available pool by simply updating the employees table. You wouldn't need to revise your VBA code each time you add/remove employees.
I'm trying to figure out if there is a way to take a code that I have for merging to a document and set up an if...then that will merge to a different document based on a specific text in a field in a table.
My code that works.
Sub SendConfirmation_Click(CourseNumber As Index)
DoCmd.SetWarnings False
DoCmd.OpenQuery "ConfirmationMailMerge"
Dim LevelIConf As String
Dim OpenWord As Object
'Path to word document
LevelIConf = "G:\POSTPROFESSIONAL\NAIOMT\Classes\PTH536 Level I\LevelIConf.doc"
'Create instance of Word
Set OpenWord = CreateObject("Word.Application")
OpenWord.Visible = True
'Open the document
OpenWord.Documents.Open FileName:=LevelIConf
DoCmd.SetWarnings True
End Sub
I have several Courses that I send out confirmation letters for and each letter is different based on the course. I would like to be able to push a button on the form and have correct document come up based on the course number.
Any help is appreciated. I am a self taught coder and still have lots to learn.
Thanks,
Please note I provide generic solution that may need to be adapted depending on your actual requirements/setup.
Option 1: Your approach (using Word's mail-merge)
You need the following setup:
Two related tables:
Query that returns path to the Conf Letter based on Course ID:
Function that calls the query from previous step:
Function GetConfLetterPathByCourseID(CourseID As Integer) As String
GetConfLetterPathByCourseID = ""
Dim qdf As QueryDef
Dim rs As Recordset
Set qdf = CurrentDb.QueryDefs("GetConfLetterPathByCourseId")
qdf.Parameters("CourseID_par") = CourseID
Set rs = qdf.OpenRecordset
If rs.RecordCount > 0 Then
GetConfLetterPathByCourseID = rs("ConfLetterPath")
End If
End Function
Form with the Send button. Something like this:
And, finally, the Sub for the Send Button:
Sub ConfLetterButton_Click()
DoCmd.SetWarnings False
Dim LevelIConf As String
Dim OpenWord As Object
'Path to word document
LevelIConf = GetConfLetterPathByCourseID(Me.CourseID)
'Create instance of Word
Set OpenWord = CreateObject("Word.Application")
OpenWord.Visible = True
'Open the document
OpenWord.Documents.Open FileName:=LevelIConf
DoCmd.SetWarnings True
End Sub
Please note, I altered slightly your code (e.g. removed Index type, removed Docmd.OpenQuery)
Option 2: Compose email in VBA code and attach the Conf Letter doc file using the Query/Function from option 1. I think this link from Microsoft can provide some details. I did implement similar solution in the past. Worked pretty well.