2010-05-06 18 views
5

Estoy tratando de comportarme. Así, en lugar de utilizar siguiente sintaxis SQL:¿Cómo anidar se une 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 

me gustaría usar el CakePHP model->find(). Esto me permitirá usar el Paginator también, ya que eso no funcionará con consultas SQL personalizadas, por lo que yo entiendo (a menos que codifique una sola consulta de paginación para el modelo que me parece un poco inflexible).

Lo que he probado hasta ahora:

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

Es decir, que anidan las uniones en la estructura. Pero eso no funciona (CakePHP simplemente ignora el 'joins' elemento anidado, que era una especie de lo que esperaba, pero triste.

he visto indicios en los comentarios sobre cómo hacer subconsultas (en la cláusula where) usando un generador de expresiones . se puede utilizar un truco similar aquí

+0

¿Por qué tiene que estar anidado? ¿No puedes hacer lo mismo con todas las combinaciones en el nivel superior? –

+0

Ojalá pudiera, pero el resultado es diferente: quiero todos los resultados de la unión interna de nivel superior con datos opcionales añadidos desde la unión interna anidada. Si aplanar esto, entonces pierdo todas las filas en (tabla A junta interna TableB) que no tienen TableD correspondiente ... –

+0

Wow eso es bastante complejo. Probablemente trataré de extender find() de alguna manera para agregar algunos parámetros adicionales –

Respuesta

2

Resulta que no se puede. Al menos no con la sintaxis proporcionada anteriormente y no con CakePHP 1.2.6. Revisé la fuente (¡yay! Para abrir los marcos de código fuente!) Y encontré el archivo cake/libs/model/datasources/dbo_source.php que contiene el código para las uniones.

todo comienza con DboSource::renderStatement() que hace un pie poco profunda de la matriz de $query['joins'], en sustitución de los que se unen a las definiciones con fragmentos SQL a través de DboSource::buildJoinStatement($join), que hace algo de poner en orden de los argumentos (rellenar espacios en blanco, etc.) y luego llama DboSource::renderJoinStatement para crear el fragmento de SQL de una cláusula de unión única.

me : Eso debería ser fácil de arreglar!

me dijeron que no a editar cosas en cake/libs, así que en vez copiado el archivo dbo_source.php a app/models/datasources/ para su edición.Entonces tomé mi hacha y refactorizado el paseo superficial de la matriz $query['joins'] en DboSource::renderStatement() en un nuevo método DboSource::buildJoinStatementArray() que resulta en estos dos métodos:

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 vez que tuve DboSource::buildJoinStatementArray(), ya era hora de cambiar DboSource::buildJoinStatement() - todo lo que hice fue añadido un cheque por $data['joins'] y un método de representación alternativa para ese 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); 
    } 
} 

El nuevo renderNestedJoinStatement() método es bastante similar 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

Si estoy recibiendo este derecho, usted tiene las siguientes relaciones (con suerte en sus modelos):?

TableA hasMany TableB. 
TableA hasMany TableC. 

TableB belongsTo TableA. 

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

TableD hasMany TableC. (might be hasOne) 

Si está utilizando el comportamiento Containable (Lo recomiendo mucho, y lo configuro en el nivel de app_model para que todos los modelos lo hereden), creo que puedes hacer algo como th es ...

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

Si usted necesita escoger los campos específicos, a continuación, tendrá que especificarlos en el parámetro contener, por ejemplo aquí Restrinjo campos devueltos de TableB:

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

El devueltos los datos deben ser así:

[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' 
     ) 
    ) 
    ) 
) 

sin embargo, nunca he hecho esto en la tabla anidada es el padre del contenedor (presentación y TableC), por lo que podrían no funcionar, pero es probable que vale la pena intentarlo .

+0

Esto no es realmente lo que hace una combinación externa izquierda. Quiero tener campos nulos en [TableC] si la unión (TableC x TableD) está vacía. Pero agregar uniones anidadas a CakePHP fue realmente fácil, ver mi propia respuesta. –

+0

Buen punto, me alegro de que hayas encontrado una solución. Sería genial si pudiera verificar si su solución es aplicable a la rama 1.3.x y enviarla como una solución para su caso de uso, estoy seguro de que hay muchas personas que se beneficiarían. – ianmjones