为了账号安全,请及时绑定邮箱和手机立即绑定

CakePHP3:如何即时更改关联策略?

CakePHP3:如何即时更改关联策略?

PHP
慕姐8265434 2021-04-08 18:15:54
我想将关联策略(hasMany)即时更改为“输入”(默认)为“选择”。因为这样可以纠正这种情况下的结果:“获取所有出版商,仅获取前五本书”:$publishersTable = TableRegistry::getTableLocator()->get('Publishers');$publishersTable->getAssociation('Books')->setStrategy('select');               $query = $publishersTable->find()                ->contain(['Books'=> function(Query $q){                    return $q->limit(5);                }]);不幸的是,Cake仍然使用“输入”来运行查询,而不是“单独的查询”,结果只有5个出版商(而不是所有拥有前5本书的出版商)。是否可以即时更改策略?提前致谢 !
查看完整描述

2 回答

?
沧海一幻觉

TA贡献1824条经验 获得超5个赞

一个hasMany协会将始终使用一个单一的单独的查询,从不多个单独的查询。select和subquery策略之间的区别在于,一个策略将直接与主键数组进行比较,另一个将与将与所选父记录匹配的联接子查询进行比较。


您要尝试的是选择 每组最多,这对于内置的关联加载程序来说是不可能的,并且这可能会有些棘手,具体取决于您所使用的DBMS,例如检查如何限制每个记录/组包含的关联?有关使用自定义关联和加载程序的MySQL <8.x的示例。


对于确实支持它的DBMS,请查看窗口函数。这是一个使用本机窗口函数的加载器示例,应该可以用它替换链接示例中的那个,但是请记住,它并不是经过真正测试的东西,我还是从一些实验中获得了它:


namespace App\ORM\Association\Loader;


use Cake\Database\Expression\OrderByExpression;

use Cake\ORM\Association\Loader\SelectLoader;


class GroupLimitedSelectLoader extends SelectLoader

{

    /**

     * The group limit.

     *

     * @var int

     */

    protected $limit;


    /**

     * The target table.

     *

     * @var \Cake\ORM\Table

     */

    protected $target;


    /**

     * {@inheritdoc}

     */

    public function __construct(array $options)

    {

        parent::__construct($options);


        $this->limit = $options['limit'];

        $this->target = $options['target'];

    }


    /**

     * {@inheritdoc}

     */

    protected function _defaultOptions()

    {

        return parent::_defaultOptions() + [

            'limit' => $this->limit,

        ];

    }


    /**

     * {@inheritdoc}

     */

    protected function _buildQuery($options)

    {

        $key = $this->_linkField($options);

        $keys = (array)$key;

        $filter = $options['keys'];

        $finder = $this->finder;


        if (!isset($options['fields'])) {

            $options['fields'] = [];

        }


        /* @var \Cake\ORM\Query $query */

        $query = $finder();

        if (isset($options['finder'])) {

            list($finderName, $opts) = $this->_extractFinder($options['finder']);

            $query = $query->find($finderName, $opts);

        }


        $rowNumberParts = ['ROW_NUMBER() OVER (PARTITION BY'];

        for ($i = 0; $i < count($keys); $i ++) {

            $rowNumberParts[] = $query->identifier($keys[$i]);

            if ($i < count($keys) - 1) {

                $rowNumberParts[] = ',';

            }

        }

        $rowNumberParts[] = new OrderByExpression($options['sort']);

        $rowNumberParts[] = ')';


        $rowNumberField = $query

            ->newExpr()

            ->add($rowNumberParts)

            ->setConjunction('');


        $rowNumberSubQuery = $this->target

            ->query()

            ->select(['__row_number' => $rowNumberField])

            ->where($options['conditions']);


        $columns = $this->target->getSchema()->columns();

        $rowNumberSubQuery->select(array_combine($columns, $columns));


        $rowNumberSubQuery = $this->_addFilteringCondition($rowNumberSubQuery, $key, $filter);


        $fetchQuery = $query

            ->select($options['fields'])

            ->from([$this->targetAlias => $rowNumberSubQuery])

            ->where([$this->targetAlias . '.__row_number <=' => $options['limit']])

            ->eagerLoaded(true)

            ->enableHydration($options['query']->isHydrationEnabled());


        if (!empty($options['contain'])) {

            $fetchQuery->contain($options['contain']);

        }


        if (!empty($options['queryBuilder'])) {

            $fetchQuery = $options['queryBuilder']($fetchQuery);

        }


        $this->_assertFieldsPresent($fetchQuery, $keys);


        return $fetchQuery;

    }

}


查看完整回答
反对 回复 2021-04-23
?
慕桂英3389331

TA贡献2036条经验 获得超8个赞

我发现了另一个更短的解决方案:


$publishersTable->find()

                    ->formatResults(function ($results) use ($publishersTable) {

                       return $results->map(function ($row) use ($publishersTable) {

                            $row['books'] = $publishersTable->Books->find()

                                                ->where(['publisher_id'=>$row['id']])

                                                ->limit(5)

                                                ->toArray();

                            return $row;

                        });

            });


查看完整回答
反对 回复 2021-04-23
  • 2 回答
  • 0 关注
  • 160 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信