Define relationship to tables as a whole - sql

I need to define a table which basically will contain an Id for a user, and a second column which will list names of tables to which the user has access. I can't think of anyway to define any relationships here in case the original table names change. All the logic will be at the application level. However, I would like to be able to define some sort of constraints. How can I do this? Also, I am open to advice regarding any other way to do this.

I am really confused. Doesn't the grant command do exactly what you want? This assumes that the operations you want are database operations.
If you have a more customized set of operations, then you can keep track of table name changes via DDL triggers.

Here's a detailed code example of how to achieve this using RLS
USE tempdb;
GO
CREATE TABLE dbo.OKTable
(
OKTableID int IDENTITY(1,1) NOT NULL
CONSTRAINT PK_dbo_OKTable PRIMARY KEY,
SecuredInfo varchar(100)
);
GO
INSERT dbo.OKTable (SecuredInfo)
VALUES ('Very'), ('Secret'), ('Stuff');
GO
CREATE TABLE dbo.NotOKTable
(
NotOKTableID int IDENTITY(1,1) NOT NULL
CONSTRAINT PK_dbo_NotOKTable PRIMARY KEY,
SecuredInfo varchar(100)
);
GO
INSERT dbo.NotOKTable (SecuredInfo)
VALUES ('Other'), ('Important'), ('Things');
GO
CREATE SCHEMA [Security] AUTHORIZATION dbo;
GO
CREATE TABLE [Security].PermittedTableUsers
(
PermittedTableUsers int IDENTITY(1,1) NOT NULL
CONSTRAINT PK_Security_PermittedTableUsers
PRIMARY KEY,
UserName sysname,
SchemaName sysname,
TableName sysname
);
GO
INSERT [Security].PermittedTableUsers (UserName, SchemaName, TableName)
VALUES (N'dbo', N'dbo', 'OKTable');
GO
ALTER FUNCTION [Security].CheckUserAccess
(
#SchemaName AS sysname,
#TableName AS sysname
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS CheckUserAccessOutcome
WHERE EXISTS (SELECT 1 FROM [Security].PermittedTableUsers AS ptu
WHERE ptu.UserName = USER_NAME()
AND ptu.SchemaName = #SchemaName
AND ptu.TableName = #TableName);
GO
CREATE SECURITY POLICY OKTableAccessFilter
ADD FILTER PREDICATE [Security].CheckUserAccess (N'dbo', N'OKTable')
ON dbo.OKTable
WITH (STATE = ON);
GO
CREATE SECURITY POLICY NotOKTableAccessFilter
ADD FILTER PREDICATE [Security].CheckUserAccess (N'dbo', N'NotOKTable')
ON dbo.NotOKTable
WITH (STATE = ON);
GO
SELECT * FROM dbo.OKTable;
SELECT * FROM dbo.NotOKTable;
GO
It's described more fully in this link:
https://blog.greglow.com/2019/10/10/sql-how-to-control-access-to-sql-server-tables-by-entries-in-another-table/

Since users are NOT SQL Server Logins, therefore, I guess you can use the DDL trigger to monitor table rename where you can change the table name in your custom security table. But I don't know if you can throw exception within this trigger to prevent table rename (simulating some type of constraint). Also it would be better if you store the table name in each line rather saving comma separated table names in 1 field.
If you can utilize SQL Logins then the Gordan's solution is also applicable, but sometimes you cannot create SQL Logins, in-case if you have different application databases along with hundreds of thousands of users.

Related

Alter an existing Identity Column's Increment value

I am stumped,
I am trying to alter the increment value of Identity columns in a collection of existing MS SQL tables (which all have data) and have been trying to research if it is possible to do without writing custom scripts per table.
I can't find a solution that doesn't require dropping and recreating the tables which would require a different script for each table as they each have different column lists.
for example i want to change the existing table
CREATE TABLE [dbo].[ActionType](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Action] [varchar](100) NOT NULL,
CONSTRAINT [PK_ActionType] PRIMARY KEY CLUSTERED
(
[ID] ASC
) ON [PRIMARY]
) ON [PRIMARY]
To
CREATE TABLE [dbo].[ActionType](
[ID] [int] IDENTITY(1,5) NOT NULL,
[Action] [varchar](100) NOT NULL,
CONSTRAINT [PK_ActionType] PRIMARY KEY CLUSTERED
(
[ID] ASC
) ON [PRIMARY]
) ON [PRIMARY]
Via something like
exec sp_AlterIncrement #TABLE_NAME = 'ActionType', #NEW_ICREMENT = 5
While keeping the data.
This would fix a big deployment issue i am facing right now so any help would be appreciated
You can not alter identity increment after you create it.It is possible just to change seed value with DBCC Chekident .
You should drop and recreate the column.
I had to do that before on a small table and it's fairly easy to do, trick is that you have to update it to something that currently doesn't exist as a key, and then back, since you can't increment it by 1 because that key already exists. It takes 2 updates, for a table with IDs smaller than 100 for example:
update my_table set id = id+100;
update my_table set id = id-99;
But anyways , I do not understand why you want to alter the identity value, Because anyhow you will keep the same as primary key or part of the clustered key.
Also, if any change in the column type is being required then i don't think that there is a possibility without altering the table structure.
Alter table ActionType
Alter column ID
You can also revert to the original structure when not required. This can be used for the specified case as well, As if you require this on demand basis.
Please suggest so that i can provide the further feedback.
Couple of things, maybe too much info but helpful when do stuff like this. The following will set the increment to whatever you want:
DBCC CHECKIDENT ([DB.Schema.Table], reseed, 0) --First record will have a 1. You can set it to any value
If you want to insert data into a table that has an identity but you need to force the value to something specific, do this:
SET IDENTITY_INSERT [DB].[schema].[Table] ON
...Add your data here
SET IDENTITY_INSERT [DB].[schema].[Table] OFF
Sometimes this is necessary.this might provide an answer. For example existing table is identity(1,1) [ex below would be A]
It contains value but you would like to change it to increment of to let's say so that it works well with another table [ex below would be B]
So a would have odd ids + whatever it use to contains.while be would now have even number
this script show you how to do it.
create table A(id int identity(1,1),v char)
insert into A
Select 'A'
union select 'B'
union select 'C'
go
create table B(id int identity(1,2),v char)
go
SET IDENTITY_INSERT B ON
GO
insert into B(Id,v)
Select Id,v from A
go
SET IDENTITY_INSERT B OFF
GO
insert into B
Select 'D'
union select 'E'
go
drop table A
go
EXEC sp_RENAME 'B' , 'A'
go
Select * from A
go
Select max(Id)+1 from A
go
create table B(id int identity(8,2),v char)
go
insert into B
Select 'A'
union select 'B'
union select 'C'
go
Select * from B
If you need to reenumerate or compress your Identity field, the easiest way is as follows:
Convert, temporarily, your identity filed into an integer
Replace the values using for example an Excel sheet in other to fill them up
Copy and Paste the column in your Excel file into the Int field.
Save the table
Open it again in design mode and change back the Int field into an Identity
If this Identity field is used in a child table, make sure you have a trigger to also export the new values into the dependant tables .
And that's all.
If you need to control Identity data in your applicaton, just change it to Int and manage the incremental values with code with the Dmax function.
Hope it helps

SQL Server 2008 stored procedure for a table having two foreign keys and both of them is a composite key

I have the following tables :
create table ApartmentInfo(
ApartmentId int primary key identity,
ApName nvarchar(50))
create table [User](
UserId int primary key identity,
FirstName nvarchar(50),
LastName nvarchar(50),
Username nvarchar(50),
[Password] nvarchar(50),
[Description] nvarchar(200))
create table ApUser(
ApartmentId int foreign key references ApartmentInfo(ApartmentId),
UserId int foreign key references [User](UserId),
primary key(ApartmentId,UserId))
Summary of the usage is: suppose I have 10 apartments in my Apartmentinfo table and 3 users in the [User] table.
Now I want to write a stored procedure such that:
every UserId has all 10 ApartmentId's, and whenever a new apartment is created in ApartmentInfo table it will also be added in ApUser table again having all 3 userId's.
And if a new user is created in User table then it will also have all the 10 ApartmentId's related with it in ApUser table.
Thanks in advance, I am new to SQL Server and I don't know how it can be done or not but, if it is possible then please let me know, I will be grateful to you, thanks.
If you're not dead set on using a stored procedure this use case seems like an excellent fit for server side triggers:
IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'[dbo].[trigger_insert_apartment]'))
DROP TRIGGER [dbo].[trigger_insert_apartment]
GO
CREATE TRIGGER trigger_insert_apartment
ON ApartmentInfo FOR INSERT
AS INSERT ApUser(ApartmentId, UserId ) SELECT i.ApartmentId, u.UserId FROM [User] AS u, inserted AS i
GO
IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'[dbo].[trigger_insert_user]'))
DROP TRIGGER [dbo].[trigger_insert_user]
GO
CREATE TRIGGER trigger_insert_user
ON [User] FOR INSERT
AS INSERT ApUser(ApartmentId,UserId ) SELECT a.ApartmentId, i.UserId FROM ApartmentInfo AS a, inserted AS i
GO
These two triggers will insert all apartments or users into the apinfo table when you add a new user or apartment, and if I understood your question that was what you wanted?
To address your question about what you'd do to associate apartments with users IF YOU WANTED TO LATER...
If you're restricting which apartments a user has access to view, I would NOT associate them individually to every user. BUT, if they're manually adding a "watch" on an apartment in your application, then this is ok to do. If you want to only show them a certain set of apartments, try to group them in a logical way if you can. So maybe have an ApartmentGroup lookup table:
ApartmentGroup(ApartmentGroupId (Int), ApartmentGroupName varchar(50))
and assign an ApartmentGroupId to each apartment. Then you can have a join table for ApUser and ApartmentGroup. That way you're not associating every apartment with every user.
However, if you want to add the apartment to each user still, then in your InsertApartment stored procedure, just insert it into the ApUser table as well.

Single default value in a table

I'm creating a table that'll have a single bit not null column IsDefault. I need to write a constraint that'll make sure there'll be only one default value per UserId (field in the same table).
I can't use unique constraint on this because it is possible to have many non-default values.
What is the best approach to do this using MS SQL Server 2008?
Thanks.
The easiest way I see is a check constraint with a UDF (User Defined function).
Look at here, for example.
http://sqljourney.wordpress.com/2010/06/25/check-constraint-with-user-defined-function-in-sql-server/
Untested example
CREATE FUNCTION dbo.CheckDefaultUnicity(#UserId int)
RETURNS int
AS
BEGIN
DECLARE #retval int
SELECT #retval = COUNT(*) FROM <your table> where UserId = #UserId and <columnwithDefault> = 1-- or whatever is your default value
RETURN #retval
END;
GO
and alter your table
ALTER TABLE <yourTable>
ADD CONSTRAINT Ck_UniqueDefaultForUser
CHECK (dbo.CheckDefaultUnicity(UserId) <2)
Another relatively simple option is to use a CLUSTERED INDEXED VIEW. The gist of this is to
Select all UserID's from your table where IsDefault=1 in a view.
Add a unique index on UserID
Clustered indexed view
CREATE VIEW dbo.VIEW_Users_IsDefault WITH SCHEMABINDING AS
SELECT UserID, IsDefault
FROM dbo.Users
WHERE IsDefault = 1
GO
CREATE UNIQUE CLUSTERED INDEX UIX_VIEW_USERS_ISDEFAULT
ON dbo.VIEW_Users_IsDefault (UserID)
GO
Test script
BEGIN TRAN
CREATE TABLE dbo.Users (UserID INT, IsDefault BIT)
GO
CREATE VIEW dbo.VIEW_Users_IsDefault WITH SCHEMABINDING AS
SELECT UserID, IsDefault
FROM dbo.Users
WHERE IsDefault = 1
GO
CREATE UNIQUE CLUSTERED INDEX UIX_VIEW_USERS_ISDEFAULT ON dbo.VIEW_Users_IsDefault (UserID)
GO
INSERT INTO dbo.Users VALUES (1, 0)
INSERT INTO dbo.Users VALUES (1, 1)
INSERT INTO dbo.Users VALUES (1, 1) -- Fails because of clustered index
ROLLBACK TRAN
A Check Constraint would definitely work, however it's not good a design choice in my opinion. The reason being that your UDF for the constraint would be something like
SELECT #Count = COUNT(UserId)
FROM User
WHERE IsDefault = 1
GROUP BY UserId
HAVING COUNT(UserId) > 1
IF #Count > 0
....'FAIL
As this touches 2 columns it would thus need to be a Table level constraint and the more records you have the slower a Insert/Update/Delete will take.
A better option would be to only allow access to that table via Stored Procedure, so before an insert/update you could run a very quick
IF EXISTS(SELECT UserId FROM User where UserId = #UserId and IsDefault = 1)
before your inserts/updates/deletes
I can however appreciate that you may be using an ORM and might not want to have Stored Procs in your system so you could change the design of your table to the below. This assumes that
tblUser: UserId, FirstName, Suraname, etc
tblUserDefault: UserId (Unique Constraint)
I'm not sure what IsDefault represents in your system so I'm assuming in the above that Users are either default or not. Anybody you can use that as a reference. It allows you to enforce the constraint without using USP's or horrid tablewide check constraints (or triggers) and would be mappable in any decent ORM
What about CHECK Constraints. See here: http://msdn.microsoft.com/en-us/library/ms188258(v=sql.105).aspx
ALTER TABLE yourtable
ADD CONSTRAINT IsDefaultChecked CHECK (IsDefault = T );
While I think the trigger and constraint solutions are better, if you control insert/update via stored procedure a much simpler approach would be to just update the conflicting rows first (assuming the new default always wins):
ALTER PROCEDURE dbo.UserWhateverTable_<action>
#UserID INT,
#CardID INT
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.UserWhatever
SET IsDefault = 0
WHERE UserID = #UserID
AND CardID = #CardID
AND IsDefault = 1;
-- insert or update here
END
GO
In fact it might not be a bad idea to do this (so the business logic is 100% clear in your DML procedures) in addition to guarding it with a trigger or a constraint (to catch cases where updates are made outside of your procedures).
You might want to use a trigger in this case. When the user is changing their default, the trigger could automatically flip the current default for the current user to false.
Basically, use an AFTER insert/update trigger to set the IsDefault column to 0 for any user in the insert/update where the IsDefault value is being set to 1.
CREATE TRIGGER dbo.tr_default
ON dbo.MyTable
AFTER INSERT, UPDATE
AS
if(exists(select * from inserted where IsDefault = 1)
begin
update dbo.MyTable
set IsDefault = 0
from inserted i
join dbo.MyTable t on i.userid = t.userid
where i.IsDefault = 1
and i.TheValue != t.TheValue
end
I don't see any problem with any of the answers so far suggesting CHECK CONSTRAINTS and TRIGGERS however, it seems like a bit of a backwards solution to me.
I can only assume UserID in your table is a foreign key to a User table, so why not add a column to your User table to store the default CardID, rather than marking one as default? This makes it impossible for a user to have multiple default CardIDs without costly triggers/constraints. If you make the column non nullable then it is also impossible for a user not to have a default CardID if you so wish.

Mysql Cross Schema Queries Based on Row Data

So I'm working on a web application which our clients want their user data separated from other client's user data. Since our clients can be collaborative with each other, we can't just create a new instance of the application for each client, since we have to make sure user ids match up and are unique between clients.
Basically there's our "MasterApplication" database which has a "User" table which contains a UserId and the name of the database where to find the remaining user data. Each client has their own database user which only has access to the "MasterApplication" and their own client table.
Here's an example of how it looks.
CREATE DATABASE MasterDatabase
CREATE DATABASE Client1
CREATE DATABASE Client2
CREATE TABLE `MasterDatabase`.`Person` (
`PersonId` INT NOT NULL AUTO_INCREMENT,
`DatabaseName` Varchar(20) NOT NULL,
PRIMARY KEY (`PersonId`)
)
CREATE TABLE `Client1`.`Person` (
`PersonId` INT NOT NULL,
`FirstName` Varchar(20) NOT NULL,
PRIMARY KEY (`PersonId`)
)
CREATE TABLE `Client2`.`Person` (
`PersonId` INT NOT NULL,
`FirstName` Varchar(20) NOT NULL,
PRIMARY KEY (`PersonId`)
)
INSERT INTO MasterDatabase.Person VALUES (1, 'Client1');
INSERT INTO MasterDatabase.Person VALUES (2, 'Client2');
INSERT INTO Client1.Person VALUES (1, 'Matt');
INSERT INTO Client2.Person VALUES (2, 'Scott');
So Client1 would have a database user which has access to MasterDatabase and Client1 tables. Client2 would have a user with access to MasterDatabase and Client2.
I'm hoping there's some way that I can do a cross schema query easily using the data from the "DatabaseName" field in MasterDatabase, but I can't figure it out. I know for the most part I can store the client's database name in the application logic and just insert it into the query, but there will be a few spots which I'll need to join all clients into one query.
My attempt was to do
SELECT *, `DatabaseName` INTO #DB FROM MasterDatabase.Person
LEFT JOIN #DB.Person ON MasterDatabase.Person.PersonId = #DB.Person.PersonId
But that didn't work as I was hoping. Is there maybe someway I can do it with a procedure? I'm also open to other ideas of separating out the user data per client too if you have any.
I think you can do it in stored procedure. It will look something like
DELIMITER //
CREATE DEFINER='root'#'localhost' PROCEDURE GetData(IN user_id INT)
BEGIN
DECLARE db_name varchar(100);
SELECT DatabaseName INTO db_name FROM MasterDatabase.Person WHERE PersonId = user_id;
SET #t1 =CONCAT('SELECT * FROM ',db_name,'.Person' );
PREPARE stmt1 FROM #t1;
EXECUTE stmt1;
END//
UPDATE Sorry, I forgot DEALLOCATE PREPARE stmt1; ; it should be after EXECUTE stmt1;

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