Cascade update sql trigger - sql

I have two tables in two different databases Lab15DB_1 and Lab15DB_2.
The parent table is Author and the child table is Book.
Here they are:
CREATE TABLE Author
(
AuthorId INT NOT NULL PRIMARY KEY,
AuthorName NVARCHAR(50) NOT NULL,
AuthorSurname NVARCHAR(50) NOT NULL,
)
CREATE TABLE Book
(
BookId INT NOT NULL PRIMARY KEY,
BookName NVARCHAR(50) NOT NULL,
BookType NVARCHAR(50) NOT NULL CHECK(BookType IN ('Science', 'Hobby', 'Cooking', 'Fishing', 'Nature', 'Favorites')),
PublishYear INT NOT NULL,
AuthorId INT NOT NULL,
Price MONEY CHECK (PRICE > 0.0)
)
I want to create cascade update via triggers.
Here is an attempt to create trigger for cascade update on the Author table:
CREATE TRIGGER UpdateAuthorTrigger
ON Author
FOR UPDATE
AS
DECLARE #intRowCount int
SELECT #intRowCount = ##ROWCOUNT
IF #intRowCount > 1
BEGIN
IF UPDATE(AuthorId)
BEGIN
UPDATE Author
SET AuthorId = AuthorId
/* some code for update Book*/
END
END
ELSE
IF #intRowCount = 1
BEGIN
IF UPDATE(AuthorId)
BEGIN
UPDATE Author
SET Author.AuthorId = (SELECT AuthorId FROM INSERTED)
FROM Author
INNER JOIN DELETED ON Author.AuthorId = DELETED.AuthorId
UPDATE Lab15DB_1.dbo.Book
SET Lab15DB_1.dbo.Book.AuthorId = (SELECT AuthorId FROM INSERTED)
FROM Lab15DB_1.dbo.Book
INNER JOIN DELETED ON Lab15DB_1.dbo.Book.AuthorId = DELETED.AuthorId
END
END
GO
There is no problem with case #intRowCount = 1, but I have difficulties with case #intRowCount > 1.
Could you please give me any advice?

Related

how to add a database constraint 18 or above

i have create a table and I need to add a database constraint "A customer with an age under 18 cannot rent a movie rated “18 or above”." How do I use SQL to add this in my table
You can use a trigger. When you want to enter information in the system to order a movie, do not allow the user to create a record if the age is less than the minimum required.
The following code snippet implements this scenario completely.
CREATE TABLE Customers
(
ID int identity,
FirstName nvarchar(100),
LastName nvarchar(100),
Age int,
primary key(ID))
CREATE TABLE Films
(
ID int identity,
FilmName nvarchar(100),
MinimumAge int,
primary key(ID))
CREATE TABLE OrderFilms
(
ID int identity,
CustomerID int,
FilmID int,
primary key(ID),
foreign key(CustomerID) references Customers,
foreign key(FilmID) references Films)
create trigger trigger_for_insert_into_OrderFilms
On OrderFilms
For Insert
AS
Declare #film int
Declare #customer int
Declare #filmage int
Declare #customerage int
set #film = (SELECT top 1 FilmID FROM inserted)
set #customer = (SELECT top 1 CustomerID FROM inserted)
set #filmage = (SELECT MinimumAge FROM Films WHERE ID = #film)
set #customerage = (SELECT Age FROM Customers WHERE ID = #customer)
IF(#filmage > 18 AND #customerage < 18)
BEGIN
PRINt 'ERROR, CUSTOMER AGE THE CUSTOMER IS YOUNG'
RollBack
END

How to move hierarchyid subtree if the new parent has already got children?

I guess the problem is described in the title pretty well. I've got a table like this:
CREATE TABLE [Employees] (
[Id] INT identity(1, 1) PRIMARY KEY
,[Hid] HIERARCHYID NOT NULL
,[Name] VARCHAR(50) NULL
,[Secondname] VARCHAR(50)
,[Surname] VARCHAR(50)
,[BossId] INT FOREIGN KEY REFERENCES [Employees]([Id])
,[PositionId] INT FOREIGN KEY REFERENCES [Positions]([Id])
,[DepartmentId] INT FOREIGN KEY REFERENCES [Departments]([Id])
,[RecruitDate] DATE NOT NULL
);
And I need to change employee's boss from one to another. Obviously, there is a nice solution - GetReparentedValue(), and I used this example that seemed to be exactly what I needed.
But this solution happens to be not working. Hid.GetAncestor(1) is equal to previous BossHid. Probably, the problem is that it can't work well if new parent already got children. That really upsets me. Does it mean I need to write recursive CTE on my own?
Here is the code that was supposed to be working but it didn't:
CREATE PROCEDURE UpdateEmployee #Id INT
,#Name VARCHAR(50)
,#Secondname VARCHAR(50)
,#Surname VARCHAR(50)
,#BossId INT
,#PosId INT
,#DepId INT
,#Rdate DATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #BossExist INT
,#OldBossHid HIERARCHYID
,#NewBossHid HIERARCHYID
,#LastHid HIERARCHYID;
SELECT #BossExist = count(*)
FROM dbo.Employees
WHERE [Hid] = HIERARCHYID::GetRoot();
IF #BossExist != 0
AND #BossId IS NULL
RETURN;
SELECT #OldBossHid = (
SELECT [Hid].GetAncestor(1)
FROM dbo.Employees
WHERE #Id = [Id]
);
SELECT #NewBossHid = (
SELECT [Hid].GetAncestor(1)
FROM dbo.Employees
WHERE #BossId = BossId
);
SELECT #LastHid = #NewBossHid.GetDescendant(MAX([Hid]), NULL)
FROM dbo.Employees
WHERE [Hid].GetAncestor(1) = #NewBossHid;
UPDATE dbo.Employees
SET [Hid] = [Hid].GetReparentedValue(#OldBossHid, #NewBossHid)
WHERE [Hid].IsDescendantOf(#OldBossHid) = 1;
UPDATE dbo.Employees
SET [Name] = #Name
,[Secondname] = #Secondname
,[Surname] = #Surname
,[BossId] = #BossId
,[PositionId] = #PosId
,[DepartmentId] = #DepId
,[RecruitDate] = #Rdate
WHERE #Id = Id;
END
GO
Thank you.

How to trigger a table to change the value of another table column

I've created three tables.
CREATE TABLE Clients
(
ClientID INT IDENTITY(1,1) PRIMARY KEY,
First_Name VARCHAR(50) NOT NULL,
Last_Name VARCHAR(50) NOT NULL,
)
CREATE TABLE Reservation
(
ReservationID INT IDENTITY(1,1) PRIMARY KEY,
ClientID INT FOREIGN KEY (ClientID) REFERENCES Clients(ClientID),
Reservation_paid VARCHAR(3) DEFAULT 'NO',
)
CREATE TABLE Payment
(
Payment_ID INT IDENTITY(1,1) PRIMARY KEY,
ClientID INT FOREIGN KEY (ClientID) REFERENCES Clients(ClientID),
ReservationID INT FOREIGN KEY (ReservationID) REFERENCES Reservation(ReservationID),
)
I would like to change the value of the column Reservation_paid to YES at the Reservation table whenever the Client does pay the reservation, and i want to do it automatically with trigger.
Example: If the ClientID at the Reservation table exists at the Payment table automatically the value of the Reservation_paid will set to YES.
Thank you in advance.
CREATE TRIGGER trgAfterInsert ON [dbo].[Payment]
FOR INSERT
AS
declare #ClientID int;
select #ClientID =i.ClientID from inserted i;
if update(ClientID)
UPDATE Reservation set Reservation_paid='Yes' WHERE
ClientID=#ClientID;
--PRINT 'AFTER INSERT trigger fired.'
After Insert Trigger should do something like this
UPDATE R
SET Reservation_paid = 'Yes'
FROM reservation R
WHERE EXISTS (SELECT 1
FROM INSERTED I
WHERE I.clientid = R.clientid
AND I.reservationid = R.reservationid)
CREATE TRIGGER trgAfterInsert ON [dbo].[Payment]
FOR INSERT
AS
declare #ClientID int;
select #ClientID =i.ClientID from inserted i;
insert into Reservation(ClientID,Reservation_paid)
values(#ClientID,'Yes');
--PRINT 'AFTER INSERT trigger fired.'
GO
Write a trigger that will work on table Reservation after any insert or update on ClientId column of table Payment. Then match the ClientID with ClientID column of Reservation table and update the corresponding Reservation_paid to YES.
Edit:
The trigger will be like this
CREATE TRIGGER `UpdateReservation_paid` AFTER INSERT OR UPDATE ON `Payment`
FOR EACH ROW BEGIN
AS
begin
update Reservation
SET Reservation_paid='YES'
Where NEW.ClientID = Reservation.ClientID
and NEW.ReservationID = Reservation.ReservationID
end

One Active Record per ID

The address table is shared for both companies and contacts. I have a constraint that prevents the user from adding a record with both a Company ID and a Contact ID. I am trying to add another constraint to have only 1 active (ACTIVE FLAG = 'TRUE') address per Company ID or Contact ID. But I want to be able to have unlimited inactive (ACTIVE FLAG = 'FALSE').
ALTER TABLE [dbo].[ADDRESSES] WITH CHECK ADD CONSTRAINT [chk_ONLY_ONE_ACTIVE_ADDRESS] CHECK (([COMPANY_ID] IS NOT NULL AND [CONTACT_ID] IS NULL AND [ACTIVE] = 'TRUE' OR [COMPANY_ID] IS NULL AND [CONTACT_ID] IS NOT NULL AND [ACTIVE] = 'TRUE' OR [COMPANY_ID] IS NULL AND [CONTACT_ID] IS NOT NULL AND [ACTIVE] = 'FALSE' OR [COMPANY_ID] IS NOT NULL AND [CONTACT_ID] IS NULL AND [ACTIVE] = 'FALSE'))
GO
Where am I missing it?
Thanks
jlimited
You are adding the constraint 'active=true' and 'active=false' for both combinations of contact id and company id so it's a tautology, you may as well remove this constraint.
I believe the only way to enforce the rule 'only one active flag and 0 to many inactive' is by triggers.
Thanks for the reply Jayvee. Here is the solution that I figured out.
I first created two functions...
One
CREATE FUNCTION [dbo].[fnCheckForActiveContactAddress](
#id int
)
RETURNS INT
AS
BEGIN
DECLARE #result INT
IF #id IS NOT NULL
SET #result = ISNULL((select count(*) from dbo.Addresses where CONTACT_ID = #ID AND ACTIVE = 'TRUE'),0)
ELSE
SET #result = 1
RETURN #result
END
GO
Two
CREATE FUNCTION [dbo].[fnCheckForActiveCompanyAddress](
#id int
)
RETURNS INT
AS
BEGIN
DECLARE #result INT
If #id IS NOT NULL
SET #result = ISNULL((select count(*) from dbo.Addresses where COMPANY_ID = #ID AND ACTIVE = 'TRUE'),0)
ELSE
SET #result = 1
RETURN #result
END
GO
Then I added the following constraints...
ALTER TABLE [dbo].[ADDRESSES] WITH CHECK ADD CONSTRAINT [chk_COMPANY_OR_CONTACT] CHECK (([COMPANY_ID] IS NOT NULL AND [CONTACT_ID] IS NULL OR [COMPANY_ID] IS NULL AND [CONTACT_ID] IS NOT NULL))
GO
ALTER TABLE [dbo].[ADDRESSES] WITH NOCHECK ADD CONSTRAINT [CHK_ADDRESSES_ONLY_ONE_ACTIVE_CONTACT] CHECK (([dbo].[fnCheckForActiveContactAddress]([CONTACT_ID])=(1)))
GO
ALTER TABLE [dbo].[ADDRESSES] WITH NOCHECK ADD CONSTRAINT [CHK_ADDRESSES_ONLY_ONE_ACTIVE_COMPANY] CHECK (([dbo].[fnCheckForActiveCompanyAddress]([COMPANY_ID])=(1)))
GO
This solution seems to work well.
Any thoughts on improving it?
jlimited
If I understand your requirements, this should work (if -1 is not a valid CompanyID or ContactID):
create table T (
CompanyID int,
ContactID int,
BothIDs as
CASE WHEN CompanyID IS NOT NULL and ContactID IS NOT NULL
THEN 1 ELSE 0 END PERSISTED
check (BothIDs = 0),
ActiveFlag varchar(5) check (ActiveFlag in ('TRUE','FALSE')),
ActiveCheck as
CASE WHEN ActiveFlag='TRUE' then -1 ELSE COALESCE(CompanyID,ContactID) END,
unique (ActiveCheck)
);
insert into T values
(1,NULL,'FALSE'),
(NULL,2,'FALSE'),
(3,NULL,'TRUE'),
(4,NULL,'FALSE');
GO
UPDATE T SET
ActiveFlag = 'TRUE'
WHERE CompanyID = 4;

Tuple Versioning and composite primary key

I have to create a database and have to make sure that we can load the data as it was at a specific date, so I decided to use tuple versioning.
Let's say we have the following two tables:
CREATE TABLE Author
(
Id UNIQUEIDENTIFIER NOT NULL,
Firstname VARCHAR(100) NOT NULL,
Surname VARCHAR(200) NOT NULL,
ValidFrom DATETIME NOT NULL,
ValidUntil DATETIME NULL,
PRIMARY KEY (ID, ValidFrom)
)
CREATE TABLE Book
(
Id UNIQUEIDENTIFIER NOT NULL,
Title VARCHAR(100) NOT NULL,
ISBN VARCHAR(100) NOT NULL,
AuthorId UNIQUEIDENTIFIER NOT NULL,
ValidFrom DATETIME NOT NULL,
ValidUntil DATETIME NULL,
PRIMARY KEY (Id, ValidFrom)
)
The first time when I enter a new author I will generate a new GUID. I use this GUID in the book table as well to make a reference to the author.
If there is an update on the author, I create a new record with the same GUID, but define the current date as "ValidFrom" and also set the "ValidUntil" from the original record to the current date.
I don't have to change the book table because Author.Id did not change.
The problem I'm facing now is that I would like to add a foreign key constraint on Book.AuthorId = Author.Id
Unfortunately this does not work because I use a composite primary key. I do not want to add the Author.ValidFrom to my Book table because I just want to reference the most recent one and not a specific version.
Any idea on how I can solve this? I think I could add a trigger that makes sure that you can't delete an author if there is already a book recorded, but I have no solution to allow cascade delete.
I'm grateful for every hint or advise.
This works on 2008 (relies on using a MERGE statement to change which row is being referenced by Book atomically). It does introduce new columns, you might want to hide them behind a view:
CREATE TABLE Author
(
Id UNIQUEIDENTIFIER NOT NULL,
Firstname VARCHAR(100) NOT NULL,
Surname VARCHAR(200) NOT NULL,
ValidFrom DATETIME NOT NULL,
ValidUntil DATETIME NULL,
Active as CASE WHEN ValidUntil is null THEN CONVERT(datetime,'99991231',112) ELSE ValidUntil END Persisted
PRIMARY KEY (ID, ValidFrom),
UNIQUE (ID,Active)
)
go
CREATE TABLE Book
(
Id UNIQUEIDENTIFIER NOT NULL,
Title VARCHAR(100) NOT NULL,
ISBN VARCHAR(100) NOT NULL,
AuthorId UNIQUEIDENTIFIER NOT NULL,
ValidFrom DATETIME NOT NULL,
ValidUntil DATETIME NULL,
PRIMARY KEY (Id, ValidFrom),
FK_Link as CONVERT(datetime,'99991231',112) persisted,
Foreign key (AuthorID,FK_Link) references Author (Id,Active) on delete cascade
)
go
declare #AuthorId uniqueidentifier
set #AuthorId = NEWID()
insert into Author(Id,Firstname,Surname,ValidFrom)
select #AuthorId,'Boris','McBoris',CURRENT_TIMESTAMP
insert into Book(Id,Title,ISBN,AuthorId,ValidFrom)
select NEWID(),'How to use tuple versioning','12345678',#AuthorId,CURRENT_TIMESTAMP
;with newAuthorInfo as (
select #AuthorId as Id,'Steve' as Firstname,'McBoris' as Surname,t.Dupl
from (select 0 union all select 1) t(Dupl)
)
merge into Author a
using newAuthorInfo nai
on
a.Id = nai.Id and
a.ValidUntil is null and
nai.Dupl = 0
when matched then update set ValidUntil = CURRENT_TIMESTAMP
when not matched then insert (Id,Firstname,Surname,ValidFrom)
values (nai.Id,nai.Firstname,nai.Surname,CURRENT_TIMESTAMP);
;with newAuthorInfo as (
select #AuthorId as Id,'Steve' as Firstname,'Sampson' as Surname,t.Dupl
from (select 0 union all select 1) t(Dupl)
)
merge into Author a
using newAuthorInfo nai
on
a.Id = nai.Id and
a.ValidUntil is null and
nai.Dupl = 0
when matched then update set ValidUntil = CURRENT_TIMESTAMP
when not matched then insert (Id,Firstname,Surname,ValidFrom)
values (nai.Id,nai.Firstname,nai.Surname,CURRENT_TIMESTAMP);
go
select * from Author
select * from Book
delete from Author where ValidUntil is not null
select * from Author
select * from Book
delete from Author
select * from Author
select * from Book
For a pre-2008 solution, I don't think you can do better than triggers. You can introduce a second Author table that does just have the Id column (uniquely), which you can FK against from Book, and cascade delete from that table to Book. Then you just need a delete trigger on Author, such that if you're removing the final row from Author for a particular Author Id, you delete the row from this new table