使用与外键链接的表中的日期进行SQL约束检查

时间:2014-11-21 10:46:58

标签: sql sql-server sql-server-2012

我有两个表,使用以下SQL查询创建:

CREATE TABLE user
(
     user_email varchar(255) not null primary key,
     --other unimportant columns
     subscription_start date not null,
     subscription_end date,
     CONSTRAINT chk_end_start CHECK (subscription_start != subscription_end)
)

CREATE TABLE action
(
     --unimportant columns
     user_email varchar(255) not null,
     action_date date not null,
     CONSTRAINT FK_user FOREIGN KEY (user_email) REFERENCES user(user_email)
)

我想要做的是确保某种检查约束,action_date介于subscription_startsubscription_end之间。

4 个答案:

答案 0 :(得分:3)

我总是试图在可能的情况下避免触发器,所以我想在混合物中添加替代品。您可以使用索引视图在此处验证数据。首先,您需要创建一个新表,它只包含两行:

CREATE TABLE dbo.Two (Number INT NOT NULL);
INSERT dbo.Two VALUES (1), (2);

现在您可以创建索引视图,我已使用ActionID作为Action表的隐含主键,但您可能需要更改此内容:

CREATE VIEW dbo.ActionCheck
WITH SCHEMABINDING
AS
    SELECT  a.ActionID
    FROM    dbo.[User] AS u
            INNER JOIN dbo.[Action] AS a
                ON a.user_email = u.user_email
            CROSS JOIN dbo.Two AS t
    WHERE   a.Action_date < u.subscription_start
    OR      a.Action_date > u.subscription_end
    OR      t.Number = 1;
GO;
CREATE UNIQUE CLUSTERED INDEX UQ_ActionCheck_ActionID ON dbo.ActionCheck (ActionID);

因此,您的视图将始终为每个操作返回一行(t.Number = 1子句),但是,如果操作日期超出订阅日期,则将返回dbo.Two中number = 2的行,这将导致ActionID的重复,这将违反索引的唯一约束,因此将停止插入。 e.g:

INSERT [user] (user_email, subscription_start, subscription_end) 
VALUES ('test@test.com', '20140101', '20150101');

INSERT [Action] (user_email, action_date) VALUES ('test@test.com', '20140102');
-- WORKS FINE UP TO THIS POINT

-- THIS NEXT INSERT THROWS AN ERROR
INSERT [Action] (user_email, action_date) VALUES ('test@test.com', '20120102');
  

Msg 2601,Level 14,State 1,Line 1

     

无法在对象&#39; dbo.ActionCheck&#39;中插入重复的键行。具有唯一索引&#39; UQ_ActionCheck_ActionID&#39;。重复键值为(6)。

     

声明已经终止。

答案 1 :(得分:2)

使用检查约束不可能这样做,因为检查约束只能引用同一个表中的列。此外,外键约束仅支持等连接。

如果必须在数据库级别而不是应用程序级别执行此检查,则可以在action-table上使用INSERT / UPDATE上的触发器执行此操作。每次插入或更新记录时,都要检查action_date是否位于user-table上相应的subscription_start / end日期内。如果不是这种情况,则使用RAISERROR函数标记无法插入/更新行。

CREATE TRIGGER ActionDateTrigger ON tblaction
AFTER INSERT, UPDATE
AS
IF NOT EXISTS (
    SELECT * FROM tbluser u JOIN inserted i ON i.user_email = u.user_email
      AND i.action_date BETWEEN u.subscription_start AND u.subscription_end
)
BEGIN
    RAISERROR ('Action_date outside valid range', 16, 1);
    ROLLBACK TRANSACTION;
END

答案 2 :(得分:0)

解决方案是这样的:
- 在表操作中创建一个检查约束,该约束调用如下函数:check(dbo.FcToCheckValidity(user_email,action_date)= 1)
- dbo.FcToCheckValidity函数类似于:
- 返回BIT的FcToCheckValidity(user_email,action_date) - 查询用户表和:如果在subscription_start和subscription_end之间为正确的用户输入action_date,则返回1,否则为0。

答案 3 :(得分:0)

实际上,当表中的数据发生变化时,索引视图需要“重新索引”。如果它是一个相当复杂的视图或有很多行,性能将受到影响。我也相信在这种情况下触发器会表现得更好......