具有自身外键的 Rails 模型

发布于 2024-10-30 10:02:47 字数 995 浏览 0 评论 0原文

我有一个 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

当我尝试保存第一个用户时,问题就出现了。尚不存在其他用户,但我不能有空的 editor_id 或 Creator_id。如果我尝试将编辑器和创建器设置为自身,则会出现堆栈溢出。

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

I have an Oracle DB schema which includes a "users" table. This table has two non-null foreign keys to an editor and creator which are also users.

The schema dump looks like this:

  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

My User model looks like the following:

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

  validates_presence_of :creator, :editor
end

The problem comes when I try to save the first User. No other users exist yet, but I cannot have a null editor_id or creator_id. If I try to set the editor and creator to itself, I get a stack overflow.

In theory, it makes sense that all users (except the first) have a creator and editor. Is there any way to accomplish this without temporarily removing the non-null constraint?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

北城挽邺 2024-11-06 10:02:47

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

“从理论上讲,所有这些都是有道理的
用户(除了第一个)有一个
创作者和编辑。有什么办法吗
实现这一目标无需暂时
删除非空约束?”

最关键的是,如果第一个用户没有 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 作为自己的编辑器。但是,有一个解决方法,那就是 <。 code>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 中处理分层数据。这一切都在文档中。 了解一下更多

So the problem is, there has to be a user at the top of the hierarchy, a user for whom there is no manager (editor in your example). That is why the classic solution to this sort of structure is to allow null values. You acknowledge this in your closing paragraph:

"In theory, it makes sense that all
users (except the first) have a
creator and editor. Is there any way
to accomplish this without temporarily
removing the non-null constraint?"

The kicker is, if the first user doesn't have a CREATOR or an EDITOR then there is no "temporary": you have to ditch the mandatory constraint. If you do this, the problem with the recursive foreign key constraint will disappear.


The alternative is to introduce what Aristotle called a Prime Mover, a User whose Creator is itself. Given this table:

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)
)
/

it's pretty simple to create such a user:

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

1 row created.

SQL> commit;

Commit complete.

SQL>

So why isn't this the canonical solution. Well it leads to a slightly wacky data model which can create havoc with hierarchical queries once we add a few more users.

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> 

Basically the database doesn't like USERID being its own editor. However, there is a workaround, which is the NOCYCLE keyword (introduced with 10g). This tells the database to ignore circular references in the hierarchy:

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>

Here it doesn't matter because the data is still correctly hierarchical. But what happens if we do this:

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

1 row updated.

SQL> 

We lose a relationship ( 1 -> 7). We can use the CONNECT_BY_ISNOCYCLE pseudo-column to see which row is cycling.

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 has lots of additional functionality to make it easier to work with hierarchical data in pure SQL. It is all in the documentation. Find out more.

野稚 2024-11-06 10:02:47

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

I would have thought you'd remove the NOT NULL constraint (i.e. allow the first user to have NULL for creator and editor).

You can then implement constraints to make sure that all subsequent entries are not null, e.g.:

CONSTRAINT creator_required CHECK (creator IS NOT NULL OR userid = 1)
CONSTRAINT editor_required CHECK (editor IS NOT NULL OR userid = 1)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文