Creating a trigger to store an audit trail - sql

I have to create a trigger on the Claims table that is triggered whenever a new record is inserted into the Claims table. This trigger should store the customer name and the total amount_of_claim by that customer.
Claim_audits (audit table) have already been created.
Schema:
Claims
id int(11)
status_id int(11)
customer_policy_id int(11)
date_of_claim date
amount_of_claim float
> one or many to one(and only one) towards Customer_policy
Customer_policy
id int(11)
policy_start_date date
policy_renewal_date date
policy_id int(11)
customer_id int(11)
agent_id(11)
> one or many to one (and only one) towards Customer
Customer
id int(11)
first_name varchar(30)
last_name varchar(30)
email varchar(30)
address_id int(11)
Output should look like this:
customer_name amount_of_claim
abhinav 195000
This is what I have tried:
CREATE TRIGGER claim_audits on claims
for insert
as
declare #custname varchar(25);
declare #amount varchar(25);
declare #action varchar(25);
select #custname = first_name from customer c
join inserted i on i.id=c.id;
select #amount = i.amount_of_claim from inserted i;
select #action = 'Updated customer claimed amount';
insert into claim_audits values(#custname , #amount , #action);
select * from claim_audits;
go

The Inserted pseudo-table can have 0-N rows, and you need to handle that. And as with anything SQL related you should approach it using a set-based approach - not a procedural approach.
You also don't appear to have been obtaining the customer id correctly - at least based on your table definitions. I must say, its very odd to be storing the first name of the customer in your audit table. Why not store the customer id? The name is not unique, so you haven't provided a reliable audit trail.
create trigger claim_audits
on claims
for insert
as
begin
set nocount on;
insert into dbo.claim_audits (custname, amount, [action])
select C.first_name, I.amount_of_claim, 'Updated customer claimed amount'
from Inserted I
inner join Customer_Policy CP on CP.id = I.customer_policy_id
inner join Customer C on C.id = CP.customer_id;
end;
Note - you do not want to be attempting to return data from a trigger.
And as pointed out by #Squirral: amount_of_claim float: float is an approximate value and should never be used for money. Use decimal or numeric instead.

Related

How to automaticaly fill a foreign key when a row is inserted in Postgresql

I am currently working under postgres 14.
I got two tables customers and contacts, and i want to automatically find the customer_id from the customer name when i add a new contact in the table contacts.
CREATE TABLE customers(
customer_id INT GENERATED ALWAYS AS IDENTITY,
customer_name VARCHAR(255) NOT NULL UNIQUE,
PRIMARY KEY(customer_id)
);
CREATE TABLE contacts(
contact_id INT GENERATED ALWAYS AS IDENTITY,
customer_id INT,
contact_name VARCHAR(255) NOT NULL UNIQUE,
PRIMARY KEY(contact_id),
CONSTRAINT fk_customer
FOREIGN KEY(customer_id)
REFERENCES customers(customer_id)
);
INSERT INTO customers(customer_name) VALUES('my_first_customer')
So from here i want to do something like that :
INSERT INTO contacts(customer_id, contact_name ) VALUES('my_first_customer',"thomas")
and i want to get this result in contacts table :
contact_id
customer_id
contact_name
1
1
"thomas"
i tried to make a function to change the value from name to id but get an error of type.
Because the the type error is catch before the trigger. Here is my function
CREATE FUNCTION get_id_fct()
RETURNS TRIGGER AS $get_id_fct$
DECLARE val_id INTEGER;
BEGIN
SELECT customer_id INTO val_id FROM customers WHERE customers.customer_id = NEW.customer_id;
NEW.customer_id := val_id;
RETURN NEW
END
$get_id_fct$ LANGUAGE plpgsql;
CREATE TRIGGER get_id
BEFORE INSERT ON contacts
FOR EACH ROW EXECUTE PROCEDURE get_id_fct();'
Is their a way around that ? or specific method to do this task that i don't know about ?
I am quite a beginner at SQL.
You can make a subquery in insert statement
INSERT INTO contacts(customer_id, contact_name ) VALUES (
(select customer_id from customers where customer_name = 'my_first_customer') ,'thomas');
This query will fail if there is more than one customer with given name, and insert a null value for customer_id if there is no customers with given name
Different approach (proposed by #wildplasser)
INSERT INTO contacts(customer_id, contact_name )
select customer_id,'thomas' from customers where customer_name = 'my_first_customer';
In this case, when there is no customer with given name, no row will be created. When there is more than one customer with given name, for each of them record will be created.
Or you can create view with INSTEAD OF trigger.
create view v_contacts as
select customer_name, contact_name from customers
join contacts on customers.customer_id = contacts.customer_id;
CREATE FUNCTION emp () RETURNS trigger AS $$
BEGIN
INSERT INTO contacts(customer_id, contact_name )
VALUES((select customer_id from customers where customer_name =
NEW.customer_NAME),NEW.contact_NAME);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER v_contactst
INSTEAD OF INSERT ON v_contacts
FOR EACH ROW
EXECUTE FUNCTION emp();
Insert statement
INSERT INTO v_contacts(customer_name, contact_name ) VALUES('my_first_customer','thomas')
And just a note, that columns customer_name and contact_name are not unique so this code is not safe and throw error if customer_name didn't exists or there is more than one record with this customer_name

SQL Server 2012 Trigger

I have a small little thing with SQL that's been bothering me now for a while, let's say I have two tables (Customer and Loan). However, I want a trigger that's checking based on the Borrowertype attribute. I suppose with the second query after AND I need something to check whether the userID in Loans are the same as the one in Customer, but must be messing it up or I'm completely thinking this the wrong way.
CREATE TABLE Customer
(
userID int identity primary key,
Name varchar(20),
Borrowertype varchar(20)
);
CREATE TABLE Loan
(
Id int identity primary key,
userID int,
FOREIGN KEY (userID) REFERENCES Customer(userID)
);
IF OBJECT_ID ('Customer.maximum_books_per_user','TR') IS NOT NULL
DROP TRIGGER Customer.maximum_books_per_user;
GO
CREATE TRIGGER maximum_books_per_user ON Customer
AFTER INSERT
AS
IF (SELECT Borrowertype FROM Customer) = 'diffborrowertypehere'
AND (SELECT COUNT(*) FROM inserted AS i JOIN Customer AS c
ON ??? WHERE ???
) > 5
BEGIN
ROLLBACK TRANSACTION
RAISERROR('You have reached maximum allowed loans.', 16, 1)
END
GO
Your trigger needs to be on the Loan table, as that's where a row would be being inserted that could be rejected. Something like this:
EDIT: rewritten to handle inserts for multiple Customers at once
CREATE TRIGGER maximum_books_per_user ON Loan
FOR INSERT
AS
-- Fail if there are any customers that will have more than the maximum number of loans
IF EXISTS (
SELECT i.userID, COUNT(*)
FROM inserted i
JOIN Loan l
ON i.userID = l.userID
GROUP BY i.userID
HAVING COUNT(*) >= 5
)
BEGIN
ROLLBACK TRANSACTION
RAISERROR('You have reached maximum allowed loans.', 16, 1)
END

How would I do this with SQL Server?

I have two tables users and address
users table schema
user_id
name
address_id
sent
address table schema
address_id
create_date
location
user_id
I have this query that returns 16 rows
select * from users where sent = 1;
but all the address_id are all NULL because they have not been created yet
So what I need to do is create 16 rows in the address table one for each of the users with the user_id of the user and then set the address_id in the users table of that address
For now I can leave the location field blank. All I need to do is set the create_date to CURRENT_DATE
Is there a way to do this in one query?
Try this:
declare #user table(user_id [int] IDENTITY(1,1), name varchar(25), address_id int, sent int)
declare #address table(address_id [int] IDENTITY(1,1) NOT NULL, create_date datetime default getdate(), location varchar(100), user_id int)
declare #t table(user_id int, address_id int)
insert #user (name) values('you')
insert #user (name) values('someone else')
begin Transaction
insert #address (user_id)
output inserted.user_id, inserted.address_id
into #t
select user_id from #user u
where not exists (select 1 from #address where u.user_id = user_id)
update u
set u.address_id = t.address_id
from #user u
join #t t
on u.user_id = t.user_id
commit transaction
select * from #user
select * from #address
Not exactly sure what your mean but this should at least point you in the right direction
Begin Try
Begin Transaction
Update Users
Set Users.address = Address.address, create_date = GetDate()
From Addresses
Inner Join Users On Addresses.userid = Users.userid
Commit Transaction
End Try
Begin Catch
Rollback Transaction
End Catch
It should be something like this. There are a couple of ways of doing the problem so have fun with it and hopefully this helped. For testing it write two Select * From Users statements one before and one after. Also change Commit Transaction to Rollback Transaction so you don't have to worry about making a mistake.
Just reread question yea you can't do that in one shot just replace the Update statement with
Insert Into Addresses (address_id, create_date, location, user_id)
Values ('#ddr355_1d', GetDate(), '1234theLocation', 123478)
and you will have to do that for each one but should be easy with only 16 entries in the User table. You might want to look into writing a Stored Procedure if you plan on adding more to the table. Something kind of like this
Create Procedure [dbo].[AddressesInsertData]
(
#Address Int,
#Location varchar(100),
#UserId Int
)
As
Begin Try
Begin Transaction
Insert Into Addresses (address_id, create_date, location, user_id)
Values (#Address, GetDate(), #Location, #UserId)
Commit Transaction
End Try
Begin Catch
Rollback Transaction
End Catch
Basic structure of a stored procedure. I would add an if not exists in there that would update instead of insert but this should be plenty to get you started. Hopefully these examples should clear up somethings for you and help you out.
There is a design rules of thumb that a table models EITHER an entity/class OR the relationship between entities/classes but not both.
Therefore, I suggest you remove the address_id column from the users table, remove user_id from the address table and create a third table comprising both user_id and address_id to model the relationship between users and their addresses. This will also rid you of the need to have nullable columns.

i am trying to execute the before insert trigger , but i m getting the sql errors

what i want to achieve is i have a table called orders.
i want to perform the before insert trigger on my orders table.i want to capture the
username of person performing INSERT into table.
one table called info which contain the user.
this is my code
create table orders
(
order_id int,
quantity int,
cost int,
total_cost int,
created_date datetime,
created_by varchar(20)
)
create trigger beforeInsertdata
before insert
on orders
for each row
declare
v_username varchar2(10);
begin
-- Find username of person performing INSERT into table
SELECT user INTO v_username
FROM info;
-- Update create_date field to current system date
:new.create_date := sysdate;
-- Update created_by field to the username of the person performing the INSERT
:new.created_by := v_username;
END;
--user information--
create table info
(
userid int ,
user_name varchar(10)
)
insert into info values(1,'vivek')
select * from info
Basically, triggers are classified into two main types:-
1)After Triggers (For Triggers)
2)Instead Of Triggers
and the syntax for trigger is
CREATE TRIGGER trigger_name ON table_name
[FOR|AFTER|INSTEAD OF] [INSERT|UPDATE|DELETE]
AS
//your code goes here
GO
NOTE : FOR keyword used for INSERT |UPDATE Command where as AFTER USED FOR DELETE Command.
It's hard to tell what you're really trying to do. I've modified your code sample so that it will work on SQL2K5 and made some assumptions about how you're wanting to use the connected user account.
CREATE TABLE orders (
order_id int,
quantity int,
cost int,
total_cost int,
created_date datetime,
created_by varchar(20)
);
CREATE TABLE info (
userid int,
user_name varchar(10)
);
INSERT INTO info
VALUES (1, 'vivek');
SELECT *
FROM info;
CREATE TRIGGER orders_InsteadOfInsert ON orders
INSTEAD OF INSERT AS BEGIN
SET NOCOUNT ON;
-- varchar(10) is to match your table, but probably should be larger
DECLARE #CurrentUser VarChar(10);
SELECT #CurrentUser = SYSTEM_USER;
IF (#CurrentUser NOT IN (SELECT user_name FROM info)) BEGIN
-- consider using an identity column for the key instead of this
INSERT INTO info (userid, user_name)
SELECT
ISNULL((SELECT MAX(userid) FROM info), 0) + 1,
#CurrentUser;
END;
INSERT INTO orders (order_id, quantity, cost, total_cost, created_date, created_by)
SELECT
INS.order_id,
INS.quantity,
INS.cost,
INS.total_cost,
GETDATE(),
#CurrentUser
FROM INSERTED INS;
END;

How do I insert from a table variable to a table with an identity column, while updating the the identity on the table variable?

I'm writing a SQL script to generate test data for our database. I'm generating the data in table variables (so I can track it later) and then inserting it into the real tables. The problem is, I need to track which rows I've added to the parent table, so that I can generate its child data later on in the script. For example:
CREATE TABLE Customer (
CustomerId INT IDENTITY,
Name VARCHAR(50)
)
CREATE TABLE Order (
OrderId INT IDENTITY,
CustomerId INT,
Product VARCHAR(50)
)
So, in my script, I create equivalent table variables:
DECLARE #Customer TABLE (
CustomerId INT IDENTITY,
Name VARCHAR(50)
) -- populate customers
DECLARE #Order TABLE (
OrderId INT IDENTITY,
CustomerId INT,
Product VARCHAR(50)
) -- populate orders
And I generate and insert sample data into each table variable.
Now, when I go to insert customers from my table variable into the real table, the CustomerId column in the table variable will become meaningless, as the real table has its own identity seed for its CustomerId column.
Is there a way I can track the new identity of each row inserted into the real table, in my table variable, so I can use a proper CustomerId for the order records? Or, is there a better way I should be going about this?
(Note: I originally started with an application to generate the test data, but it ran too slow during insert as > 1,000,000 records need to be generated.)
WHy do you need identity values on the table variables? If you use just int, you can isnert the ids after the insert is done. Grab them using the output clause. YOu might need an input values and an output values table varaiable to get this just right like this:
DECLARE #CustomerInputs TABLE (Name VARCHAR(50) )
DECLARE #CustomerOutputs TABLE (CustomerId INT ,Name VARCHAR(50) )
INSERT INTO CUSTOMERS (name)
OUTPUT inserted.Customerid, inserted.Name INTO #CustomerOutputs
SELECT Name FROM #CustomerInputs
SELECT * from #CustomerOutputs
You can insert the data to the table with a cursor and use the built-in function SCOPE_IDENTITY() to get the last id which was inserted in the current scope (by your script).
See this MSDN article for more information on SCOPE_IDENTITY.
Here is one way of doing it. If you can use it depends on your situation. You should not do it in production environment when users use your db.
-- Get the next identity values for Customer and Order
declare #NextCustomerID int
declare #NextOrderID int
set #NextCustomerID = IDENT_CURRENT('Customer')+1
set #NextOrderID = IDENT_CURRENT('Order')+1
-- Create tmp tables
create table #Customer (CustomerID int identity, Name varchar(50))
create table #Order (OrderID int identity, CustomerID int, Product varchar(50))
-- Reseed the identity columns in temp tables
dbcc checkident(#Customer, reseed, #NextCustomerID)
dbcc checkident(#Order, reseed, #NextOrderID)
-- Populate #Customer
-- Populate #Order
-- Allow insert to identity column on Customer
set identity_insert Customer on
-- Add rows to Customer
insert into Customer(CustomerId, Name)
select CustomerID, Name
from #Customer
-- Restore identity functionality on Customer
set identity_insert Customer off
-- Add rows to Order
set identity_insert [Order] on
insert into [Order](OrderID, CustomerID, Product)
select OrderID, CustomerID, Product
from #Order
set identity_insert [Order] off
-- Drop temp tables
drop table #Customer
drop table #Order
-- Check result
select * from [Order]
select * from Customer
The way I'd do it its first obtain the MAX(CustomerId) from your Customer Table. Then I'd get rid of the IDENTITY column on your variable table and do my own CustomerId using ROW_NUMBER() and the MaxCustomerId. It should be something like this:
DECLARE #MaxCustomerId INT
SELECT #MaxCustomerId = ISNULL(MAX(CustomerId),0)
FROM Customer
DECLARE #Customer TABLE (
CustomerId INT,
Name VARCHAR(50)
)
INSERT INTO #Customer(CustomerId, Name)
SELECT #MaxCustomerId + ROW_NUMBER() OVER(ORDER BY SomeColumn), Name
FROM YourDataTable
Or insert the values on a temp table, so you can use the same ids to fill your Order table.