Rails模型与外键本身

时间:2011-04-05 04:53:03

标签: ruby-on-rails oracle database-design foreign-keys

我有一个包含“用户”表的Oracle数据库架构。此表有两个非空外键给编辑器和创建者,它们也是用户。

架构转储如下所示:

  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

我的用户模型如下所示:

class User < ActiveRecord::Base
  belongs_to :creator, :class_name => "User"
  belongs_to :editor, :class_name => "User"

  validates_presence_of :creator, :editor
end

当我尝试保存第一个用户时出现问题。还没有其他用户存在,但我没有null editor_id或creator_id。如果我尝试将编辑器和创建者设置为自身,则会出现堆栈溢出。

理论上,所有用户(第一个除外)都有创建者和编辑者。有没有办法在不暂时删除非空约束的情况下完成此操作?

2 个答案:

答案 0 :(得分:5)

所以问题是,层次结构顶部必须有一个用户,没有经理的用户(在你的例子中为编辑器)。这就是为什么这种结构的经典解决方案是允许空值。您在结束段中承认这一点:

  

“从理论上讲,所有人都有道理   用户(第一个除外)有一个   创作者和编辑。有什么办法吗?   没有暂时完成这个   删除非空约束?“

踢球者是,如果第一个用户没有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)
)
/

创建这样的用户非常简单:

SQL> insert into t72 values (1,1,1)
  2  /

1 row created.

SQL> commit;

Commit complete.

SQL>

那么为什么这不是规范的解决方案呢。好吧,它导致了一个稍微古怪的数据模型,一旦我们添加了更多的用户,它就会对分层查询造成破坏。

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> 

基本上,数据库不喜欢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>

这里没关系,因为数据仍然是正确的分层。但是如果我们这样做会发生什么:

SQL> update t72 set editor = 7
  2  where userid = 1
  3  /

1 row updated.

SQL> 

我们失去了关系(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>  

Oracle具有许多其他功能,可以更轻松地在纯SQL中使用分层数据。这一切都在文档中。 Find out more

答案 1 :(得分:1)

我原以为你会删除NOT NULL约束(即允许第一个用户为创建者和编辑者设置NULL)。

然后,您可以实现约束以确保所有后续条目都不为空,例如:

CONSTRAINT creator_required CHECK (creator IS NOT NULL OR userid = 1)
CONSTRAINT editor_required CHECK (editor IS NOT NULL OR userid = 1)