I'm running MS Access 2016 connecting via ODBC to SQLServer 2016. I have a simple form based on a linked table. When I display it in Form View, change some data, and click for next record, it updates the current record and moves to the next, but when I use a combo box to select the record to move to - it displays the record I've selected and if I change some data on it - it returns the error
'ODBC -update on linked table failed;
Microsoft ODBC SQL Server Driver Query timeout expired (#0)’
I tried 2 different methods shown below by the code for the 2 different combo boxes
Private Sub cboFindRecord_AfterUpdate()
Dim rst As DAO.Recordset
Set rst = Me.RecordsetClone
rst.FindFirst "Id=" & Nz(Me.cboFindRecord, 0)
If Not rst.EOF Then
Me.Bookmark = rst.Bookmark
End If
rst.Close
Set rst = Nothing
End Sub
Private Sub FindRecord2_AfterUpdate()
Me.txtId.Enabled = True
Me.txtId.Locked = False
Me.txtId.SetFocus
If IsNull(Me.FindRecord2) Then
Exit Sub
End If
DoCmd.FindRecord Me.FindRecord2, acEntire, , acSearchAll, , acCurrent
End Sub
(I have this code on other forms in this database, and they work, but for this form, for this table it doesn’t)
There are 500 records on the table, its seems to be a blocking issue rather than a timeout issue.
If I run an Sp_who2 on SQL Server it shows there is a process block – but why, can anyone help me?
We've just had a similar issue with an Access system with a SQL backend, which was experiencing intermittent timeouts seemingly at random, that has also turned out to be a combo box issue:
We inherited this system and the original developers would use a combo box to lookup an IDs text equivalent and then reference that combo box from say a label. This meant that, for example, on the Sales Line form there was a hidden combo that contained the entire Stock table just to get the name of the stock item.
That combo appears to have been locking the entire Stock table and preventing inserts/updates/deletes.
We could consistently do the following to illustrate the issue:
Open a sales line on one copy of the system - that sales line has a combo that looks up data from the Stock table
Open the stock item in another copy of the system
Edit and save that stock item
At that point the system showing the stock item hangs
Then close the sales line on the first copy
That seems to release the lock on the Stock table as the stock item will then immediately save on the second copy
We resolved this by creating a SQL pass through query to the stock table and using that as the record source in the combo, rather than using an Access linked-table onto the Stock table in the SQL database as we had been previously. Doing that stopped the table locking occurring.
As you have noted that the form/table relationship work ok when using the record selector - - then the whole thing comes down to the combo box. The first thing I would do is simply create a new one from scratch. When you do that - be sure the key field is the first column and becomes the bound value of the combobox. This is the way it will want to set up inherently - but just be sure that is the case.
Related
Summary
In a Microsoft Access 2010 database (accdb), I have a form that dynamically loads other forms into a subform object on the main form. The forms used in the subform object are bound to ODBC pass-through queries that execute stored procedures to return recordsets. I can't figure out why I can use one sproc and it works perfectly fine, but if I bind the form to another sproc, it fails to load the subform.
Technical Walkthrough
I have two pass-through ODBC queries. qryGood and qryBad. They use identical ODBC connection strings (ODBC;DRIVER=SQL Server;SERVER=MyServer;UID=MyUser;Trusted_Connection=Yes;DATABASE=MyDatabase), and the SQL behind them is identical, but pointing to two different SQL stored procedures on the SQL 2012 database server.
qryGood source: exec spGoodProc 123456
qryBad source: exec spBadProc 123456
The SQL behind the sproc is very simple. Return records from a single table, filtering by the ID passed as a parameter. (Some will do more complex things, but I am just focusing on a simplified example here that demonstrates the problem.)
The RecordSource property of frmMySubform is set to qryBad.
The subform SourceObject is set via VBA code: sfrmMain.SourceObject = "frmMySubform" No errors are thrown at this point. While the SourceObject property now returns frmMySubform, the .Form object does not seem to be set.
I then try to reference a property on the subform: Debug.Print sfrmMain.Form.Name This fails with error 2467: The expression you entered refers to an object that is closed or doesn't exist.
I can then open frmMySubform in design view, change the RecordSource property to qryGood and it works just fine. This seems to point to a problem with spBadProc that only manifests itself when used as the RecordSource on a subform.
What I Have Tried
In an effort to troubleshoot this problem, I have used the process of elimination to narrow this down as far as I can, but I am still not understanding why the one sproc works and the other doesn't. Both return records just fine in SQL and when running the pass-through query directly. Both work fine when opening the form directly. It only becomes a problem when the form is set as a SourceObject in a subform control.
I have used sp_procedure_params_rowset to compare the parameters in the sprocs, and they are identical. I have compared the data types of the columns in SQL and there is nothing new or different in tblBad that isn't in tblGood. I have also tried profiling the SQL server while setting the form, and it seems to call the sproc just fine. I didn't see any clues when comparing the trace between the bad and the good calls.
Setting the RecordSet directly to an ODBC link to tblBad works just fine (and I presume a view would be fine as well) but having the simple stored procedure wrapper somehow triggers the error.
I have also compared the security, properties and extended properties for spGoodProc and spBadProc and they are identical.
My Question
What can I do on the troubleshooting side to reduce this down further? Has anyone out there encountered similar issues with bound sprocs on subforms? I am working on a very complex database with hundreds of forms, tables and queries, so I would really like to understand why this is occurring before I go too far down this path.
Thanks in advance for any insight you are able to share on this perplexing problem. :-)
Found it!
After tracing it back to something with the specific table, I removed all constraints, keys, and then columns from a copy of the table, systematically testing to see if I could pinpoint the problem. Sure enough, it was a specific column name in the stored procedure!
Simply aliasing this column to a different name solved the problem. (See below for expanded details)
Update after Further Testing
After additional testing to further pinpoint the issue, I think I now understand why this was occurring. When you link an ODBC table and specify a unique (key) column, Access will automatically attempt to set the LinkMasterFields and LinkChildFields to the key column name when a subform is loaded and the subform has a column with the same name. While this works fine with linked tables or views, it does not work when the RecordSource of the subform is set to a stored procedure.
If you attempt to do this by manually adding the subform, you will see the following notification:
However, if you set the subform target through VBA code, you don't get any warning or error message. It simply doesn't (fully) load the subform. #Albert D. Kallal, you were right on about this being related to a master/child fields issue!
I was able to consistently reproduce the issue in a test database file in both Access 2010 and Access 2016. If you would like to see this for yourself, you can use the following steps to reproduce it:
Create a SQL table with a PrimaryID column.
CREATE TABLE [dbo].[tblBugTest](
[PrimaryID] [int] NOT NULL,
[TestColumn] [nchar](10) NULL
) ON [PRIMARY]
Add a couple test records to the table you just created.
Create a Stored Procedure to return the records.
CREATE PROCEDURE [dbo].[spBugExample]
AS SELECT * FROM tblBugTest
Create a blank Microsoft Access database (accdb).
Using ODBC, create a linked table to tblBugTest.
Important: Select PrimaryID as the unique column.
Create a pass-through query named qryPassThrough using ODBC to the same database, and set the SQL to exec spBugExample.
Open the query to verify that it returns records.
Create three blank forms in the database. frmMain, frmSubForm, and frmBlank.
Add frmBlank as a subform to frmMain. Name the subform sfrmSubform.
Set the RecordSource of frmMain to the linked table.
Add a button to frmMain to switch the subform from frmBlank to frmSubForm.
Private Sub cmdShowBug_Click()
With Me.sfrmSubform
.SourceObject = "frmSubForm"
Debug.Print .LinkMasterFields
Debug.Print .LinkChildFields
Debug.Print .Form.Name
.SourceObject = "frmBlank"
End With
End Sub
Set the RecordSource of frmSubForm to qryPassThrough.
Drop a couple bound controls onto frmSubForm.
Test frmSubForm by itself. It should load a record from tblBugTest.
Open frmMain and click the button. It should throw an error.
If you step through the code, you will see that before setting the SourceObject, the LinkMasterFields property is blank. After setting SourceObject, you can hover over LinkMasterFields and see that it is now set to the PrimaryID column.
Workarounds
Changing any of the following will work around the error by avoiding the problematic auto-linking of the master/child fields.
Delete and relink the linked table, this time not specifying a unique column.
Alias the column in the Stored Procedure to a different name than the unique column.
Clear the RecordSource property of the parent form.
Clear the subform RecordSource property and set the RecordSet after loading the subform.
Use a view or linked table instead of a stored procedure in the subform.
Keep in mind that the subform data source will be attempted to be loaded BEFORE the main form loads. What this suggests is that on the main form's load event, you will
First setup the PT query.
Then set the OBJECT source of the sub form.
In other words, the source object of the sub form control should be blank.
You code then to set up the PT query will be:
With currentdb.queryDefs("qryGood")
.SQL = "EXEC spGoodProc " & 123456
end with
Of course you can replace the 123456 with a varible, or even a value from a text box (from the main form).
Now that the PT query is setup, you THEN are to set the form that the sub-form is to load.
So, after above code, we then have:
me.mySubForm.SourceObject = "name of subform goes here"
So, it should be about a total of 4 lines of code. And as above shows, you don't even need any connection string stuff in your VBA code.
So, just keep in mind:
Setup the PT query as per above. You can then launch a report, or even a form, or in this case set the form that the sub-form control is to load. This also suggests/hints that you need to remove the source object of the sub form control (leave it blank).
You can have the sub-from source object set, but then this would suggest that you setup the PT query source as per above BEFORE you launch the main form with the sub form based on the PT query. As noted, this set of steps is required since the sub form actually load and resolves it data source BEFORE the main form displays and renders. So, by leaving the source-object blank for the sub form, then you the developer re-gains complete control over the order of loading.
I have an MS-Access front end to a MSSQL server back end. The existing, functional updates that the tool makes are applied through a MSSQL view which is inserted to MS-Access as a linked table. There is a primary key defined for this linked 'table' (view).
The user sees a subset of records that match previously selected criteria, and uses comboboxes (unbound) to select the value of several fields that are then applied to all matching records using DoCmd.RunSQL with Me.Filter on the "After Update" Event.
Users have requested an additional piece of functionality.
I have:
Added the new column required to the underlying table referenced in the view
Added the column to be output in the view
Refreshed the linked table in MS-Access
Added the new field to the form that will be updating it, and modified the DoCmd.RunSQL statement to enact the UPDATE
When updating the new field via the form, I get the standard message "You are about to change x rows" where x is the appropriate number. Pressing OK gives no errors, but the table is not updated.
To debug, I attempted to change the record in the linked table view directly. Again no errors were thrown, and the row seems to be updated, but this is not reflected in SSMS, and reloading the table in MS-Access the change is no longer present. I can change the values of columns other than the new one.
I also tested adding the underlying table as a linked table and I can edit the rows in MS-Access in this table.
(Update)
At #ErikvonAsmuth suggestion below I tried using Recordsets on the bound form instead of the DoCmd.RunSQL. Again could access the record and an update gave no error on rst.Update, but the change is not reflected in the database for the new field. I can change a previously existing field using this method as above.
Seems my problem is independent of the update method.
(/Update)
I would appreciate any ideas for next steps to check.
I found the issue.
There was a trigger defined on the view to handle saving to the table. I altered this in SSQL to add the new column and everything is working now.
Found a hint when I tried to edit the field in SSMS and it wouldn't work there either - was getting a Row failed to retrieve on last operation error.
That lead me to a thread referencing triggers on ExpertsExchange
I will be going back and changing the DoCMD.RunSQL statements to use recordsets.
How can I clear pivot table cache with VBA, but not destroy pivot table structure? My pivot table is connected to external data source. The SQL source determines which user should see which portion of the data. The source populates the table as the table is refreshed. I want to save the Excel file and distribute it with clean pivot table (no data inside).
As a result I want to get exactly this:
I have experimented around this code with no success. There is no such thing like PivotCaches.Clear in VBA.
Sub PT_cache_clear()
For Each pc In ActiveWorkbook.PivotCaches
pc.Clear
Next pc
End Sub
The only good solution I found is to refresh the table with a user which has access to SQL server source but is not allowed to see any single record of the data.
The idea:
ActiveSheet.PivotTables("PivotTable1").SaveData = False
seems not to lead to desired results.
The way I do it is to refresh with a query that will return the table structure but with 0 records. So if selecting from a view something like:
select top 0 *
from vw_MyPivotData
If using a stored procedure, you can send a parameter that ensures that no records will be returned such as a filter that you know doesn't exist in the data or a special parameter devised for the purpose of returning no records.
You cannot just clear the PivotCache without affecting the pivot table, they are inexorably linked. You can trick the PivotCache into loading and empty result set with the same structure/schema. Use the PivotTable.ChangeConnection function to switch the connection before closing the document.
If you have two external connections defined in your excel file. One that returns the correct data and another that returns the same structure but no rows. You can switch the connection to the no rows version and flush the cache that way. If there are any differences between the structure/schema of the connection resultset then excel will throw an error message.
Change the connection on demand, just before you distribute your file.
Sub PT_cache_clear()
'change connection'
Dim con as vartype
Set con = ActiveWorkbook.Connections("MyNoResultConnection")
Worksheets(1).PivotTables(1).ChangeConnection (con)
'refresh the pivot table cache'
Worksheets(1).PivotTables(1).PivotCache.Refresh
'clear the cache of any orphaned items'
Dim pc As PivotCache
Dim ws As Worksheet
With ActiveWorkbook
For Each pc In .PivotCaches
pc.MissingItemsLimit = xlMissingItemsNone
Next pc
End With
End Sub
Change to the good connection every time the sheet opens
Private Sub Workbook_Open()
Dim con as vartype
Set con = ActiveWorkbook.Connections("MyGoodResultConnection")
Worksheets(1).PivotTables(1).ChangeConnection (con)
Worksheets(1).PivotTables(1).PivotCache.Refresh
End Sub
You should only need to setup the MyNoResultConnection connection on your local PC since users will not be calling PT_cache_clear().
Update:
Instead of always changing the pivot table connection every time you could set it conditionally by getting the current connection name from the PivotCache.WorkbookConnection property and comparing the names.
Worksheets(1).PivotTables(1).PivotCache.WorkbookConnection
Note:
It is not possible to just flush or empty the pivot cache. You can call a refresh method or set it to refresh automatically when the file opens. You can clear the cache of orphaned items.
PivotTable.ChangeConnection and PivotCache.WorkbookConnection can only be used with external data sources.
Setting PivotTable.SaveData to false will not clear the pivot cache. This stops the data source from being saved it does not affect the PivotCache. The PivotCache lies between the data source and pivot table. When using external data sources this property will have no effect and for OLAP data sources it is always false.
See posts here and here for getting the current state of your PivotCache.
Alternative ways to implement this:
Create the connection object and pivot table with a VBA macro when the workbook opens. Delete the connection, pivot table before the workbook closes. The cache will then be automatically deleted when the workbook is saved. This approach can be difficult depending on how your external datasource is setup. It may require your to store username and passwords inside your VBA code.
I created a form in Visual Studio 2012 binding table 1 to a few of the fields in order to force the user to use specific data in both a textbox and a combobox. They select what they need out of those fields, and fill in a few others, and then click save, which will take the data to table 2. While I've created a dataset, bindingsource, tableadapter, and tableadaptermanager for table 2, it does not save to the table if I use...
Me.Validate()
Me.table2BindingSource.EndEdit()
Me.table2TableAdapter.Update(table2DataSet)
...like you can if you use the bound source that's autofilling the form. How do I wire the save to hit the proper table? Do I have to go the long way and open up a connection creating the sql statement, executing that business, etc.?
So I did have to code the connection sequence into the button click. I've included the code below for posterity and comment.
Using connection As New SqlClient.SqlConnection(My.Settings.MyConnectionString)
Using Command As New SqlClient.SqlCommand("INSERT INTO table2 (stuff1, stuff2, stuff3) VALUES (#stuff1, #stuff2, #stuff3)", connection)
Command.Parameters.AddWithValue("#stuff1", stuff1TextBox.Text)
Command.Parameters.AddWithValue("#stuff2r", stuff2ComboBox.Text)
Command.Parameters.AddWithValue("#stuff3", stuff3TextBox.Text)
connection.Open()
Command.ExecuteNonQuery()
Dim rowsAffected As Integer = Command.ExecuteNonQuery()
Console.WriteLine("RowsAffected: {0}", rowsAffected)
End Using
It worked well enough for me to be comfortable and submit it for user testing. Please feel free to pull it apart.
I used the SSMA to migrate data from an access database to sql server, while still using the user interface in the access database.
Now, I need to rename that sql server database, but I'm not sure how to update the Access file to use the renamed database.
You can use the Linked Table Manager.
View or refresh links
Use this procedure to view or to refresh links when the structure or
location of a linked table has changed. The Linked Table Manager lists
the paths to all currently linked tables.
Open the database that contains links to tables.
On the Tools menu,
point to Database Utilities, and then click Linked Table Manager.
Select the check box for the tables whose links you want to refresh.
Click OK to refresh the links.
Microsoft Access confirms a successful
refresh or, if the table wasn't found, displays the Select New
Location of dialog box in which you can specify the
table's new location. If several selected tables have moved to the new
location that you specify, the Linked Table Manager searches that
location for all selected tables, and updates all links in one step.
To change the path for a set of linked tables
Open the database that contains links to tables.
On the Tools menu,
point to Database Utilities, and then click Linked Table Manager.
Select the Always prompt for new location check box.
Select the check
box for the tables whose links you want to change, and then click OK.
In the Select New Location of dialog box, specify the new
location, click Open, and then click OK.
You can do this in VBA. This code looks for linked tables that begin with "dbo_" and it removes that part of the name. You'll need to modify it to suit your needs. I recommend your call this from your autoexec macro or an unbound form that starts up with your database.
If you're linking to multiple SQL Server databases then this solution might now work.
Public Sub subChangeLinkedTableNames()
Dim dbCurr As DAO.Database
Dim tdfCurr As DAO.TableDef
Set dbCurr = CurrentDb()
For Each tdfCurr In dbCurr.TableDefs
If Len(tdfCurr.Connect) > 0 Then
If Left(tdfCurr.Name, 4) = "dbo_" Then
tdfCurr.Name = Replace(tdfCurr.Name, "dbo_", "")
End If
End If
Next
Set tdfCurr = Nothing
Set dbCurr = Nothing
End Sub