2011-04-05 52 views
1

我有一個包含「用戶」表的Oracle數據庫模式。這個表有兩個非空的外鍵給編輯和創建者,它們也是用戶。帶外鍵的Rails模型本身

架構轉儲看起來是這樣的:

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。如果我嘗試將編輯器和創建器設置爲自身,則會發生堆棧溢出。

從理論上講,所有用戶(第一個除外)都有創建者和編輯者是有道理的。有沒有什麼辦法可以在不暫時刪除非空約束的情況下完成這個任務?

+0

這是雞和蛋的問題類型是這樣,使外鍵有點討厭的。大多數Rails應用程序不會將它們用於慣例之外,這會讓人頭痛不已,並且承認引用不一致的潛在風險。 – tadman 2011-04-05 05:00:14

回答

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

我原以爲你會刪除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)