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.
Related
This question already has answers here:
Any way to edit data on MS-Access form using SQL View with two tables
(3 answers)
Closed 2 years ago.
I'm using MS Access 2016 and SQL Server 2012.
I have single table with a schemabound view over the table. Our design was to allow our users to insert data through the view so we can do some id lookups for them and audit who inserted the record using an instead of insert trigger.
No matter how I configure it, Access doesn't want to let me add records to the view. If I use the table, it works as expected and it lets me insert new records.
I've tried the following:
Opening the external table and try to add new record at bottom - no luck
Creating an "Update" query - no luck
Creating an "Append" query - no luck
Creating a form, setting the view as a source, and setting Data Entry = Yes - no luck
Is what I'm trying to do even possible? I come from a SQL/C# background, and have only basic MSAccess skills. Any help is appreciated!
EDIT: I found out the View had a group by when it shouldn't have. I corrected that, but now it is complaining that it is not updatable because there are multiple underlying tables.
EDIT2: I've made progress but it's not ideal.
More info:
This view represents a datawarehouse fact, and joins to two dimension tables to get business keys.
I was able to get this to work by creating a "dummy" table with all the same fields, then creating a view over that. I was able to insert, but the data comes back "deleted" immediately because my instead of trigger fires.
Data went to the right place, but obviously the view doesn't show any data since it's not actually querying the correct tables anymore.
Clearly the view is irrelevant now, and I'll likely write the trigger over the dummy table.
It's not ideal, but I think my solution will be to have them insert through this dummy table, and then have a second view that does the joins so they can view the results.
I appreciate the detailed answer that was given, but unfortunately it didn't help this situation.
If the view is up-datable from SSMS, then it will be so in Access. So, before you try anything in Access, you have to first ensure that the view is up-datable in SSMS. So not all SQL views are up-datable.
You would do beyond well to test/check this in SSMS.
Once you done this, then you have to link the table from access. (and if you messed around with modify to the view, then delete the linked view in access and re-link). YOU MUST delete the access link and re-link.
And the linking process needs extra CARE. WHEN you link, you will be asked for a primary key for the view. Because a view can have multiple PK's (as a result of several tables), then Access can't know or guess. And far worse is that a SQL view does not in fact have a defined PK, and there is no command or means that Access can use to determine this. So you are PROMPTED during that link process. I note this issue, since if you are using some VBA re-link code, then if you re-link and change the database (or server), then during that re-link process, the PK setting you had will be lost. You ONLY get this prompt during a link of a new table - not a re-link. So, keep this important detail in mind.
You can after the fact (after linking a table) execute the following command to re-enable or "set" which column is to be the PK with this command in Access:
I in fact use this routine:
Sub createPK(strTable As String, strPK As String)
CurrentDb.Execute "CREATE UNIQUE INDEX " & strPK & _
" ON " & strTable & " (" & strPK & ") WITH PRIMARY"
End Sub
So, to set a PK for a linked view, then I use this:
Call createPK("dbo_tblHotels","ID")
As a FYI:
The above command DOES NOT create a index in access for that linked view, but is a MEANS to ONLY TELL Access what column to use for the PK. So, in this context, the create index command is not creating a index, but is the means/approach/how/process in which you can tell/set in Access what column is to be used for the PK view. As noted, you only need to do the above if you using code to re-link (or create) a table (view in this case).
So, if you using the Access UI, and you link to a view? Then Access will prompt/ask you to choose a column for the PK. You can as noted after linking use the above routine/command in Access to set which column is to be used as the PK if you missed the prompt, or as noted are using some VBA code to re-link.
A re-link (refresh) with the Access UI to the SAME database will preserve the PK setting. But if you change the connection string (database or server name), then the PK setting will be lost.
First, this is a really good answer to a similar question. Try adding an INSTEAD OF trigger to the view in SQL Server. In order to insert into a view the key columns must all be present and each table must be UNION'ed together. INSTEAD OF trigger can be made to do exactly as you wish.
I'm getting the error message:
This record has been changed by another user since you started editing it. If you save the record, you will overwrite the changes the other user made...
I know this is a common question and I've spent hours researching and testing but to no avail. A few notes:
There are no bit fields anywhere in my database
All tables have a primary key, data type = identity
All tables have a create/modified timestamp trigger on updates and insert
I'm fairly certain that the problem has to do when the form (and multiple subforms) are creating the identity fields and/or the timestamp triggers. Specifically, I only get this error on the "Individual Fish" table when I go back to edit an old 'fish' (as shown on the screenshot). If I just tab through the form and don't make any edits, it works fine. But if I need to edit anything on a previous 'fish' - after the identity / trigger fires - then it gives me the error.
I've gone through and added If Me.Dirty Then Me.Dirty = False End If to every form for the following events:
On Current, On Load, On Click, After Update, Before Update, Before Insert, On Dirty.
I also added DoCmd.RunCommand acCmdSaveRecord to On Deactivate. I will admit that I am not great at VBA, so there could be something silly I did here. Code attached. I've also messed around with Record Locks = Edited Record.
So far nothing seems to work. Please let me know if you think I'm missing something. Also, if you have any random comments or suggestions about my database design or anything else, I always welcome feedback.
Thanks!
UPDATE:
Albert's answers got me to the right place. The short version is to add a rowversion (aka timestamp) field to all tables. This was mentioned on several other posts, but i didn't realize the [awfully named] "timestamp" didn't actually have to do with date or time. Thanks for the help!
]3
Ok, lots of things here to check. First of all, placing a me.Dirty = false in on-current, or events like before update will cause the "same" event to try and trigger again. You really (but really really) don't want to do this. (so wild random tossing in of me.dirty in those events will only make this issue much worse and often cause the very same event to trigger again.
next up:
All tables have a create/modified timestamp trigger on updates and insert
Ok, now the above is confusing. Do you have an actual trigger - as that is a separate issue and will often trigger the record been modified by someone else.
Also when we speak of a timestamp column, do keep in mind that such columns have ZERO ZERO ZERO to do with datetime. Over the years Microsoft has attempted to "change" the name used. The correct name is ROWVERSION, and this column is NOT a datetime data type column, but is called timestamp. Do NOT in ANY WAY confuse this rowversion system/column with that of a datetime column.
So, the assumptions are:
You have a timestamp column - this is of data type timestamp. This column is NOT touched by your code or trigger in ANY WAY. This column is NOT of datetime, nor of datetime2, but is of data type timestamp.
If you don't have a actual timestamp column here (it does not need to be on the form), then you will get constant "dirty" warnings. You also get this with any real data type columns - especially if they are set by server code. (access will round differnt).
Bottom line:
You need a actual rowversion column (of type timestamp) in that table. Behind the scenes if access does NOT find this column, then it will do a column by column compare, and without question with a trigger to set some LastUpdated column with GETDATE() on the server side trigger, then this will cause nothing but problems. Introduction of a timestamp column will STOP access from doing the column by column compare after a update, and it will ONLY look at the timestamp column. So, your trigger can now update the LastUpdated, and the timestamp column should not change from access points of view.
So, you need to be sure:
A PK column is seen by access - all sql tables need a PK.
All tables should have a rowversion column.
If you do add a timestamp (rowverison) column to the problem table, then make sure you re-link on the access client side. In fact after ANY table change or modifications server side, then you should re-link the client side.
I would remove any stray me.Dirty = False code in that form.
You can place a "save" button on the form if you wish, and simply have it go
if me.dirty = true then me.Dirty = False
Edit
With the above setup, you should be able to re-introduce your server side trigger that sets the LastUpdated. However, you not want any code in the form that "touches" or uses that column. You can however should be able to drop in that LastUpdated column into the form and see it update after you save.
Bottom line as I have run across this error on an upgrade of SQL Server to 2016, due not assume "timestamp" is of data type "datetime". It is not. The data type that Access requires is of type "timestamp". Add a column with that data type to any table that is editable through Access and that will clear the "Write conflict with grayed out save button" message.
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.
In my Access application's data entry form I'm using Me.Refresh to show the user the new value of a calculated field based on data they entered in a non-calculated field. The purpose is to give them the deadline for doing a job next.
This Access app is using linked SQL Server 2012 tables via ODBC. The calculated field is in the underlying SQL Server table, not part of the Access Record Source query because I want to store the calculated value in the actual data, not just as an interface element.
The calculation is very simple:
nextjob = jobdate + 79
So I have the field for jobdate set to run Me.Refresh after update. All well and good.
The problem is that if the user updates jobdate, triggers the refresh by moving to another field, then returns to the jobdate field and changes the date they entered Access throws a "The data has been changed by another user" error.
I tested the method using native Access tables and the problem does not occur. However the data needs to stay on the server, so moving to native tables is not a solution for me.
There are several possible solutions.
1- If it's always jobdate + 79, don't store it at all, use a view that has the calculated field.
2- Use Me.Requery instead of Me.Refresh. If the form shows multiple records, you must navigate back to the current record, you can use Me.Bookmark for that.
3- Move the calculation into the Access frontend - make nextjob an ordinary column and set it in the form, so it isn't another user (the server) that updates the data.
I am getting the following error in an MS Access ADP when trying to add a record on a form linked to a MS SQL Server 2000 table:
Run-time error '31004':
The value of an (AutoNumber) field
cannot be retrived prior to being
saved.
Please save the record that contains
the (AutoNumber) field prior to
performing this action.
note: retrieved is actually spelled wrong in the error.
Does anyone know what this means?
I've done a web search and was only able to find the answer at a certain site that only experts have access to.
First of all, if you are going to look at experts-exchange - do it in FireFox, you'll see the unblocked answers at the bottom of the page.
Second, do you have a subform on that form that's using the autonumber/key field on the master form? Do you require the data that's on that subform to be saved (i.e., having its own key) before the main form is saved. You could be into a deadlock of A and B requiring each other to be saved first.
Other than that, you must somehow be accessing that autonumber field whenyou are saving it. The best I can suggest is to step through the code line by line.
Are you trying to assign the value of an Identity field to a variable or something else before you have saved the record?
For whatever reason, your app is trying to read the value of the identity field before the record has been saved, which is what generates that identity field. In other words, no value exists for the Autonumber field until the row is saved.
I think we'd need to see more code or know more about the steps that lead up to this error to resolve it in more detail.
You should have add some lines of code to show us how you're managing your data and what you are doing exactly. But I am suspecting an issue related to a recordset update. can you identify when the autonumber value is created? Is it available in a control on a form? Can you add a control to display this value to check how it is generated when adding a new record? Is the underlying recordset properly updated? Can you add something like me.recordset.update on some form events: I would try the OnCurrent one ...