2010-08-19 18 views
9

Bene, questa è una domanda di design semplice che mi sono chiesto molte volte e non ho mai trovato una soluzione soddisfacente. Il mio esempio è con php-sql, ma questo vale certamente anche per altre lingue.Progettazione database: chiavi di database sql corrispondenti alle costanti php?

Ho una piccola tabella di database contenente solo pochissime voci e che non ha quasi mai bisogno di essere aggiornata. ad esempio questo usertype tabella:

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

Ora nel codice php, ho spesso devono verificare o confrontare il tipo di utente ho a che fare. Poiché i tipi di utente sono memorizzati nel database, posso:

1) Selezionare * dalla tabella di tipi di utente all'istanza di classe e memorizzarlo in una matrice.
Quindi tutti gli ID sono disponibili per il codice e posso fare una semplice selezione per ottenere le righe che mi servono. Questa soluzione richiede una matrice e una query db ogni volta che la classe viene istanziata.

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

2) utilizzare la colonna name per selezionare la corretta usertype_id, in modo che possiamo efficacemente unirsi con altre tabelle. Questo è più o meno equivalente a 1), ma senza la necessità di memorizzare nella cache l'intera tabella usertype nell'oggetto php:

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

3) Definire costanti corrispondenti ai tasti nella tabella tipo utente:

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

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

E poi fai una semplice selezione.

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

Questa è probabilmente la soluzione più efficiente delle risorse, ma è male per mantenere, come è necessario aggiornare sia la tabella e il codice, se è necessario modificare qualcosa nella tabella usertype ..

4) Elimina la tabella usertype e mantieni i tipi solo nel codice php. Non mi piace molto perché consente a qualsiasi valore di entrare nel database e di essere assegnato al tipo di utente. Ma forse, tutto sommato, non è così male e sto solo complicando qualcosa che dovrebbe essere semplice ..

In ogni caso, per riassumere la soluzione che mi piace di più è # 2 perché è coerente e con un indice su usertype.name, non può essere così male. Ma quello che ho finito per usare è # 3, per efficienza.

Come lo faresti? Qualche soluzione migliore?

(edit: interrogazione fisso a # 2)

risposta

0

Perché non denormalizzare tabella DB così invece di avere usertype_id, avresti usertype con il tipo di stringa (admin). Quindi in PHP puoi semplicemente fare define('USERTYPE_ADMIN', 'admin');. Ti evita di dover modificare due posizioni se vuoi aggiungere un tipo di utente ...

E se sei davvero preoccupato per qualsiasi valore, puoi sempre rendere la colonna un tipo di dati ENUM, quindi autogestire ...

+0

Ciò funzionerà se la tabella di tipo sta per essere utilizzato da un solo tavolo (come nell'esempio). Ma cosa succede se più tabelle devono utilizzare lo stesso tipo di enumerazione? – Vincent

+0

Bene, allora dovrai usare il metodo normalizzato. E poi sei bloccato o interrogando costantemente il DB per i valori. O quello, o mantenendolo in due posti (che non è divertente) ... – ircmaxell

0

Per le tabelle che conterranno valori di "tipo" soprattutto quando è previsto che tale tabella cambi nel tempo, io tendo ad utilizzare un approccio semplice: Aggiungi colonna Varchar denominata hid (deriva da "id leggibile")) con chiave unica.Poi ho riempirlo con id significativo per gli esseri umani come:

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

Quando è necessario l'id reale si dovrà fare selezionare sulla base di colonna HID, cioè

select usertype_id from tablename where hid = "admin" 

Questo non è un approccio efficiente ma assicurerà la compatibilità della tua applicazione tra diverse distribuzioni (ad esempio un client può avere 1.admin, 2. ospite, altro client 1.admin, 2. utente, ecc.). Per il tuo caso, penso che il # 3 sia abbastanza adatto, ma se ti aspetti di avere più di 10 ruoli utente diversi, prova l'approccio "nascosto".

+0

Questo è quello che intendevo con la soluzione n. 2. Anche se devo ammettere che il nome che ho scelto per la mia colonna id leggibile dall'uomo ('name') non è molto intuitivo. – Vincent

+0

Oh, mio ​​male. Mi sono confuso dal join sulla colonna varchar (non efficiente penso). btw questa affermazione sql produrrà alcun risultato ?! Un'altra cosa su questo approccio: è una buona idea limitare i valori che può contenere con qualcosa di simile: ereg ("^ [az] {1} [a-z0-9 _] {0,254} $", $ nascosto) vale a dire minuscolo, alfanumerico, underscore consentito. È utile se non sarai l'unica persona in grado di inserire valori ... – Ognyan

+0

Sì, la colonna nome dovrebbe essere un indice univoco, altrimenti sarebbe piuttosto inefficiente. Hai ragione la query in # 2 è errata: S - l'ho appena aggiustata, grazie! – Vincent

0

Stai usando qualche tipo di struttura qui? Questi valori potrebbero essere archiviati in una singola fonte - un file di configurazione - che crea entrambi un elenco degli oggetti in PHP e popola la tabella anche quando si avvia il database? Sto pensando da una prospettiva Rails, dato che è da un po 'che non scrivo alcun PHP. La soluzione ci sarebbe probabilmente da fare.

0

Perché non farlo proprio

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

Non dovrebbe essere necessario un JOIN in ogni query per recuperare le informazioni sui tipi/ruoli. È possibile mantenere separati il ​​modello "utente" e i modelli "ruolo" negli oggetti di accesso ai dati (DAO), in particolare perché ci sono così pochi record per i tipi di utenti.

Nella maggior parte dei casi in cui dispongo di un numero limitato di opzioni che altrimenti dovrei associare a una tabella di grandi dimensioni, le ho memorizzate nella cache in memcached come array associativo. Nel caso in cui avessi bisogno di alcune informazioni su una particolare relazione (come un ruolo), sono solo pigro a caricarlo.

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

Il codice per $ user-> getRole() può essere qualcosa del tipo:

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(); 
} 

Questo funziona anche se si desidera visualizzare un elenco con 1000 utenti su di esso. È possibile semplicemente visualizzare i valori per quella colonna da un singolo array associativo $ roles.

Questo è un importante miglioramento delle prestazioni sul lato SQL, e fa molto per ridurre la complessità nella base di codice. Se nella tabella degli utenti sono presenti diverse altre chiavi esterne, è comunque possibile utilizzare questo approccio per acquisire le informazioni necessarie quando necessario. Significa anche che puoi avere classi Model_ * affidabili senza dover creare ibridi per ogni possibile combinazione di tabelle che potresti JOIN - il che è molto meglio che ottenere semplicemente un set di risultati, iterandolo e liberandolo.

Anche con più di 100 righe su entrambi i lati del JOIN, è ancora possibile utilizzare l'approccio del carico lento per informazioni poco frequenti o altamente ridondanti. Con un ragionevole servizio di memorizzazione nella cache nel codice, non è prevista alcuna penalità per la chiamata a DAO_UserRole :: get (1500) più volte poiché le chiamate successive durante la stessa richiesta non dovrebbero colpire il database due volte. Nella maggior parte dei casi visualizzerai solo 10-25 righe per pagina su 1000, e il caricamento lento salverà il tuo motore di database dal dover accedere a tutte le righe estranee prima che tu ne abbia effettivamente bisogno.

Il motivo principale per eseguire un JOIN è se la logica WHERE lo richiede o se è necessario ORDER BY dati da una chiave esterna. Trattare i JOIN come proibitivi è una buona abitudine.

1

Quasi sempre scelgo l'opzione 3). È possibile generare automaticamente il codice necessario in base a ciò che è disponibile nel DB.L'unica cosa che devi ricordare è che devi eseguire lo script per aggiornare/riscrivere quelle informazioni quando aggiungi un altro ruolo (ma se stai usando phing o uno strumento simile per distribuire le tue app, aggiungi semplicemente una regola di costruzione per il tuo script di distribuzione e verrà sempre eseguito ogni volta che si distribuisce il codice: p).

0

Per tabelle di ricerca statiche di base, in genere faccio file statici e costanti (come il n. 3). Io generalmente uso classi come:

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

$id = Constants\UserTypes::ADMIN; 

Quando sto utilizzando la ricerca assume che sono un po 'più variabile, poi mi tirerò in un oggetto e poi in cache per 24 ore. In questo modo viene aggiornato solo una volta al giorno. Ciò ti eviterà di effettuare round trip del database, ma ti consentirà di gestire facilmente le cose nel codice.

0

Sì, hai ragione a evitare il n. 3 e il n. 2. Per quanto possibile, le ricerche come quando si utilizza una tabella usertype per contenere i ruoli e quindi correlarle alla tabella utente utilizzando i valori id devono rimanere nel database. Se si utilizzano le costanti, i dati devono sempre basarsi sul codice php per essere interpretati. Inoltre, è possibile rafforzare l'integrità dei dati utilizzando chiavi esterne (laddove i server lo consentono) e consente di trasferire i report dal codice php ad altri strumenti di reporting. Anche la manutenzione diventa più facile. Gli amministratori di database non avranno bisogno di conoscere php per ricavare il significato dei numeri se si è utilizzato il # 3, nel caso si dovesse mai chiedere aiuto allo sviluppo di report. Potrebbe non sembrare troppo rilevante, ma in termini di manutenzione, l'utilizzo di stored procedure rispetto a sql incorporato nel codice php sarebbe anche di facile manutenzione in vari modi e sarà anche vantaggioso per gli amministratori di database.

0

Vorrei andare per l'opzione n. 2 e utilizzare il join in quanto è destinato a essere utilizzato. Non sai mai cosa vomiterà il futuro, è sempre meglio essere preparati oggi!

Per quanto riguarda la possibilità di lasciare il database il più possibile per tali operazioni, esiste anche la possibilità di effettuare il caching a lungo termine. Per questa rotta, all'interno di PHP, un'opzione consiste nell'utilizzare una cache di file, una che verrà aggiornata solo quando il tempo lo richiede. Per il framework che ho creato, ecco un esempio; Sarei curioso di sapere cosa pensa la gente:

Nota:

(LStore, LFetch, GetFileName) appartengono ad un oggetto di cache che viene chiamato in modo statico.

(Blobify e Unblobify) appartengono ad un oggetto SystemComponent che è sempre vivo

Ogni pezzo di dati della cache ha una chiave. questa è l'unica cosa che mai necessario ricordare

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; 
    } 
} 

Ora, quando si tratta di accedere ai dati effettivi, mi basta chiamare un recupero. Per assicurarmi che sia aggiornato, lo dico per memorizzare. Nel tuo caso, ciò avverrebbe dopo aver aggiornato la tabella usertype.

+0

So che questo potrebbe sembrare eccessivo per le tue necessità, ma può essere utilizzato più e più volte in tutto il sito. – Simon

1

Vorrei suggerire # 3 per evitare domande inutili, e prevenire il rischio di cambiamenti di comportamento se le righe della tabella DB esistenti tra l'altro modificati:

  • Aggiungendo le costanti necessarie nella classe del modello:

    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; 
    
        //... 
    

    }

  • Poi l'interrogazione come questo ha un senso:

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

    Con un ORM (ad es. Propel nell'esempio qui sotto) si finisce nel fare:

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