SQL Server triggers - insert new rows into audit table - sql

I have a simple table as below.
I want to create a trigger to insert new values into "SectionsAudit" Table.
Means:
If a new row is inserted into the Sections table, I want to insert the same row into the Audit table
If an existing row is updated in the Sections table, I want to create a new row in the Audit table with the updated row.
How can I do that in SQL Server? Also, I would like to know if this a good practice?
CREATE TABLE [dbo].[Sections]
(
[Id] int IDENTITY(1,1) NOT NULL,
[Name] varchar(25) NOT NULL,
[InsertedBy] varchar(25) NOT NULL,
[InsertedDateTime] datetime NOT NULL,
[UpdatedBy] varchar(25) NOT NULL,
[UpdatedDateTime] datetime NOT NULL,
CONSTRAINT [PK_Id] PRIMARY KEY CLUSTERED([Id])
)

The trigger will look something like this, its a good practice depending of the MS SQL version you're using.
Note that for using this approach is necessary to have in your dbo.SectionsAudit a field to track the ModificationId of the original Record (in the example I called it IdModi)
CREATE TRIGGER [dbo].[tr_SectionsAudit]
ON [dbo].[Sections]
AFTER INSERT, UPDATE
NOT FOR REPLICATION
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
INSERT SectionsAudit
(Id, IdModi, fieldname, ...)
SELECT Id, IdModi = (ISNULL((SELECT ISNULL(MAX(SA.IdModi), 0)
FROM SectionsAudit SA
WHERE SA.Id = I.Id
) , 0) + 1),
fieldname, ...
FROM INSERTED I
END

In your case I suggest you to use Rowversion or Change Data Capture.
Below some option to do it:
Rowversion
It is a unique binary numbers of 8 bytes. It is updated every time that you insert or update any column of any records
Example:
CREATE TABLE dbo.Test (ID int PRIMARY KEY, RowVersion rowversion) ;
Change Data Capture
Change data capture records insert, update, and delete activity that is applied to a SQL Server table. This makes the details of the changes available in an easily consumed relational format. Column information and the metadata that is required to apply the changes to a target environment is captured for the modified rows and stored in change tables that mirror the column structure of the tracked source tables. Table-valued functions are provided to allow systematic access to the change data by consumers.
Temporal Tables
Start from SQLServer 2016 you can use the system-versioned temporal table that is a type of user table designed to keep a full history of data changes and allow easy point in time analysis. This type of temporal table is referred to as a system-versioned temporal table because the period of validity for each row is managed by the system (i.e. database engine).
Every temporal table has two explicitly defined columns, each with a datetime2 data type. These columns are referred to as period columns. These period columns are used exclusively by the system to record period of validity for each row whenever a row is modified.
Example:
CREATE TABLE dbo.test
(
[ID] int NOT NULL PRIMARY KEY CLUSTERED
,[ValidFrom] datetime2 GENERATED ALWAYS AS ROW START
,[ValidTo] datetime2 GENERATED ALWAYS AS ROW END
,PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo))
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.TestHistory));
Triggers
Creates a DML, DDL, or logon trigger. A trigger is a special type of stored procedure that automatically runs when an event occurs in the database server. DML triggers run when a user tries to modify data through a data manipulation language (DML) event. DML events are INSERT, UPDATE, or DELETE statements on a table or view. These triggers fire when any valid event fires, whether table rows are affected or not.

Related

SQL Server: how to insert a record into related table during an update?

Let's say we have two tables, Incidents and IncidentTracking:
Incidents (Id INT PRIMARY KEY,
CreatedOn Datetime,
State VARCHAR(50))
IncidentTracking (Id INT PRIMARY KEY,
IncidentId INT FOREIGN KEY REFERENCES TO Incidents.Id,
TrackingDate Datetime,
NewState VARCHAR(50))
How do I insert a new record into IncidentTracking while updating some incidents?
For example, I want to change the State for all incidents that are more than 90 days old to "Outdated", and insert a tracking info record where IncidentId is the updated incident (SCOPE_IDENTITY() maybe?), TrackingDate is GETDATE() and NewState is also "Outdated".
Can it be done all in one statement or should I write a cursor?
I'd use OUTPUT clause.
As IncidentTracking has a foreign key, it is not possible to OUTPUT directly to it. You'll get an error message if you try:
The target table 'dbo.IncidentTracking' of the OUTPUT INTO clause
cannot be on either side of a (primary key, foreign key) relationship.
Found reference constraint 'FK_IncidentTracking_Incidents'.
So, we can use a temporary table or table variable.
Like this:
BEGIN TRANSACTION;
DECLARE #T TABLE (IncidentId int, TrackingDate datetime, NewState varchar(50));
UPDATE [dbo].[Incidents]
SET [State] = 'Outdated'
OUTPUT
inserted.Id AS [IncidentId],
GETDATE() AS [TrackingDate],
inserted.[State] AS [NewState]
INTO #T ([IncidentId], [TrackingDate], [NewState])
WHERE [CreatedOn] < DATEADD(day, -90, GETDATE())
;
INSERT INTO [dbo].[IncidentTracking] ([IncidentId], [TrackingDate], [NewState])
SELECT [IncidentId], [TrackingDate], [NewState]
FROM #T;
COMMIT TRANSACTION;
Write a stored procedure to perform your task. You can put that kind of logic in a stored procedure easily enough.
If you will allow access to the table(s) outside of the procedure and still want the same behavior, a trigger is likely what you want (not a fan of them myself). Make sure, when writing your trigger, you remember that it will run against a recordset not a single record.
You could have a trigger on the state field that inserts into IncidentTracking, then you just need the one update. Or the other way around (adding to IncidentTracking updates State)
But why even have the State in the Incidents table? Just have an IncidentState table where you add every state change, and the latest added one is the current state for the incident.
You'd also probably want to make the State field a StateId column instead of a text code. Connected to a State table containing all the states.

Update identity column sql [duplicate]

I have a MS SQL 2005 database with a table Test with column ID. ID is an identity column.
I have rows in this table and all of them have their corresponding ID auto incremented value.
Now I would like to change every ID in this table like this:
ID = ID + 1
But when I do this I get an error:
Cannot update identity column 'ID'.
I've tried this:
ALTER TABLE Test NOCHECK CONSTRAINT ALL
set identity_insert ID ON
But this does not solve the problem.
I need to have identity set to this column, but I need to change values as well from time to time. So my question is how to accomplish this task.
You need to
set identity_insert YourTable ON
Then delete your row and reinsert it with different identity.
Once you have done the insert don't forget to turn identity_insert off
set identity_insert YourTable OFF
IDENTITY column values are immutable.
However it is possible to switch the table metadata to remove the IDENTITY property, do the update, then switch back.
Assuming the following structure
CREATE TABLE Test
(
ID INT IDENTITY(1,1) PRIMARY KEY,
X VARCHAR(10)
)
INSERT INTO Test
OUTPUT INSERTED.*
SELECT 'Foo' UNION ALL
SELECT 'Bar' UNION ALL
SELECT 'Baz'
Then you can do
/*Define table with same structure but no IDENTITY*/
CREATE TABLE Temp
(
ID INT PRIMARY KEY,
X VARCHAR(10)
)
/*Switch table metadata to new structure*/
ALTER TABLE Test SWITCH TO Temp;
/*Do the update*/
UPDATE Temp SET ID = ID + 1;
/*Switch table metadata back*/
ALTER TABLE Temp SWITCH TO Test;
/*ID values have been updated*/
SELECT *
FROM Test
/*Safety check in case error in preceding step*/
IF NOT EXISTS(SELECT * FROM Temp)
DROP TABLE Temp /*Drop obsolete table*/
In SQL Server 2012 it is possible to have an auto incrementing column that can also be updated more straightforwardly with SEQUENCES
CREATE SEQUENCE Seq
AS INT
START WITH 1
INCREMENT BY 1
CREATE TABLE Test2
(
ID INT DEFAULT NEXT VALUE FOR Seq NOT NULL PRIMARY KEY,
X VARCHAR(10)
)
INSERT INTO Test2(X)
SELECT 'Foo' UNION ALL
SELECT 'Bar' UNION ALL
SELECT 'Baz'
UPDATE Test2 SET ID+=1
Through the UI in SQL Server 2005 manager, change the column remove the autonumber (identity) property of the column (select the table by right clicking on it and choose "Design").
Then run your query:
UPDATE table SET Id = Id + 1
Then go and add the autonumber property back to the column.
Firstly the setting of IDENTITY_INSERT on or off for that matter will not work for what you require (it is used for inserting new values, such as plugging gaps).
Doing the operation through the GUI just creates a temporary table, copies all the data across to a new table without an identity field, and renames the table.
This can be done using a temporary table.
The idea
disable constraints (in case your id is referenced by a foreign key)
create a temp table with the new id
delete the table content
copy back data from the copied table to your original table
enable previsously disabled constraints
SQL Queries
Let's say your test table have two additional columns (column2 and column3) and that there are 2 tables having foreign keys referencing test called foreign_table1 and foreign_table2 (because real life issues are never simple).
alter table test nocheck constraint all;
alter table foreign_table1 nocheck constraint all;
alter table foreign_table2 nocheck constraint all;
set identity_insert test on;
select id + 1 as id, column2, column3 into test_copy from test v;
delete from test;
insert into test(id, column2, column3)
select id, column2, column3 from test_copy
alter table test check constraint all;
alter table foreign_table1 check constraint all;
alter table foreign_table2 check constraint all;
set identity_insert test off;
drop table test_copy;
That's it.
DBCC CHECKIDENT ( ‘databasename.dbo.orders’,RESEED, 999)
you can change any identity column number with this command,and also you can start that field number from every number you want.for example in my command i ask to start from 1000 (999+1)
hope that it would be enough...good luck
If the column is not a PK you could always create a NEW column in the table with the incremented numbers, drop the original and then alter the new one to be the old.
curious as to why you might need to do this... most I've ever had to futz with Identity columns was to backfill numbers and I just ended up using DBCC CHECKIDENT ( tablename,RESEED,newnextnumber)
good luck!
Identity modifying may fail depending on a number of factors, mainly revolving around the objects/relationships linked to the id column. It seems like db design is as issue here as id's should rarely if ever change (i'm sure you have your reasons and are cascasding the changes). If you really need to change id's from time to time, I'd suggest either creating a new dummy id column that isn't the primary key/autonumber that you can manage yourself and generate from the current values. Alternately, Chrisotphers idea above would be my other suggestion if you're having issues with allowing identity insert.
Good luck
PS it's not failing because the sequential order it's running in is trying to update a value in the list to an item that already exists in the list of ids? clutching at straws, perhaps add the number of rows+1, then if that works subtract the number of rows :-S
If you need to change the IDs occasionally, it's probably best not to use an identity column. In the past we've implemented autonumber fields manually using a 'Counters' table that tracks the next ID for each table. IIRC we did this because identity columns were causing database corruption in SQL2000 but being able to change IDs was occasionally useful for testing.
You can insert new rows with modified values and then delete old rows. Following example change ID to be same as foreign key PersonId
SET IDENTITY_INSERT [PersonApiLogin] ON
INSERT INTO [PersonApiLogin](
[Id]
,[PersonId]
,[ApiId]
,[Hash]
,[Password]
,[SoftwareKey]
,[LoggedIn]
,[LastAccess])
SELECT [PersonId]
,[PersonId]
,[ApiId]
,[Hash]
,[Password]
,[SoftwareKey]
,[LoggedIn]
,[LastAccess]
FROM [db304].[dbo].[PersonApiLogin]
GO
DELETE FROM [PersonApiLogin]
WHERE [PersonId] <> ID
GO
SET IDENTITY_INSERT [PersonApiLogin] OFF
GO
First save all IDs and alter them programmatically to the values you wan't, then remove them from database and then insert them again using something similar:
use [Name.Database]
go
set identity_insert [Test] ON
insert into [dbo].[Test]
([Id])
VALUES
(2)
set identity_insert [Test] OFF
For bulk insert use:
use [Name.Database]
go
set identity_insert [Test] ON
BULK INSERT [Test]
FROM 'C:\Users\Oscar\file.csv'
WITH (FIELDTERMINATOR = ';',
ROWTERMINATOR = '\n',
KEEPIDENTITY)
set identity_insert [Test] OFF
Sample data from file.csv:
2;
3;
4;
5;
6;
If you don't set identity_insert to off you will get the following error:
Cannot insert explicit value for identity column in table 'Test' when
IDENTITY_INSERT is set to OFF.
I saw a good article which helped me out at the last moment .. I was trying to insert few rows in a table which had identity column but did it wrongly and have to delete back. Once I deleted the rows then my identity column got changed . I was trying to find an way to update the column which was inserted but - no luck. So, while searching on google found a link ..
Deleted the columns which was wrongly inserted
Use force insert using identity on/off (explained below)
http://beyondrelational.com/modules/2/blogs/28/posts/10337/sql-server-how-do-i-insert-an-explicit-value-into-an-identity-column-how-do-i-update-the-value-of-an.aspx
Very nice question, first we need to on the IDENTITY_INSERT for the specific table, after that run the insert query (Must specify the column name).
Note: After edit the the identity column, don't forget to off the IDENTITY_INSERT. If you not done, you cannot able to Edit the identity column for any other table.
SET IDENTITY_INSERT Emp_tb_gb_Menu ON
INSERT Emp_tb_gb_Menu(MenuID) VALUES (68)
SET IDENTITY_INSERT Emp_tb_gb_Menu OFF
http://allinworld99.blogspot.com/2016/07/how-to-edit-identity-field-in-sql.html

Insert into column without having IDENTITY

I have a table
CREATE TABLE [misc]
(
[misc_id] [int] NOT NULL,
[misc_group] [nvarchar](255) NOT NULL,
[misc_desc] [nvarchar](255) NOT NULL
)
where misc_id [int] not null should have been IDENTITY (1,1) but is not and now I'm having issues
With a simple form that insert into this table but since misc_id is looking for a number that a user would not know unless they have access to the database.
I know a option would be to create another column make it IDENTITY(1,1) and copy that data.
Is there another way I will be able to get around this?
INSERT INTO misc (misc_group, misc_desc)
VALUES ('#misc_group#', '#misc_desc#')
I have SQL Server 2012
You should re-create your table with the desired identity column. The following statements will get you close. SQL Server will automatically adjust the table's identity field to MAX(misc_id) + 1 as you're migrating data.
You'll obviously need to stop trying to insert misc_id with new records. You'll want to retrieve the SCOPE_IDENTITY() column after inserting records.
-- Note: I'd recommend having SSMS generate your base create statement so you know you didn't miss anything. You'll have to export the indexes and foreign keys as well. Add them after populating data to improve performance and reduce fragmentation.
CREATE TABLE [misc_new]
(
[misc_id] [int] NOT NULL IDENTITY(1,1),
[misc_group] [nvarchar](255) NOT NULL,
[misc_desc] [nvarchar](255) NOT NULL
-- Todo: Don't forget primary key but can be added later (not recommended).
)
GO
SET IDENTITY_INSERT misc_new ON;
INSERT INTO misc_new
(
[misc_id],
[misc_group],
[misc_desc]
)
SELECT
[misc_id],
[misc_group],
[misc_desc]
FROM misc
ORDER BY misc_id;
SET IDENTITY_INSERT misc_new OFF;
GO
EXEC sp_rename 'misc', 'misc_old';
EXEC sp_rename 'misc_new', 'misc';
GO
If altering the table is not an option, you can try having a different table with the latest [misc_id] value inserted, so whenever you insert a new record into the table, you retrieve this value, add 1, and use it as your new Id. Just don't forget to update the table after.
Changing a int column to an identity can cause problems because by default you cannot insert a value into an identity column without use the set identity_insert command on. So if you have existing code that inserts a value into the identity column it will fail. However its much easier to allow SQL Server to insert values(that is change it to an identity column) so I would change misc_id into an identity column and make sure that there are no programs inserting values into misc_id.
In MSSQL 2012 you can use SEQUENCE objects:
CREATE SEQUENCE [dbo].[TestSequence]
AS [BIGINT]
START WITH 1
INCREMENT BY 1
GO
Change 1 in START WITH 1 with MAX value for [misc_id] + 1.
Usage:
INSERT INTO misc (misc_id, misc_group, misc_desc)
VALUES (NEXT VALUE FOR TestSequence, '#misc_group#','#misc_desc#')

Get last UNIQUEIDENTIFIER inserted in a table inside a TRIGGER from a SQL 2005 database

I need to be able to monitor a Table and react very time a record is inserted. This table has no ITN IDENTITY field, only a UNIQUEIDENTIFIER as its primary key. Without any alteration of existing inputs, SPs, etc. I need to be able to find the last inserted ID from within a trigger. This is what I have (obviously does not work):
CREATE TRIGGER TR_UserInserted
ON Users
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
EXEC UserInserted (SELECT User_Id FROM INSERTED);
END
GO
Here I am trying to get the User_Id from the last inserted record in the Users table and run it through the UserInserted SP. Thank you for the help, I am stumped.
HLGEM Made a great point - even on a bulk insert, I only need the last record inserted - I know this is a strange request.
You need to change your trigger to fire INSTEAD OF INSERT. A uniqueidentifier variable must be generated using the NEWID() function. In the INSERT statement in the trigger body, the columns must be provided in order. Assuming a table defined this way:
CREATE TABLE Users (
First int,
User_Id uniqueidentifier PRIMARY KEY,
Third int,
Fourth int)
Then the trigger is:
CREATE TRIGGER TR_UserInserted ON Users
INSTEAD OF INSERT AS
BEGIN
DECLARE #newid uniqueidentifier = NEWID()
INSERT INTO Users
SELECT
First,
#newid,
Third,
Fourth
FROM inserted
EXECUTE UserInserted(#newid)
-- you can actually provide all the columns to UserInserted
END
For this to work properly, make sure that the table does not have a default for the primary key as NEWID().

Constraint for only one record marked as default

How could I set a constraint on a table so that only one of the records has its isDefault bit field set to 1?
The constraint is not table scope, but one default per set of rows, specified by a FormID.
Use a unique filtered index
On SQL Server 2008 or higher you can simply use a unique filtered index
CREATE UNIQUE INDEX IX_TableName_FormID_isDefault
ON TableName(FormID)
WHERE isDefault = 1
Where the table is
CREATE TABLE TableName(
FormID INT NOT NULL,
isDefault BIT NOT NULL
)
For example if you try to insert many rows with the same FormID and isDefault set to 1 you will have this error:
Cannot insert duplicate key row in object 'dbo.TableName' with unique
index 'IX_TableName_FormID_isDefault'. The duplicate key value is (1).
Source: http://technet.microsoft.com/en-us/library/cc280372.aspx
Here's a modification of Damien_The_Unbeliever's solution that allows one default per FormID.
CREATE VIEW form_defaults
AS
SELECT FormID
FROM whatever
WHERE isDefault = 1
GO
CREATE UNIQUE CLUSTERED INDEX ix_form_defaults on form_defaults (FormID)
GO
But the serious relational folks will tell you this information should just be in another table.
CREATE TABLE form
FormID int NOT NULL PRIMARY KEY
DefaultWhateverID int FOREIGN KEY REFERENCES Whatever(ID)
From a normalization perspective, this would be an inefficient way of storing a single fact.
I would opt to hold this information at a higher level, by storing (in a different table) a foreign key to the identifier of the row which is considered to be the default.
CREATE TABLE [dbo].[Foo](
[Id] [int] NOT NULL,
CONSTRAINT [PK_Foo] PRIMARY KEY CLUSTERED
(
[Id] ASC
) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[DefaultSettings](
[DefaultFoo] [int] NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[DefaultSettings] WITH CHECK ADD CONSTRAINT [FK_DefaultSettings_Foo] FOREIGN KEY([DefaultFoo])
REFERENCES [dbo].[Foo] ([Id])
GO
ALTER TABLE [dbo].[DefaultSettings] CHECK CONSTRAINT [FK_DefaultSettings_Foo]
GO
You could use an insert/update trigger.
Within the trigger after an insert or update, if the count of rows with isDefault = 1 is more than 1, then rollback the transaction.
CREATE VIEW vOnlyOneDefault
AS
SELECT 1 as Lock
FROM <underlying table>
WHERE Default = 1
GO
CREATE UNIQUE CLUSTERED INDEX IX_vOnlyOneDefault on vOnlyOneDefault (Lock)
GO
You'll need to have the right ANSI settings turned on for this.
I don't know about SQLServer.But if it supports Function-Based Indexes like in Oracle, I hope this can be translated, if not, sorry.
You can do an index like this on suposed that default value is 1234, the column is DEFAULT_COLUMN and ID_COLUMN is the primary key:
CREATE
UNIQUE
INDEX only_one_default
ON my_table
( DECODE(DEFAULT_COLUMN, 1234, -1, ID_COLUMN) )
This DDL creates an unique index indexing -1 if the value of DEFAULT_COLUMN is 1234 and ID_COLUMN in any other case. Then, if two columns have DEFAULT_COLUMN value, it raises an exception.
The question implies to me that you have a primary table that has some child records and one of those child records will be the default record. Using address and a separate default table here is an example of how to make that happen using third normal form. Of course I don't know if it's valuable to answer something that is so old but it struck my fancy.
--drop table dev.defaultAddress;
--drop table dev.addresses;
--drop table dev.people;
CREATE TABLE [dev].[people](
[Id] [int] identity primary key,
name char(20)
)
GO
CREATE TABLE [dev].[Addresses](
id int identity primary key,
peopleId int foreign key references dev.people(id),
address varchar(100)
) ON [PRIMARY]
GO
CREATE TABLE [dev].[defaultAddress](
id int identity primary key,
peopleId int foreign key references dev.people(id),
addressesId int foreign key references dev.addresses(id))
go
create unique index defaultAddress on dev.defaultAddress (peopleId)
go
create unique index idx_addr_id_person on dev.addresses(peopleid,id);
go
ALTER TABLE dev.defaultAddress
ADD CONSTRAINT FK_Def_People_Address
FOREIGN KEY(peopleID, addressesID)
REFERENCES dev.Addresses(peopleId, id)
go
insert into dev.people (name)
select 'Bill' union
select 'John' union
select 'Harry'
insert into dev.Addresses (peopleid, address)
select 1, '123 someplace' union
select 1,'work place' union
select 2,'home address' union
select 3,'some address'
insert into dev.defaultaddress (peopleId, addressesid)
select 1,1 union
select 2,3
-- so two home addresses are default now
-- try adding another default address to Bill and you get an error
select * from dev.people
join dev.addresses on people.id = addresses.peopleid
left join dev.defaultAddress on defaultAddress.peopleid = people.id and defaultaddress.addressesid = addresses.id
insert into dev.defaultaddress (peopleId, addressesId)
select 1,2
GO
You could do it through an instead of trigger, or if you want it as a constraint create a constraint that references a function that checks for a row that has the default set to 1
EDIT oops, needs to be <=
Create table mytable(id1 int, defaultX bit not null default(0))
go
create Function dbo.fx_DefaultExists()
returns int as
Begin
Declare #Ret int
Set #ret = 0
Select #ret = count(1) from mytable
Where defaultX = 1
Return #ret
End
GO
Alter table mytable add
CONSTRAINT [CHK_DEFAULT_SET] CHECK
(([dbo].fx_DefaultExists()<=(1)))
GO
Insert into mytable (id1, defaultX) values (1,1)
Insert into mytable (id1, defaultX) values (2,1)
This is a fairly complex process that cannot be handled through a simple constraint.
We do this through a trigger. However before you write the trigger you need to be able to answer several things:
do we want to fail the insert if a default exists, change it to 0 instead of 1 or change the existing default to 0 and leave this one as 1?
what do we want to do if the default record is deleted and other non default records are still there? Do we make one the default, if so how do we determine which one?
You will also need to be very, very careful to make the trigger handle multiple row processing. For instance a client might decide that all of the records of a particular type should be the default. You wouldn't change a million records one at a time, so this trigger needs to be able to handle that. It also needs to handle that without looping or the use of a cursor (you really don't want the type of transaction discussed above to take hours locking up the table the whole time).
You also need a very extensive tesing scenario for this trigger before it goes live. You need to test:
adding a record with no default and it is the first record for that customer
adding a record with a default and it is the first record for that customer
adding a record with no default and it is the not the first record for that customer
adding a record with a default and it is the not the first record for that customer
Updating a record to have the default when no other record has it (assuming you don't require one record to always be set as the deafault)
Updating a record to remove the default
Deleting the record with the deafult
Deleting a record without the default
Performing a mass insert with multiple situations in the data including two records which both have isdefault set to 1 and all of the situations tested when running individual record inserts
Performing a mass update with multiple situations in the data including two records which both have isdefault set to 1 and all of the situations tested when running individual record updates
Performing a mass delete with multiple situations in the data including two records which both have isdefault set to 1 and all of the situations tested when running individual record deletes
#Andy Jones gave an answer above closest to mine, but bearing in mind the Rule of Three, I placed the logic directly in the stored proc that updates this table. This was my simple solution. If I need to update the table from elsewhere, I will move the logic to a trigger. The one default rule applies to each set of records specified by a FormID and a ConfigID:
ALTER proc [dbo].[cpForm_UpdateLinkedReport]
#reportLinkId int,
#defaultYN bit,
#linkName nvarchar(150)
as
if #defaultYN = 1
begin
declare #formId int, #configId int
select #formId = FormID, #configId = ConfigID from csReportLink where ReportLinkID = #reportLinkId
update csReportLink set DefaultYN = 0 where isnull(ConfigID, #configId) = #configId and FormID = #formId
end
update
csReportLink
set
DefaultYN = #defaultYN,
LinkName = #linkName
where
ReportLinkID = #reportLinkId