Sql 实现表级检查约束

Sql 实现表级检查约束,sql,oracle,relational-database,constraints,range,Sql,Oracle,Relational Database,Constraints,Range,我们有一个表,其中包含的价格取决于基本金额。例如,如果基本金额小于或等于100,则价格为10;如果基本金额大于100,但小于或等于1000,则价格为20;最后,如果基本金额大于1000,则价格为30。此表的简化版本应如下所示: PRICE_CODE START_RANGE END_RANGE PRICE_AMOUNT 100 0,00 100,00 10,00 100 100,01

我们有一个表,其中包含的价格取决于基本金额。例如,如果基本金额小于或等于100,则价格为10;如果基本金额大于100,但小于或等于1000,则价格为20;最后,如果基本金额大于1000,则价格为30。此表的简化版本应如下所示:

PRICE_CODE START_RANGE     END_RANGE  PRICE_AMOUNT 
100               0,00        100,00         10,00           
100             100,01       1000,00         20,00          
100            1000,01   99999999,99         30,00           
110               0,00   99999999,99         15,00 
SELECT * FROM (              
SELECT price_code, start_range, end_range, price_amount
     , lag (end_range) OVER (PARTITION BY price_code ORDER BY end_range) prev_end
     , lead (start_range) OVER (PARTITION BY price_code ORDER BY start_range) next_start
  FROM my_test
ORDER BY price_code, start_range, end_range) 
 WHERE start_range <= prev_end
    OR end_range >= next_start
    OR (next_start - end_range) > 0.01
    OR (start_range - prev_end) > 0.01
CREATE TABLE T (
    PRICE_CODE int not null,
    START_RANGE decimal(10,2) null,
    END_RANGE decimal(10,2) null,
    constraint UQ_T_START UNIQUE (PRICE_CODE,START_RANGE),
    constraint UQ_T_END UNIQUE (PRICE_CODE,END_RANGE),
    constraint FK_T_PREV FOREIGN KEY (PRICE_CODE,START_RANGE) references T (PRICE_CODE,END_RANGE),
    constraint FK_T_NEXT FOREIGN KEY (PRICE_CODE,END_RANGE) references T (PRICE_CODE,START_RANGE),
    constraint CK_T_SANERANGE CHECK (START_RANGE < END_RANGE)
)
使用列级别检查约束,您可以轻松确保每个记录都包含有效的范围信息。问题是,我们还需要某种表级检查约束,以确保每个价格代码的范围信息不包含任何重叠或间隙,如本例所示:

PRICE_CODE START_RANGE     END_RANGE  PRICE_AMOUNT 
100               0,00        200,00         10,00           
100             100,01       1000,00         20,00          
100            1100,01   99999999,99         30,00           
我创建了一个有效的验证过程,但问题是我在数据库中找不到任何地方可以调用验证逻辑。当然,您不能放置在记录级触发器中,但当可以单独执行插入、更新和删除操作时,语句级触发器也不能工作,并且只能为最终结果验证范围。验证逻辑应该是这样的:

PRICE_CODE START_RANGE     END_RANGE  PRICE_AMOUNT 
100               0,00        100,00         10,00           
100             100,01       1000,00         20,00          
100            1000,01   99999999,99         30,00           
110               0,00   99999999,99         15,00 
SELECT * FROM (              
SELECT price_code, start_range, end_range, price_amount
     , lag (end_range) OVER (PARTITION BY price_code ORDER BY end_range) prev_end
     , lead (start_range) OVER (PARTITION BY price_code ORDER BY start_range) next_start
  FROM my_test
ORDER BY price_code, start_range, end_range) 
 WHERE start_range <= prev_end
    OR end_range >= next_start
    OR (next_start - end_range) > 0.01
    OR (start_range - prev_end) > 0.01
CREATE TABLE T (
    PRICE_CODE int not null,
    START_RANGE decimal(10,2) null,
    END_RANGE decimal(10,2) null,
    constraint UQ_T_START UNIQUE (PRICE_CODE,START_RANGE),
    constraint UQ_T_END UNIQUE (PRICE_CODE,END_RANGE),
    constraint FK_T_PREV FOREIGN KEY (PRICE_CODE,START_RANGE) references T (PRICE_CODE,END_RANGE),
    constraint FK_T_NEXT FOREIGN KEY (PRICE_CODE,END_RANGE) references T (PRICE_CODE,START_RANGE),
    constraint CK_T_SANERANGE CHECK (START_RANGE < END_RANGE)
)

一种方法当然是将验证逻辑放在数据访问层中,但是仍然可以通过直接使用SQL绕过验证。我感兴趣的是,是否有人知道如何在数据库中实现这种表级约束,以确保没有人能够提交无效的范围数据。我们使用的是Oracle,因此我对基于Oracle的解决方案感兴趣,但我也感兴趣的是其他RDBMS是如何解决这个问题的。

是否需要结束范围列?结束\u范围值也可以是下一个较高的开始\u范围值。如果这样做,就不可能出现间隙和重叠。

我已经看到了一种利用快速刷新物化视图的表级或集合级约束实施方法的概念

其思想是将集合级需求转换为MV查询中的行级需求,然后对物化视图行应用传统的基于行的检查约束


例如,如果要将用户输入的数量限制为一定数量,请创建“按用户选择计数”组。查看,然后应用checkmv\u count\u column一种实现方法是使用相互引用的外键

要使其工作,您往往需要一个支持合并语句或延迟约束的数据库,并且唯一约束只允许一个NULL或一些解决方法

您要做的是首先切换到使用半开放区间表示范围。这样做的目的是,一个间隔的结束可以是对另一行开始的外键引用,反之亦然

如果我在任何地方使用方言,很可能是TSQL,而不是Oracle,因为我已经习惯了,但同样的概念也应该适用

创建的表如下所示:

PRICE_CODE START_RANGE     END_RANGE  PRICE_AMOUNT 
100               0,00        100,00         10,00           
100             100,01       1000,00         20,00          
100            1000,01   99999999,99         30,00           
110               0,00   99999999,99         15,00 
SELECT * FROM (              
SELECT price_code, start_range, end_range, price_amount
     , lag (end_range) OVER (PARTITION BY price_code ORDER BY end_range) prev_end
     , lead (start_range) OVER (PARTITION BY price_code ORDER BY start_range) next_start
  FROM my_test
ORDER BY price_code, start_range, end_range) 
 WHERE start_range <= prev_end
    OR end_range >= next_start
    OR (next_start - end_range) > 0.01
    OR (start_range - prev_end) > 0.01
CREATE TABLE T (
    PRICE_CODE int not null,
    START_RANGE decimal(10,2) null,
    END_RANGE decimal(10,2) null,
    constraint UQ_T_START UNIQUE (PRICE_CODE,START_RANGE),
    constraint UQ_T_END UNIQUE (PRICE_CODE,END_RANGE),
    constraint FK_T_PREV FOREIGN KEY (PRICE_CODE,START_RANGE) references T (PRICE_CODE,END_RANGE),
    constraint FK_T_NEXT FOREIGN KEY (PRICE_CODE,END_RANGE) references T (PRICE_CODE,START_RANGE),
    constraint CK_T_SANERANGE CHECK (START_RANGE < END_RANGE)
)
通过只允许一行具有空的起始值范围,只有一行可以表示最低的范围。同样,对于END_范围和最高范围。中间的所有行都必须引用其上一个和下一个范围行

您需要延迟约束或合并语句,因为为了(例如)在末尾插入新行,您需要插入引用上一行的此行,并更新上一行以引用所有要满足的约束的新行。这需要一个INSERT和一个UPDATE语句,两者之间不进行约束检查,或者一个MERGE语句可以在一个语句中同时完成这两个任务


如果您不想将最低和最高范围保留为未定义的边界,那么只需强制执行一条规则,即具有NULL START\u RANGE或END\u RANGE的行不代表有效范围。但是将这些行保留在表中,以允许上面的结构工作。

在标准SQL中,这将是创建断言,但我没有研究过任何实际实现了这一点的RDBMS。非常有趣的是,似乎没有RDBMS实现过这样的功能。这不是我第一次遇到多记录验证的需要。我相信没有人真正实现它的原因是,它很容易成为一个非常昂贵的成本,添加到每个涉及表的事务中,除非数据库系统能够找到一种方法,能够以增量方式对其进行评估时尚-这意味着对断言的严重限制或对表+1的严重限制-每个边界只需存储一次;在查询时可以很容易地导出范围。您是对的,这是解决此类问题的一种方法,但在我看来,这将使查询数据变得更加困难。选择开始值,从表t2中选择minstart值,其中t2.start\u值>t1.start\u值作为结束值fr
当然,这是一条路要走,但这并不是完整的解决方案。如何轻松获取最后一个范围的结束值?添加另一个开始值=的范围,或使用NVL,生成最后一个结束值有趣的解决方案,但我不认为这是我们的方法。我对快速刷新的物化视图没有任何经验,所以请您向我解释一下,当物化视图的检查约束失败时,表中的数据会发生什么情况?该语句将回滚,换句话说,您的意思是物化视图的刷新将在更新的同一事务中发生桌子的位置?我误报了你。当您发出commit语句时,mat视图将被刷新,因此,如果违反mat视图约束,整个事务将回滚。感谢您提供了非常有趣的解决方案。Oracle版本的UNIQUE constraint允许一个以上的空值,但存在解决该问题的方法。在我看来,这有一个小问题,那就是在以某种方式进行查询时必须解决的半开放区间。@PeterÅkerlund-它通常意味着做列Ok,我想当给出更多的时候,它应该非常简单。