CakePHP несколько вложенных соединений - PullRequest
3 голосов
/ 25 марта 2010

У меня есть приложение, в котором несколько моделей связаны ассоциациями hasMany / ownTo. Так, например, A hasMany B, B hasMany C, C hasMany D и D hasMany E. Также, E принадлежит D, D принадлежит C, C принадлежит B, и B принадлежит A. Использование поведения Containable было отлично подходит для контроля количества информации, возвращаемой с каждым запросом, но у меня, похоже, возникают проблемы при попытке получить данные из таблицы A при использовании условия, включающего таблицу D. Например, вот пример моего 'A' модель:

class A extends AppModel {
    var $name = 'A';

    var $hasMany = array(
        'B' => array('dependent' => true)
    );

    function findDependentOnE($condition) {
        return $this->find('all', array(
            'contain' => array(
                'B' => array(
                    'C' => array(
                        'D' => array(
                            'E' => array(
                                'conditions' => array(
                                    'E.myfield' => $some_value
                                )
                            )
                        )
                    )
                )
            )
        ));
    }
}

Это все равно возвращает мне все записи в 'A', и если связанные записи 'E' не удовлетворяют условию, то я просто получаю это:

Array(
    [0] => array(
        [A] => array(
            [field1] => // stuff
            [field2] => // more stuff
            // ...etc
        ),
        [B] => array(
            [field1] => // stuff
            [field2] => // more stuff
            // ...etc
        ),
        [C] => array(
            [field1] => // stuff
            [field2] => // more stuff
            // ...etc
        ),
        [D] => array(
            [field1] => // stuff
            [field2] => // more stuff
            // ...etc
        ),
        [E] => array( 
            // empty if 'E.myfield' != $some_value'
        )
    ),
    [1] => array( // ...etc )
)

Когда If 'E.myfield'! = $ Some_value, я вообще не хочу, чтобы запись возвращалась.

Надеюсь, это достаточно ясно выражает мою проблему ...

По сути, я хочу следующий запрос, но в виде, независимом от базы данных / CakePHP-y:

SELECT * 
FROM A INNER JOIN
        (B INNER JOIN 
            (C INNER JOIN 
                (D INNER JOIN 
                    E ON D.id=E.d_id) 
                ON C.id=D.c_id) 
            ON B.id=C.b_id) 
        ON A.id=B.a_id 
    WHERE E.myfield = $some_value

1 Ответ

2 голосов
/ 25 марта 2010

Ваша проблема - неправильное представление о том, что делает поведение Containable и что опция contain делает в Model::find. Вызов Model::find в вашем первом примере кода будет примерно равен:

Найти все А; затем найдите все B, связанные с каждым A; затем найдите все C, связанные с каждым B; затем найдите все D, связанные с каждым C; наконец, найдите все E, связанные с каждым D, где одно поле в E соответствует указанному значению.

Оператор условия фильтрует только результаты D, не вверх по цепочке до C, затем B, затем A. Если вы просканируете журнал SQL, вы увидите огромное количество запросов, вытягивающих каждый уровень вашего contain цепь.

Чтобы CakePHP мог возвращать результаты по вашему желанию, прямо из базы данных, вам нужно настроить hasOne связь между A и E. С длинной цепочкой, как вы описываете, это может быть довольно громоздкий. Это будет выглядеть примерно так (читай: не проверено):

$this->bindModel(array('hasOne'=>array(
    'B'=>array(
        'foreignKey' => false,
        'conditions' => array('A.id = B.a_id')
    ),
    'C'=>array(
        'foreignKey' => false,
        'conditions' => array('B.id = C.b_id')
    ),
    'D'=>array(
        'foreignKey' => false,
        'conditions' => array('C.id = D.c_id')
    ),
    'E'=>array(
        'foreignKey' => false,
        'conditions' => array('D.id = E.d_id')
    )
)));

$this->find('all', array(
    'conditions' => array( 'E.my_field' => $some_value )
));

Альтернативой является полное удаление условия E.my_value из вызова Model::find и вместо этого выполнение довольно сложного Set::extract в конце:

$results = $this->find('all', array(
    'contain' => array(
        'B' => array(
            'C' => array(
                'D' => array(
                    'E' => array()
                )
            )
        )
    )
));
return Set::extract("/A/B/C/D/E[my_field={$some_value}]/../../../../", $results);

Производительность была бы реальной проблемой с глубоким Set::extract, особенно если вы работали с большим количеством строк.

РЕДАКТИРОВАТЬ : Я просто хочу подчеркнуть, насколько ужасна идея Set::extract, если эту операцию нужно масштабировать. Он переносит всю нагрузку фильтрации с ядра базы данных на функции массива PHP.

...