CakePHP 3 ORM生成允许的内存大小耗尽

时间:2018-05-24 13:07:59

标签: cakephp-3.0

我在CakePHP 3.5.13中有一个应用程序。我已经编写了一个遗留数据库,该数据库尚未根据Cake的命名约定编写。

应用程序的一部分有一个255,693行的表,名为substances。有一个相关的CAS编号放在一个名为cas的表中,并且这两个表之间的映射称为cas_substances

我正在尝试使用CakePHP的ORM编写一个搜索给定CAS的查询。

我似乎无法获得我想要在ORM中执行的查询,即使MySQL相当于它非常简单。假设我正在搜索所有物质ID,其中包含“1234”的CAS,查询在MySQL中会是这样的:

SELECT DISTINCT( s.id ) FROM substances s 
JOIN cas AS cas 
ON ( (cas.value LIKE '%1234%') ) 
JOIN cas_substances AS cassub 
ON (s.id = cassub.substance_id AND cassub.cas_id = cas.id)

直接在数据库上运行(通过Navicat),在0.39秒内给出了63行 - 预期。

因此,在尝试在Cake中编写此代码时,我已按如下方式配置了Table类:

// src/Model/Table/CasTable.php
public function initialize(array $config)
{
    parent::initialize($config);

    $this->setTable('cas');
    $this->setDisplayField('value');
    $this->setPrimaryKey('id');

    $this->belongsToMany('Substances', [
        'foreignKey' => 'cas_id',
        'targetForeignKey' => 'substance_id',
        'joinTable' => 'cas_substances'
    ]);
}


// src/Model/Table/CasSubstancesTable.php
public function initialize(array $config)
{
    parent::initialize($config);

    $this->setTable('cas_substances');
    $this->setDisplayField('id');
    $this->setPrimaryKey('id');

    $this->belongsTo('Cas', [
        'foreignKey' => 'cas_id',
        'joinType' => 'INNER'
    ]);
    $this->belongsTo('Substances', [
        'foreignKey' => 'substance_id',
        'joinType' => 'INNER'
    ]);
}


// src/Model/Table/SubstancesTable.php
public function initialize(array $config)
{
    parent::initialize($config);

    $this->setTable('substances');
    $this->setDisplayField('name');
    $this->setPrimaryKey('id');

    $this->belongsToMany('Cas', [
        'foreignKey' => 'substance_id',
        'targetForeignKey' => 'cas_id',
        'joinTable' => 'cas_substances'
    ]);
    // ...
 }

然后在Controller中我试图获得不同的(MySQL等效DISTINCT()substances.id

// Begin the query
$query = $Substances->find()->select(['id' => 'id'])->distinct();

然后修改查询以过滤我的CAS:

$query = $query->contain('Cas', function ($q) {
    return $q->where(['Cas.value' => '%'.$this->request->getData('cas_number').'%']);
});

当我尝试使用debug($query->all())输出结果时,它会给我一个PHP致命错误:

  

允许的内存大小为134217728字节耗尽(尝试分配20480字节)

仔细观察,似乎没有应用我根据CAS过滤查询的条件。如果我做debug($query->all()->count())它给了我255,693 - 整个物质表没有任何过滤。

我遇到了一些问题:

  1. 如何编写此查询以过滤关联数据?我在这里的工作基于文档的Passing Conditions to Contain部分。

  2. 我担心要返回多少数据。如果我运行相当于该查询的MySQL,它只会给我substances.id这就是我想要的。 Cake正在生产大型对象 - 我知道这是因为ORM的工作方式 - 但肯定会有内存含义吗?我需要将查询结果写入另一个表。如何更好地(或更简单地)使用ORM而不仅仅是编写vanilla SQL然后执行CREATE TABLE tmp_table AS . $sql_select_string(其中$sql_select_string是前面给出的SELECT语句)?

1 个答案:

答案 0 :(得分:1)

为什么您的代码内存不足

当你使用contains时告诉cake检索所有记录及其相关的记录

换句话说,你的代码将获得255,693行物质,并且每行都有Cas数字,但只有匹配LIKE的那些

相反,您想要检索所有且仅检索具有匹配Cas编号

的记录

一种可能的解决方案

似乎这里需要matching方法

$cas_number = $this->request->getData('cas_number');
$query = $Substances->find()
    ->select(['id' => 'Substances.id'])
    ->distinct()
    ->matching('Cas', function ($q) use ($cas_number) {
        return $q->where([
             'Cas.value LIKE' => '%'.$cas_number.'%'
        ]);
     });

以这种方式,cake连接三个表并执行搜索

通常此查询会提供重复记录,您必须分组才能过滤它们。在这种情况下,您正在使用DISTINCT来完成工作

这会给你一个听起来像

的查询
SELECT DISTINCT Substances.id AS `id` 
FROM substances Substances
INNER JOIN cas_substances CasSubstances 
ON Substances.id = CasSubstances.substance_id 
INNER JOIN cas Cas 
ON (
    Cas.value like %1234% 
    AND Cas.id = CasSubstances.cas_id
)

请参阅手册here

更简单的解决方案

因为你只需要ids就可以了

$Substances->Cas->find()
    ->where([
         'Cas.value LIKE' => '%'.$cas_number.'%'
    ])
    ->contain(['CasSubstances'])
    ->select(['id' => 'CasSubstances.substance_id'])
    ->distinct();

这将为您节省一次加入