Architecture for audits - sql

I am designing a database to capture audits that my company performs. I am having a bit of trouble finding an efficient way to capture all of the audit points without making 60 columns in a single table. Is there an efficient way to capture multiple data points in a single column and still be able to query without trouble.
Each audit may have anywhere from 0 to 60 unique citations. I will make a reference table to hold every regulatory citation, but how do I design the central table so that the 'citation' column can have , or , or any number of other combinations?

I usually try to keep auditing info in a single table.
In order to do this, I go something like this:
TABLE: Audit
**Id** (PK)
**EntityClass** (the Class, or type, or whatever you want to identify your entities by)
**EntityId** (the id of the entity in it's own table)
**PropertyChanged** (the name of the property of the entity that changed)
**OldValue** (the old value of the property)
**NewValue** (the revised value of the property)
**TimeStamp** (moment of the revision)
**RevisionType** (transaction type: Insert, Update, Delete)
This is the simplest schema, you can build on that with additional columns if you wish.
Hope this helps. Cheers!

In this example, I'm assuming, since you refer to a specific number if citations, there is -- or can be -- a taxonomic table holding 60 definitions or references, one for each kind of citation.
The Audits table contains the relevant info about each audit. I'm guessing most of this, but note there is no reference to any citation.
create table Audits(
ID int identity( 1, 1 ),
Started date,
Completed date,
CustomerID int,
AuditorID int, -- More than one possible auditor? Normalize.
VerifierID int,
Details ...,
constraint PK_Audits primary key( ID ),
constraint FK_Audits_Customer( foreign key( CustomerID )
references Customers( ID ),
constraint FK_Audits_Auditor( foreign key( AuditorID )
references Auditors( ID ),
constraint FK_Audits_Verifier( foreign key( VerifierID )
references Auditors( ID ),
constraint CK_Audits_Auditor_Verifier check( AuditorID <> VerifierID )
);
The AuditCitations table contains each citation for each audit, one entry for each citation. Note that the PK will prevent the same audit from having more than one reference to the same citation (if, of course, that is your rule).
create table AuditCitations(
AuditID int,
CitID int,
Details ...,
constraint FK_AuditCitations_Audit( foreign key( AuditID )
references Audits( ID ),
constraint FK_AuditCitations_Citation( foreign key( CitID )
references Citations( ID ),
constraint PK_AuditCitations primary key( AuditID, CitID )
);
A citation may well have its own auditor and verifier/checker or just about anything that applies to the particular citation. This example mainly just shows the relationship between the two tables.

Related

Database design issue. Column value pointing to different tables

Description of what I have to do
I have a table that should be related to Table1 OR Table2 OR Table3
For instance, there's a table Employees and it has:
Id,
Name,
Address,
Age,
Salary,
EmployerId
The second table is RegisterEmployeeRequirements:
Id,
RequirementType,
EmployerId,
EntryId.
Where requirement type could be CreditStatusRequirement or EmployeeDegreeRequirement).
The Problem: CreditStatusRequirement includes both the CreditStatus and the date it was acquired (to check if it was in the last year). I also have additional table which is named CreditStatusRequirements with columns:
CreditStatus,
DateTimeAcquired
On the other hand, the degree requirement which has the following properties: DegreeName and MinGpa.
To solve this I created another table with these properties. If the requirement type in the RegisterEmployeeRequirements is CreditStatusRequirement I will use the entryId column to look at the CreditStatusRequirements table and then to check if it is completed.
Otherwise, if it is EmployeeDegreeRequirement, I will use the entryId column to look into DegreeRequirements table. I suppose it is not a good practice to use such a column like entryId.
What is the way to solve this architecture issue?
It's fairly simple. Don't have the FK equivalent in the RegisterEmployeeRequirements table. Have the FKs in each of the Credit and Degree requirements details tables to RegisterEmployeeRequirements.
Like this:
create table RegisterEmployeeRequirements(
EmployeeId int references ( ID ),
RequirementType char( 1 ) not null,
..., -- Other common fields
constraint PK_RegisterEmployeeRequirements primary key( EmployeeID, RequirementType ),
constraint FK_RegisterEmployeeRequirements_Empe( EmployeeId )
references Employees( ID ),
constraint FK_RegisterEmployeeRequirements_Type( RequirementType )
references RequirementTypes( ID ),
);
Notice the key is the combination of employee id and requirement id. This ensures each employee can have no more than one of each of the two defined requirements. I assume that is in line with your database requirements.
Then each requirements detail table can be defined something like this:
create table CreditRequirements(
EmployeeId int primary key,
RequirementType char( 1 ) check( CreditType = 'C' ),
Status ...,
Acquired datetime,
constraint FK_CreditRequirements_Emp foreign key( EmployeeID, RequirementType )
references RegisterEmployeeRequirements( EmployeeID, RequirementType )
);
create table DegreeRequirements(
EmployeeId int primary key,
RequirementType char( 1 ) check( DegreeType = 'D' ),
DegreeName varchar( 64 ),
MinGPA float,
constraint FK_DegreeRequirements_Emp foreign key( EmployeeID, RequirementType )
references RegisterEmployeeRequirements( EmployeeID, RequirementType )
);
An entry in the credit details table can only be made for an employee that has a credit type entry made in the RegisterEmployeeRequirements table. Same for the degree details table for a degree type entry in RegisterEmployeeRequirements. No more than one of each type of requirement can be inserted in RegisterEmployeeRequirements and only one entry for each employee can be inserted in each of the details table.
Your data integrity is sound and the design is scalable. If a third requirement type is created, the type entry is inserted in the RequirementTypes table and a new details table is created for that type. None of the existing tables would need altering.
Why not just use separate tables for credit status and degree requirements, and remove the need for a RequirementType column?
table RegisterEmployeeCreditStatusRequirements has:
Id, EmployerId, CreditStatus, DateTimeAcquired
table RegisterEmployeeEmployeeDegreeRequirements has:
Id,EmployerId,DegreeName,MinGpa

Modeling a 1:Many relationship with an attribute

I've come across a table design that immediately struck me as odd, but now that I've thought through it I can't seem to come up with a design that I'm really happy about.
The existing design (simplified) is:
CREATE TABLE Accounts (
account_id INT NOT NULL,
account_name VARCHAR(50) NOT NULL,
CONSTRAINT PK_Accounts PRIMARY KEY CLUSTERED (account_id)
)
CREATE TABLE Groups (
group_id INT NOT NULL,
group_name VARCHAR(50) NOT NULL,
CONSTRAINT PK_Groups PRIMARY KEY CLUSTERED (group_id)
)
CREATE TABLE Group_Accounts (
group_id INT NOT NULL,
account_id INT NOT NULL,
is_primary BIT NOT NULL,
CONSTRAINT PK_Group_Accounts PRIMARY KEY CLUSTERED (group_id, account_id)
)
While it looks like a standard many:many relationship, an account never actually belongs to more than one group. I immediately thought, "Ok, we could put the group_id into the Accounts table and that should work." But then what would I do with the is_primary attribute?
I could put an account_id into the Groups table as primary_account_id and then I believe that I could enforce RI with a foreign key on the primary_account_id, group_id to account_id, group_id.
Alternatively, I could move the "is_primary" flag into the Accounts table. Maybe that's the best solution?
Any thoughts on pros/cons for each approach? Am I missing any potential issues? Is there some other alternative that I've missed?
Is there any way to enforce a single primary account within a group in any of these situations outside of triggers (so primarily declarative RI)?
Thanks!
Relationship Cardinality
Judging by your description, you need 1:N relationship, which means you do not need the junction table Group_Accounts. Just a simple FK from Accounts to Groups should do.
Special Row
The next question is how you pick one row at the N side (Accounts) to be "special". You can either:
use the Accounts.is_primary flag and enforce its uniqueness (per group) through a filtered unique index (if your DBMS supports it),
or you could have a FK in Groups pointing to the primary account. In the latter case, though, you have to be careful to pick a primary account which actually belongs to the group.
The second approach can be modeled similar to this:
Groups.FK1 denotes:
FOREIGN KEY (group_id, primary_account_no) REFERENCES Accounts (group_id, account_no)
The presence of group_id in the FK above is what enforces primary account to belong to the group it is the primary account of.
Just be careful how you generate account_no when creating new accounts. You'll need to do something like this to avoid race conditions in concurrent environment (the actual code will varry by DBMS, of course).
Pick the first approach if your DBMS supports filtered indexes and there is no specific reason to pick the second approach.
Pick the second if:
you DBMS doesn't support filtered indexes,
or your DBMS supports deferred constraints and you need to enforce the presence of primary account at all times (just make primary_account_no NOT NULL),
or you don't actually need account_id, so you can have potentially one index less (depending on how strictly your DBMS requires indexes on FKs, and your actual workload, you may be able to avoid index on primary_account_no, as opposed to index that must be present on is_primary).
It is definitely possible to get rid of Group_Accounts.
From your description, it seems each group has many accounts, but each account only has one group. So you would put the group_id into the Accounts table as you suggest, and then put primary_account_id as a field in Groups.
It is possible to change the m:n intersection table, Group_Accounts, to a 1:n table by changing the PK to just the account id instead of both account and group. However, you would still be stuck with the additional overhead of enforcing the constraint that one and only one account is primary for any group.
However, if you move the group FK to the account record, where it really should be for 1:n cardinality, you can create a Primary_Accounts table kinda like the Group_Accounts table except the PK would be the group id. So each group could have one only one entry and that would be the one primary account. It would look like this:
create table Groups (
Id int not null,
Name varchar( 50 ) not null,
constraint PK_Groups primary key( Id )
);
create table Accounts (
Id int not null,
Name varchar( 50 ) not null,
GroupID int not null,
constraint PK_Accounts primary key( Id ),
constraint FK_AccountGroup foreign key( GroupID )
references Groups( ID )
);
create table PrimaryAccounts (
GroupID int not null,
AccountID int not null,
constraint PK_PrimaryAccounts primary key( GroupId ),
constraint FK_PrimaryGroup foreign key( GroupID )
references Groups( ID ),
constraint FK_PrimaryAccount foreign key( AccountID )
references Accounts( ID )
);
Now you have the 1:n cardinality design properly and you have the ability to designate one and only one account per group as the primary account.
However, there is one flaw. The PrimaryAccounts table must refer to an existing group and an existing account, but there is nothing that enforces the implicit requirement that the account be associated with the group.
Fortunately, this is easily fixed. Just add a constraint to the Accounts table:
constraint UQ_AccountGroup unique( GroupID, ID ),
Then, instead of creating two FKs in the PrimaryAccounts table, you need only one:
constraint FK_PrimaryGroupAccount foreign key( GroupID, AccountID )
references Accounts( GroupID, ID )
Now there can be only one primary account for each group and that account must be associated with the group.

How can I share the same primary key across two tables?

I'm reading a book on EF4 and I came across this problem situation:
So I was wondering how to create this database so I can follow along with the example in the book.
How would I create these tables, using simple TSQL commands? Forget about creating the database, imagine it already exists.
You've been given the code. I want to share some information on why you might want to have two tables in a relationship like that.
First when two tables have the same Primary Key and have a foreign key relationship, that means they have a one-to-one relationship. So why not just put them in the same table? There are several reasons why you might split some information out to a separate table.
First the information is conceptually separate. If the information contained in the second table relates to a separate specific concern, it makes it easier to work with it the data is in a separate table. For instance in your example they have separated out images even though they only intend to have one record per SKU. This gives you the flexibility to easily change the table later to a one-many relationship if you decide you need multiple images. It also means that when you query just for images you don't have to actually hit the other (perhaps significantly larger) table.
Which bring us to reason two to do this. You currently have a one-one relationship but you know that a future release is already scheduled to turn that to a one-many relationship. In this case it's easier to design into a separate table, so that you won't break all your code when you move to that structure. If I were planning to do this I would go ahead and create a surrogate key as the PK and create a unique index on the FK. This way when you go to the one-many relationship, all you have to do is drop the unique index and replace it with a regular index.
Another reason to separate out a one-one relationship is if the table is getting too wide. Sometimes you just have too much information about an entity to easily fit it in the maximum size a record can have. In this case, you tend to take the least used fields (or those that conceptually fit together) and move them to a separate table.
Another reason to separate them out is that although you have a one-one relationship, you may not need a record of what is in the child table for most records in the parent table. So rather than having a lot of null values in the parent table, you split it out.
The code shown by the others assumes a character-based PK. If you want a relationship of this sort when you have an auto-generating Int or GUID, you need to do the autogeneration only on the parent table. Then you store that value in the child table rather than generating a new one on that table.
When it says the tables share the same primary key, it just means that there is a field with the same name in each table, both set as Primary Keys.
Create Tables
CREATE TABLE [Product (Chapter 2)](
SKU varchar(50) NOT NULL,
Description varchar(50) NULL,
Price numeric(18, 2) NULL,
CONSTRAINT [PK_Product (Chapter 2)] PRIMARY KEY CLUSTERED
(
SKU ASC
)
)
CREATE TABLE [ProductWebInfo (Chapter 2)](
SKU varchar(50) NOT NULL,
ImageURL varchar(50) NULL,
CONSTRAINT [PK_ProductWebInfo (Chapter 2)] PRIMARY KEY CLUSTERED
(
SKU ASC
)
)
Create Relationships
ALTER TABLE [ProductWebInfo (Chapter 2)]
ADD CONSTRAINT fk_SKU
FOREIGN KEY(SKU)
REFERENCES [Product (Chapter 2)] (SKU)
It may look a bit simpler if the table names are just single words (and not key words, either), for example, if the table names were just Product and ProductWebInfo, without the (Chapter 2) appended:
ALTER TABLE ProductWebInfo
ADD CONSTRAINT fk_SKU
FOREIGN KEY(SKU)
REFERENCES Product(SKU)
This simply an example that I threw together using the table designer in SSMS, but should give you an idea (note the foreign key constraint at the end):
CREATE TABLE dbo.Product
(
SKU int NOT NULL IDENTITY (1, 1),
Description varchar(50) NOT NULL,
Price numeric(18, 2) NOT NULL
) ON [PRIMARY]
ALTER TABLE dbo.Product ADD CONSTRAINT
PK_Product PRIMARY KEY CLUSTERED
(
SKU
)
CREATE TABLE dbo.ProductWebInfo
(
SKU int NOT NULL,
ImageUrl varchar(50) NULL
) ON [PRIMARY]
ALTER TABLE dbo.ProductWebInfo ADD CONSTRAINT
FK_ProductWebInfo_Product FOREIGN KEY
(
SKU
) REFERENCES dbo.Product
(
SKU
) ON UPDATE NO ACTION
ON DELETE NO ACTION
See how to create a foreign key constraint. http://msdn.microsoft.com/en-us/library/ms175464.aspx This also has links to creating tables. You'll need to create the database as well.
To answer your question:
ALTER TABLE ProductWebInfo
ADD CONSTRAINT fk_SKU
FOREIGN KEY (SKU)
REFERENCES Product(SKU)

What is causing Foreign Key Mismatch error?

I have an sqlite database structured as follows:
CREATE TABLE IF NOT EXISTS Patient
( PatientId INTEGER PRIMARY KEY AUTOINCREMENT );
CREATE TABLE IF NOT EXISTS Event
(
PatientId INTEGER REFERENCES Patient( PatientId ),
DateTime TEXT,
EventTypeCode TEXT,
PRIMARY KEY( PatientId, DateTime, EventTypeCode )
);
CREATE TABLE IF NOT EXISTS Reading
(
PatientId INTEGER REFERENCES Patient( PatientId ),
DateTime TEXT REFERENCES Event (DateTime),
EventTypeCode TEXT REFERENCES Event (EventTypeCode),
Value REAL,
PRIMARY KEY( PatientId, DateTime, EventTypeCode )
);
I insert a Patient with Id #1
then I run:
INSERT INTO Event (PatientId, DateTime, EventTypeCode) VALUES (1, '2011-01-23 19:26:59', 'R')
which works
then I run:
INSERT INTO Reading (PatientId, DateTime, EventTypeCode, Value) VALUES (1, '2011-01-23 19:26:59', 'R', 7.9)
and it gives me a foreign key mismatch. Patient Id is '1' in all cases, and the datetime and typecodes match in the 2nd and 3rd queries. I do not understand what is mismatching, but I'm a bit new to actually defining foreign keys and i do not know what I am doing wrong.
I'm not familiar with SQLite but a little Google'ing turned up this. The documentation says
If the database schema contains
foreign key errors that require
looking at more than one table
definition to identify, then those
errors are not detected when the
tables are created. Instead, such
errors prevent the application from
preparing SQL statements that modify
the content of the child or parent
tables in ways that use the foreign
keys. Errors reported when content is
changed are "DML errors" and errors
reported when the schema is changed
are "DDL errors". So, in other words,
misconfigured foreign key constraints
that require looking at both the child
and parent are DML errors. The English
language error message for foreign key
DML errors is usually "foreign key
mismatch" but can also be "no such
table" if the parent table does not
exist. Foreign key DML errors are may
be
reported if:
The parent table does not exist, or
The parent key columns named in the foreign key constraint do not exist,
or
The parent key columns named in the foreign key constraint are not the
primary key of the parent table and
are not subject to a unique constraint
using collating sequence specified in
the CREATE TABLE, or
The child table references the primary key of the parent without
specifying the primary key columns and
the number of primary key columns in
the parent do not match the number of
child key columns.
I suspect you might be running into #3 in that list.
Also, while other DBs might support using a non-unique index as a foreign key reference, (see answers here), it's a bad design choice in my opinion. I would restructure so that either
Reading.PatientId references Event.PatientId so that the complete composite key from Event is referenced by Reading or,
Add an EventId auto-increment, primary key to the Event table and use that as the foreign key in the Reading table (so that you only have EventId and Value under Reading and you can get the PatientId, DateTime, EventTypeCode out of Event).
I'd suggest #2 so that you can avoid the redundancy of PatientId, DateTime and EventTypeCode in both Event and Reading.

How do you store business activities in a SQL database?

The goal is to store activities such as inserting, updating, and deleting business records.
One solution I'm considering is to use one table per record to be tracked. Here is a simplified example:
CREATE TABLE ActivityTypes
(
TypeId int IDENTITY(1,1) NOT NULL,
TypeName nvarchar(50) NOT NULL,
CONSTRAINT PK_ActivityTypes PRIMARY KEY (TypeId),
CONSTRAINT UK_ActivityTypes UNIQUE (TypeName)
)
INSERT INTO ActivityTypes (TypeName) VALUES ('WidgetRotated');
INSERT INTO ActivityTypes (TypeName) VALUES ('WidgetFlipped');
INSERT INTO ActivityTypes (TypeName) VALUES ('DingBatPushed');
INSERT INTO ActivityTypes (TypeName) VALUES ('ButtonAddedToDingBat');
CREATE TABLE Activities
(
ActivityId int IDENTITY(1,1) NOT NULL,
TypeId int NOT NULL,
AccountId int NOT NULL,
TimeStamp datetime NOT NULL,
CONSTRAINT PK_Activities PRIMARY KEY (ActivityId),
CONSTRAINT FK_Activities_ActivityTypes FOREIGN KEY (TypeId)
REFERENCES ActivityTypes (TypeId),
CONSTRAINT FK_Activities_Accounts FOREIGN KEY (AccountId)
REFERENCES Accounts (AccountId)
)
CREATE TABLE WidgetActivities
(
ActivityId int NOT NULL,
WidgetId int NOT NULL,
CONSTRAINT PK_WidgetActivities PRIMARY KEY (ActivityId),
CONSTRAINT FK_WidgetActivities_Activities FOREIGN KEY (ActivityId)
REFERENCES Activities (ActivityId),
CONSTRAINT FK_WidgetActivities_Widgets FOREIGN KEY (WidgetId)
REFERENCES Widgets (WidgetId)
)
CREATE TABLE DingBatActivities
(
ActivityId int NOT NULL,
DingBatId int NOT NULL,
ButtonId int,
CONSTRAINT PK_DingBatActivities PRIMARY KEY (ActivityId),
CONSTRAINT FK_DingBatActivities_Activities FOREIGN KEY (ActivityId)
REFERENCES Activities (ActivityId),
CONSTRAINT FK_DingBatActivities_DingBats FOREIGN KEY (DingBatId)
REFERENCES DingBats (DingBatId)
CONSTRAINT FK_DingBatActivities_Buttons FOREIGN KEY (ButtonId)
REFERENCES Buttons (ButtonId)
)
This solution seems good for fetching all activities given a widget or dingbat record id, however it doesn't seem so good for fetching all activities and then trying to determine to which record they refer.
That is, in this example, all the account names and timestamps are stored in a separate table, so it's easy to create reports focused on users and focused on time intervals without the need to know what the activity is in particular.
However, if you did want to report on the activities by type in particular, this solution would require determining to which type of activity the general activity table refers.
I could put all my activity types in one table, however the ID's would not be able to be constrained by a foreign key, instead the table name might be used as an id, which would lead me to use dynamic queries.
Note in the example that a DingBatActivity has an optional button Id. If the button name were to have been edited after being added to the dingbat, the activity would be able to refer to the button and know its name, so if a report listed all activities by dingbat and by button by name, the button name change would automatically be reflected in the activity description.
Looking for some other ideas and how those ideas compromise between programming effort, data integrity, performance, and reporting flexibility.
The way that I usually architect a solution to this problem is similar to inheritance in objects. If you have "activities" that are taking place on certain entities and you want to track those activities then the entities involved almost certainly have something in common. There's your base table. From there you can create subtables off of the base table to track things specific to that subtype. For example, you might have:
CREATE TABLE Objects -- Bad table name, should be more specific
(
object_id INT NOT NULL,
name VARCHAR(20) NOT NULL,
CONSTRAINT PK_Application_Objects PRIMARY KEY CLUSTERED (application_id)
)
CREATE TABLE Widgets
(
object_id INT NOT NULL,
height DECIMAL(5, 2) NOT NULL,
width DECIMAL(5, 2) NOT NULL,
CONSTRAINT PK_Widgets PRIMARY KEY CLUSTERED (object_id),
CONSTRAINT FK_Widgets_Objects
FOREIGN KEY (object_id) REFERENCES Objects (object_id)
)
CREATE TABLE Dingbats
(
object_id INT NOT NULL,
label VARCHAR(50) NOT NULL,
CONSTRAINT PK_Dingbats PRIMARY KEY CLUSTERED (object_id),
CONSTRAINT FK_Dingbats_Objects
FOREIGN KEY (object_id) REFERENCES Objects (object_id)
)
Now for your activities:
CREATE TABLE Object_Activities
(
activity_id INT NOT NULL,
object_id INT NOT NULL,
activity_type INT NOT NULL,
activity_time DATETIME NOT NULL,
account_id INT NOT NULL,
CONSTRAINT PK_Object_Activities PRIMARY KEY CLUSTERED (activity_id),
CONSTRAINT FK_Object_Activities_Objects
FOREIGN KEY (object_id) REFERENCES Objects (object_id),
CONSTRAINT FK_Object_Activities_Activity_Types
FOREIGN KEY (activity_type) REFERENCES Activity_Types (activity_type),
)
CREATE TABLE Dingbat_Activities
(
activity_id INT NOT NULL,
button_id INT NOT NULL,
CONSTRAINT PK_Dingbat_Activities PRIMARY KEY CLUSTERED (activity_id),
CONSTRAINT FK_Dingbat_Activities_Object_Activities
FOREIGN KEY (activity_id) REFERENCES Object_Activities (activity_id),
CONSTRAINT FK_Dingbat_Activities_Buttons
FOREIGN KEY (button_id) REFERENCES Object_Activities (button_id),
)
You can add a type code to the base activity if you want to for the type of object which it is affecting or you can just determine that by looking for existence in a subtable.
Here's the big caveat though: Make sure that the objects/activities really do have something in common which relates them and requires you to go down this path. You don't want to store disjointed, unrelated data in the same table. For example, you could use this method to create a table that holds both bank account transactions and celestial events, but that wouldn't be a good idea. At the base level they need to have something in common.
Also, I assumed that all of your activities were related to an account, which is why it's in the base table. Anything in common to ALL activities goes in the base table. Things relevant to only a subtype go in those tables. You could even go multiple levels deep, but don't get carried away. The same goes for the objects (again, bad name here, but I'm not sure what you're actually dealing with). If all of your objects have a color then you can put it in the Objects table. If not, then it would go into sub tables.
I'm going to go out on a limb and take a few wild guesses about what you're really trying to accomplish.
You say you're trying to track 'store activities' I'm going to assume you have the following activities:
Purchase new item
Sell item
Write off item
Hire employee
Pay employee
Fire employee
Update employee record
Ok, for these activities, you need a few different tables: one for inventory, one for departments, and one for employees
The inventory table could have the following information:
inventory:
item_id (pk)
description (varchar)
number_in_stock (number)
cost_wholesale (number)
retail_price (number)
dept_id (fk)
department:
dept_id (pk)
description (varchar)
employee
emp_id (pk)
first_name (varchar)
last_name (varchar)
salary (number)
hire_date (date)
fire_date (date)
So, when you buy new items, you will either update the number_in_stock in inventory table, or create a new row if it is an item you've never had before. When you sell an item, you decriment the number_in_stock for that item (also for when you write off an item).
When you hire a new employee, you add a record from them to the employees table. When you pay them, you grab their salary from the salary column. When you fire them, you fill in that column for their record (and stop paying them).
In all of this, the doing is not done by the database. SQL should be used for keeping track of information. It's fine to write procedures for doing these updates (a new invoice procedure that updates all the items from an invoice record). But you don't need a table to do stuff. In fact, a table can't do anything.
When designing a database, the question you need to ask is not "what do I need to do?" it is "What information do I need to keep track of?"
New answer, based on an different interpretation of the question.
Are you just trying to keep a list of what has happened? If you just need a ordered list of past events, you just need 1 table for it:
action_list
action_list_id (pk)
action_desc (varchar)
event_log:
event_log_id (pk)
event_time (timestamp)
action_list_id (fk)
new_action_added (fk)
action_details_or_description (varchar)
In this, the action_list would be something like:
1 'WidgetRotated'
2 'WidgetFlipped'
3 'DingBatPushed'
4 'AddNewAction'
5 'DeleteExistingAction'
The event_log would be a list of what activities happened, and when. One of your actions would be "add new action" and would require the 'new_action_added' column to be filled in on the event table anytime the action taken is "add new action".
You can create actions for update, remove, add, etc.
EDIT:
I added the action_details_or_description column to event. In this way, you can give further information about an action. For example, if you have a "product changes color" action, the description could be "Red" for the new color.
More broadly, you'll want to think through and map out all the different types of actions you'll be taking ahead of time, so you can set up your table(s) in a way that can accurately contain the data you want to put into them.
How about the SQL logs?
The last time I needed a database transaction logger I used an Instead Of trigger in the database so that it would instead of just updating the record, the database would insert a new record into the log table. This technique meant that I needed an additional table to hold the log for each table in my database and the log table had an additional column with a time stamp. Using this technique you can even store the pre and post update state of the record if you want to.