Как вложить соединения с CakePHP?

Я пытаюсь вести себя. Так, вместо того, чтобы использовать следующий синтаксис SQL:

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

Я хотел бы использовать CakePHP model->find(). Это позволит мне использовать Paginator также, так как это не будет работать с пользовательскими SQL-запросами насколько я понимаю (если Вы hardcode один единственный запрос разбиения на страницы к модели, которая кажется немного негибкой мне).

Что я попробовал до сих пор:

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

Таким образом, вложение соединения в структуру. Но это не работает (CakePHP просто игнорирует вложенный 'joins' элемент, который был отчасти, что я ожидал, но печальный.

Я видел подсказки в комментариях, как сделать подзапросы (в where пункт) использование разработчика оператора. Подобное может обмануть использоваться здесь?

6
задан Daren Thomas 6 May 2010 в 07:37
поделиться

2 ответа

Оказывается, нельзя. По крайней мере, не с указанным выше синтаксисом и не с CakePHP 1.2.6. Я просмотрел исходный код (ура! К фреймворкам с открытым исходным кодом!) И нашел файл cake / libs / model / datasources / dbo_source.php , который содержит код для объединений.

Все начинается с DboSource :: renderStatement () , который выполняет неглубокий обход массива $ query ['joins'] , заменяя эти определения соединения на фрагменты SQL через DboSource :: buildJoinStatement ($ join) , который выполняет некоторую уборку аргументов (заполняет пробелы и т. Д.), А затем вызывает DboSource :: renderJoinStatement для создания фрагмента SQL одного присоединиться к статье.

я: Это должно быть легко исправить!

Мне сказали не редактировать файлы в cake / libs , поэтому вместо этого я скопировал файл dbo_source.php в app / models / datasources / для редактирование.Затем я взял свой топор и переработал неглубокий обход массива $ query ['joins'] в DboSource :: renderStatement () в новый метод DboSource :: buildJoinStatementArray () , результатом чего стали следующие два метода:

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

Как только у меня был DboSource :: buildJoinStatementArray () , пришло время изменить DboSource :: buildJoinStatement () - все я did была добавлена ​​проверка для $ data ['joins'] и альтернативный метод рендеринга для этого случая:

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

Новый метод renderNestedJoinStatement () очень похож на 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})");
}
2
ответ дан 17 December 2019 в 18:11
поделиться

Если я правильно понимаю, у вас есть следующие отношения (надеюсь, в ваших моделях ):

TableA hasMany TableB.
TableA hasMany TableC.

TableB belongsTo TableA.

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

TableD hasMany TableC. (might be hasOne)

Если вы используете поведение Containable (я очень рекомендую его и устанавливаю его на уровне app_model для всех моделей для наследования), я думаю, вы можете сделать что-то вроде этого ...

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

Если вам нужно чтобы выбрать определенные поля, вам нужно будет указать их в параметре contain, например, здесь я ограничиваю возвращаемые поля TableB:

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

Возвращаемые данные должны быть такими:

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

Однако я никогда не делал этого, где вложенная таблица является родительской для контейнера (TableD и TableC), поэтому она может не работать, но, вероятно, стоит попробовать.

1
ответ дан 17 December 2019 в 18:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: