Is a single field contain multiple foreign key relationship from different table [SQL] - sql

I have 3 table Student,Teacher,User.
Student:
CREATE TABLE Student( id INT NOT NULL PRIMARY KEY,name VARCHAR(50) NOT NULL);
INSERT INTO [dbo].[Student]([id],[name]) VALUES(4,'Ram'),(5,'Raman');
Teacher:
CREATE TABLE Teacher( id INT NOT NULL PRIMARY KEY,name VARCHAR(50) NOT NULL);
INSERT INTO [dbo].[Student]([id],[name]) VALUES(1,'Raj'),(2,'Rahul');
User:
CREATE TABLE [dbo].[User](
id INT NOT NULL PRIMARY KEY,
user_id INT NOT NULL,
user_type CHAR(1) NOT NULL,
user_name VARCHAR(10) NOT NULL,
user_password VARCHAR(255) NOT NULL,
CONSTRAINT FOREIGN KEY (user_id) REFERENCES Student (id),
CONSTRAINT FOREIGN KEY (user_id) REFERENCES Teacher (id) );
Now I try to INSERT in User table with below query
INSERT INTO [dbo].[User] ([id] ,[user_id] ,[user_type],[user_name] ,[user_password]) VALUES (1 ,1,'S','Raj_001','********')
It gives me error for violation of foreign key due to
value of user_id is available in Teacher and not in Student
So my question is: How can I achieve that a value of user_id is present in one of those table and data should be inserted in User table.

Your table structure is flawed. A foreign key tells the database that there is definitely one and only one row on one side of the relationship. You can't have a partial match, it's all or nothing. This is before considering how you would ensure that you don't end up with the same id in both the teacher and student table.
It would be better to have two columns in your user table, one for teacher id and one for student id. In fact going further given the only extra data in both student and teacher tables is their name why not just eliminate both and store the name in the user table?
Another option to consider is that your foreign key is pointed in the wrong direction. Perhaps a better approach is reversing it to ensure each student and teacher is a user rather than that a user is either a student or a teacher.

First of all get rid of those key words from table name like [User],user_id etc.
It really is problematic and irritating.
Secondly why 2 key in [User] table,id, user_id ? It is not require.
I will keep only id or user_id.
Thirdly, knowing the real table structure or even purpose of each table help in better data modeling.
From [User] table what it appear is that id and user_type are composite primary key.
It should be. If this is true then you can't define FK constraint, as user_type is not available in either Teacher table and Student Table.
And what is appear that ,for example first data is inserted in Student or Teacher then data is inserted in User table in same Transaction.
So in all above scenario, Instead of Trigger is ideal scenario in this condition.
My script is just demo,
Create Proc spStudentInsert
as
set nocount on
set xact_abort on
begin try
begin tran
--bulk insert or single insert ,no problem
insert into Student
insert into [User]
if (##Trancount>0)
commit
end try
begin catch
if (##Trancount>0)
rollback
end catch
CREATE TRIGGER INSTEADOF_TR_I_User ON [user]
INSTEAD OF INSERT
AS
BEGIN
DECLARE #Flag BIT = 1
IF NOT EXISTS (
SELECT 1
FROM Student S
INNER JOIN inserted i ON i.id = S.id
)
SET #Flag = 0
ELSE IF NOT EXISTS (
SELECT 1
FROM Teacher T
INNER JOIN inserted i ON i.id = T.id
)
AND #Flag = 1
SET #Flag = 0
IF (#Flag = 0)
BEGIN
RAISERROR (
N'Invalid user'
,16
,1
)
RETURN
END
END
In case I am wrong about id, user_type composite PK then you can do other way,
PK of User id is FK in Student table as well as Teacher table.
Also , id are PK in their respective table.
So first you insert in User table then you insert in Student or Teacher table.
So design in this case will be,
CREATE TABLE [dbo].[User](
id INT NOT NULL ,
user_type CHAR(1) NOT NULL,
user_name VARCHAR(10) NOT NULL,
user_password VARCHAR(255) NOT NULL,
CONSTRAINT [PK_user] PRIMARY KEY (id)
)
INSERT INTO [dbo].[User] ([id] ,[user_type],[user_name] ,[user_password])
VALUES (1 ,1,'S','Ram_001','********')
--drop table [User]
--alter table [user]
-- drop constraint PK_user
CREATE TABLE Student( id INT NOT NULL PRIMARY KEY,name VARCHAR(50) NOT NULL);
ALTER TABLE Student
add CONSTRAINT FK_StudentUser FOREIGN KEY (id) REFERENCES [User] (id);
INSERT INTO [dbo].[Student]([id],[name]) VALUES(1,'Ram'),(5,'Raman');
--select * from [Student]
CREATE TABLE Teacher( id INT NOT NULL PRIMARY KEY,name VARCHAR(50) NOT NULL);
ALTER TABLE Teacher
add CONSTRAINT FK_TeacherUser FOREIGN KEY (id) REFERENCES [User] (id);
INSERT INTO [dbo].Teacher([id],[name]) VALUES(1,'Raj'),(2,'Rahul');
So what it appear from your question, I will create Instead of Trigger and go with that model.

There are two ways to do this without re-doing your table schema
Create a 4th table that contains the union of ID from Student and Teacher. Presumably, you would insert to that table whenever you insert into Student and Teacher, and then have the constraint act against that table.
Create a custom function based constraint rather than a foreign key which looks up against a union of both the student and teacher tables.
Neither of these are great/clean solutions, and as others have noted, you probably are dealing with the fact that the schema isn't ideal.
Still, if you're just modifying an existing system (and I assume this is a simplified version of what you're actually dealing with), then one of the two solutions I mentioned id easier than redoing the schema.

Your foreign key definition has some logical problems. It forces the user_id to exists in both tables. The solution here is depended on the business needs and real data.
You can create a Person table with 1-1 relation to the student and the Teacher tables and then use the Person.Id column in the foreign key definition. This solution assumes that the students' and teachers' data may change differently.
As another way (which is explained in other answers), If your student and teachers' data is similar, you can combine both tables, and difference data by one added "Type" column.

SO you want to tell the system that your User must be in one of your tables .
it's not possible in databases logic but you can write a script that have a condition (IF exist) then insert you user data
notice : you have to remove your foreign keys .
its a wrong logic !
you are telling your system that your user is a student and a teacher to !
that is absolutely wrong .

I feel like there were some excellent responses in this thread, but I'm going to take a stab at giving you a different direction. I'll try to be clear on why, and try to acknowledge your situation as I do so.
Student/Teacher Data is Often Messy
As someone with experience normalizing data sets in higher education, the issue you've run into resonated with me. Educational users could be in all three categories (Student, Teacher, and User) or just one of them, depending on the how and why the category was linked. Worse, they can enter from multiple directions and end up with multiple unlinked accounts. More mature institutions and tools have protections against this, but I still see user-created databases and ten year old 'it was temporary' solutions that cause me existential pain.
The Main Stumbling Block
Any database with tables that independently define who is a user based on different criteria have a potential point of failure.
Foreign keys was the right direction to be thinking in for this problem. You want these tables to connect and you want them to stay consistent with one another, regardless of which side of the data gets altered. We just need to add a little extra.
One Table To Rule Them All
Before I go further, I want to say that it is possible to get all of the fields you're tracking into a single table, but having multiple tables with distinct purposes is an easy way to protect against changes later.
The foreign key table must inherit the key from another table, but people often say foreign keys can't be primary keys as well. Why?
Foreign keys are not automatically unique keys in the tables they're in. If there can be multiple fields tied to that same key, the table ends up worthless.
We fix that with the Unique constraint. Applied to a foreign key field, Unique essentially makes it act as a primary key would.
Sample Method
Below is an alternative design for what you seemed to be after, creating a master list of IDs that can link across all tables. I tossed in a few minor tracking fields that can be useful for debugging.
/*Create Tables*/
CREATE TABLE ID(
USER_ID int NOT NULL PRIMARY KEY AUTO_INCREMENT,
USER_CREATED timestamp
);
CREATE TABLE USER(
USER_ID int NOT NULL UNIQUE FOREIGN KEY REFERENCES ID(USER_ID),
USER_LOGIN VARCHAR(10) NOT NULL UNIQUE,
USER_PASSWORD VARCHAR(255) NOT NULL,
USER_NAME VARCHAR(50) NOT NULL
);
CREATE TABLE PERMISSIONS(
USER_ID int NOT NULL UNIQUE FOREIGN KEY REFERENCES ID(USER_ID),
STUDENT CHAR(1),
TEACHER CHAR(1)
);
This creates a flag for student and teacher that could both be true or both be false. If you want the code to force them into only one or the other, you can still have the permissions table do a USER_TYPE field instead. I suggest a null or neither value being possible in either case if you plan to use this for any length of time. Best of luck.

Related

How to insert values into a junction/linking table in SQL Server?

I am piggy backing off this question regarding creating a junction/linking table. It is clear how to create a junction table, but I am concerned about how to fill the junction table with data. What is the simplest and/or best method for filling out the junction table (movie_writer_junction) with data between two other tables (movie, writer)
CREATE TABLE movie
(
movie_id INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
movie_name NVARCHAR(100),
title_date DATE
);
CREATE TABLE writer
(
writer_id INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
writer_name NVARCHAR(100),
birth_date DATE
);
INSERT INTO movie
VALUES ('Batman', '2015-12-12'), ('Robin', '2016-12-12'),
('Charzard, the movie', '2018-12-12')
INSERT INTO writer
VALUES ('Christopher', '1978-12-12'), ('Craig', '1989-12-12'),
('Ash', '1934-12-12')
CREATE TABLE movie_writer_junction
(
movie_id INT,
writer_id INT,
CONSTRAINT movie_writer_pk
PRIMARY KEY(movie_id, writer_id),
CONSTRAINT movie_id_fk
FOREIGN KEY(movie_id) REFERENCES movie(movie_id),
CONSTRAINT writer_fk
FOREIGN KEY(writer_id) REFERENCES writer(writer_id)
);
The final junction table is currently empty. This is a simple example, and you can manually fill the data into the junction table, but if I have two tables with millions of rows, how is something like this completed?
Hi I'm guessing this relates to the fact that you can't rely on the Identity Columns being the same in different regions.
You can write your inserts as a cross join from the 2 src tables
Insert junc_table (writer_id, movie_id)
Select writer_id , movie_id
from writer
CROSS Join
movie
where writer_name = 'Tolkien' and movie_name = 'Lord of the Ring'
This way you always get the correct Surrogate Key (the identity) from both tables.
Its pretty easy to generate a SQL statement for all your existing junction combinations using a bit of Dynamic SQL
Another Approach is to Use SET IDENTITY_INSERT ON - but this needs to be done when loading the 2 other tables and that ship may already have sailed!

SQL : Self referencing row

I have a Users table:
Create table dbo.Users (
Id int IDENTITY (1, 1) PRIMARY KEY,
ManagerId int FOREIGN KEY references dbo.Users(Id)
);
Users have Managers.
Managers are themselves Users.
Is it possible to have ManagerId NOT NULL and insert a row telling SQL it references itself? i.e. ManagerID = Id?
PS: I already know I can do it in two steps with ManagerID NULL, I also know I could code my app so that NULL means the user is managing himself, but that's not the point of my question.
With an identity column, I don't think so. But we've had sequences for a long time. You could define your table like this:
create sequence [SQ_Users] as int start with 1;
create table dbo.Users (
Id int NOT NULL
constraint [PK_Users] PRIMARY KEY
constraint [DF_UserID] DEFAULT (next value for [dbo].[SQ_Users]),
ManagerId int not null
constraint [FK_User_Manager] FOREIGN KEY references dbo.Users(Id)
);
(Note: I named your constraints because I so loathe system-named ones; it's a service I provide). Now, if you want to have a user be their own manager, you do it like this:
declare #UserId int = next value for [dbo].[SQ_Users];
insert into dbo.Users (ID, ManagerID) values (#UserID, #UserID);
Note that this doesn't take away the common use case of the table itself auto-generating its own ID. That is, you can still do:
insert into dbo.Users (ManagerID) values (#ManagerID);
For what it is worth, it is a common idiom in hierarchical data (like what you have) that a NULL value for the parent ID means that the element is at the top or references itself. That is, your current setup shouldn't cause any sideways glances from "the next guy".

Update trigger old values natural key

I have an accounts table with the account owner as the primary key. In the update trigger, I want to update some accounts to new owners. Since this table doesn't have an id field, how do I use the inserted/updated tables in the trigger? DB is sql server 2008.
CREATE TRIGGER accounts_change_owner on accounts AFTER INSERT
AS BEGIN
MERGE INTO accounts t
USING
(
SELECT *
FROM inserted e
INNER JOIN deleted f ON
e.account_owner = f.account_owner ---this won't work since the new account owner value is diff
) d
ON (t.account_owner = d.account_owner)
WHEN MATCHED THEN
UPDATE SET t.account_owner = d.account_owner
END
I think I understood your question, but I am not sure. You want to be able update account owner name in one table and to have this update propagated to the referencing tables?
If so you don't really need a trigger, you can use on update cascade foreign key.
Like this:
create table AccountOwner
(
Name varchar(100) not null
constraint PK_AccountOwner primary key
)
create table Account
(
AccountName varchar(100) not null,
AccountOwnerName varchar(100) not null
constraint FK_Account_AccountOwnerName references AccountOwner(Name) on update cascade
)
insert AccountOwner values('Owner1')
insert Account values('Account1', 'Owner1')
Now if I update table AccountOwner like this
update AccountOwner
set Name = 'Owner2'
where Name = 'Owner1'
it will automatically update table 'Account'
select *
from Account
AccountName AccountOwnerName
----------- -----------------
Account1 Owner2
I think you need to modify the design of your table. Recall that the three attributes of a primary key are that the primary key must be
Non-null
Unique
Unchanging
(If the primary key consists of multiple columns, all columns must follow the rules above). Most databases enforce #1 and #2, but the enforcement of #3 is usually left up to the developers.
Changing a primary key value is a classic Bad Idea in a relational database. You can probably come up with a way to do it; that doesn't change the fact that it's a Bad Idea. Your best choice is to add an artificial primary key to your table, put NOT NULL and a UNIQUE constraints on the ACCOUNT_OWNER field (assuming that this is the case), and change any referencing tables to use the artificial key.
The next question is, "What's so bad about changing a primary key value?". Changing the primary key value alters the unique identifier for that particular data; if something else is counting on having the original value point back to a particular row, such as a foreign key relationship, after such a change the original value will no longer point where it's supposed to point.
Good luck.

Need help understanding this SQL (generated by doctrine)

i am actually reading Doctrine Reference: One to Many, Unidirectional with Join table. but this will probably be more of a SQL quesiton. basically, this is supposed to model a one to many, unidirectional relationship. i guess from the PHP code (in that link), its such that 1 user have many phonenumbers.
the question is from the SQL, it seems like 1 user can have many phonenumbers. and 1 phonenumber can only belong to 1 user. am i right?
CREATE TABLE User (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE users_phonenumbers (
user_id INT NOT NULL,
phonenumber_id INT NOT NULL,
UNIQUE INDEX users_phonenumbers_phonenumber_id_uniq (phonenumber_id),
PRIMARY KEY(user_id,
phonenumber_id)
) ENGINE = InnoDB;
CREATE TABLE Phonenumber (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE users_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES User(id);
ALTER TABLE users_phonenumbers ADD FOREIGN KEY (phonenumber_id) REFERENCES Phonenumber(id);
can't i just simplify the database to ... below ... no need for join tables and what not?
Users (id, name)
Phonenumbers (id, user [FK], number)
Correct, these are two valid approaches to the same problem. And yes, the unique index on users_phonenumbers means that each phone number can belong to only one user.
The design is actually suboptimal.
The idea must have been that there are telephone numbers, users, and that they can be linked many-to-many. Because of the unique index on phonenumberid hoever, each number can only be assigned to one user.
Then the whole users_phonenumbers has become redundant, because they could just have added a userid column on the phonenumbers table and save themselves a join.
BAd table design if you ask me.

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.