繁体   English   中英

带有外键的Rails模型

[英]Rails model with foreign key to itself

我有一个包含“用户”表的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

我的用户模型如下所示:

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。 如果尝试将编辑器和创建器设置为自身,则会出现堆栈溢出。

从理论上讲,所有用户(第一个用户除外)都具有创建者和编辑者是有意义的。 有什么方法可以在不临时删除非null约束的情况下完成此操作?

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

“从理论上讲,所有用户(第一个用户除外)都具有创建者和编辑者是有道理的。在不临时消除非null约束的情况下,有什么方法可以做到这一点?”

更重要的是,如果第一个用户没有CREATOR或EDITOR,那么就没有“临时”:您必须放弃强制性约束。 如果您这样做,递归外键约束的问题将消失。


另一种方法是介绍亚里斯多德所说的“原动力”,即“创造者”本身的用户。 给定此表:

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的分层数据更容易使用。 全部在文档中。 了解更多

我以为您会删除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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM