I'll start with the background story before explaining the problem with my code. I'm using MS Access 2010. I've been able to import a table of data with two columns. Then I was able to curate the data by adding fields with appropriate values to the imported table. Now, I need to take the curated table and integrate it into my data base. However, I cannot use any of Microsofts built in queries as none of these appear to be able to do what I need. The integration breaks the table apart, yes, but it needs to preserve the relationships of the data in each record.
To this end I've been writing some code in VBA:
Function IntegrateNIRData(curatedTable, queryRecords)
On Error GoTo Error_Handler
Dim db As DAO.Database
Dim rsCuratedTable, rsDBRecords As DAO.Recordset
Dim iCount As Integer
Set db = CurrentDb()
Set rsCuratedTable = db.OpenRecordset(curatedTable, dbOpenTable) 'open the recordset for use (table, Query, SQL Statement)
Set rsDBRecords = db.OpenRecordset("NIR_Samples_verify", dbOpenDynaset, dbExecDirect, dbOptimisticValue)
With rsCuratedTable
If Not (.BOF And .EOF) Then
Do While Not .EOF
' Rest of your code here.
rsDBRecords.AddNew
'Assign Fields here.
rsDBRecords![Product Name] = rsCuratedTable![productName]
rsDBRecords![Lot Number] = rsCuratedTable![lotNumber]
rsDBRecords!counts = rsCuratedTable![counts]
rsDBRecords![subsets] = rsCuratedTable![subsets]
rsDBRecords![Date Taken] = rsCuratedTable![dateTaken]
rsDBRecords.Update
rsDBRecords.Bookmark = rsDBRecords.LastModified
.MoveNext
Loop
End If
End With
rsCuratedTable.Close 'Close the recordset
rsDBRecords.Close 'Close the recordset
Error_Handler_Exit:
On Error Resume Next
'Cleanup after ourselves
Set rs = Nothing
Set db = Nothing
Exit Function
Error_Handler:
MsgBox "MS Access has generated the following error" & vbCrLf & vbCrLf & "Error Number: " & _
Err.Number & vbCrLf & "Error Source: IntegrateNIRData" & vbCrLf & "Error Description: " & _
Err.Description, vbCritical, "An Error has Occured!"
Resume Error_Handler_Exit
End Function
The Function hangs on this line, the second OpenRecordset:
Set rsDBRecords = db.OpenRecordset("NIR_Samples_verify", dbOpenDynaset, dbExecDirect, dbOptimisticValue)
To my understanding this may have something to do with Workspaces and the Jet engine not accepting a ms query that spans multiple tables. Of course, I could also be way off. Any advice at this point would be greatly appriciated.
Update:
Several of you have asked similar questions so I felt I should clarify the following:
1) NIR_Samples_verify is an MS access select query that generates a table of records from several of the tables in the database.
2) I keep getting two errors depending on what I set the RecordsetOptionEnum and LockTypeEnum to in the OpenRecordset method.
One is Error Number 3027 Database is Read-Only
Two is Error Number 3001 Invalid Arguement
3) To my understanding the rest of the code should be fine, it is just the OpenRecordset method that is causing the problem.
Update 2:
I am thinking that maybe access is not capable of doing what I would like. Let me illustrate. If I had two tables both with primary keys and these keys are referenced in a third table that links the two tables causing a many-to-many relationship, then the code would have to not only add the new data to the two tables, but also generate an appropriate record in the third table to maintain the relationship in the data. Hope that makes since. I do appriciate the help and experience.
Update 3:
Have been searching the net and found the following:
From this post it says the query is only updatable when:
1) It is based on a single table.
2) It is based on a query based on a single table.
3) It is based on a query based on tables with a one-to-one relationship.
Not knowing what the contents of NIR_Samples_verify are, I'd be highly suspicious of the dbExecDirect
From the help file
dbExecDirect
"Runs a query by skipping SQLPrepare and directly calling
SQLExecDirect (ODBCDirect workspaces only). Use this option only when
you’re not opening a Recordset based on a parameter query. For more
information, see the "Microsoft ODBC 3.0 Programmer’s Reference." "
I don't see you supplying any parameters.
-- Edit --
Typically I'll open a record set like this
Set rsDBRecords = db.OpenRecordset("select bar from foo where bar > 10", _
dbOpenDynaset, _
dbSeeChanges)
(Especially if I want to alter the data init)
Hopefully that'll move you further in your project.
-- Edit 2 --
It sounds like NIR_Samples_verify is to complicated to be edited. Actually, given that it is a join of multiple tables doing an Add on it doesn't make much sense, and Update MIGHT make sense in some cases.
Your ultimate solution is really going to be doing multiple Adds on multiple record sets (one for each table being referenced in NIR_Samples_verify); much like if you were entering the data into the DB by hand. You add the records that aren't dependant on anything else first (remembering to grab keys to use in the dependant tables).
As it turns out my hunch was correct. The problem had to do with MS Access having updatable and non-updatble queries (See my edits of the question). The main problem was not only does Microsoft not make this information apparent, but there is no master list on their site either. Thank you everyone for the help. Feel free to see this article for more details.
Related
I am just starting to move our Access DB to SQL Server and am having trouble.
I have a stored procedure that successfully returns rows to an ado recordset.
When I try to bind the rs containing the results of the stored procedure to the Access form, Access crashes without displaying any error messages. I'm on O365 32b and SQL Server 2019.
Here's the code:
Dim sSQL As String, rs As ADODB.Recordset
1 sSQL = "Exec usp_TaskStatusWidget " & Me.Tag & ",0"
2 ADOConn.ConnectionString = conADO
4 ADOConn.Open
6 Set rs = New ADODB.Recordset
7 rs.CursorLocation = adUseClient
8 rs.Open sSQL, ADOConn
10 Set Me.Recordset = rs ' Access crashes here
. . .
Any help would be greatly appreciated!
tia.
SR
Ok, are you previous using ADO, or are you just introducing this?
In most cases, you are better off to just use a view. (replace the access query with a linked view), and then continue useing client side where clauses or filters (access will ONLY pull down the rows you request). So linked views are often a better choice and much less work (in fact, even existing filter for a open report etc. will work and only critera matching the were clause records are pulled.
And in most cases, i don't introduce ADO.
So for a PT query, I often do this:
dim rs as DAO.RecordSet
with CurrentDb.queryDefs("qryPt")
.SQL = "Exec usp_TaskStatusWidget " & Me.Tag & ",0"
set rs = .OpenRecordSet
end with
So, above assumes you have a pt query called qryPt. This also means that you never deal with or worry about connection strings in code. The pt query has the connection. (and your re-link code now can re-link tables and pt queries).
I ONLY suggest the above as a FYI in case that you introducing ADO for calling store procedures, and the rest of the application was previous DAO. If the application was previous DAO, then leave it alone, and use above approach for your PT queries - even code that needs to call store procedures.
Access tends to try and parse the query text to get filters/sorts/etc to work, and if it isn't a plain syntax error but isn't Access SQL either, strange things tend to happen, mostly crashes.
Try adding a comment up front to make sure Access knows not to parse:
sSQL = "-- Access no parse pls" & vbCrLf & "Exec usp_TaskStatusWidget " & Me.Tag & ",0"
The content of the comment is not relevant, of course, its purpose is to immediately cause a syntax error when Access tries to parse it as Access SQL (which doesn't have comments)
This one has had me tearing my hair out for ages but the solution still eludes me. And my last 15 years before retiring were working with Access & VBA, so my pride is hurting even more than my hair!
The project:
A database to catalogue portraits recording for each artist name, sitter name, date painted (when known), brief details on content (may be none), and the portrait’s location (if known). In addition to the table for portrait details are a table for artist information and one for sitter information (plus various lookup & other tables that aren’t relevant to this issue).
My client was super-keen to get data entered so she uses the memo field on the artist form (the first one designed) to enter details of portraits painted by that artist – following a strict pattern set by me (and checked before attempting to process). That’s not the issue – my parsing routine correctly identifies the data to be processed for the creation of the new sitter records and portrait records: I know this to be true because my code is liberally scattered with message boxes showing the VBA’s interpretation of the data (vital for debugging!)
The process of parsing + posting is briefly as follows (the memo field is assigned to a variable which is then parsed one character at a time):
The sitter’s name is identified (always the first entry on a new line) and assigned to a variable, then (if relevant/if known) brief details, date painted & location;
once it’s reached the end of the details for the portrait it shows a message box giving that information as interpreted by the routine for me to confirm;
it then appends the sitter’s name to the sitters’ table and returns the ID of the new record. This works. It then appends (or, rather, it should append but doesn’t!) the relevant data, including the sitter ID (and the artist ID from the form) to the portraits table.
It then continues (on that line if there are more portraits for that sitter, otherwise on the next line, until it reaches the end of the memo).
Originally the two append processes were by dynamically-created SQL – building the SQL strings using the appropriate variables – using database.Execute. But when I found that the second append was failing – with no error messages – I spent several hours looking at various tech sites, and one message was coming through strongly – use parameterised queries! OK, hadn’t used these before (not in VBA – of course I’d used them on forms for select & other queries) so I set up a simple test database to mimic the process but without putting my client’s data at risk!
It took a while – and a bit more time online – before I got it right, but yay, it worked with all combinations of missing data. (Incidentally, as you’ll note from the table defs below, the “year painted” is an integer field, which of course doesn’t accept “Nulls”, and I don’t want a zero where there’s no date, so there are two append portrait queries, one omitting posting to the ‘year’ field.)
And now I’ve gone back to the live database – and the query that appends the sitter is working, but the other two aren’t, again with no error message. So it’s over to you, please!
TABLE DEFS (I’m only listing the relevant fields; no fields have Required set to Yes; zero-length strings allowed):
taArtists: arID – autonumber; arNotes – memo; (plus other fields)
taSitters_Sub: ssID – autonumber; ssFullname – text 70; (plus other fields)
xtaPortraits: xrID – autonumber; xrArtistRef – long integer (link to taArtists = arID);
xrSitterRef – long integer (link to taSitters_sub = ssID); xrPortraitName – text 25; xrLocationCode – text 20; (plus other fields)
PARAMETERISED QUERIES
qu_app_sitter: INSERT INTO taSitters_Sub ( ssFullname ) SELECT [par1] AS Expr1;
qu_app_portrait: INSERT INTO xtaPortraits ( xrArtistRef, xrSitterRef, xrYearPainted, xrPortraitName, xrLocationCode ) SELECT [par2] AS Expr1, [par3] AS Expr2, [par4] AS Expr3, [par5] AS Expr4, [par6] AS Expr5;
qu_app_portrait_NoYear: INSERT INTO xtaPortraits ( xrArtistRef, xrSitterRef, xrPortraitName, xrLocationCode ) SELECT [par2] AS Expr1, [par3] AS Expr2, [par5] AS Expr4, [par6] AS Expr5;
EXTRACTS OF VBA
(Were I to start again I’d probably feed the values for par2 to par6 to a separate subroutine but while that would reduce the amount of code I’m not sure it’d actually be more efficient! I am, of course, open to expert advice on that! Were my client to start again I’d get her to wait for the system to be complete before entering data – making all this redundant – or get her to enter the data in an Excel spreadsheet & I’d process it from there. One lives, one learns [hopefully!])
1 – declarations, setting database & querydefs:
[only showing relevant Dims:
Private Sub Command77_Click()
On Error GoTo myError
Dim myID As Long 'the ID for the painter
Dim myName As String 'the name of the sitter
Dim myDesc As String 'any text description **could include part-dates or other digits
Dim myLoc As String 'the location code
Dim myDate As Integer 'myNum converted from string if it's 4 digits
Dim Errline As Integer ‘used so error messages get me close to the problem
'**** Now the database stuff
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim mySitterID As Long, myNewRows As Integer
Dim qd1 As DAO.QueryDef, qd2 As DAO.QueryDef, qd3 As DAO.QueryDef
Dim par1 As DAO.Parameter, par2 As DAO.Parameter, par3 As DAO.Parameter, par4 As DAO.Parameter, par5 As DAO.Parameter, par6 As DAO.Parameter
'**** Set these once, and turn off in 'Leave'
Set db = CurrentDb()
Set qd1 = db.QueryDefs("qu_app_sitter")
Set qd2 = db.QueryDefs("qu_app_portrait")
Set qd3 = db.QueryDefs("qu_app_portrait_noyear")
2 showing routine calling the insert queries plus preceding message box
[there are 9 of these, similar but not identical, depending on missing information]
MsgBox "Got name & location only" & vbCrLf & vbCrLf & "Sitter: " & myName & vbCrLf & "Location: " & myLoc, vbInformation, "LINE " & Errline
qd1.Parameters("par1").Value = myName
qd1.Execute
myNewRows = qd1.RecordsAffected
MsgBox myNewRows & " record added", vbInformation, "NEW SITTER"
myNewRows = 0
qd1.Close
Set rs = db.OpenRecordset("SELECT ##IDENTITY AS LastID;")
mySitterID = rs!lastid
qd3.Parameters("par2").Value = myID
qd3.Parameters("par3").Value = mySitterID
qd3.Parameters("par5").Value = myDesc
qd3.Parameters("par6").Value = myLoc
qd3.Execute
myNewRows = qd3.RecordsAffected
MsgBox myNewRows & " record added", vbInformation, "NEW PORTRAIT (year not known)"
myNewRows = 0
qd3.close
Both queries should insert 1 row - the first always does, the second always reports 0 rows inserted (and indeed none is inserted)
I'm painfully aware that this post is much longer than many here but hopefully I've given all the info required.
I did a quick lookover of your code.. I suggest
are you getting the correct value of mySitterID ?
im not 100% sure but isnt the ##Identity actually Artistid??
Meir
OK, thanks partly to a prompt from Meir Rotfleisch, I dug deeper - running the query manually (omitting each field in turn) revealed that one was causing a key violation error. Then typing manually into that field pointed me to a need for a related entry in another table (which table wasn't visible on the relationship diagram!) Removing that link (which will be reinstated once the full system has been built & tested) resolved the problem.
One vital lesson I'd like to pass on - make sure your relationship diagram shows ALL tables!
A software management system let's call it "MainSystems" takes orders, creates invoices, etc. MainSystems uses multiple .DBF files as a database.
There are several third party applications that retrieve certain info (all written in VBA). For example, I created an Excel VBA macro which pulls debt and applied credits from the .dbfs and generates a batch file to upload to a merchant.
I have the VFPOLEDBSetup driver installed to do this. It has run for the past year with no errors.
MainSystems did a system update and now third party applications return errors.
Excel returns
"Run-time error '-2147467259(80004005)': DIRECTORY\FILE.dbf is not a table."
at line rs.Open sql, con
and Visual Fox applications solely returns
"Not a table."
These applications all worked before the update, only thing I noticed that might be different is within the folder with the .dbfs there are .cdx created the day of the updates and am wondering if this could have caused the error. If so, is there a solution to fix this?
I'm not very knowledgeable with databases.
'Changing directories
DBFFolder = "G:\DIRECTORY\"
FileName = "File.DBF"
On Error Resume Next
'Create the ADODB connection object.
Set con = CreateObject("ADODB.connection")
'Check if the object was created.
If Err.Number <> 0 Then
MsgBox "Connection was not created!", vbCritical, "Connection error"
Exit Sub
End If
On Error GoTo 0
'Open the connection.
con.Open "Provider=vfpoledb;" & "Data Source=" & DBFFolder & FileName & ";Collating Sequence=machine"
'Create the SQL statement to read the file.
'Note that the filename is used instead of the table name.
sql = "SELECT * FROM " & Left(FileName, (InStrRev(FileName, ".", -1, vbTextCompare) - 1))
On Error Resume Next
Set rs = CreateObject("ADODB.recordset")
'Check if the object was created.
If Err.Number <> 0 Then
MsgBox "Connection was not created!", vbCritical, "Connection error"
Exit Sub
End If
On Error GoTo 0
'Open the recordset.
rs.Open sql, con
Expected result: Access the DBF table and copy info.
UPDATE
Found the cause of the error. MainSystems added columns to the table making it surpass 256 columns. (262) causing the Microsoft driver to not recognize it as a table. Is there a workaround?
Although I have and use for supporting a system requiring database querying but can not be via a "Server" product, there is a product from SAP called Advantage Database. There is an Advantage LOCAL Server and Advantage Database Server. The local version runs based on a set of libraries and is more file-sharing over network capabilities. Works similar with OleDb connections, querying, parameterize queries. It works with VFP tables, but also has its own support to recognize more than the 255 columns. I don't know if this is the answer you need, but MIGHT work. It also apparently supports reading DBase IV files as well.
As for finding the library download, that might be a bit tricky. I don't remember the last time I had to look for the "LOCAL" database version vs server (where they make their sales)
Hope this guides you in a direction of possible resolution.
Hello Stackoverflow community!
I have run into an issue and I'd love some advice.
I'm working with MS Access and I am trying to append two particular fields from one table to another; however this implementation is in a form and it gets a little complicated... So, I'll explain everything the best that I can
BACKGROUND INFORMATION:
First and fore most, I have two tables; one of which is a linked excel spread sheet from another directory (who is not willing to change any formatting what so ever, so I CANNOT make ANY changes to this file and it is being updated on a daily basis). This excel spreadsheet is very large and contains somewhere around 50 columns
The other table is not anywhere near as large but has around 20 columns and is meant to extract two columns from the excel spreadsheet (the first column and the third column). I'm trying to make a form for this database to be as user-friendly as possible and not many people in my office are familiar with the technicalities of Access queries and programming in VBA.
THE SITUATION:
On my form, the user will enter data into TextBoxA, from there they will click a button; this button will trigger a search through the linked excel spreadsheet for the data that was typed into TextBoxA. It will then copy the data from Field1 (which was the typed data) and Field3 and append these selected fields into the first two fields of the table in my Access Database. All of this is being done through a segment of VBA code
Private Sub CmdCloseForm_Click()
If IsNull(Me.TextBoxA) Or Me.TextBoxA = "" Then
MsgBox ("Field is empty, please try again!")
Else
Dim VendorNum As String
SearchingValue = Me.TextBoxA
Dim SQL As String
SQL = "INSERT INTO tbleRecord (Field1,Field2)" & _
"SELECT * " & _
"FROM tbleLinkedExcel " & _
"WHERE Field1 = '" & SearchingValue & "';"
DoCmd.RunSQL SQL
End If
End Sub
So the biggest issue here is that in Field1, and every time I try to run the code,
I receive an error; which I am assuming it is because of the space (unfortunately I cannot give the ACTUAL variable names out as it is confidential)
ERROR MESSAGE
The INSERT INTO statement contains the following unknown field name: 'FIELD 1'. Make sure you have typed the name correctly, and try the operation again.
The thing is, is that this 'FIELD 1' variable/name is not in my code, but in the linked excel spreadsheet (again, I am not able to change ANYTHING on this spreadsheet).
Thanks guys!
I am trying to store and retrieve data that is entered into Excel into Access. I am an Access newbie and already have an Excel program on the front end, leading me to drive the import from Excel. I have successfully figured out how to save my Excel Named Range into the desired Access table, but now I need to figure out how to pull specific data from that Access table back into Excel. I know I can simply use the Get External Data feature from Excel to import the entire Access table into Excel, but I need to be able to only import a specific portion of the table into Excel based upon a predetermined set of parameters. Is this possible to do?
As a background to the program, basically it stores data from part number runs. Not only do I need to save new part runs into an Access database (which I figured out), but I also need to be able to pull previous part number runs from Access back into Excel to perform further analysis. That is why I need to figure out how to import only a specific portion of the table. I'm not sure what code, if any, I can post since I'm basically looking for code from scratch; but if there's any code you think you need from my Excel program I'm happy to provide it. Thanks for your help. Below is the code:
Sub GetSpecData()
Application.ScreenUpdating = False
'*******************************************************************************
'Open the connection to the data source.
Set Connection = New ADODB.Connection
Connection.Open "Provider=Microsoft.Jet.OLEDB.4.0; " & "Data Source=\\Server2013\network_shared\QC SHARED\Databases\P&Q_Tracking_Data_Storage.mdb;"
'*******************************************************************************
'Create the new RecordSet.
Set Recordset = New ADODB.Recordset
With Recordset
'Define the appropriate Filter(s) and notify the user of the selection criteria.
Src = "Select * from Raw_Data where Tag = 'GHI' "
Src = Src & "or Tag = 'DEF' "
Src = Src & "or Tag = 'LMN'"
.Open Source:=Src, ActiveConnection:=Connection
'Write the field names.
For ODCol = 0 To .Fields.Count - 1
Tracking.Sheets("Selected Past Data").Range("B7").Offset(0, ODCol).Value = .Fields(ODCol).Name
Next
'Write the recordset.
Tracking.Sheets("Selected Past Data").Range("B7").Offset(1, 0).CopyFromRecordset Recordset
End With
Set Recordset = Nothing
Connection.Close
Set Connection = Nothing
'*******************************************************************************
'Create and format the table from the Recordset.
With Tracking.Sheets("Selected Past Data")
DataLastRow = .Range("A" & Rows.Count).End(xlUp).row
.ListObjects.Add(xlSrcRange, Range("B7:M" & DataLastRow), , xlYes).Name = "INC2tbl"
.ListObjects("INC2tbl").ShowTotals = True
End With
Application.ScreenUpdating = True
'*******************************************************************************
End Sub
After googling the suggestion in the comment below, I have a couple questions. First, the code above seems to filter access data by three keys: GHI, DEF, and LMN. Am I interpreting that correctly? Second, where it says "Select * from Raw_Data where Tag = 'GHI' ", since that's in quotes, that's not actual code that will be executed, correct? That's simply a prompt or something like it, correct?
SELECT * FROM TABLE; OUTPUT TO TABLEName.EXL
FORMAT sql;
First, the code above seems to filter access data by three
keys: GHI, DEF, and LMN. Am I interpreting that correctly?
Second,
where it says "Select * from Raw_Data where Tag = 'GHI' ", since
that's in quotes, that's not actual code that will be executed,
correct? That's simply a prompt or something like it, correct?
First and second questions relate closely, so I'll answer as one. The basic idea here is that there is a connection to the database you defined (in the Connection.Open statement). The connection itself does nothing then that; establish a connection. The actual communication with the database is done in a specific language, named SQL (there are many dialects, but for simplicity sake, call it SQL for now). So your code in in the VBA language, but the communication with the database is done in SQL. The VBA code has to produce SQL statements (and those are strings, so you need the quotes). That's what the Src variable holds; and SQL statement. In this case:
"Select * from Raw_Data where Tag = 'GHI' or Tag = 'DEF' or Tag = 'LMN'"
I think this is not the place to teach you SQL, but there is plenty information on the net about it. What this statement does is:
select all columns from table "Raw_Data", but only those rows of data that has 'GHI', 'DEF' or 'LMN' in the Tag column.
So to receive all the data from table "part_numbers", you would have to use:
"SELECT * FROM part_numbers;"
And if you need to import only columns "col_1" and "col_2", you would use:
"SELECT col_1,col_2 FROM part_numbers;"
And if you need to import only columns "col_1" and "col_2", and only rows with "part_id" lower than 1000, you would use:
"SELECT col_1,col_2 FROM part_numbers WHERE part_id < 1000;"
That's it really. Now if your recordset (think of it as an array that holds the data you asked for) has queried the database, you can loop it to parse the data. Something like:
Set Recordset = Connection.Execute(Src)
Do until Recordset.EOF
debug.print Recordset!col_1
Recordset.movenext
loop
Again, google something like: "vba excel adodb access", and you'll hit plenty examples.
After researching SQL code formatting more I have made more sense of the subject. Thanks for the help.