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.
sql
add a comment |
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.
sql
Is the default mandatory? There must be a default for each FormID? Or could some IDs be without a default?
– David Balažic
Mar 18 '17 at 17:31
@DavidBalažic I can no longer remember.
– ProfK
Mar 19 '17 at 4:24
check out this one stackoverflow.com/questions/4810615/…
– Joe B
Aug 28 '17 at 13:19
Has anyone solved the problem of how to enforce having that one mandatory default (i.e. if any records exist, then there must be a default. If only one record exists, then it must be the one marked as default)?
– DaveBoltman
Aug 6 '18 at 16:52
add a comment |
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.
sql
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.
sql
sql
edited Mar 12 '09 at 16:12
ProfK
asked Mar 12 '09 at 9:41
ProfKProfK
14.6k85319631
14.6k85319631
Is the default mandatory? There must be a default for each FormID? Or could some IDs be without a default?
– David Balažic
Mar 18 '17 at 17:31
@DavidBalažic I can no longer remember.
– ProfK
Mar 19 '17 at 4:24
check out this one stackoverflow.com/questions/4810615/…
– Joe B
Aug 28 '17 at 13:19
Has anyone solved the problem of how to enforce having that one mandatory default (i.e. if any records exist, then there must be a default. If only one record exists, then it must be the one marked as default)?
– DaveBoltman
Aug 6 '18 at 16:52
add a comment |
Is the default mandatory? There must be a default for each FormID? Or could some IDs be without a default?
– David Balažic
Mar 18 '17 at 17:31
@DavidBalažic I can no longer remember.
– ProfK
Mar 19 '17 at 4:24
check out this one stackoverflow.com/questions/4810615/…
– Joe B
Aug 28 '17 at 13:19
Has anyone solved the problem of how to enforce having that one mandatory default (i.e. if any records exist, then there must be a default. If only one record exists, then it must be the one marked as default)?
– DaveBoltman
Aug 6 '18 at 16:52
Is the default mandatory? There must be a default for each FormID? Or could some IDs be without a default?
– David Balažic
Mar 18 '17 at 17:31
Is the default mandatory? There must be a default for each FormID? Or could some IDs be without a default?
– David Balažic
Mar 18 '17 at 17:31
@DavidBalažic I can no longer remember.
– ProfK
Mar 19 '17 at 4:24
@DavidBalažic I can no longer remember.
– ProfK
Mar 19 '17 at 4:24
check out this one stackoverflow.com/questions/4810615/…
– Joe B
Aug 28 '17 at 13:19
check out this one stackoverflow.com/questions/4810615/…
– Joe B
Aug 28 '17 at 13:19
Has anyone solved the problem of how to enforce having that one mandatory default (i.e. if any records exist, then there must be a default. If only one record exists, then it must be the one marked as default)?
– DaveBoltman
Aug 6 '18 at 16:52
Has anyone solved the problem of how to enforce having that one mandatory default (i.e. if any records exist, then there must be a default. If only one record exists, then it must be the one marked as default)?
– DaveBoltman
Aug 6 '18 at 16:52
add a comment |
10 Answers
10
active
oldest
votes
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)
7
+1 for what "serious relational folks would do" answer.. I went from having a boolean to splitting it out into a separate table
– Asfand Qazi
May 5 '15 at 4:56
You can only create an index on a view that has WITH SCHEMABINDING.
– DaveBoltman
Aug 6 '18 at 15:14
add a comment |
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
2
Filtered indexes require ANSI PADDING ON. So executeSET ANSI PADDING ONbefore creating a filtered index.
– naXa
May 11 '17 at 8:37
add a comment |
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
This method will not ensure that only one row has isDefault = 1
– Andy Jones
Mar 12 '09 at 14:00
@Andy Jones, you're right, it won't. I wouldn't have an isDefault column at all. I'm suggesting an alternative solution.
– Ian Nelson
Mar 12 '09 at 14:03
3
Yes, but this alternative solution still allows for multiple defaults by inserting many rows into dbo.DefaultSettings. Could maybe add a trigger to dbo.DefaultSettings to only allow 1 row in there if you chose that method.
– Andy Jones
Mar 12 '09 at 14:12
@Andy Jones - true, good point.
– Ian Nelson
Mar 12 '09 at 14:29
@AndyJones In theory, it does allow more than one default. However - if you are using this table as a '1 row table that stores a setting or row of settings' - then you wouldn't ever be inserting rows into that table. I think this is a far clearer, simpler solution less prone to misunderstanding and errors later.
– niico
Nov 21 '16 at 16:09
add a comment |
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.
add a comment |
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.
This looks good, but complicated. I'd have to build thecreate viewstatement dynamically, as the default constraint isn't table wide, it's per FormID in the underlying table.
– ProfK
Mar 12 '09 at 16:02
When you have a complicated problem, you will need a complicated solution. This will not address all the other problems I mentioned about how you want to handle things as data changes.
– HLGEM
Mar 12 '09 at 18:21
4
Also remember to use WITH SCHEMABINDING when creating the view
– kristof
Mar 13 '09 at 10:57
This got my vote because it actually answers the original question. It also neatly solved a very similar problem I was having in a way that wasn't covered by any of the other answers.
– Daniel Schealler
May 15 '15 at 6:09
add a comment |
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.
add a comment |
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
What if you insert intodefaultAddressan address that belongs to someone else? This won't catch it.
– Kiran Jonnalagadda
Aug 9 '17 at 6:34
How would it programmatically know that the address you entered wasn't the address of the person? If you are saying that the rule you need to enforce is no person can have the same default address as another person you can add the addressId to the unique address but that would assume no married couples/roommates/etc. in the database
– billpennock
Aug 10 '17 at 13:24
I meant database constraint.Addresses.peopleIdlinks an address to a person. The same row clearly can't link to another person. ThereforedefaultAddressneeds to validate that the address marked as default for a givenpeopleIdalso has a matchingpeopleId. (I just implemented this in PostgreSQL and addressed it with a trigger validator.)
– Kiran Jonnalagadda
Aug 16 '17 at 10:53
Sorry it had been awhile since I answered this and I should have spent more time understanding your comment. It's a good catch. I had alot of trouble getting code in this comment, maybe it's not possible. Anyway I added a multi-column foreign key to the code above to answer the very excellent concern. I think this will do what you identified without a trigger.
– billpennock
Aug 17 '17 at 15:43
add a comment |
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)
add a comment |
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
add a comment |
@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
ALways a bad idea to put required logic in a stored proc vice a trigger. You won't always know when you need to change to a trigger later because multiple applications are using this. You are creating a data integrity nightmare for some future person to fix.
– HLGEM
Mar 12 '09 at 18:24
I'm responsible for the single application area that maintains that table, and we release tomorrow, so not having to add to our SQL update script reduces risk. The update script for sp's is generated, other objects it's manual, with risk. I will schedule a trigger for next cycle, in 6 weeks time.
– ProfK
Mar 12 '09 at 18:34
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f637894%2fconstraint-for-only-one-record-marked-as-default%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
10 Answers
10
active
oldest
votes
10 Answers
10
active
oldest
votes
active
oldest
votes
active
oldest
votes
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)
7
+1 for what "serious relational folks would do" answer.. I went from having a boolean to splitting it out into a separate table
– Asfand Qazi
May 5 '15 at 4:56
You can only create an index on a view that has WITH SCHEMABINDING.
– DaveBoltman
Aug 6 '18 at 15:14
add a comment |
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)
7
+1 for what "serious relational folks would do" answer.. I went from having a boolean to splitting it out into a separate table
– Asfand Qazi
May 5 '15 at 4:56
You can only create an index on a view that has WITH SCHEMABINDING.
– DaveBoltman
Aug 6 '18 at 15:14
add a comment |
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)
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)
edited Jun 3 '11 at 15:18
Andriy M
63.5k1378131
63.5k1378131
answered Mar 12 '09 at 22:18
Tom FutureTom Future
9222914
9222914
7
+1 for what "serious relational folks would do" answer.. I went from having a boolean to splitting it out into a separate table
– Asfand Qazi
May 5 '15 at 4:56
You can only create an index on a view that has WITH SCHEMABINDING.
– DaveBoltman
Aug 6 '18 at 15:14
add a comment |
7
+1 for what "serious relational folks would do" answer.. I went from having a boolean to splitting it out into a separate table
– Asfand Qazi
May 5 '15 at 4:56
You can only create an index on a view that has WITH SCHEMABINDING.
– DaveBoltman
Aug 6 '18 at 15:14
7
7
+1 for what "serious relational folks would do" answer.. I went from having a boolean to splitting it out into a separate table
– Asfand Qazi
May 5 '15 at 4:56
+1 for what "serious relational folks would do" answer.. I went from having a boolean to splitting it out into a separate table
– Asfand Qazi
May 5 '15 at 4:56
You can only create an index on a view that has WITH SCHEMABINDING.
– DaveBoltman
Aug 6 '18 at 15:14
You can only create an index on a view that has WITH SCHEMABINDING.
– DaveBoltman
Aug 6 '18 at 15:14
add a comment |
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
2
Filtered indexes require ANSI PADDING ON. So executeSET ANSI PADDING ONbefore creating a filtered index.
– naXa
May 11 '17 at 8:37
add a comment |
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
2
Filtered indexes require ANSI PADDING ON. So executeSET ANSI PADDING ONbefore creating a filtered index.
– naXa
May 11 '17 at 8:37
add a comment |
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
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
edited Apr 29 '14 at 21:46
answered Apr 3 '13 at 0:17
Yves M.Yves M.
18.7k127199
18.7k127199
2
Filtered indexes require ANSI PADDING ON. So executeSET ANSI PADDING ONbefore creating a filtered index.
– naXa
May 11 '17 at 8:37
add a comment |
2
Filtered indexes require ANSI PADDING ON. So executeSET ANSI PADDING ONbefore creating a filtered index.
– naXa
May 11 '17 at 8:37
2
2
Filtered indexes require ANSI PADDING ON. So execute
SET ANSI PADDING ON before creating a filtered index.– naXa
May 11 '17 at 8:37
Filtered indexes require ANSI PADDING ON. So execute
SET ANSI PADDING ON before creating a filtered index.– naXa
May 11 '17 at 8:37
add a comment |
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
This method will not ensure that only one row has isDefault = 1
– Andy Jones
Mar 12 '09 at 14:00
@Andy Jones, you're right, it won't. I wouldn't have an isDefault column at all. I'm suggesting an alternative solution.
– Ian Nelson
Mar 12 '09 at 14:03
3
Yes, but this alternative solution still allows for multiple defaults by inserting many rows into dbo.DefaultSettings. Could maybe add a trigger to dbo.DefaultSettings to only allow 1 row in there if you chose that method.
– Andy Jones
Mar 12 '09 at 14:12
@Andy Jones - true, good point.
– Ian Nelson
Mar 12 '09 at 14:29
@AndyJones In theory, it does allow more than one default. However - if you are using this table as a '1 row table that stores a setting or row of settings' - then you wouldn't ever be inserting rows into that table. I think this is a far clearer, simpler solution less prone to misunderstanding and errors later.
– niico
Nov 21 '16 at 16:09
add a comment |
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
This method will not ensure that only one row has isDefault = 1
– Andy Jones
Mar 12 '09 at 14:00
@Andy Jones, you're right, it won't. I wouldn't have an isDefault column at all. I'm suggesting an alternative solution.
– Ian Nelson
Mar 12 '09 at 14:03
3
Yes, but this alternative solution still allows for multiple defaults by inserting many rows into dbo.DefaultSettings. Could maybe add a trigger to dbo.DefaultSettings to only allow 1 row in there if you chose that method.
– Andy Jones
Mar 12 '09 at 14:12
@Andy Jones - true, good point.
– Ian Nelson
Mar 12 '09 at 14:29
@AndyJones In theory, it does allow more than one default. However - if you are using this table as a '1 row table that stores a setting or row of settings' - then you wouldn't ever be inserting rows into that table. I think this is a far clearer, simpler solution less prone to misunderstanding and errors later.
– niico
Nov 21 '16 at 16:09
add a comment |
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
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
edited Mar 12 '09 at 11:29
answered Mar 12 '09 at 9:53
Ian NelsonIan Nelson
35.4k196699
35.4k196699
This method will not ensure that only one row has isDefault = 1
– Andy Jones
Mar 12 '09 at 14:00
@Andy Jones, you're right, it won't. I wouldn't have an isDefault column at all. I'm suggesting an alternative solution.
– Ian Nelson
Mar 12 '09 at 14:03
3
Yes, but this alternative solution still allows for multiple defaults by inserting many rows into dbo.DefaultSettings. Could maybe add a trigger to dbo.DefaultSettings to only allow 1 row in there if you chose that method.
– Andy Jones
Mar 12 '09 at 14:12
@Andy Jones - true, good point.
– Ian Nelson
Mar 12 '09 at 14:29
@AndyJones In theory, it does allow more than one default. However - if you are using this table as a '1 row table that stores a setting or row of settings' - then you wouldn't ever be inserting rows into that table. I think this is a far clearer, simpler solution less prone to misunderstanding and errors later.
– niico
Nov 21 '16 at 16:09
add a comment |
This method will not ensure that only one row has isDefault = 1
– Andy Jones
Mar 12 '09 at 14:00
@Andy Jones, you're right, it won't. I wouldn't have an isDefault column at all. I'm suggesting an alternative solution.
– Ian Nelson
Mar 12 '09 at 14:03
3
Yes, but this alternative solution still allows for multiple defaults by inserting many rows into dbo.DefaultSettings. Could maybe add a trigger to dbo.DefaultSettings to only allow 1 row in there if you chose that method.
– Andy Jones
Mar 12 '09 at 14:12
@Andy Jones - true, good point.
– Ian Nelson
Mar 12 '09 at 14:29
@AndyJones In theory, it does allow more than one default. However - if you are using this table as a '1 row table that stores a setting or row of settings' - then you wouldn't ever be inserting rows into that table. I think this is a far clearer, simpler solution less prone to misunderstanding and errors later.
– niico
Nov 21 '16 at 16:09
This method will not ensure that only one row has isDefault = 1
– Andy Jones
Mar 12 '09 at 14:00
This method will not ensure that only one row has isDefault = 1
– Andy Jones
Mar 12 '09 at 14:00
@Andy Jones, you're right, it won't. I wouldn't have an isDefault column at all. I'm suggesting an alternative solution.
– Ian Nelson
Mar 12 '09 at 14:03
@Andy Jones, you're right, it won't. I wouldn't have an isDefault column at all. I'm suggesting an alternative solution.
– Ian Nelson
Mar 12 '09 at 14:03
3
3
Yes, but this alternative solution still allows for multiple defaults by inserting many rows into dbo.DefaultSettings. Could maybe add a trigger to dbo.DefaultSettings to only allow 1 row in there if you chose that method.
– Andy Jones
Mar 12 '09 at 14:12
Yes, but this alternative solution still allows for multiple defaults by inserting many rows into dbo.DefaultSettings. Could maybe add a trigger to dbo.DefaultSettings to only allow 1 row in there if you chose that method.
– Andy Jones
Mar 12 '09 at 14:12
@Andy Jones - true, good point.
– Ian Nelson
Mar 12 '09 at 14:29
@Andy Jones - true, good point.
– Ian Nelson
Mar 12 '09 at 14:29
@AndyJones In theory, it does allow more than one default. However - if you are using this table as a '1 row table that stores a setting or row of settings' - then you wouldn't ever be inserting rows into that table. I think this is a far clearer, simpler solution less prone to misunderstanding and errors later.
– niico
Nov 21 '16 at 16:09
@AndyJones In theory, it does allow more than one default. However - if you are using this table as a '1 row table that stores a setting or row of settings' - then you wouldn't ever be inserting rows into that table. I think this is a far clearer, simpler solution less prone to misunderstanding and errors later.
– niico
Nov 21 '16 at 16:09
add a comment |
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.
add a comment |
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.
add a comment |
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.
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.
answered Mar 12 '09 at 9:54
Andy JonesAndy Jones
1,265813
1,265813
add a comment |
add a comment |
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.
This looks good, but complicated. I'd have to build thecreate viewstatement dynamically, as the default constraint isn't table wide, it's per FormID in the underlying table.
– ProfK
Mar 12 '09 at 16:02
When you have a complicated problem, you will need a complicated solution. This will not address all the other problems I mentioned about how you want to handle things as data changes.
– HLGEM
Mar 12 '09 at 18:21
4
Also remember to use WITH SCHEMABINDING when creating the view
– kristof
Mar 13 '09 at 10:57
This got my vote because it actually answers the original question. It also neatly solved a very similar problem I was having in a way that wasn't covered by any of the other answers.
– Daniel Schealler
May 15 '15 at 6:09
add a comment |
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.
This looks good, but complicated. I'd have to build thecreate viewstatement dynamically, as the default constraint isn't table wide, it's per FormID in the underlying table.
– ProfK
Mar 12 '09 at 16:02
When you have a complicated problem, you will need a complicated solution. This will not address all the other problems I mentioned about how you want to handle things as data changes.
– HLGEM
Mar 12 '09 at 18:21
4
Also remember to use WITH SCHEMABINDING when creating the view
– kristof
Mar 13 '09 at 10:57
This got my vote because it actually answers the original question. It also neatly solved a very similar problem I was having in a way that wasn't covered by any of the other answers.
– Daniel Schealler
May 15 '15 at 6:09
add a comment |
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.
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.
answered Mar 12 '09 at 15:22
Damien_The_UnbelieverDamien_The_Unbeliever
195k17252338
195k17252338
This looks good, but complicated. I'd have to build thecreate viewstatement dynamically, as the default constraint isn't table wide, it's per FormID in the underlying table.
– ProfK
Mar 12 '09 at 16:02
When you have a complicated problem, you will need a complicated solution. This will not address all the other problems I mentioned about how you want to handle things as data changes.
– HLGEM
Mar 12 '09 at 18:21
4
Also remember to use WITH SCHEMABINDING when creating the view
– kristof
Mar 13 '09 at 10:57
This got my vote because it actually answers the original question. It also neatly solved a very similar problem I was having in a way that wasn't covered by any of the other answers.
– Daniel Schealler
May 15 '15 at 6:09
add a comment |
This looks good, but complicated. I'd have to build thecreate viewstatement dynamically, as the default constraint isn't table wide, it's per FormID in the underlying table.
– ProfK
Mar 12 '09 at 16:02
When you have a complicated problem, you will need a complicated solution. This will not address all the other problems I mentioned about how you want to handle things as data changes.
– HLGEM
Mar 12 '09 at 18:21
4
Also remember to use WITH SCHEMABINDING when creating the view
– kristof
Mar 13 '09 at 10:57
This got my vote because it actually answers the original question. It also neatly solved a very similar problem I was having in a way that wasn't covered by any of the other answers.
– Daniel Schealler
May 15 '15 at 6:09
This looks good, but complicated. I'd have to build the
create view statement dynamically, as the default constraint isn't table wide, it's per FormID in the underlying table.– ProfK
Mar 12 '09 at 16:02
This looks good, but complicated. I'd have to build the
create view statement dynamically, as the default constraint isn't table wide, it's per FormID in the underlying table.– ProfK
Mar 12 '09 at 16:02
When you have a complicated problem, you will need a complicated solution. This will not address all the other problems I mentioned about how you want to handle things as data changes.
– HLGEM
Mar 12 '09 at 18:21
When you have a complicated problem, you will need a complicated solution. This will not address all the other problems I mentioned about how you want to handle things as data changes.
– HLGEM
Mar 12 '09 at 18:21
4
4
Also remember to use WITH SCHEMABINDING when creating the view
– kristof
Mar 13 '09 at 10:57
Also remember to use WITH SCHEMABINDING when creating the view
– kristof
Mar 13 '09 at 10:57
This got my vote because it actually answers the original question. It also neatly solved a very similar problem I was having in a way that wasn't covered by any of the other answers.
– Daniel Schealler
May 15 '15 at 6:09
This got my vote because it actually answers the original question. It also neatly solved a very similar problem I was having in a way that wasn't covered by any of the other answers.
– Daniel Schealler
May 15 '15 at 6:09
add a comment |
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.
add a comment |
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.
add a comment |
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.
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.
edited Jan 28 '13 at 9:15
Mridang Agarwalla
17.1k54172323
17.1k54172323
answered Mar 12 '09 at 11:34
FerranBFerranB
20.3k175880
20.3k175880
add a comment |
add a comment |
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
What if you insert intodefaultAddressan address that belongs to someone else? This won't catch it.
– Kiran Jonnalagadda
Aug 9 '17 at 6:34
How would it programmatically know that the address you entered wasn't the address of the person? If you are saying that the rule you need to enforce is no person can have the same default address as another person you can add the addressId to the unique address but that would assume no married couples/roommates/etc. in the database
– billpennock
Aug 10 '17 at 13:24
I meant database constraint.Addresses.peopleIdlinks an address to a person. The same row clearly can't link to another person. ThereforedefaultAddressneeds to validate that the address marked as default for a givenpeopleIdalso has a matchingpeopleId. (I just implemented this in PostgreSQL and addressed it with a trigger validator.)
– Kiran Jonnalagadda
Aug 16 '17 at 10:53
Sorry it had been awhile since I answered this and I should have spent more time understanding your comment. It's a good catch. I had alot of trouble getting code in this comment, maybe it's not possible. Anyway I added a multi-column foreign key to the code above to answer the very excellent concern. I think this will do what you identified without a trigger.
– billpennock
Aug 17 '17 at 15:43
add a comment |
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
What if you insert intodefaultAddressan address that belongs to someone else? This won't catch it.
– Kiran Jonnalagadda
Aug 9 '17 at 6:34
How would it programmatically know that the address you entered wasn't the address of the person? If you are saying that the rule you need to enforce is no person can have the same default address as another person you can add the addressId to the unique address but that would assume no married couples/roommates/etc. in the database
– billpennock
Aug 10 '17 at 13:24
I meant database constraint.Addresses.peopleIdlinks an address to a person. The same row clearly can't link to another person. ThereforedefaultAddressneeds to validate that the address marked as default for a givenpeopleIdalso has a matchingpeopleId. (I just implemented this in PostgreSQL and addressed it with a trigger validator.)
– Kiran Jonnalagadda
Aug 16 '17 at 10:53
Sorry it had been awhile since I answered this and I should have spent more time understanding your comment. It's a good catch. I had alot of trouble getting code in this comment, maybe it's not possible. Anyway I added a multi-column foreign key to the code above to answer the very excellent concern. I think this will do what you identified without a trigger.
– billpennock
Aug 17 '17 at 15:43
add a comment |
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
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
edited Aug 17 '17 at 15:41
answered Jul 30 '16 at 0:46
billpennockbillpennock
149111
149111
What if you insert intodefaultAddressan address that belongs to someone else? This won't catch it.
– Kiran Jonnalagadda
Aug 9 '17 at 6:34
How would it programmatically know that the address you entered wasn't the address of the person? If you are saying that the rule you need to enforce is no person can have the same default address as another person you can add the addressId to the unique address but that would assume no married couples/roommates/etc. in the database
– billpennock
Aug 10 '17 at 13:24
I meant database constraint.Addresses.peopleIdlinks an address to a person. The same row clearly can't link to another person. ThereforedefaultAddressneeds to validate that the address marked as default for a givenpeopleIdalso has a matchingpeopleId. (I just implemented this in PostgreSQL and addressed it with a trigger validator.)
– Kiran Jonnalagadda
Aug 16 '17 at 10:53
Sorry it had been awhile since I answered this and I should have spent more time understanding your comment. It's a good catch. I had alot of trouble getting code in this comment, maybe it's not possible. Anyway I added a multi-column foreign key to the code above to answer the very excellent concern. I think this will do what you identified without a trigger.
– billpennock
Aug 17 '17 at 15:43
add a comment |
What if you insert intodefaultAddressan address that belongs to someone else? This won't catch it.
– Kiran Jonnalagadda
Aug 9 '17 at 6:34
How would it programmatically know that the address you entered wasn't the address of the person? If you are saying that the rule you need to enforce is no person can have the same default address as another person you can add the addressId to the unique address but that would assume no married couples/roommates/etc. in the database
– billpennock
Aug 10 '17 at 13:24
I meant database constraint.Addresses.peopleIdlinks an address to a person. The same row clearly can't link to another person. ThereforedefaultAddressneeds to validate that the address marked as default for a givenpeopleIdalso has a matchingpeopleId. (I just implemented this in PostgreSQL and addressed it with a trigger validator.)
– Kiran Jonnalagadda
Aug 16 '17 at 10:53
Sorry it had been awhile since I answered this and I should have spent more time understanding your comment. It's a good catch. I had alot of trouble getting code in this comment, maybe it's not possible. Anyway I added a multi-column foreign key to the code above to answer the very excellent concern. I think this will do what you identified without a trigger.
– billpennock
Aug 17 '17 at 15:43
What if you insert into
defaultAddress an address that belongs to someone else? This won't catch it.– Kiran Jonnalagadda
Aug 9 '17 at 6:34
What if you insert into
defaultAddress an address that belongs to someone else? This won't catch it.– Kiran Jonnalagadda
Aug 9 '17 at 6:34
How would it programmatically know that the address you entered wasn't the address of the person? If you are saying that the rule you need to enforce is no person can have the same default address as another person you can add the addressId to the unique address but that would assume no married couples/roommates/etc. in the database
– billpennock
Aug 10 '17 at 13:24
How would it programmatically know that the address you entered wasn't the address of the person? If you are saying that the rule you need to enforce is no person can have the same default address as another person you can add the addressId to the unique address but that would assume no married couples/roommates/etc. in the database
– billpennock
Aug 10 '17 at 13:24
I meant database constraint.
Addresses.peopleId links an address to a person. The same row clearly can't link to another person. Therefore defaultAddress needs to validate that the address marked as default for a given peopleId also has a matching peopleId. (I just implemented this in PostgreSQL and addressed it with a trigger validator.)– Kiran Jonnalagadda
Aug 16 '17 at 10:53
I meant database constraint.
Addresses.peopleId links an address to a person. The same row clearly can't link to another person. Therefore defaultAddress needs to validate that the address marked as default for a given peopleId also has a matching peopleId. (I just implemented this in PostgreSQL and addressed it with a trigger validator.)– Kiran Jonnalagadda
Aug 16 '17 at 10:53
Sorry it had been awhile since I answered this and I should have spent more time understanding your comment. It's a good catch. I had alot of trouble getting code in this comment, maybe it's not possible. Anyway I added a multi-column foreign key to the code above to answer the very excellent concern. I think this will do what you identified without a trigger.
– billpennock
Aug 17 '17 at 15:43
Sorry it had been awhile since I answered this and I should have spent more time understanding your comment. It's a good catch. I had alot of trouble getting code in this comment, maybe it's not possible. Anyway I added a multi-column foreign key to the code above to answer the very excellent concern. I think this will do what you identified without a trigger.
– billpennock
Aug 17 '17 at 15:43
add a comment |
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)
add a comment |
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)
add a comment |
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)
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)
answered Mar 12 '09 at 9:54
cmsjrcmsjr
38.4k96361
38.4k96361
add a comment |
add a comment |
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
add a comment |
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
add a comment |
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
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
answered Mar 12 '09 at 14:44
HLGEMHLGEM
80.1k889157
80.1k889157
add a comment |
add a comment |
@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
ALways a bad idea to put required logic in a stored proc vice a trigger. You won't always know when you need to change to a trigger later because multiple applications are using this. You are creating a data integrity nightmare for some future person to fix.
– HLGEM
Mar 12 '09 at 18:24
I'm responsible for the single application area that maintains that table, and we release tomorrow, so not having to add to our SQL update script reduces risk. The update script for sp's is generated, other objects it's manual, with risk. I will schedule a trigger for next cycle, in 6 weeks time.
– ProfK
Mar 12 '09 at 18:34
add a comment |
@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
ALways a bad idea to put required logic in a stored proc vice a trigger. You won't always know when you need to change to a trigger later because multiple applications are using this. You are creating a data integrity nightmare for some future person to fix.
– HLGEM
Mar 12 '09 at 18:24
I'm responsible for the single application area that maintains that table, and we release tomorrow, so not having to add to our SQL update script reduces risk. The update script for sp's is generated, other objects it's manual, with risk. I will schedule a trigger for next cycle, in 6 weeks time.
– ProfK
Mar 12 '09 at 18:34
add a comment |
@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
@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
answered Mar 12 '09 at 16:19
ProfKProfK
14.6k85319631
14.6k85319631
ALways a bad idea to put required logic in a stored proc vice a trigger. You won't always know when you need to change to a trigger later because multiple applications are using this. You are creating a data integrity nightmare for some future person to fix.
– HLGEM
Mar 12 '09 at 18:24
I'm responsible for the single application area that maintains that table, and we release tomorrow, so not having to add to our SQL update script reduces risk. The update script for sp's is generated, other objects it's manual, with risk. I will schedule a trigger for next cycle, in 6 weeks time.
– ProfK
Mar 12 '09 at 18:34
add a comment |
ALways a bad idea to put required logic in a stored proc vice a trigger. You won't always know when you need to change to a trigger later because multiple applications are using this. You are creating a data integrity nightmare for some future person to fix.
– HLGEM
Mar 12 '09 at 18:24
I'm responsible for the single application area that maintains that table, and we release tomorrow, so not having to add to our SQL update script reduces risk. The update script for sp's is generated, other objects it's manual, with risk. I will schedule a trigger for next cycle, in 6 weeks time.
– ProfK
Mar 12 '09 at 18:34
ALways a bad idea to put required logic in a stored proc vice a trigger. You won't always know when you need to change to a trigger later because multiple applications are using this. You are creating a data integrity nightmare for some future person to fix.
– HLGEM
Mar 12 '09 at 18:24
ALways a bad idea to put required logic in a stored proc vice a trigger. You won't always know when you need to change to a trigger later because multiple applications are using this. You are creating a data integrity nightmare for some future person to fix.
– HLGEM
Mar 12 '09 at 18:24
I'm responsible for the single application area that maintains that table, and we release tomorrow, so not having to add to our SQL update script reduces risk. The update script for sp's is generated, other objects it's manual, with risk. I will schedule a trigger for next cycle, in 6 weeks time.
– ProfK
Mar 12 '09 at 18:34
I'm responsible for the single application area that maintains that table, and we release tomorrow, so not having to add to our SQL update script reduces risk. The update script for sp's is generated, other objects it's manual, with risk. I will schedule a trigger for next cycle, in 6 weeks time.
– ProfK
Mar 12 '09 at 18:34
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f637894%2fconstraint-for-only-one-record-marked-as-default%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Is the default mandatory? There must be a default for each FormID? Or could some IDs be without a default?
– David Balažic
Mar 18 '17 at 17:31
@DavidBalažic I can no longer remember.
– ProfK
Mar 19 '17 at 4:24
check out this one stackoverflow.com/questions/4810615/…
– Joe B
Aug 28 '17 at 13:19
Has anyone solved the problem of how to enforce having that one mandatory default (i.e. if any records exist, then there must be a default. If only one record exists, then it must be the one marked as default)?
– DaveBoltman
Aug 6 '18 at 16:52