Использование функций SQL в cakephp возвращает ошибку

Я следую Использование функций SQL для построения моего запроса. Я обычно получаю первый взнос. Мой запрос работал (на cakephp3.1) до обновления cakephp до версии 3.3 (составитель).

В моем контроллере

$this->loadModel('Orders');
$order = $this->Orders->find()
        ->where(['order_id' => $orderId])
        ->contain([
            'PaymentInstalments' => function($q) {
                return $q->find('firstDue');
            },
            'Users' => function($q) {
                return $q->select(['email']);
            }
])
->first();

На моем платежном столе

public function findFirstDue(Query $query, array $options)
{
    $alias = $this->alias();

    $query
        ->select([
//          "id" => $query->func()->min("$alias.id"),  ## This use to work before but now is not working
            'id' => $query->func()->min(function($row){
                return $row->id;
            }),
            'order_id', 'amount', 'date_due', 'transaction_id'
        ])
        ->contain([
            'Transactions' => function($q) {
                return $q
                    ->select([
                        'id', 'transaction_date'
                    ])
                    ->where(function ($exp, $q) {
                        return $exp->isNull('transaction_date');
                    });
            }
        ]);

    debug($query->__debugInfo()['sql']);
    die;    
    return $query;
}

Вот распечатка моего запроса.

'SELECT PaymentInstalments.id AS `PaymentInstalments__id`, PaymentInstalments.order_id AS `PaymentInstalments__order_id`, PaymentInstalments.instalment_num AS `PaymentInstalments__instalment_num`, PaymentInstalments.amount AS `PaymentInstalments__amount`, PaymentInstalments.date_due AS `PaymentInstalments__date_due`, PaymentInstalments.payment_email_sent AS `PaymentInstalments__payment_email_sent`, PaymentInstalments.transaction_id AS `PaymentInstalments__transaction_id`, {
    "id": 41408,
    "order_id": "10000",
    "instalment_num": 1,
    "amount": 100,
    "date_due": "2016-08-25T12:15:00+01:00",
    "payment_email_sent": false,
    "transaction_id": null
} AS `PaymentInstalments`.`id`, Transactions.id AS `Transactions__id`, Transactions.transaction_date AS `Transactions__transaction_date` FROM payment_instalments PaymentInstalments LEFT JOIN transactions Transactions ON ((Transactions.transaction_date) IS NULL AND Transactions.id = (PaymentInstalments.transaction_id)) WHERE PaymentInstalments.order_id in (:c0)'

Проблема в том, если я использую "id" => $query->func()->min("$alias.id"), Я получаю эту ошибку:

Вам необходимо выбрать поля "PaymentInstalments.order_id"

И если я использую это

'id' => $query->func()->min(function($row){
    return $row->id;
}),`

Я получаю эту ошибку:

Ошибка: SQLSTATE[42000]: синтаксическая ошибка или нарушение прав доступа: 1064 В синтаксисе SQL имеется ошибка; проверьте руководство, соответствующее вашей версии сервера MySQL, чтобы узнать правильный синтаксис для использования рядом с '"id": 41408, "order_id": "10000", "instalment_num": 1, "amount": "в строке 2

Любая помощь, пожалуйста

2 ответа

Решение

Вы не можете использовать методы сбора

$query->min() является методом сбора, соответственно выполнит запрос и вызовет метод для результирующего набора результатов, то есть он не имеет ничего общего с функциями SQL. Просто посмотрите на результаты отладки, в вашем SQL-запросе есть данные набора результатов.

См. Поваренная книга> Доступ к базе данных и ORM > Построитель запросов> Запросы являются объектами коллекции

$query->func()->min() это путь, это то, что генерирует объект выражения функции SQL.

В ядре есть ошибка, связанная с выражениями функций

При этом, то, что вы испытываете, - это ошибка, которая присутствует в проверке наличия присутствия внешнего ключа и запускается при наличии выражений в списке выбора. Вы должны увидеть еще больше ошибок, соответственно, как предупреждения

Объект класса Cake\Database\Expression\FunctionExpression не может быть преобразован в строку

Пожалуйста, убедитесь, что всегда включаете такие вещи в свои вопросы! Если вы этого не видите, попробуйте повысить уровень сообщений об ошибках.

Причиной этого предупреждения в сочетании с ошибкой PHP является источник всей проблемы: список выбора не передается array_diff(), который использует сравнение строк, т.е. (string)$elementA === (string)$elementB, что не удастся, поскольку объект выражения не может быть преобразован в строку.

PHP странный

А теперь возникает интересная особенность, без которой вы бы только увидели предупреждение, но запрос будет работать нормально.

До PHP 7, если вы поставите ключ, который ищется, т.е. order_id непосредственно после выражения функции в списке выбора, затем array_diff() не найдет его и скажет, что его нет. Однако, если у вас есть хотя бы один дополнительный элемент между ним и выражением функции, то есть что-то вроде

'id' => $query->func()->min("$alias.id"),
// instead of here
'amount',
'order_id', // put it here
'date_due',
'transaction_id'

затем array_diff() найдет его, и запрос будет выполнен нормально, несмотря на генерируемую ошибку преобразования строки. Но это еще не все, нет, не было бы PHP, если бы не было действительно странных вещей.

Целостное " размещение ключа по-разному и изменение поведения " происходит только тогда, когда функции массива asort() вызываются внутри обратного вызова обработчика ошибок. Неважно, по каким данным они вызываются, это никак не должно быть связано с ошибкой, это может быть что-то вроде $foo = []; asort($foo);, это уже вызвало бы это странное поведение.

https://3v4l.org/2nT5M

Ты должен любить PHP:)

Используйте жестко закодированный фрагмент SQL в качестве обходного пути

В качестве обходного пути, пока ошибка не будет устранена, вы можете вместо этого передать фрагмент SQL в виде строки, например

'id' => 'MIN(PaymentInstalments.id)'

Последний, но тем не менее важный

Пожалуйста, сообщите о проблеме преобразования строк как об ошибке на GitHub.

Вы можете временно заставить его работать, жестко запрограммировав его:

$alias = $this->alias();
$query->select(['id' => "MIN($alias.id)"]);
Другие вопросы по тегам