Aar*_*son 1 oracle database-design ruby-on-rails foreign-keys
我有一个Oracle DB模式,其中包含一个"用户"表.此表有两个非空外键给编辑器和创建者,它们也是用户.
架构转储如下所示:
create_table "users", :force => true do |t|
t.integer "creator_id", :precision => 38, :scale => 0, :null => false
t.integer "editor_id", :precision => 38, :scale => 0, :null => false
end
add_foreign_key "users", "users", :column => "creator_id", :name => "r_user_creatorid", :dependent => :nullify
add_foreign_key "users", "users", :column => "editor_id", :name => "r_user_editorid", :dependent => :nullify
Run Code Online (Sandbox Code Playgroud)
我的用户模型如下所示:
class User < ActiveRecord::Base
belongs_to :creator, :class_name => "User"
belongs_to :editor, :class_name => "User"
validates_presence_of :creator, :editor
end
Run Code Online (Sandbox Code Playgroud)
当我尝试保存第一个用户时出现问题.还没有其他用户存在,但我没有null editor_id或creator_id.如果我尝试将编辑器和创建者设置为自己,我会得到堆栈溢出.
从理论上讲,所有用户(第一个除外)都有创建者和编辑者是有道理的.有没有办法在不暂时删除非null约束的情况下完成此操作?
所以问题是,层次结构顶部必须有一个用户,没有经理的用户(在您的示例中为编辑器).这就是为什么这种结构的经典解决方案是允许空值.您在结束段落中承认这一点:
"理论上,所有用户(第一个除外)都有创建者和编辑器是有道理的.有没有办法在不暂时删除非空约束的情况下实现这一点?"
踢球者是,如果第一个用户没有CREATOR或EDITOR那么就没有"临时":你必须抛弃强制性约束.如果这样做,递归外键约束的问题将消失.
另一种方法是介绍亚里士多德所谓的Prime Mover,一个创造者本身的用户.鉴于此表:
create table t72
( userid number not null
, creator number not null
, editor number not null
, constraint t72_pk primary key (userid)
, constraint t72_cr_fk foreign key (creator)
references t72 (userid)
, constraint t72_ed_fk foreign key (editor)
references t72 (userid)
)
/
Run Code Online (Sandbox Code Playgroud)
创建这样的用户非常简单:
SQL> insert into t72 values (1,1,1)
2 /
1 row created.
SQL> commit;
Commit complete.
SQL>
Run Code Online (Sandbox Code Playgroud)
那么为什么这不是规范的解决方案呢.好吧,它导致了一个稍微古怪的数据模型,一旦我们添加了更多的用户,就会对分层查询造成破坏.
SQL> select lpad(' ', level-1)|| u.userid as userid
2 , u.name
3 , u.editor
4 from t72 u
5 connect by
6 prior userid = editor
7 start with userid=1
8 /
ERROR:
ORA-01436: CONNECT BY loop in user data
no rows selected
SQL>
Run Code Online (Sandbox Code Playgroud)
基本上数据库不喜欢USERID是它自己的编辑器.但是,有一个解决方法,即NOCYCLE关键字(10g引入).这告诉数据库忽略层次结构中的循环引用:
SQL> select lpad(' ', level-1)|| u.userid as userid
2 , u.name
3 , u.editor
4 from t72 u
5 connect by nocycle
6 prior userid = editor
7 start with userid=1
8 /
USERID NAME EDITOR
---------- ---------- ----------
1 ONE 1
2 TWO 1
3 THREE 2
4 FOUR 2
5 FIVE 2
6 SIX 2
7 SEVEN 6
7 rows selected.
SQL>
Run Code Online (Sandbox Code Playgroud)
这没关系,因为数据仍然是正确的分层.但如果我们这样做会发生什么:
SQL> update t72 set editor = 7
2 where userid = 1
3 /
1 row updated.
SQL>
Run Code Online (Sandbox Code Playgroud)
我们失去了一段感情(1 - > 7).我们可以使用CONNECT_BY_ISNOCYCLE伪列来查看哪一行正在循环.
SQL> select lpad(' ', level-1)|| u.userid as userid
2 , u.name
3 , u.editor
4 , connect_by_iscycle
5 from t72 u
6 connect by nocycle
7 prior userid = editor
8 start with userid=1
9 /
USERID NAME EDITOR CONNECT_BY_ISCYCLE
---------- ---------- ---------- ------------------
1 ONE 7 0
2 TWO 1 0
3 THREE 2 0
4 FOUR 2 0
5 FIVE 2 0
6 SIX 2 0
7 SEVEN 6 1
7 rows selected.
SQL>
Run Code Online (Sandbox Code Playgroud)
Oracle具有许多附加功能,可以更轻松地在纯SQL中使用分层数据.这一切都在文档中. 了解更多.