【问题标题】:Using IFNULL ... GROUP_CONCAT() in CakePHP 3在 CakePHP 3 中使用 IFNULL ... GROUP_CONCAT()
【发布时间】:2020-06-25 10:08:45
【问题描述】:

我有一个普通的 MySQL 查询,如下所示:

SELECT d.*, IFNULL(
     (SELECT GROUP_CONCAT(value) FROM display_substances `ds` 
         WHERE `ds`.`display_id` = `d`.`id`
         AND ds.substance_id = 2 
         GROUP BY `ds`.`display_id`
     ), 'Not Listed'
) `substances` FROM displays `d`;

背景是我有两张桌子,displaysdisplay_substances。我想为给定的物质 ID(在上面的查询中由 ds.substance_id = 2 表示)呈现 displays 中每一行的表以及 display_substances 中的相应值。

并非displays 中的每一行都有对应的display_substances 值。如果是这种情况,它应该输出“未列出”字样。这实际上意味着如果display_substances 中没有相应的行,那么display.id 就没有数据,因此该特定行的数据库中的数据“未列出”。上面的查询正是这样做的。

我希望能够使用 CakePHP 的 ORM 语法编写查询。

表格的结构如下。

mysql> DESCRIBE displays;
+----------+----------------------+------+-----+---------+----------------+
| Field    | Type                 | Null | Key | Default | Extra          |
+----------+----------------------+------+-----+---------+----------------+
| id       | smallint(5) unsigned | NO   | PRI | NULL    | auto_increment |
| label    | varchar(255)         | NO   |     | NULL    |                |
+----------+----------------------+------+-----+---------+----------------+

mysql> DESCRIBE display_substances;
+--------------+-----------------------+------+-----+---------+----------------+
| Field        | Type                  | Null | Key | Default | Extra          |
+--------------+-----------------------+------+-----+---------+----------------+
| id           | mediumint(8) unsigned | NO   | PRI | NULL    | auto_increment |
| display_id   | smallint(5) unsigned  | NO   | MUL | NULL    |                |
| substance_id | mediumint(8) unsigned | NO   | MUL | NULL    |                |
| value        | text                  | NO   |     | NULL    |                |
+--------------+-----------------------+------+-----+---------+----------------+

表类存在并定义了适当的关系。

// src/Model/Table/DisplaysTable.php
$this->hasMany('DisplaySubstances', [
    'foreignKey' => 'display_id'
]);

// src/Model/Table/DisplaysSubstancesTable.php
$this->belongsTo('Displays', [
   'foreignKey' => 'display_id',
   'joinType' => 'INNER'
]);

到目前为止,我有这个:

    $substance_id = 2;

    $data = 
        $DisplaySubstances->find()
        ->contain(['Displays'])
        ->where(['DisplaySubstances.substance_id' => $substance_id)
        ->select(['Displays.id', 'Displays.label', 'Displays.anchor', 'DisplaySubstances.value'])
        ->enableHydration(false)->toArray();

这将为我提供displays 中的行,这些行在display_substances 中具有对应于物质 ID 2 的值。这实际上是我们拥有数据的所有内容,但不包括任何“未列出”的行。

我不知道如何使用 Cake 的 ORM 语法编写我的普通 SQL 查询的 IFNULL...GROUP_CONCAT 部分。

我已经阅读了How to use group_contact in cakephp query?,因此可以看到可以在->select() 条件中使用GROUP_CONCAT。但是链接的示例要简单得多,因为它没有使用IFNULL 条件和相应的操作(返回“未列出”)。

如果可能的话,请有人建议如何用 Cake 的 ORM 语法写这个?

CakePHP 版本为 3.5.18

【问题讨论】:

  • (不相关):建议:INDEX(substance_id, display_id, value)

标签: php mysql cakephp-3.5


【解决方案1】:

IFNULLGROUP_CONCAT 都是 SQL 函数,所以你可以使用函数生成器,它可以用来创建你想要的任何函数调用,如果没有具体的,它的魔法调用处理程序将创建一个通用函数调用调用方法的实现,即$functionsBuilder->IFNULL()$functionsBuilder->GROUP_CONCAT() 将正常工作。

函数构建器还接受表达式,因此您可以将另一个查询对象传递给您的IFNULL() 调用,即示例 SQL 中的子查询。

$subquery = $DisplaySubstances->find()
    ->select(function (\Cake\ORM\Query $query) {
        return [
            $query->func()->GROUP_CONCAT(['value' => 'identifier'])
        ];
    })
    ->where(function (\Cake\Database\Expression\QueryExpression $exp) use ($substance_id) {
        return [
            $exp->equalFields('DisplaySubstances.display_id', 'Displays.id'),
            'DisplaySubstances.substance_id' => $substance_id
        ];
    })
    ->group('DisplaySubstances.display_id');

$query = $Displays->find()
    ->select(function (\Cake\ORM\Query $query) use ($subquery) {
        // Before CakePHP 3.8.3 queries need to be wrapped in an additional expression
        // in order for the query builder to generate them wrapped in parentheses in SQL
        $subquery = $query->newExpr($subquery);

        return [
            'substances' => $query->func()->IFNULL([$subquery, 'Not Listed'])
        ];
    })
    ->select($Displays);

另见

【讨论】:

  • 我刚刚有机会尝试这个。它给出了一个 SQL 错误Error: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'SELECT (GROUP_CONCAT(value)) FROM display_substances DisplaySubstances WHERE (Di' at line 1。我无法发现这个错误在哪里,因为它看起来像是有效的 SQL。
  • 整个查询是SELECT (IFNULL(SELECT (GROUP_CONCAT(value)) FROM display_substances DisplaySubstances WHERE (DisplaySubstances.display_id = (Displays.id) AND DisplaySubstances.substance_id = :c0) GROUP BY DisplaySubstances.display_id , :param1)) AS 物质, Displays.id AS Displays__id , Displays.name AS Displays__name , Displays.label AS Displays__label , Displays.link AS Displays__link , Displays.anchor AS Displays__anchor , Displays.group_id AS Displays__group_id , Displays.comment AS Displays__comment` FROM显示器Displays` 跨度>
  • @Andy 请尝试在外部托管此类代码 sn-ps(例如要点),它们在评论中非常难以阅读。话虽如此,子查询应该用括号括起来,即IFNULL((SELECT ...), ...)(注意双括号,一个用于函数调用,一个作为包装器)。默认情况下,CakePHP 应该这样做,也许您使用的版本太旧,或者您修改了示例代码以更改输出(尽管我不确定这怎么可能)。跨度>
  • @Andy 啊,看,是我修复了它github.com/cakephp/cakephp/pull/13537:P 作为一种解决方法,将查询包装在另一个表达式中应该适用于旧版本:$query->newExpr($subquery)
  • 非常感谢@ndm。我可以确认这适用于 CakePHP 3.5.13
猜你喜欢
  • 2016-07-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多