Run Query Against ODBC Connected Table VBA - sql

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?

Related

Create and run append queries for multiple linked tables

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 :)

CurrentProject vs CurrentDb

I'm new to VBA and very new to SQL so I don't quite understand why I am having this problem. I'm trying to import excel files into an access database. For a few reasons I have to store my excel data into an array and then upload that array into a temporary table and then append the pertinent parts of the table. To my current understanding, the best way to get my data from my array into a new temporary table would be to create that table and then populate it row by row using SQL. My problem is that when I try to create the table, I get an error. I've simplified my code, I believe this contains what you need to know.
Dim strSQL As String
strSQL = "CREATE TABLE TestTable (`Value` VARCHAR(20))"
CurrentDb.Execute strSQL
This works fine
Dim strSQL As String
strSQL = "CREATE TABLE TestTable (`Value` DECIMAL(4,2))"
CurrentDb.Execute strSQL
This results in run-time error 3292 "Syntax error in field definition"
Dim strSQL As String
strSQL = "CREATE TABLE TestTable (`Value` DECIMAL(4,2))"
CurrentProject.Connection.Execute strSQL
This works fine
Is there a reason why I should ever use one over the other? Simply changing to CurrentProject seems to fix any problems I have but I just want to understand what is happening here. I have done my own research but any answer I find goes over my head (again, I am relatively new to VBA). I apologize in advance, there may be no answer that I am currently able to comprehend
IIRC, CurrentDb.Execute supports a different ANSI SQL standard, I think ANSI 89, although I could be wrong. CurrentProject.Connection.Execute supports ANSI 92, which allows that statement to execute correctly. It's a different spec of the SQL language. If you've ever used or will use SQL server this becomes obvious pretty quickly.
You should notice that your SQL statement won't work either trying to run it as a plain SQL query either. To run this successfully as a query, one option is to enable ANSI 92 as it isn't enabled by default in Access. See below for the relevant option to toggle.
That being said, changing the SQL standard may be less than ideal if you are familiar with Access' SQL syntax, however it might be very helpful if you already have experience with SQL Server. Also worth noting, changing the ANSI option in Access won't change how CurrentDB.Execute runs executes SQL statements in VBA, you'll still need the current approach, or running a stored query.

Using ADO/DAO Connection to Download data from SQL Server

I am trying to figure out how to download using an ADO/DAO connection in Access VBA to get the contents of a table from SQL server. I am trying to avoid using a linked table because the DB requires a password and I keep running into issues with getting it to not ask for the login info. Are there any ideas or references for me to start with on this matter?
It appears either way you'll need to provide SQL credentials.
There's more involved without linking a table, basically you'd want a recordset for the source and the "target" table to iterate over.
targetrs = CurrentDb.OpenRecordset("Target", dbOpenTable)
Dim Con As New ADODB.Connection
Dim sqlStr As String
Con.Open _
"Provider = sqloledb;" & _
"Data Source=SqlServer;" & _
"Initial Catalog=MyDB;" & _
"User ID=sa;" & _
"Password=p#ssW0rd;"
Dim rsSource As New ADODB.Recordset
rsSource.Open "select * from SOURCE", Con
do until rsSource.eof
targetrs.addnew
for each field in rsSource
targetrs.fields(field.Name) = rsSource.fields(field.Name)
next
targetrs.update
rssource.movenext
loop
Since you still have to have the credentials, you could dynamically link the table instead:
docmd.TransferDatabase acLink,"ODBC Database",
"ODBC;Driver={SQL Server};Server=MySQLServer;Database=MYSQLDB;
Uid=USER;Pwd=PASSWORD",acTable,"SQLtable","MyAccessTable"
Use of a linked table does not require you store or have the user password in that linked table.
If you execute a SINGLE logon at application startup then all linked tables will work.
Linked tables work WITHOUT a prompt for user or password.
Linked tables work WITHOUT you having to store the user ID or password in the link.
Access will cache the user name + password if you logon as per the instructions here:
http://blogs.office.com/b/microsoft-access/archive/2011/04/08/power-tip-improve-the-security-of-database-connections.aspx
So to download a table to a local, then you ONLY need this code:
For a new local table (create table query):
CurrentDb.Execute "SELECT * INTO LocalTableCreate FROM ServerTable"
Append to existing table:
CurrentDb.Execute "INSERT INTO LocalTable SELECT * FROM ServerTable"
And if some really strange reason and desire exists create and promote world poverty and do things the hard way like a turtle with time to waste and not use a linked table?
Well you could create a linked table via the “transfer database” command. It is only one extra line of code in front of the above code and then AGAIN the above two examples would work fine.
However I see little if any advantage to creating + deleting a linked table.
I suppose for reasons of performance or perhaps for security or the legitimate reason of you not knowing the table ahead of time? Then I would suggest you use a saved a pass-though query as performance will be even faster.
So you can use this code:
Dim qdfPass As DAO.QueryDef
Set qdfPass = CurrentDb.QueryDefs("MyPass")
qdfPass.SQL = "select * from dbo.MyTable;"
CurrentDb.Execute "INSERT INTO LocalTable SELECT * FROM MyPass”
Note that the sql used in above qerydef MUST be native T-SQL and can be a view or even a store procedure like:
qdfPass.SQL = "exec sp_myCoolStoreProc;"
And the stored procedure can even be passed a parameter like this:
qdfPass.SQL = "exec sp_myCoolStoreProc " & strMyParam
and then :
CurrentDb.Execute "INSERT INTO LocalTable SELECT * FROM MyPass”
So we can even use a select into/append from a store procedure by doing the above and the table/sql server side is dynamic or can even be a stored procedure. Again VERY little code.
I would suggest you avoid the idea proposed here to write recordset looping code unless one really has the desire to write looping code when none is required. And things like PK would have to be dealt with separate in code if you use such loops since the local pk column may need to be skipped (you simply leave that column out of the select SQL).
Note again that the connection string saved for the pass-though query does NOT require the user ID and password by using the above link showing how to “logon” to SQL Server. And if the table is known, then again a saved table link or pass-though query will suffice here.

Reference a field on a form within a query using SQL

I have an Access 2007 database that will be housing tables which refer to the bill of materials for multiple products. On the main form I want a user to be able to select one of the products - OK, easy. Now, I want two queries to run once they press a button after choosing their product from a dropdown. The first query is a simple delete query to delete all information on a table. The second query is where I'm having my issue with my SQL syntax. I want the information from a static table to be appended to the table where the delete query just removed everything from.
Now, each table that houses the bill of material for each product is labeled with the product's name. So I want the dropdown (combo0) to be the reference point for the table name in the FROM clause within the SQL string. Code is as follows:
INSERT INTO tblTempSassyInfo (Concat, TableName, AddressName, PartNumber, [L/R], FeederSize, QtyPerBoard, SASSYname, RawBoard)
SELECT TableName & AddressName & PartNumber, TableName, AddressName, PartNumber, [L/R], FeederSize, QtyPerBoard, SassyName, RawBoard
FROM [FORMS]![DASHBOARD]![Combo0];
So you can see where I'm trying to reference the product name in the dropdown on the form as the table name. Please let me know if this is possible.
"... I'm trying to reference the product name in the dropdown on the form as the table name. Please let me know if this is possible."
It is not possible with Access SQL.
The db engine can only accept the actual table name --- it isn't equipped to reference a form control to find the table name nor to accept any other type of parameter to obtain the table name.
You could change the query to include your combo's value as the table name and then rewrite the SQL from the combo's after update event.
"SELECT * FROM [" & [FORMS]![DASHBOARD]![Combo0] & "]"
A similar approach could keep Access happy. But it may not be the best fit for your application.
So, the user essentially wants 2 queries to run. A DELETE * FROM Table query, and an Append query. The user wants to know what table to utilize for the Append query by using the Combobox (may just be my assumption/interpretation). That being said, why not use something along the lines of:
If IsNull(Me.[Combo0].Value) Then
MsgBox "Please select something."
Me.[Combo0].SetFocus
Cancel = True
Else
Select Case Me.Form!Combo0
Case 1
DoCmd.OpenQuery "DeleteMaterialsTableData" 'Query to delete appropriate table data dependent on Combobox selection'
DoCmd.OpenQuery "QueryNameMaterial1" 'Append records to appropriate table dependent on Combo0 selection'
Case 2
DoCmd.OpenQuery "DeleteMaterialsTableData" 'Query to delete appropriate table data dependent on Combobox selection'
DoCmd.OpenQuery "QueryNameMaterial2" 'Append records to appropriate table dependent on Combo0 selection'
This is just trying to use the users' combobox values to determine which table to run the queries against, instead of the user trying to use the Combobox's value as a table name.
You're pressing a button to do this. This implies that some VBA code is running behind the scene (the Click event of the button). In that case, the answer is a resounding Yes.
Dim strSQL as String
Dim strSQL2 as String
strSQL = "DELETE * FROM tblTempSassyInfo;"
DoCmd.RunSQL (strSQL)
strSQL2 = "INSERT INTO tblTempSassyInfo (Concat, TableName, AddressName, PartNumber, [L/R], FeederSize, QtyPerBoard, SASSYname, RawBoard)
SELECT TableName & AddressName & PartNumber, TableName, AddressName, PartNumber, [L/R], FeederSize, QtyPerBoard, SassyName, RawBoard
FROM " & [FORMS]![DASHBOARD]![Combo0].SelectedValue & ";"
DoCmd.RunSQL (strSQL2)
You may need to tweak that a bit, but it should get you pretty close.
You MAY need to use [FORMS]![DASHBOARD]![Combo0].Columns(0) or Columns(1) instead, I can't remember...
As was stated; Access (and just about any brand database) can definitely do append and delete queries.
The problem is the design. Specifically:
FROM [FORMS]![DASHBOARD]![Combo0];
From clause must be a record set (table) not a call to a control on a form.
My suggestion is to first establish a Select query that has the correct data that you want to append. Save that with a name. You need to be able to do this first.
Once that is done - then create an Append query that uses that saved Select query as its starting record set.
You then just need to trigger the Append query (the Select query will automatically run) using vba behind your button click event:
Docmd.OpenQuery "Append Query Name"
This is 100% possible in MS Access 2010 onward based on my experience. I've not used 2007, but MS says it is possible (see link below). I'm using parametrized queries in a few databases.
PARAMETERS [forms].[dash].[dt_val] DateTime;
SELECT a.F3 AS AdEnt, [forms].[dash].[dt_val] AS Expr1...
The important thing I've found is using a form the user will be interacting with and setting the Date as "DateTime" within the parameter. Here is a video from Microsoft that shows how to and says that it applies to 2007.
Use Parameters in MS Access Queries
Additionally, if you want to do a delete or append, save it as a query then place a button on the form that executes the docmd.runquery for the name of that saved delete/append query.

Append query with no From clause

I have a MS Access database that records financial transactions. It has a form named frmDeposits that is based on table tblDep. One of the buttons on that form runs a procedure that inserts data from the current record into another table, tblAccount, using DoCmd.RunSql. The code looks like this:
DoCmd.RunSQL "INSERT INTO tblAccount ([Date], CheckNo, Amount, Vendor, Cleared, Deposit, Printed, Misc) " _
& "SELECT [Date], CheckNo, Amount, Vendor, Cleared, " & ident & " AS Deposit, Printed, Misc " & _
& "FROM tblDep WHERE Form=" & formnum & ";"
The formnum variable is set elsewhere, and identifies the current record in the form. The ident variable is also defined elsewhere.
Somehow, in the process of debugging other parts of my code, the last line of this SQL statement was erased, leaving:
DoCmd.RunSQL "INSERT INTO tblAccount ([Date], CheckNo, Amount, Vendor, Cleared, Deposit, Printed, Misc) " _
& "SELECT [Date], CheckNo, Amount, Vendor, Cleared, " & ident & " AS Deposit, Printed, Misc"
Surprisingly, the form continued to operate correctly! This RunSQL statement was still picking up the current record from frmDeposits and inserting it into the tblAccount table.
My question is, why does this work? I thought that SQL statements with a SELECT had to have a FROM clause. I know you can use INSERT INTO ... VALUES with no FROM to insert a single record, but even then you have to supply actual values and not field names.
One other thing I noticed: This only seems to work on code in the form's module. If I paste the SQL string into a query design and run it, or run similar code outside the form's scope, I get the 'Enter Parameter Value' popup box for each field. Is VBA pre-parsing the SQL string, and grabbing the form fields or text boxes for the parameters? Or is it defaulting the FROM to the form's RecordSource? I don't have text boxes on the form for all the fields referenced in the SQL string, although they all exist in the form's underlying table.
Btw, this is running in Access 2000. Yes, I know it's an outdated version, but that's what I have to work with.
**Edit -
One added note, this behavior only shows up when using DoCmd.RunSQL. If I put the same SQL string into a db.Execute, it returns an 'Wrong number of parameters' error.
VBA code exists in two forms in an Access application: plain text source; and compiled "p-code". Perhaps what has happened is the stored p-code didn't get changed when that part of the source code text went missing. IOW, it still includes symbols for the missing FROM clause.
In your situation, I would suspect corruption and perform a decompile to clean up the compiled code. First make a backup of the database for safe keeping. You can find detailed instructions for decompile in the 2 answers to this question: HOW TO decompile and recompile.
Frankly corruption is sort of a wild guess. However, since part of your source code mysteriously disappeared and Access operates as if it's still there, corruption seems like a stronger possibility.
Another possibility is you have DoCmd.SetWarnings = False. Turning SetWarnings off silently discards the error message about the failed INSERT attempt. So you don't see an error message but nothing actually gets inserted, either.