在 cakephp 中使用 SQL 函数 returns 错误

Using SQL Functions in cakephp returns error

我正在关注 Using SQL Functions 来构建我的查询。我倾向于得到第一期到期的分期付款。在将 cakephp 更新到版本 3.3(由 composer)之前,我的查询(在 cakephp3.1 上)有效。

在我的控制器中

$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();

关于我的分期付款Table

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"), 我会得到这个错误:

You are required to select the "PaymentInstalments.order_id" field(s)

如果我使用这个

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

我收到这个错误:

Error: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"id": 41408, "order_id": "10000", "instalment_num": 1, "amount": ' at line 2

请帮忙

您不能使用收集方法

$query->min()是集合方法,分别会在结果集上执行查询和调用方法,即与SQL函数无关。看看你的调试输出,你的 SQL 查询中有结果集数据。

Cookbook > Database Access & ORM > Query Builder > Queries Are Collection Objects

$query->func()->min() 是要走的路,这就是生成 SQL 函数表达式对象的原因。

内核中存在一个关于函数表达式的错误

话虽如此,您遇到的是 the foreign key presence existence check 中存在的错误,并且是由 select 列表中的表达式触发的。您应该会看到更多错误,分别是

之类的警告

Object of class Cake\Database\Expression\FunctionExpression could not be converted to string

请确保在您的问题中始终包含此类内容!如果您没有看到它,请尝试提高您的错误报告级别。

该警告的原因,加上 PHP 错误,是整个问题的根源,select 列表被传递给 array_diff(),它使用字符串比较,即 (string)$elementA === (string)$elementB,这将失败,因为无法将表达式对象转换为字符串。

PHP很奇怪

现在出现了一个有趣的怪癖,如果没有它,您只会看到警告,但查询会 运行 正常。

在PHP 7之前,如果把正在查找的key,即order_id,直接放在select列表中函数表达式的后面,那么[=14= 】 不会找的,说丢了。但是,如果您在它和函数表达式之间至少有一个附加元素,例如

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

最后但并非最不重要的

请将字符串转换问题作为错误报告 over at GitHub

您可以通过硬编码暂时让它工作:

$alias = $this->alias();
$query->select(['id' => "MIN($alias.id)"]);