楼主: newkid

[精华] 利用11G的虚拟列实现复杂的外键约束

[复制链接]
论坛徽章:
519
奥运会纪念徽章:垒球
日期:2008-09-15 01:28:12生肖徽章2007版:鸡
日期:2008-11-17 23:40:58生肖徽章2007版:马
日期:2008-11-18 05:09:48数据库板块每日发贴之星
日期:2008-11-29 01:01:02数据库板块每日发贴之星
日期:2008-12-05 01:01:03生肖徽章2007版:虎
日期:2008-12-10 07:47:462009新春纪念徽章
日期:2009-01-04 14:52:28数据库板块每日发贴之星
日期:2009-02-08 01:01:03生肖徽章2007版:蛇
日期:2009-03-09 22:18:532009日食纪念
日期:2009-07-22 09:30:00
11#
 楼主| 发表于 2010-7-8 04:07 | 只看该作者
在上贴的test_parent表中,因为delete_flag恒为N, 所以其实也可作为主键的一部分:

CREATE TABLE test_parent (
       parent_id    NUMBER(10)
      ,deleted_date DATE
      ,delete_flag  VARCHAR2(1) AS ('N')      -------- 虚拟一个永远为 N 的删除标记
       );

ALTER TABLE test_parent ADD CONSTRAINT test_parent_pk PRIMARY KEY (parent_id,delete_flag);

这样的话,原来的parent_id主键索引就可以省下来了。就是硬凑出个常量作主键显得有点怪。还有个副作用就是CBO不知道parent_id是唯一的,这样查询计划就会受影响。

[ 本帖最后由 newkid 于 2010-7-8 08:53 编辑 ]

使用道具 举报

回复
论坛徽章:
519
奥运会纪念徽章:垒球
日期:2008-09-15 01:28:12生肖徽章2007版:鸡
日期:2008-11-17 23:40:58生肖徽章2007版:马
日期:2008-11-18 05:09:48数据库板块每日发贴之星
日期:2008-11-29 01:01:02数据库板块每日发贴之星
日期:2008-12-05 01:01:03生肖徽章2007版:虎
日期:2008-12-10 07:47:462009新春纪念徽章
日期:2009-01-04 14:52:28数据库板块每日发贴之星
日期:2009-02-08 01:01:03生肖徽章2007版:蛇
日期:2009-03-09 22:18:532009日食纪念
日期:2009-07-22 09:30:00
12#
 楼主| 发表于 2010-7-8 04:48 | 只看该作者
7楼的那一个UPDATE语句:
UPDATE test_parent SET deleted_date = DATE '2010-6-1' WHERE parent_id=1;
顺利通过,没有引发子表上的校验,这是令人遗憾的地方,另外还有一个小小的隐患:
我们声明为DETERMINISTIC的函数f_parent_id, 其实是不确定的,传入相同的parent_id参数,返回值可能随父表的数据而变化。
就这点而言我们欺骗了ORACLE。如果在虚拟列上有索引,这个其实已经陈旧的数据就会被“物化”在索引中,假设某个查询条件用到了虚拟列,那么就可能返回错误答案。
因此如果使用了这个技巧,要注意不能把虚拟列用于查询。

使用道具 举报

回复
论坛徽章:
226
BLOG每日发帖之星
日期:2010-02-11 01:01:06紫蛋头
日期:2013-01-12 23:45:222013年新春福章
日期:2013-02-25 14:51:24问答徽章
日期:2013-10-17 18:06:40优秀写手
日期:2013-12-18 09:29:10马上有车
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上加薪
日期:2014-02-19 11:55:14
13#
发表于 2010-7-8 11:59 | 只看该作者
原帖由 newkid 于 2010-7-7 23:41 发表


杨兄可否把“将CATAGORY独立成维度表”的思路展开一下?是不是在我2楼例子的order_products表增加CATEGORY列?如果是这样也不简单,因为这个CATEGORY列必须和orders表的匹配,还要和products匹配。



怎么设计都会比较复杂:

CREATE TABLE products (
       product_id    NUMBER PRIMARY KEY
      ,product_name  VARCHAR2(20)
      ,category    VARCHAR2(20)
      ,primary key (product_id, category)
       );


CREATE TABLE orders (
       order_id      NUMBER
      ,category     VARCHAR2(20)         ----- 假设一个订单只能订购某一种类别的产品
    , primary key (order_id, category)
       );


CREATE TABLE order_products (   ---- 每个订单包含哪些产品
       order_id      NUMBER
       ,category       VARCHAR2(20)
      ,product_id    NUMBER      
      ,quantity      NUMBER
      ,PRIMARY KEY (order_id,product_id)
      ,FOREIGN KEY (ORDER_ID, CATEGORY) REFERENCES ORDERS
      ,FOREIGN KEY (PRODUCT_ID, CATEGORY) REFERENCES PRODUCTS
      );


其实完全靠约束是不可能解决所有数据问题的
虚拟列可以解决ORDER_PRODUCTS表数据的合法性问题,但是解决不了PRODUCTS表的合法性问题:
INSERT INTO products VALUES (7,'水彩笔','服装类');

使用道具 举报

回复
论坛徽章:
69
生肖徽章2007版:羊
日期:2008-11-14 14:42:19复活蛋
日期:2011-08-06 08:59:05ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:542012新春纪念徽章
日期:2012-02-13 15:13:202012新春纪念徽章
日期:2012-02-13 15:13:202012新春纪念徽章
日期:2012-02-13 15:13:202012新春纪念徽章
日期:2012-02-13 15:13:202012新春纪念徽章
日期:2012-02-13 15:13:20版主4段
日期:2012-05-15 15:24:11
14#
发表于 2010-7-8 18:02 | 只看该作者
牛!  才看到, 下班了, 先留个脚印, 再慢慢看.

嗯,不错, 又学到新东西了, 谢谢 newkid.

[ 本帖最后由 nyfor 于 2010-7-8 20:05 编辑 ]

使用道具 举报

回复
论坛徽章:
519
奥运会纪念徽章:垒球
日期:2008-09-15 01:28:12生肖徽章2007版:鸡
日期:2008-11-17 23:40:58生肖徽章2007版:马
日期:2008-11-18 05:09:48数据库板块每日发贴之星
日期:2008-11-29 01:01:02数据库板块每日发贴之星
日期:2008-12-05 01:01:03生肖徽章2007版:虎
日期:2008-12-10 07:47:462009新春纪念徽章
日期:2009-01-04 14:52:28数据库板块每日发贴之星
日期:2009-02-08 01:01:03生肖徽章2007版:蛇
日期:2009-03-09 22:18:532009日食纪念
日期:2009-07-22 09:30:00
15#
 楼主| 发表于 2010-7-8 22:12 | 只看该作者
原帖由 yangtingkun 于 2010-7-8 11:59 发表

怎么设计都会比较复杂:

CREATE TABLE products (
       product_id    NUMBER PRIMARY KEY
      ,product_name  VARCHAR2(20)
      ,category    VARCHAR2(20)
      ,primary key (product_id, category)
       );


CREATE TABLE orders (
       order_id      NUMBER
      ,category     VARCHAR2(20)         ----- 假设一个订单只能订购某一种类别的产品
    , primary key (order_id, category)
       );


CREATE TABLE order_products (   ---- 每个订单包含哪些产品
       order_id      NUMBER
       ,category       VARCHAR2(20)
      ,product_id    NUMBER      
      ,quantity      NUMBER
      ,PRIMARY KEY (order_id,product_id)
      ,FOREIGN KEY (ORDER_ID, CATEGORY) REFERENCES ORDERS
      ,FOREIGN KEY (PRODUCT_ID, CATEGORY) REFERENCES PRODUCTS
      );


其实完全靠约束是不可能解决所有数据问题的
虚拟列可以解决ORDER_PRODUCTS表数据的合法性问题,但是解决不了PRODUCTS表的合法性问题:
INSERT INTO products VALUES (7,'水彩笔','服装类');


这样一来你就有逻辑主键+业务主键构成的混合型主键,挺新颖的!
不过你也可以保留原来的id做逻辑主键,只要在id,category上再建唯一约束就可以了。代价就是会多一个索引。
这个'水彩笔'的例子也太霸道了吧,外键当然约束不了父表上的数据。不过有了这个外键之后,如果有人想在一批订货中包含'水彩笔'和其他文具,就会报错,从一定程度上也能帮助发现错误!

总结一下这个虚拟列作外键的用法,无非就是想在外键约束加入其他条件:
1. 如果这个条件可表示为同一行的列间关系:
在父表上可以用CASE表达式、甚至规则表达式凑出一个是否可用的标志位来。
在子表上同样可以用各种方法凑出标志位,如果不需要引用让它返回NULL, 如果需要引用而且合法则返回父表上的相同标志,如果不合法让它返回一个父表中没有的标记。
2. 如果这个条件是行间关系:想不出有什么办法。
3. 如果这个条件是表间关系:可以用伪装成DETERMINISTIC的函数,但由于它不是真正确定性的函数,所以有漏洞。

杨兄说那个delete_flag的例子其实更复杂,可否分享一下?

使用道具 举报

回复
论坛徽章:
519
奥运会纪念徽章:垒球
日期:2008-09-15 01:28:12生肖徽章2007版:鸡
日期:2008-11-17 23:40:58生肖徽章2007版:马
日期:2008-11-18 05:09:48数据库板块每日发贴之星
日期:2008-11-29 01:01:02数据库板块每日发贴之星
日期:2008-12-05 01:01:03生肖徽章2007版:虎
日期:2008-12-10 07:47:462009新春纪念徽章
日期:2009-01-04 14:52:28数据库板块每日发贴之星
日期:2009-02-08 01:01:03生肖徽章2007版:蛇
日期:2009-03-09 22:18:532009日食纪念
日期:2009-07-22 09:30:00
16#
 楼主| 发表于 2010-7-8 23:21 | 只看该作者
感谢 Frank Zhou 给我提供的资料,类似的思路早就有人实践过了:

http://asktom.oracle.com/pls/ask ... #678658300346935451

TOM 用了个简洁的例子证明它的威力:
ops$tkyte%ORA11GR1> create table c
  2  ( a varchar2(10),
  3    b as (substr( a, 5, 2 )) references p
  4  );

这位Lucas Jellema的博客:
http://technology.amis.nl/blog/2 ... oring-them-and-more
他的第一个例子和我#6的例子很像;
第二个例子逻辑上不合理:招聘的新员工必须检查同年是否招聘过,如果有则允许,没有则不允许。那么第一个就永远无法招进来了!
但他这个例子无非是想给读者一个启发。思路就是利用MVIEW复制旧数据,这样就把一些行间关系转换为表间关系。

他在另一篇还写了如何用虚拟列、自定义函数和CHECK约束实现一些复杂校验:
http://technology.amis.nl/blog/2 ... amic-business-rules


这位 Alex Nuijten 的博客:
http://technology.amis.nl/blog/3 ... -columns-the-sequel
他的用途和我在1楼的“统一编码表”相同,但实现方法相反,我是在子表上虚拟出“编码类型”,他是在父表上做文章,虚拟出很多不同的唯一键来。如果是子表数目多,用他的方法更胜一筹。

使用道具 举报

回复
论坛徽章:
55
ITPUB元老
日期:2009-12-05 20:26:01
17#
发表于 2010-7-9 11:02 | 只看该作者
如此神奇!

使用道具 举报

回复
论坛徽章:
226
BLOG每日发帖之星
日期:2010-02-11 01:01:06紫蛋头
日期:2013-01-12 23:45:222013年新春福章
日期:2013-02-25 14:51:24问答徽章
日期:2013-10-17 18:06:40优秀写手
日期:2013-12-18 09:29:10马上有车
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上加薪
日期:2014-02-19 11:55:14
18#
发表于 2010-7-9 16:39 | 只看该作者
原帖由 newkid 于 2010-7-8 22:12 发表


这样一来你就有逻辑主键+业务主键构成的混合型主键,挺新颖的!
不过你也可以保留原来的id做逻辑主键,只要在id,category上再建唯一约束就可以了。代价就是会多一个索引。
这个'水彩笔'的例子也太霸道了吧,外键当然约束不了父表上的数据。不过有了这个外键之后,如果有人想在一批订货中包含'水彩笔'和其他文具,就会报错,从一定程度上也能帮助发现错误!

总结一下这个虚拟列作外键的用法,无非就是想在外键约束加入其他条件:
1. 如果这个条件可表示为同一行的列间关系:
在父表上可以用CASE表达式、甚至规则表达式凑出一个是否可用的标志位来。
在子表上同样可以用各种方法凑出标志位,如果不需要引用让它返回NULL, 如果需要引用而且合法则返回父表上的相同标志,如果不合法让它返回一个父表中没有的标记。
2. 如果这个条件是行间关系:想不出有什么办法。
3. 如果这个条件是表间关系:可以用伪装成DETERMINISTIC的函数,但由于它不是真正确定性的函数,所以有漏洞。

杨兄说那个delete_flag的例子其实更复杂,可否分享一下?


其实从原理上你已经实现了,不过你的例子只考虑了主表利用DELETE_FLAG表示删除的情况,如果子表本身如果也用DELETE_FLAG表示删除的话,需要设计外键就会更复杂一些。

使用道具 举报

回复
论坛徽章:
226
BLOG每日发帖之星
日期:2010-02-11 01:01:06紫蛋头
日期:2013-01-12 23:45:222013年新春福章
日期:2013-02-25 14:51:24问答徽章
日期:2013-10-17 18:06:40优秀写手
日期:2013-12-18 09:29:10马上有车
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上加薪
日期:2014-02-19 11:55:14
19#
发表于 2010-7-9 17:02 | 只看该作者
想了一下,也不算太复杂:

CREATE TABLE T_FATHER
(ID NUMBER,
NAME VARCHAR2(30),
DELETE_FLAG VARCHAR2(1));

CREATE TABLE T_CHILDREN
(ID NUMBER,
FID NUMBER,
F_FLAG AS (CAST (DECODE(DELETE_FLAG, 'Y', NULL, 'N') AS VARCHAR2(1))),
NAME VARCHAR2(30),
DELETE_FLAG VARCHAR2(1));

ALTER TABLE T_FATHER ADD
PRIMARY KEY (ID, DELETE_FLAG);
ALTER TABLE T_CHILDREN ADD
FOREIGN KEY (FID, F_FLAG)
REFERENCES T_FATHER;

使用道具 举报

回复
论坛徽章:
1088
金色在线徽章
日期:2007-04-25 04:02:08金色在线徽章
日期:2007-06-29 04:02:43金色在线徽章
日期:2007-03-11 04:02:02在线时间
日期:2007-04-11 04:01:02在线时间
日期:2007-04-12 04:01:02在线时间
日期:2007-03-07 04:01:022008版在线时间
日期:2010-05-01 00:01:152008版在线时间
日期:2011-05-01 00:01:342008版在线时间
日期:2008-06-03 11:59:43ITPUB年度最佳技术原创精华奖
日期:2013-03-22 13:18:30
20#
发表于 2010-7-9 23:30 | 只看该作者
牛,好帖子要经常看看!

使用道具 举报

回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

TOP技术积分榜 社区积分榜 徽章 团队 统计 知识索引树 积分竞拍 文本模式 帮助
  ITPUB首页 | ITPUB论坛 | 数据库技术 | 企业信息化 | 开发技术 | 微软技术 | 软件工程与项目管理 | IBM技术园地 | 行业纵向讨论 | IT招聘 | IT文档
  ChinaUnix | ChinaUnix博客 | ChinaUnix论坛
CopyRight 1999-2011 itpub.net All Right Reserved. 北京盛拓优讯信息技术有限公司版权所有 联系我们 未成年人举报专区 
京ICP备16024965号-8  北京市公安局海淀分局网监中心备案编号:11010802021510 广播电视节目制作经营许可证:编号(京)字第1149号
  
快速回复 返回顶部 返回列表