MS Access "record changes" log - ms-access

Whenever a record is deleted or updated on a form, I want to save its old values in a history table (let's call it Revised). I guess I have to do the following:
For record changes:
use the BeforeUpdate event to save the data somewhere (collection ? array ? or to a recordset -the Revised table- without saving-yet ?)
use the AfterUpdate event to add/save that data to the Revised table
For Deletions:
use the OnDelete event to save the data - but again how ? several records could be deleted at once since the form (a subform in fact) is in datasheet view
use the AfterDelConfirm to add that data to the Revised table.
Do you have any clues, comments or links for this ?
This is all in a "pure Access" (no SQL Server) at the moment.
Many thanks !
Edit: as usual, properly asking the question gaves me ideas:
option 1
use the BeforeUpdate or the OnDelete to build the SQL statement, and use the AfterUpdate or the AfterDelConfirm to Execute the SQL statement. But that won't work for multiple deletions ?
option 2
have the Revised recordset defined at form level, insert the record "Before" but only Update "After". Again, problem with multiple deletes.

I've successfully used a variation of Allen Browne's approach in a couple of different projects. Check out his website for more details:
Creating an Audit Log
His solution uses temp tables and four generic function calls to handle the issue with multiple deletes.

Another approach I have considered more recently, but have not had an opportunity to actually implement, would be to use transactions to perform the change tracking. The basic algorithm would be:
use BeginTrans on the workspace prior to making any changes
in the OnDelete event
perform the deletions in code executing Delete queries against the workspace from step 1
add a record to your change auditing table
in the BeforeDelConfirm event
set Cancel = True
display your own Confirmation dialog
if user confirms then CommitTrans on workspace
otherwise Rollback the transaction on the workspace
Similar approach for Updates/Inserts. This would avoid the need for temporary tables/arrays/collections, etc. but I haven't fully thought through everything. The devil may be in the details.

An "easy" and generic solution, which could be implemented for multiple tables, would be to have a tracking table, made of the following:
Track_Table
==================================================
id_track as primary key
id_table as name of the table which has been updated
id_primaryKey as the record identifier (the PK of the updated record)
changeType, being either DEL or UPDATE
changeDate, as dateTime value
fieldName, as text
oldValue, as text or memo
newValue, as text or memo
if you have to identify the user who did the update, just add
userId
in your table ...
You could then create some generic "before update" and "after update functions" to be called on selected form's beforeUpdate and afterUpdate events. The beforeUpdate fonction will store the old value in a variable, while the afterUpdate function will populate the missing data and insert a new record in the track table.
You will have to find a way to find out the right\corresponding table name and field name. This could be difficult if you are using views or field aliases to display your data in forms.
Of course, all tables to be followed must have a primary key so you can follow changes at the record level. PKs set on multiple fields will surely be problematic ....
oldValues and newValues will have to be converted as text so you can store them in a text or memo field

Related

Maintaining AutoNumber functionality in a form while using SQL backend

Currently, I am trying to move from an Access backend to a SQL backend for my database while still keeping the Access form as the frontend. This is done via linked tables with ODBC connections to my backend. The form is designed to add new records to the table. The problem is with AutoNumber. The ID was set as an AutoNumber and the form would simply display (New) while waiting for the user to add the remaining columns. I have set up my SQL data with the IDENTITY property, so it will increment once a new record is created. However, I cannot get the form to behave the same way as it did because, even though the backend will automatically add the next sequential ID, I cannot automatically fill that data into the form prior to a user actually saving the form data. Is there a way maintain the form functionality that AutoNumber provides?
TL;DR: Form is not working correctly after AutoNumber is changed to Number.
Well, there are "rare" cases in which you actually need the autonumberr before you save the record. For example, if you have a sub-form, then Access ALWAYS does a automatic save of the main record, and thus the autonumber is and will have been created .So a main form, and sub form (child table) will work fine, and do so without code.
Now, there are some cases in which you need the autonumber. Say you have some "code" that needs to run and spit out some child records.
The general approach is to simply execute a record save at that point in time.
So, say there is a button, or some code you need to run in the form, and you NEED the PK autonumber?
You can use this code:
If isnull(me!ID) = true then
me.dirty = false ' force record save - autonumber now created
end if
the record for above to work will have to be "dirty", but in near all cases, this tends to be the case. The "rare" exceptions would suggest that you could check me.IsNewRecord, but in most cases the above bit of code will suffice.
I can't really imagine that the "display" of some autonumber is oh so important WHEN the user is starting to enter data.
However, if you want the autonumber to appear after ANY keypress (data entry on the form)?
Simply put this line of code in the after insert event:
me.dirty = false
So, now when looking at a form, the FIRST key press by the user in any text box will force the autonumber to be generated and appear. However, it is a VERY bad practice to assign any meaning of the autonumber ID to the end users. In fact that ID should in most cases be hidden.
the only issue or downside of above is of course that if you have any required columns, then the above may error out or case an issue.
Solution
For this problem, I needed to set my identity_insert to ON in the SQL backend. Here is the code to do so:
SET IDENTITY_INSERT tableName ON;
Also, if you get the error: Table <Table Name> does not have the identity property. recreate the table with the autoincrementing column having an identity.
Example:
CREATE TABLE new_employees (id_num int IDENTITY(1,1), fname varchar (20), minit char(1), lname varchar(30));
More on IDENTITY in Microsoft offical documentation: here

Access Trigger - Create table row on update

i'm wondering if anyone has a solution for an issue i'm currently having with my Access database.
The database has a number of tables. Once a new record is added, i'd like to create a new entry (copying over the account name and phase automatically if it doesnt exist) into another table - saving others having to manually enter it & minimising the risk of incorrect data input.
I've tried an onChange update function, and while this works, it doesn't check if the entry currently exists in the other table, it just adds it.
Any ideas on implementing this?
If you can use VBA, this logic should work.
IF ISNULL(DLOOKUP("FieldName","TableName","WHERE CONDITION")) = True Then
DoCmd.OpenQuery "QUeryNameForAppendRecord"
Else:
DoCmd.OpenQuery "QueryNameForUpdateRecord"
End If
The DLookup is checking to see if the record exists, so substitute the field and table names accordingly. Use the Where condition to specify which record you are looking for and you will need to use it to reference your form controls. See link for further help in syntax/referencing.
DLookup Function

Access Best method to duplicate record and related records while maintaining cancel ability

I have a Flavor Table with multiple FlavorDetails. My goal is to "Revise" the Flavor on the fly: A revise button would open a popup with the duplicated Flavor and it's duplicated FlavorDetails ready to be editted. A Save and Cancel button would either create or cancel the insertion of the new records. The part I haven't been able to wrap my head around would be the display of the related Detail records in a subform of the popup without the new Flavor record getting saved first. Should I approach this by saving the Flavor record first and having the cancel button delete the record or is there a better method? I have also considered assigning the FlavorID in the FlavorDetails record a number like 9999999 to display them in the subform and then reassigning those numbers to the newly created primary key of the Flavor after it is created upon the "Save" click. Please help! :D
I have had success using SELECT...INTO statements to create temporary tables to store your revisions, and then using VBA to validate revisions and UPDATE the main table.
For your application, the "Revise" button would capture the flavor to be edited from your form and create a temporary table. The pop up form's data source would be bound to the temporary table. Any revisions made in the form will automatically update the temporary table values.
Your "Save" command button would take the temp table data and update the permanent tables as necessary (have the ID be generated as normal if you append instead of update). You may want to delete the temporary table after the update to keep things tidy. The "Cancel" command button would just delete the temporary table and close the pop up form, keeping the original data in the main table intact and unedited.

How safe using combobox to input foreign key

In a Related Tables, you need to enter a FK to link to the parent table.
You either key in the FG from memory !!!!!! or use help.
The standard procedure is to design the field as combo box AND use SQL to select the field that will help to input the ID of the parent table. So, if you want to input foreign key 3 , the combo box will display what 3 is then when you selected Access will insert 3 for you.
My question is since I can later on, edit the table and change the the value to another value, and mess the whole thing up!!! HOW can I lock my first choice so it will stay unedited ?
My second question is: Is this the only way to input the FK if you do not remember the exact ID number or there are thousands of records in the parent table.?
Re. #1
In Access 2010 you could use a trigger to check whether the value has been used in another table as a FK and not allow the change. In previous versions all you could do, would be to hide the navigation pane or make the table hidden.
Re. #2
You could use a listbox (basically the same idea as a combobox).
You could include the lookup table in the underlying query, but then there is more danger of the values getting changed, which is exactly what you were worried about in the first question.
Stick with the combobox.
You can create different versions of a form. One might have Allow Edits, No, another might be Data Entry, Yes, meaning that it can only be used to create new records.
However, suppose someone is creating a new record and they select the wrong FK by mistake. How can they correct this? You could use VBA to produce a confirmation dialog in the first instance, and perhaps an Undo button where VBA would deliberately perform the Undo. Access 2010 also has Data Macros (equivalent to triggers) that you might use to store the old and new values, and other information, when a user changes a FK value.
Access does not implement user-level security, so the user can still open and change data in the raw-data tables. You should, however, have enforced referential integrity on the table-join, so that a user cannot enter a FK that doesn't already exist as a PK in the related table.
In summary, you could add additional columns to the combobox to help the user select the correct item, and you might consider an additional confirmation dialog (a MsgBox) before a record is saved into the data-table. But you cannot prevent people from making mistakes. (Enforcing Referential Integrity will prevent nonsense data from being entered.)
Second question:
A combobox (or possibly a listbox) is the easiest way for users to enter a FK value, without relying on memory. An alternative would be to use a button (or other control and event) that opens a secondary form. This form might have some filtering features to help the user find the correct FK value. When this form is closed you would then need to write some code to update the relevant control on the main form.

MS Access trigger?

I have two tables named [Insert_Record] and [Delete_Record] in MS Access. Both tables have the same fields but one table has records whereas another table has no record.
Question: I want, whenever I delete any record from the table [Insert_Record] that entire record should automatically insert into another table, i.e: [Delete Record].
How can I accomplish this?
Access 2010 introduced event-driven Data Macros that are similar to triggers. The process described in the question can easily be done with an After Delete data macro on the [Insert_Record] table:
As I understand it, Access doesn't have triggers.
The best you can probably do is put this sort of logic into the forms that edit the table. In other words, handle the deleted event at the form level and put your insert logic there.
If you want triggers, you'll want to use a proper RDMS that supports them (MySQL, MS SQL, Oracle, many others).
EDIT: Another way to do this (which may or may not work for you) would be to add a boolean column 'IsDeleted'. That way, you can just logically delete a record instead of moving it to another table. The downside of this approach is the deleted records stay in the main table which could cause performance woes if there are lots of deletes.
Create an append query, to add records into the second table, that is executed in the On Delete Confirm event of the form you are using to delete the record from the first table.
I imagine this 'Delete' is button driven...
So program the button to first Insert the record into one table before deleting it.
Just add the VBA for the insert above the delete.
Another method which nullifies your need for another table entirely is just one column that is a boolean... Active Yes/No (Default to yes.)
If Active then it is not actually deleted when they hit delete, just set the flag to False then nothing is actually deleted, no SQL code is required and there is no risk, you can even have a column with the user who updated it print in another column
Me.Active = False
Me.UserName = 'CurrentUser Location here
Me.RecordSet.Requery
Then there is no risk of actually losing anything is there and you have a record of who flagged it last.
In the continuous form just add the Where flag Active = True then no false flags will show and it looks to the user base as if it was deleted.