Reputation: 335
I have a table that tracks whether data has been transferred for a certain period. Here's a short example (left out a lot of columns, but this should suffice):
+----+--------+------------+------+
| Id | DealId | Date | Sent |
+----+--------+------------+------+
| 1 | 1 | 2018-01-01 | 1 |
| 2 | 1 | 2018-02-01 | 1 |
| 3 | 1 | 2018-03-01 | 0 |
| 4 | 2 | 2018-01-01 | 1 |
| 5 | 2 | 2018-02-01 | 1 |
| 6 | 2 | 2018-03-01 | 0 |
| 7 | 1 | 2018-04-01 | 0 |
+----+--------+------------+------+
I want to create a check constraint that prevents the insertion of the last record. It should not be allowed to have more than one Sent = 0 line per DealId. In other words: it should not even be possible to insert a line for a DealId where Sent = 0 if there already is a Sent = 0 line for that deal.
Here's the table script with the constraint:
CREATE TABLE [Mrd].[Snapshot]
(
[Id] INT IDENTITY(1,1) CONSTRAINT [PK_Mrd_Snapshot_Id] PRIMARY KEY,
[DealId] INT NOT NULL,
[Date] DATETIME NOT NULL,
[Sent] BIT NOT NULL CONSTRAINT [DF_Mrd_Snapshot_Sent] DEFAULT 0,
CONSTRAINT [CK_Mrd_Snapshot_Sent]
CHECK ([Function].[ValidateSent]([DealId]) = 1)
)
Here's the function script:
CREATE FUNCTION [Function].[ValidateSent]
(
@DealId INT
)
RETURNS BIT
AS
BEGIN
IF ((SELECT COUNT(*) FROM [Mrd].[Snapshot] WHERE [DealId] = @DealId AND [Sent] = 0) = 0)
BEGIN
RETURN 1;
END;
RETURN 0;
END
And the schema creation for quick copy paste:
CREATE SCHEMA [Function]
With this implementation in place, I can't insert any line with Sent = 0. It throws a conflict error with the check constraint.
Any suggestions about what I'm doing wrong in the constraint or function? Or perhaps there is a better way to do this check?
Thanks!
Upvotes: 4
Views: 1285
Reputation: 31785
For what it's worth, the reason you were getting the error with your code is because when a CHECK CONSTRAINT calls a function, the function code assumes that the row that is triggering the call has already been created/inserted/updated.
So in your function you are testing whether there are zero rows that have Sent=0
. The row being inserted COUNTS as a row in the table already, so if you only allow zero rows to have Sent=0
then you will never be able to insert even a single row with Sent=0
.
To do what you want, you should have only allowed one row per id to have Sent=0
.
Upvotes: 2
Reputation: 5274
I am personally in favor of using unique indexes instead of a constraint for cases like this. I have found that columns I want to be unique I probably want an index anyways, so 2 birds with 1 stone. So I would do this:
CREATE UNIQUE NONCLUSTERED INDEX [uidx_dealid_sent] ON [dbo].[Snapshot]
(
[dealid] ASC,
[sent] ASC
)
WHERE ([sent]=0)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
The compound unique index does what you are looking to do: You won't be able to insert a row where a specific dealid
and specific sent
already exist together. As Larnu pointed out, this should be a filtered index (for MSSQL 2008 and later). Notice the WHERE ([sent]=0)
, this should satisfy your requirement for only wanting to apply the rule when sent = 0
.
Upvotes: 5