Create and run append queries for multiple linked tables - sql

I am trying to write a VBA code in Microsoft Access that will create and run 60 queries to select new records from 60 linked tables and insert them into 60 tables of the same format. Some background may help here:
I have a large database (lets call the original database "A") that will eventually have over 60 tables, 60 forms & 60 reports. When one of our workers goes out into the field and doesn't have an internet connection, they are going to create new records on a copy of the database stored on their desktop (lets call the duplicate database "B"). Once they have an internet connection, I want them to be able to press a button on either database (I have been trying to code the macro on database A because I thought that would be easiest) and have the new records they created on database B inserted into database A.
I have found code online that seems to be just what I need but when I try to run the macro it gives Error 3022, which says:
The changes you requested to the table were not successful because they would create duplicate values in the index, primary key, or relationship
I have tried running this macro with both databases on my desktop with only 1 linked table with a primary key that is an autonumber, I tried running it with a random autonumber, I tried not having any primary key or index or autonumber and even no records at all. I even tried running it without any linked tables. All ways of trying give me the same Error 3022. I really don't want to create 60 queries one by one so any help would be greatly appreciated. Thank you wizards in advance :)
Here is the code I have tried:
Public Sub ImportTableData(ByVal pstrTable As String, ByVal pstrDb As String)
Dim strSql As String
strSql = "INSERT INTO " & pstrTable & vbNewLine & _
"SELECT *" & vbNewLine & _
"FROM " & pstrTable & " IN '" & pstrDb & "';"
CurrentDb.Execute strSql, dbFailOnError
End Sub
Public Sub ImportAllTables()
Const cstrDb As String = "C:\MyPath\DatabaseB.accdb"
Dim tdf As TableDef
Dim strMsg As String
On Error GoTo ErrorHandler
For Each tdf In CurrentDb.TableDefs
'ignore system and temporary tables '
If Not (tdf.Name Like "MSys*" Or tdf.Name Like "~*") Then
Call ImportTableData(tdf.Name, cstrDb)
End If
Next tdf
ExitHere:
On Error GoTo 0
Set tdf = Nothing
Exit Sub
ErrorHandler:
Select Case Err.Number
Case 3078
strMsg = "Input table " & tdf.Name & " not found."
MsgBox strMsg
Resume Next
Case Else
strMsg = "Error " & Err.Number & " (" & Err.Description & ") in procedure ImportAllTables"
MsgBox strMsg
GoTo ExitHere
End Select
End Sub
When I remove the piece of code: 'dbFailOnError', I get different errors. First thing that pops up when I run the macro is: ''Input table 'LocalTableName' not found'', which is the table I am trying to add the records to. Once I click 'Ok' on that pop up box, that is when Error 3134 pops up which says 'Syntax error in INSERT INTO statement'. I am assuming Error 3134 only pops up because it cannot find the local input table (the fist pop up box).
Also, I tried changing the line of code that says: 'Const cstrDb As String = ''C:\MyPath\DatabaseB.accdb''' to instead point to database A (which is the one I am coding the macro on) like this: 'Const cstrDb As String = ''C:\MyPath\DatabaseA.accdb'''. This doesn't give me the first pop up that says ''Input table 'LocalTableName' not found'' but it still gives Error 3134.
I have no idea what I am doing wrong and have spent over 20 hours on this problem trying dozens of different things. Any help would be greatly appreciated :)

Well, the main issue is how you going to ensure that the PK (primary keys) and FK (foreign keys) remain the same when they go out to the field and start entering data?
There is a good chance that PK/FK values will now be duplicated, or be the same for on one of the field users.
If a user out in the field adds a record, and someone at main work location adds record, they now are to very likly have the same PK value.
I suppose this might work if you use random autonumbers (never even knew that was a option after all these years!!!) - but I can't say even that going to be 100% reliable.
And when you import that copy from the out in the field user, then either:
You always ensure you accept and take the same PK value, or you let access generate a new PK - but if you do that, then the child records FK value would have to be updated then.
You not really try to do a simple import, but are attempting to do a database synchronization- a stunning and VERY advanced concept. And a very challenging problem.
Access (mdb format) did at one time support what is called database replication. This feature would be ideal for your setup.
However, but for newer accDB formats, it not supported anymore. (and quite sure by around access 2010, replication support was dropped anyway).
So, you could try random for the autonumber. I mean, you simple cannot have the PK's being duplicated on each computer - plain and simple.
The other possbile?
You add to each and every table that has a PK, and add a new column called PKF (f for in the field).
And for every table that has a FK, you add a new column called FKF (again, add F to this).
So, in the field, your PK/FK used is NOT the same as the main master database at work.
So, I wrote a Android sync routine based on above. The applcation was Access, but I moved the data to sql server (but same idea). I moved to sql server since my android phone could use its local database (sqlLite) and hit sql server. (but, it would be difficult to get android to hit some server and read + use a accDB file - but was easy to have Android phone hit the sql server database directly).
Gee, maybe they could use Android phones!!! but, this would assume you up to speed writing android software, 100% conformable with SQL server, and also access. I was lucky, had all 3 skill sets, so that is the road and hammer I choose.
And speaking of above?
Maybe your lucky, and you have sql server running at work (not express edition, but full edition). I suggest this considering, since the free edition of SQL express can be what we call a replication subscriber to a main sql database. This allows you to sync your local database with the main mothership database.
So, adopting free SQL server express on each field laptop could be a possible solution. Then when they get to a working network, they sync the database using replication.
But, you could try and roll your own sync system.
I did that for an android applcation I wrote, and for a desktop Access application I had. (but, to make all the moving parts easy, I did adopt sql server for database - continued to use Access as the application/UI part).
Now, using "random" for the PK looks to be a possible solution. I just don't know how random, and if this choice can reliable avoid PK collisions for autonumbers.
Random seems like the best road - but ONLY if that choice would prevent duplicate PK id's being used out in the field for new records.
Edit: Random - not even close - it not random enough
So, a bit of research - no, random PK will not work, you still often wind up with collisions - so that idea is off the table.

I figured it out finally. Basically I changed the line of code that executes the SQL to debug.print. This showed me what was going on. The problem is I had linked the tables from the other database when they didn't need to be linked. The names of the local tables would be 'Table A' & the linked tables were 'Table A1'. So there would be a query generated for 'Table A' and another query generated for 'Table A1'. Since there are no tables by the name 'Table A1' in Database B, the query wouldn't work. Plus the fact that, in the line of code executing the SQL, there was an option that says 'dbFailOnError' so since half of the queries weren't working, this option rolls back any updates made by the queries that did work.
I removed all of the linked tables and the macro runs perfectly, unless there are records that are the same on both databases. If I remove 'dbFailOnError' from the code, then the macro runs well no matter what.
So the macro is doing what I want it to but I would like to keep the 'dbFailOnError' part of the code so I will have to do 2 things. First, I have to solve the problem with the primary keys. The answer on this thread by Albert describes this problem well. Second, I have to adjust the SQL to be able to only select records that don't already exist in Database A. I am assuming I can do this by adding a WHERE to the end of the SQL.
I will make an update once I fix these problems, or if I just run an SQL server instead.
Thank you everyone for your help :)

Related

Faster alternative to DCount on SQL server-side

I want to check if some values are set to Non-Null when opening a record. Dependent on that, I will show a message box to the user.
Currently I use:
Dim blnValueExists As Boolean
If DCount("*", "dbo_tbl_Parts", "Part_Nr = '" & Me!txt_PartNr & "' AND [ValuesExist] is Null") Then
blnValueExists = False
Else
blnValueExists = True
End If
But this will do the operation on client side. How could I achieve the same by shifting the request to the server?
I have used DoCmd.RunSQL at other parts, but I do not know how to get a easy "true/false" result from SQL Server back without needing to use recordsets etc.
Using dcount() in this way, or even spending all the time and money to build server side SQL stored procedure?
ZERO difference in speed. While the dcount() function is a client side function, it creates the correct sql, sends to server, gets the value back.
As a result, there is no need to consider, build, or attempt to create any kind of say pass-though query to sql server.
Even if you migrate say a large table - 2 million rows.
Now, bind a form to that linked table.
Now, do this:
docmd.OpenForm "frmInvoices",,"InvoiceNumber = 121325"
Access will ONLY pull the one rocord down the network pipe, and dispite the form being bound directly to the table of 2 million rows?
Not all rows are pulled to the client side.
So, with your dcount(), there is no need to attempt some kind of server side SQL or even having to adopt some kind of stored procedure on the SQL server side.
Your current code is fine, and adopting a recordset to replace dcount() DOES NOT and WILL NOT run faster.
Try:
If CurrentDb.OpenRecordset("Select Top 1 Part_Nr From dbo_tbl_Parts Where Part_Nr = '" & Me!txt_PartNr.Value & "' And [ValuesExist] Is Null").RecordCount Then

Import partial table from remote DB into a local microsoft access DB

Problem:
I'm trying to import a portion of a table from a remote informix db into an access 2016 database. The table to be partially imported contains account information and is > 2 GB (which as I understand it is the max size of a access db).
I only need information for the last year which is much less than 2 GB but the information must be updated at the end of each day.
Two solutions I have seen online are:
(A) Link the tables:
I'm reluctant to do this for two reasons:
1) I don't want additional load placed on the server everytime the enduser of the access db runs a report.
2) I don't want any changes carried out in the access DB to carry over to the live informix DB (this is absolutely cruical)
(B) Copy over the entire table via the access GUI
I don't think this is a viable solution for the following reasons:
1) The table is over 2GB
2) Reports must be ran on the table everyday with daily updated information. This would require pulling the entire informix table into access everyday.
Work So far:
I have succefully connected to the informix db from access via creating a dsn for the informix db.
I have also sucessfully imported a smaller table from the informix server via VBA with the following code:
DoCmd.TransferDatabase acImport, "ODBC Database", "ODBC;DSN=My_DSN_Name;UID=odbc;PWD=My_PWD;LANGUAGE=us_english;" & "DATABASE = My_DB_Name", acTable, "My_Destination_Tbl_Name", "TestTableImport", False, True
My ideal solution would see the enduser of the access db press a button that updates the local access copy of the db with all changes made to the informix db since the last update.
I have limited experience with access and VBA so would really appreciate any pointers on how I should proceed. Perhaps I have overlooked something simple?
Thanks for reading and any advice you can provide,
John
Ummmm.... assuming the data in the table is static (no updates to column data, just new rows) you could do something like importing whatever subset of data you want in Access and then every time you click a button run a macro to check what is the latest row in the access table.
Use that info to select only new rows from the ODBC source and insert them in the access table.
I tried this with Access 2013:
Sub test()
Dim cnDB As New ADODB.Connection
Dim rsRecords As New ADODB.Recordset
' find last row
Set db = CurrentDb
strSQL = "SELECT TOP 1 empno As lastemp FROM informix_employee ORDER BY empno DESC"
Set rs = db.OpenRecordset(strSQL, dbOpenSnapshot)
lastemp = rs("lastemp")
rs.Close
' get new rows from odbc source
DoCmd.SetWarnings False
cnDB.Open "DSN=ids1210;uid=informix;pwd=password"
rsRecords.Open "Select * from employee where empno>" & lastemp, cnDB
Do While Not rsRecords.EOF
strNaam = rsRecords.Fields(0).Value
rsRecords.MoveNext
' update access table
DoCmd.RunSQL ("insert into informix_employee (empno) values ('" + strNaam + "')")
Loop
rsRecords.Close
Set rsRecords = Nothing
cnDB.Close
Set cnDB = Nothing
db.Close
End Sub
It's a basic example, but should give you an idea of how to do it.
The code above inserts only one column per row (empno), you will need go through your table and do the insert with all the columns (basically create the insert ............. statement for your table)
'informix_employee' is the Access table.
'employee' is the Informix table.
Also, you need to reference ADO, e.g. 'Microsoft ActiveX Data Objects 6.1' to get it working, otherwise it will fail as the ADODB object wont be there.
If the Informix table does get some column updates (not just new rows) it would be quite difficult, as nothing (IDS/ODBC/Access) will be able to tell you which rows were updated without getting the data and comparing it with the previous set.

Access vba SQL delete query now stalls and never executes after working for months

I have an access database which has tables linked to it from another access database to hold the data. Daily a lab manager runs a process through a button click which transfers data from this database to a MS SQL Server database and then deletes the data from the Access database. I have inherited these databases and do not have the time or the power to change database structure and we will be replacing the access databases completely with a new LIMS which we are in the process of purchasing and implementing over the next year.
My issue is that one of the DELETE sql queries has stopped executing. Whenever I execute this query the database just hangs up (have let it go as long as 20 minutes) until I pull up the task manager and kill the application. I have tried taking the SQL query out of the VBA environment and running it through a MS Access query and I get the same thing. So basically it worked for a very long time (sense August of last year) and now it has stopped working.
Here is the SQL command for the Delete statement:
strSQL = "DELETE [TableName].* " _
& "FROM [TableName] " _
& "WHERE [TableName].[Run ID] NOT IN (SELECT [Run ID] FROM [SecondTableName] " _
& "WHERE [Run ID] IS NOT NULL)"
Then that sql string is executed through a function in VBA. We use that function dozens if not a hundred times in this procedure and in other spots in the database so I know it is not in that. I also know that the above SQL works because it was working great for over a year and now when I run that statement the database hangs up. There is no invalid data in the table which information is being deleted from, we checked for that first. We also have tried compact and repair on the databases because we are worried this is a corruption in the data database though this does not rule that out and probably is what is going on.
Thank you for your help and let me know if you have questions to help you better understand this issue.
I unfortunately don't have a concrete this is whats wrong answer for you but I'm going to write out how I would approach debugging this. This may be super obvious and you've already tested but I'm a big proponent of double checking even the most trivial of things. The reason to check the SQL even if it is unchanged isn't to check the validity of the SQL but to also identify if nothing has changed on the database end (e.g. large number of new records that makes the nested query too large which causes a timeout, tables being locked etc)
Check the query SELECT [Run ID] FROM [SecondTableName] WHERE [Run ID] IS NOT NULL; and make sure it returns what you expect.
Check the query DELETE [TableName].* "FROM [TableName] WHERE [TableName].[Run ID] IN (<put in a test value>);. This probably involves a couple of test records
Check the query DELETE [TableName].* "FROM [TableName] WHERE [TableName].[Run ID] NOT IN (<put in a test values>);. This probably involves either making a copy of the table and using that or add another conditional in the WHERE statement so you can use test records in your existing table. Since it's in access I would just make the copy unless its prohibitively large.
After all this if you haven't found any issues, then I'm unfortunately out of ideas.

ODBC linked database with / in field name

I'm trying to link an ODBC database - which I have no control on - in MS Access 2007 using a Machine Data Source - I don't know if that's relevant, from what I got this means that the access is set only on this computer -.
When I follow the wizard I can select the table but when the time comes to link it I get the error message:
The database engine can't find 'WTD.DATAPOINT_5/1000'. Make sure it is a valid parameter or alias name, that it doesn't include characters or punctuation, and that the name isn't too long
I think that the problem is that one of the field is named WTD.DATAPOINT_5/1000 and that Access interprets /as a symbol of its own.
The thing is that I don't even need the data stored in this column. Right now I don't know which way to go.
Find a way to tell Access that the / is part of the field name. (Highly improbable)
Retrieve only some fields from the table using built-in Access functions.
Set the connection manually using vba and retrieving only some of the fiels. If this is the way to go I would like some pointers as I have no idea where to start.
Solution number 2: use a passthrough SQL query.
Everything is explained in this tutorial.
Solution number 3: I tried to connect directly in VBA. The code bellow works like a charm for other tables but I still get an error for the table containing the problematic field.
Dim ConnectionStr As String
ConnectionStr = "ODBC;Driver={Oracle in OraHome92};Dbq=BLA1;Uid=BLA2;Pwd=BLA3;"
DoCmd.TransferDatabase acImport, "ODBC Database", ConnectionStr, acTable, "MyTable", "NewTable"

Run Query Against ODBC Connected Table VBA

I have a table (readings) already connected by ODBC in Access that opens very quickly when I click on it.
However, when I try to run this in VBA I it locks up and never displays anything:
Dim strSql As String
strSql = "SELECT readings.ids " & _
"INTO ids_temp " & _
"FROM readings " & _
"WHERE readings.ids > 1234;" //This is id in middle of list
DoCmd.SetWarnings False
DoCmd.RunSQL strSql
DoCmd.SetWarnings True
For some reason this crashes the whole system. Any ideas?
Rather than using DoCmd, t's usually handled by your existing connection to create a Command object, which accepts SQL statements to use with the Command.Execute method.
Reading the documentation for DoCmd, it appears to primarily be intended for eexecuting Macros from the Access UI menus.
Does you Database have ids_temp table locally? If ids_temp table is Linked table it will delete the table, because select into CREATES NEW TABLE. If you want to add to table try INSERT INTO command. You can clean table before inserting the data.
So the error was actually my fault the id I was using was causing the Query to return about 6 million results. But, this method actually works great, I just create the table and link a list box on a different form to the table, then I just show the form. I do some closes and updates in between but overall it seems to work well. Thanks for the help
Let me say that DoCmd.RunSQL is never advisable, particularly with SetWarnings turned OFF, as you have no idea whether the result is what you expect or not. You've told VBA not to report errors, so you never know if all the records were inserted or not.
It's very easy to replace DoCmd.RunSQL with my SQLRun() function, posted here:
How can I get a value from the update query prompt in Access VBA?