2010-05-06 7 views
5

Sto provando a comportarmi bene. Così, invece di utilizzare seguente sintassi SQL:Come nidificare i join con CakePHP?

select * 
from tableA INNER JOIN 
     tableB on tableA.id = tableB.tableA_id LEFT OUTER JOIN 
     (tableC INNER JOIN tableD on tableC.tableD_id = tableD.id) 
     on tableC.tableA_id = tableA.id 

mi piacerebbe utilizzare la CakePHP model->find(). Questo mi consentirà di usare anche lo Paginator, dal momento che non funzionerà con query SQL personalizzate per quanto ho capito (a meno che non abbiate hardcode una singola query di impaginazione sul modello che mi sembra un po 'inflessibile).

Quello che ho provato finora:

/* inside tableA_controller.php, inside an action, e.g. "view" */ 
$this->paginate['recursive'] = -1; # suppress model associations for now 
$this->paginate['joins'] = array(
    array(
     'table' => 'tableB', 
     'alias' => 'TableB', 
     'type' => 'inner', 
     'conditions' => 'TableB.tableA_id = TableA.id', 
    ), 
    array(
     'table' => 'tableC', 
     'alias' => 'TableC', 
     'type' => 'left', 
     'conditions' => 'TableC.tableA_id = TableA.id', 
     'joins' = array(# this would be the obvious way to do it, but doesn't work 
      array(
       'table' => 'tableD', 
       'alias' => 'TableD', 
       'type' => 'inner', 
       'conditions' => 'TableC.tableD_id = TableD.id' 
      ) 
     ) 
    ) 
) 

Cioè, nesting i join nella struttura. Ma questo non funziona (CakePHP solo ignora l'nidificato 'joins' elemento che era una sorta di quello che mi aspettavo, ma triste.

ho visto suggerimenti nei commenti su come fare subquery (nella clausola where) utilizzando un costruttore di dichiarazione . può un trucco simile essere utilizzato qui

+0

Perché deve essere annidato? Non puoi semplicemente fare la stessa cosa con tutti i join al massimo livello? –

+0

Vorrei poterlo fare, ma il risultato è diverso: voglio tutti i risultati del join interno di livello superiore con dati opzionali aggiunti al join interno nidificato. Se appiattisco questo, allora perdo tutte le righe (TableA inner join TableB) che non hanno TableD corrispondente ... –

+0

Wow è piuttosto complesso. Probabilmente cercherò di estendere find() in qualche modo per aggiungere alcuni parametri aggiuntivi –

risposta

2

Si scopre che non è possibile. Almeno non con la sintassi fornita sopra e non con CakePHP 1.2.6. Sono andato oltre la fonte (yay! A framework open source!) E ho trovato il file cake/libs/model/datasources/dbo_source.php che contiene il codice per i join.

tutto inizia con DboSource::renderStatement() che fa un superficiale piedi della matrice $query['joins'], sostituendo quelli join definizioni con frammenti SQL tramite DboSource::buildJoinStatement($join), che fa alcune riordino degli argomenti (riempire gli spazi vuoti, ecc) e poi chiama DboSource::renderJoinStatement per creare il frammento SQL di una clausola di join singolo.

me: Che dovrebbe essere facile da risolvere!

mi è stato detto di non modificare roba in cake/libs, così invece ho copiato il file dbo_source.php-app/models/datasources/ per la modifica.Poi ho preso la mia ascia e refactoring il superficiale passeggiata della matrice $query['joins'] in DboSource::renderStatement() in un nuovo metodo di DboSource::buildJoinStatementArray() conseguente questi due metodi:

function buildStatement($query, $model) { 
    $query = array_merge(array('offset' => null, 'joins' => array()), $query); 

    # refactored (extract method) to make recursion easier 
    $query['joins'] = $this->buildJoinStatementArray($query['joins']); 

    return $this->renderStatement('select', array(
     'conditions' => $this->conditions($query['conditions'], true, true, $model), 
     'fields' => implode(', ', $query['fields']), 
     'table' => $query['table'], 
     'alias' => $this->alias . $this->name($query['alias']), 
     'order' => $this->order($query['order']), 
     'limit' => $this->limit($query['limit'], $query['offset']), 
     'joins' => implode(' ', $query['joins']), 
     'group' => $this->group($query['group']) 
    )); 
} 
/** 
* Replaces the join statement array syntax with SQL join clauses. 
*/ 
function buildJoinStatementArray($joins) { 
    if (!empty($joins)) { 
     $count = count($joins); 
     for ($i = 0; $i < $count; $i++) { 
      if (is_array($joins[$i])) { 
       $joins[$i] = $this->buildJoinStatement($joins[$i]); # $joins[$i] now contains something like "LEFT JOIN users As User on User.group_id = Group.id" 
      } 
     } 
    } 
    return $joins; 
} 

Una volta ho avuto DboSource::buildJoinStatementArray(), era il momento di cambiare DboSource::buildJoinStatement() - tutto quello che ho fatto è stato aggiunto un assegno di $data['joins'] e di un metodo di rendering alternativo per quel caso:

function buildJoinStatement($join) { 
    $data = array_merge(array(
     'type' => null, 
     'alias' => null, 
     'table' => 'join_table', 
     'conditions' => array() 
    ), $join); 

    if (!empty($data['alias'])) { 
     $data['alias'] = $this->alias . $this->name($data['alias']); 
    } 
    if (!empty($data['conditions'])) { 
     $data['conditions'] = trim($this->conditions($data['conditions'], true, false)); 
    } 

    # allow for nested joins 
    if (!empty($data['joins']) and is_array($data['joins'])) { 
     $data['joins'] = $this->buildJoinStatementArray($data['joins']); 
     return $this->renderNestedJoinStatement($data); 
    } 
    else 
    { 
     return $this->renderJoinStatement($data); 
    } 
} 

Il nuovo renderNestedJoinStatement() metodo è abbastanza simile a DboSource::renderJoinStatement():

/** 
* Renders a final SQL JOIN that contains nested join statements 
* 
* @param array $data 
* @return string 
*/ 
function renderNestedJoinStatement($data) { 
    extract($data); 
    $nestedJoins = implode(' ', $joins); 
    return trim("{$type} JOIN ({$table} {$alias} {$nestedJoins})ON ({$conditions})"); 
} 
0

Se sto ottenendo questo diritto, hai le seguenti relazioni (si spera nei tuoi modelli):?

TableA hasMany TableB. 
TableA hasMany TableC. 

TableB belongsTo TableA. 

TableC belongsTo TableA. 
TableC belongsTo TableD. (might be hasOne) 

TableD hasMany TableC. (might be hasOne) 

Se si utilizza il comportamento contenibile (Lo consiglio vivamente, e lo metto a livello di app_model per tutti i modelli da ereditare), penso che tu possa fare qualcosa come è ...

$this->TableA->find(
    'all', 
    array(
    'contain' => array(
     'TableB', 
     'TableC' => array(
     'TableD' 
    ) 
    ), 
    'conditions' => array(...), 
    'order' => array(...) 
) 
); 

Se avete bisogno di scegliere campi specifici, allora avrete bisogno di specificarli nel parametro contenere, ad esempio qui mi limito campi restituiti del TableB:

$this->TableA->find(
    'all', 
    array(
    'contain' => array(
     'TableB' => array(
     'fields' => array(
      'field_1', 
      'field_2' 
     ), 
    ), 
     'TableC' => array(
     'TableD' 
    ) 
    ), 
    'conditions' => array(...), 
    'order' => array(...) 
) 
); 

La restituiti dati dovrebbero essere in questo modo:

[0] => array(
    [TableA] => array(
     [id] => 12, 
     [name] => 'Foo' 
    ), 
    [TableB] => array(
     [id] => 23, 
     [table_a_id] => 12, 
     [name] => 'Bah' 
    ), 
    [TableC] => array(
     [id] => 45, 
     [table_a_id] => 12, 
     [table_d_id] => 67, 
     [name] => 'Woo', 
     [TableD] => array(
     [0] => array(
      [id] => 67, 
      [table_a_id] => 12, 
      [name] => 'Wah' 
     ) 
    ) 
    ) 
) 

Tuttavia, non ho mai fatto questo, dove la tabella nidificata è il padre del contenitore (presentate e TableC), quindi potrebbe non funzionare, ma è probabilmente la pena di provare .

+0

Questo non è esattamente ciò che fa un join esterno sinistro. Voglio avere campi null in [TableC] se il join (TableC x TableD) è vuoto. Ma aggiungere dei join nidificati a CakePHP è stato davvero facile, vedi la mia risposta. –

+0

Buon punto, felice che tu abbia trovato un lavoro. Sarebbe bello se potessi verificare se la tua soluzione è applicabile al ramo 1.3.x e inviarla come una soluzione per il tuo caso d'uso, sono sicuro che ci sono molte persone che potrebbero trarne beneficio. – ianmjones