Запрос сборки CakePHP для фильтрации по модели ИЛИ связанной модели - PullRequest
0 голосов
/ 02 января 2019

Я хочу найти строки путем фильтрации по столбцу таблицы, ИЛИ по столбцу связанной таблицы.Я могу заставить работать версию AND, но я не уверен, как изменить условие между ->where() и ->matching() на OR.

В этом примере я хочу найти пользователя либо по егоимя пользователя или его обычное имя.В этом случае Пользователь hasMany Имена.

$users = $this->Users->find()
    ->where(['username LIKE' => '%' . $search_term . '%'])
    ->matching('Names', function($q) use ($search_term){
        return $q->where(function($exp, $query) use ($search_term) {
            $full_name = $query->func()->concat([
                'first' => 'identifier', ' ',
                'last' => 'identifier'
            ]);
            return $exp->like($full_name, '%' . $search_term . '%');
        });
    })
    ->contain(['Names'])
    ->limit(10)
    ->all();

1 Ответ

0 голосов
/ 02 января 2019

matching() создает INNER объединений , с условиями, применяемыми в предложении объединения ON, в результате чего пользователи отфильтровываются, если не найдено соответствующих имен, следовательно, вынельзя использовать это для создания искомого условия OR.

Например, вы можете переместить проверку имен в коррелированный подзапрос (запрос, который ссылается на столбцы внешнего запроса), и используйте его с EXISTS, что-то вроде этого:

$namesMatcher = $this->Users->Names
    ->find()
    ->select(['Names.id'])
    ->where(function($exp, $query) use ($search_term) {
        $full_name = $query->func()->concat([
            'Names.first' => 'identifier', ' ',
            'Names.last' => 'identifier'
        ]);

        return $exp
            ->equalFields('Names.user_id', 'Users.id')
            ->like($full_name, '%' . $search_term . '%');
    });

$users = $this->Users->find()
    ->where(function($exp, $query) use ($search_term, $namesMatcher) {
        return $exp
            ->or_(['Users.username LIKE' => '%' . $search_term . '%'])
            ->exists($namesMatcher);
    })
    ->contain(['Names'])
    ->limit(10)
    ->all();

Сгенерированный запрос будет выглядеть примерно так:

SELECT
    Users.id ...
FROM
    users Users
WHERE
    Users.username LIKE '%term%' OR
    EXISTS(
        SELECT
            Names.id
        FROM
            names Names
        WHERE
            Names.user_id = Users.id AND
            CONCAT(Names.first, ' ', Names.last) LIKE '%term%'
    )

Эту проблему также можно решить, например, с помощью LEFT объединения, чтобы это не повлияло на записи пользователя (см. Query::leftJoinWith()), что позволяет создаватьсоответственно, OR, но, поскольку вам больше не нужен доступ к именам в запросе, на самом деле нет смысла выбирать их.

...