2014-11-21 58 views
0

我有两个表,用下面的SQL数据库查询: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之间。

回答

2

这不可能使用检查约束,因为检查约束只能引用同一个表内的列。而且,外键约束只支持平等连接。

如果您必须在数据库级别而不是应用级别执行此检查,则可以在操作表上使用INSERT/UPDATE上的触发器执行此操作。每次插入或更新记录时,都会检查action_date是否位于用户表的相应subscription_start /结束日期内。如果不是这种情况,则使用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 
+0

当尝试这一点的SQL,我得到以下错误:'多部分标识符“inserted.user_email”无法绑定“和类似一个关于inserted.action_date。这是什么原因? – Dirk 2014-11-21 11:10:47

+0

对不起 - '插入'虚拟表必须用于连接,而不是直接在where-criteria。我编辑了我的答案 - 现在应该可以工作。 – Dan 2014-11-21 13:17:09

0

的解决方案是像这样:
- 在表动作创建一个调用函数像这样检查约束:检查(dbo.FcToCheckValidity(USER_EMAIL,ACTION_DATE)= 1)
- 的dbo.FcToCheckValidity功能会是这样的:
- FcToCheckValidity(USER_EMAIL,ACTION_DATE)返回BIT
- 查询用户表:如果subscription_start和subscription_end正确的用户之间的输入ACTION_DATE则返回1,否则为0

+0

[在CHECK约束中包装的标量UDF非常缓慢,并且可能会因多行更新而失败](http://sqlblog.com/blogs/alexander_kuznetsov/archive/2009/06/25/scalar-udfs-wrapped-in-check-constraints -are-很慢,和可能失效换多行更新。aspx) – GarethD 2014-12-08 09:16:37

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排在那里,如果行动日期在订阅日期的外线号码= 2将被退回,这将导致重复ActionID这将违反e索引上的唯一约束,所以将停止插入。例如为:

INSERT [user] (user_email, subscription_start, subscription_end) 
VALUES ('[email protected]', '20140101', '20150101'); 

INSERT [Action] (user_email, action_date) VALUES ('[email protected]', '20140102'); 
-- WORKS FINE UP TO THIS POINT 

-- THIS NEXT INSERT THROWS AN ERROR 
INSERT [Action] (user_email, action_date) VALUES ('[email protected]', '20120102'); 

Msg 2601, Level 14, State 1, Line 1

Cannot insert duplicate key row in object 'dbo.ActionCheck' with unique index 'UQ_ActionCheck_ActionID'. The duplicate key value is (6).

The statement has been terminated.

+0

我喜欢使用索引视图在多个相互关联的表之间强制约束的创造性,但我不禁想到仍然会有这样做的某种开销。我不知道如何比较使用触发器的性能? – Dan 2014-11-21 13:22:17

+0

Re'ActionID':它只需要'Action'有一个主键,这是什么都没有关系,我想我在答案中已经介绍了这一点 - *“我已经使用ActionID作为你的Action的隐含主键表,但你可能需要改变这个“* – GarethD 2014-11-21 14:01:51

+1

重新性能:我不希望索引视图执行比触发器更差,以相同的方式外键约束比在触发器中完成相同的检查更有效。我为[这个答案](http://stackoverflow.com/a/26785510/1048425)运行了一些类似场景的测试,发现维护索引视图比使用触发器便宜,但是,我不认为这是一个适合所有人的解决方案,并且会有一些情况(这可能是一种情况)触发器的性能会更好。因人而异。 – GarethD 2014-11-21 14:02:45

0

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