2010-08-19 43 views
9

嗯,這是一個簡單的設計問題,我想了很多次,從來沒有找到一個令人滿意的解決方案。我的例子是用php-sql,但是這當然也適用於其他語言。數據庫設計:將sql數據庫鍵匹配到php常量中?

我有一個小的數據庫表,只包含很少的條目,而且幾乎從不需要更新。如該usertype表:

usertype_id (primary key) | name  | description 
---------------------------+------------+------------------- 
1       | 'admin' | 'Administrator' 
2       | 'reguser' | 'Registered user' 
3       | 'guest' | 'Guest' 
在PHP代碼

現在,我經常要檢查或比較我負責的用戶類型。由於用戶類型存儲在數據庫中,因此我可以:

1)從類實例化處的usertype表中選擇*,並將其存儲在數組中。
然後所有的ID都可用於代碼,我可以做一個簡單的選擇來獲得我需要的行。每次實例化該類時,該解決方案都需要一個數組和一個數據庫查詢。

$query = "SELECT info, foo FROM user WHERE usertype_id = ".$usertypes['admin']; 

2)使用name列選擇正確的usertype_id,所以我們可以與其他表有效連接。這是爲了1或多或少當量),但無需緩存整個用戶類型表在php對象:

$query = "SELECT info, foo FROM user JOIN usertype USING (usertype_id) WHERE usertype.name = 'admin' "; 

3)定義在用戶類型表匹配的按鍵時常量:

// As defines 
define("USERTYPE_ADMIN",1); 
define("USERTYPE_REGUSER",2); 

//Or as class constants 
const USERTYPE_ADMIN = 1; 
const USERTYPE_REGUSER = 2; 

然後做一個簡單的選擇。

$query = "SELECT info, foo FROM user WHERE usertype_id = " . USERTYPE_ADMIN; 

這可能是資源最有效的解決方案,但它是壞的保持,因爲你如果需要修改的用戶類型桌上的東西同時更新表和代碼..

4)廢棄usertype表,只保留php代碼中的類型。我不太喜歡這個,因爲它允許任何值進入數據庫並分配給用戶類型。但也許,所有的事情都考慮到了,它並沒有那麼糟糕,我只是讓一些簡單的事情變得複雜起來。

總而言之,總結一下我最喜歡的解決方案是#2,因爲它是連貫的,索引usertype.name,它不能那麼糟糕。但是我經常最終使用#3來提高效率。

你會怎麼做?有更好的解決方案

(編輯:在#2固定查詢)

回答

0

爲什麼不反規範化的數據庫表,而不是usertype_id的,你必須usertype與字符串類型(admin)。然後在PHP中,你可以做define('USERTYPE_ADMIN', 'admin');。如果你想添加一個用戶類型,它可以讓你不必修改兩個地方...

如果你真的擔心任何值進入,你總是可以使該列爲ENUM數據類型,所以它將自我管理...

+0

這將工作,如果類型的表將是由只有一個表中使用(如示例)。但是如果多個表需要使用相同的枚舉類型呢? – Vincent 2010-08-19 15:46:37

+0

那麼,你需要使用規範化的方法。然後你不停地查詢數據庫的值。要麼,要麼保持在兩個地方(這是不好玩的)...... – ircmaxell 2010-08-19 15:53:27

0

對於將包含「類型」值的表,特別是當期望這樣的表隨時間而改變時,我傾向於使用簡單的方法: 添加名爲hid的Varchar列(來自「人類可讀的ID」 )與唯一的關鍵。然後,我的id有意義填補它像人類一樣:

usertype_id (primary key) | name  | description  | hid (unique key) 
---------------------------+------------+-------------------+--------------- 
1       | 'admin' | 'Administrator' | 'admin' 
2       | 'reguser' | 'Registered user' | 'user' 
3       | 'guest' | 'Guest'   | 'guest' 

當你需要實際的ID,你會做基於HID列中選擇,即

select usertype_id from tablename where hid = "admin" 

這是不是一種有效的方法,但它將確保您的應用程序在不同部署中的兼容性(即,一個客戶端可能有1.admin,2. guest;其他客戶端1.admin,2.用戶等)。對於你的情況,我認爲#3是非常適合的,但如果你期望擁有超過10個不同的用戶角色 - 請嘗試「隱藏」方法。

+0

這就是我的解決方案#2。雖然我不得不承認我爲我的人類可讀id列('name')選擇的名稱不是很直觀。 – Vincent 2010-08-19 19:24:49

+0

哦,我的壞。被varchar列上的連接弄糊塗了(我認爲效率不高)。順便說一句,這個SQL語句會產生任何結果?! 關於這種方法還有一件事:最好限制它可以包含的值,如: ereg(「^ [az] {1} [a-z0-9 _] {0,254} $」,$ hid) 即允許小寫,字母數字,下劃線。 如果您不是唯一可以輸入值的人,那麼這很有用...... – Ognyan 2010-08-20 11:54:52

+0

是的,名稱列應該是唯一的索引,否則它的效率會非常低。 你是對的#2中的查詢是錯誤的:S - 只修復它,謝謝! – Vincent 2010-08-20 13:45:52

0

你在這裏使用任何一種框架?這些值是否可以存儲在一個單獨的源文件中 - 一個配置文件 - 它既可以在PHP中創建對象列表,也可以在引導數據庫時填充表格?我從Rails的角度思考,因爲我寫了任何PHP已經有一段時間了。解決方案可能會有固定裝置。

0

爲什麼不讓它只是

foreach (getdbarr("SELECT * FROM usertype") as $row) { 
    define($row['name'],$row['id']); 
} 
0

你不應該需要在每一個查詢中使用聯接獲取關於類型/角色的信息。您可以將數據訪問對象(DAO)中的'用戶'模型和'角色'模型分開 - 特別是因爲用戶類型的記錄太少。

在大多數情況下,如果我擁有的數據量有限,那麼我會將它們作爲關聯數組緩存在memcached中。如果我需要一些關於特定關係的信息(比如角色),我只是懶惰地加載它。

$user = DAO_User::get(1); // this pulls a JOIN-less record 
$role = $user->getRole(); // lazy-load 

代碼爲$用戶> getRole()可以是這樣的:

public function getRole() { 
    // This comes from a cache that may be called multiple 
    // times per request with no penalty (i.e. store in a registry) 
    $roles = DAO_UserRoles::getAll(); 

    if(isset($roles[$this->role_id])) 
    return $roles[$this->role_id]; 

    return null; // or: new Model_UserRole(); 
} 

,如果你想用1000級的用戶在其上顯示一個列表這也適用。您可以簡單地從單個$ roles關聯數組呈現該列的值。

這是對SQL端的一個重大性能改進,它可以大大減少代碼庫的複雜性。如果你在用戶表上還有其他幾個外鍵,你仍然可以使用這種方法在需要時獲取必要的信息。這也意味着您可以擁有可靠的Model_ *類,而無需爲可能加入的每個可能的表組合創建混合,這比簡單地獲取結果集,迭代和釋放結果集要好得多。

即使JOIN兩邊有超過100行,仍可以使用延遲加載方法來處理不頻繁或高度冗餘的信息。在您的代碼中使用合理的緩存服務時,多次調用DAO_UserRole :: get(1500)不會有任何損失,因爲在同一請求期間後續調用不應該兩次訪問數據庫。在大多數情況下,您只會在1000s中每頁顯示10-25行,並且延遲加載會使您的數據庫引擎不必在實際需要它們之前加入所有無關行。

做一個JOIN的主要原因是如果你的WHERE邏輯需要它,或者如果你需要從一個外鍵的ORDER BY數據。治療JOIN成本過高是一個好習慣。

1

我幾乎總是選擇3)。您可以根據數據庫中可用的內容自動生成所需的代碼。您唯一需要記住的是您必須運行腳本以在添加其他角色時更新/重寫該信息(但如果您使用phing或類似的構建工具來部署應用程序,只需添加構建規則對於您的部署腳本,它將始終在您部署代碼時運行:p)。

0

對於基本靜態的查找表,我通常會創建靜態常量文件(例如#3)。我一般用類,如:

namespace Constants; 
class UserTypes { 
    const ADMIN = 1; 
    const USER = 2; 
    const GUEST = 3; 
} 

$id = Constants\UserTypes::ADMIN; 

當我使用查找需要的是有點多變量,然後我會拉入一個對象,然後緩存它24小時。這樣它每天只會更新一次。這樣可以避免數據庫往返,但可以讓您輕鬆處理代碼中的事情。

0

是的,你是對的,避免#3和堅持#2。儘可能多地查找,就像使用usertype表來包含角色,然後使用id值將它們與用戶表關聯時,應該保留在數據庫中。如果你使用常量,那麼數據必須總是依賴你的php代碼來解釋。此外,您可以通過使用外鍵(服務器允許的地方)來強制執行數據完整性,並且允許您將報告從您的php代碼移植到其他報告工具。維護也變得更容易。如果您使用#3,數據庫管理員將不需要知道php以獲取數字的含義,他們是否曾被要求協助開發報表。它可能看起來不太相關,但在維護方面,使用存儲過程而不是嵌入式sql在你的php代碼中也會以多種方式維護友好,並且對DBA也是有利的。

0

我會選擇#2並使用連接,因爲它打算使用。你永遠不知道未來會發生什麼,今天做好準備總是更好!

關於儘可能多地爲這些操作留下數據庫,從長遠來看也有可能進行緩存。對於這條路線,在PHP中,一個選項是使用文件緩存,只有在需要時纔會更新。對於我創建的框架,這裏是一個例子。我很想知道是什麼人認爲:

注:

(LStore,LFetch,用GetFileName)屬於哪個被靜態調用緩存對象。

(Blobify和Unblobify)屬於SystemComponent對象這始終是活着

每條高速緩存數據的具有密鑰。這是你唯一需要記住的東西

public function LStore($key,$data, $blnBlobify=true) { 
    /* Opening the file in read/write mode */ 
    $h = fopen(self::GetFileName($key, 'longstore'),'a+'); 
    if (!$h) throw new Exception('Could not write to cache'); 
    flock($h,LOCK_EX); // exclusive lock, will get released when the file is closed 
    fseek($h,0); // go to the start of the file 
    /* truncate the file */ 
    ftruncate($h,0); 
    if($blnBlobify==true) { $data = SystemComponent::Blobify(array($data)); } 
    If (fwrite($h,$data)===false) { 
     throw new Exception('Could not write to cache'); 
    } 
    fclose($h); 
} 
public function LFetch($key) { 
    $filename = self::GetFileName($key, 'longstore'); 
    if (!file_exists($filename)){ return false;} 
    $h = fopen($filename,'r'); 
    if (!$h){ return false;} 
    /* Getting a shared lock */ 
    flock($h,LOCK_SH); 
    $data = file_get_contents($filename); 
    fclose($h); 
    $data = SystemComponent::Unblobify($data); 
    if (!$data) { 
     /* If unserializing somehow didn't work out, we'll delete the file */ 
     unlink($filename); 
     return false; 
    } 
    return $data; 
} 
/* This function is necessary as the framework scales different directories */ 

private function GetFileName($key, $strCacheDirectory='') { 
    if(!empty($strCacheDirectory)){ 
     return SystemComponent::GetCacheAdd() . $strCacheDirectory.'/' . md5($key); 
    } else {  
     return SystemComponent::GetCacheAdd() . md5($key); 
    } 
} 
public function Blobify($Source){ 
    if(is_array($Source)) { $Source = serialize($Source); } 

    $strSerialized = base64_encode($Source); 

    return $strSerialized; 

} 
public function Unblobify($strSerialized){ 
    $Decoded = base64_decode($strSerialized); 
    if(self::CheckSerialized($Decoded)) { $Decoded = unserialize($Decoded); } 

    return $Decoded;  

} 
function CheckSerialized($Source){ 
    $Data = @unserialize($Source); 
    if ($Source === 'b:0;' || $Data !== false) { 
     return true; 
    } else { 
     return false; 
    } 
} 

現在談到訪問實際數據時,我只需要調用fetch。爲了確保它是最新的,我告訴它存儲。在你的情況下,這將是在更新usertype表之後。

+0

我知道這可能看起來像你的需要的矯枉過正,但它可以一次又一次地在整個網站使用。 – Simon 2011-11-24 13:18:01

1

我建議#3,以避免無用的查詢,並防止行爲變化的風險,如果現有的數據庫錶行順帶修改:

  • 模型類添加必要的常量:

    class Role // + use namespaces if possible 
    { 
        // A good ORM could be able to generate it (see @wimvds answer) 
        const ADMIN = 1; 
        const USER = 2; 
        const GUEST = 3; 
    
        //... 
    

    }

  • 然後查詢像這樣有意義的:

    $query = "SELECT info, foo FROM user WHERE role_id = ".Role::ADMIN; 
    

    使用ORM(例如, Propel在下面的例子中),你會最終做:

    $isAdminResults = UserQuery::create()->filterByRoleId(Role::ADMIN);