在 cakephp 3 中通过嵌套关联对查询结果进行排序

Sort query results by nested association in cakephp 3

问题:

我有一个带有两个嵌套关联 Organizations.Positions.Skills 的 cakephp 3.x 查询对象,它被设置为视图变量 $Organizations。我正在尝试按第一个嵌套关联中的列对查询生成的顶级数组进行排序。也就是说,我想按 Positions 中的列对 $Organizations 进行排序,特别是 Positions.Ended)。

public function index()
{
    $this->loadModel('Organizations');
    $this->set('Organizations',
            $this->Organizations->find('all')
            ->contain([ //eager loading
            'Positions.Skills'
             ])
    );
}

模型信息

组织有很多职位

职位有很多技能

我做过的研究

订购选项

根据cookbook find()有一个顺序选项:find()->order(['field']);find('all', ['order'=>'field DESC']); 但是,这仅适用于正在调用的 table find() 中的字段。在这种情况下,组织。例如,这是它通常的使用方式。

//This works.
//Sorts Organizations by the name of the organization in DESC.
$this->loadModel('Organizations');
$this->set('Organizations',
        $this->Organizations->find('all')
        ->contain([ //eager loading
            'Positions.Skills'
         ])
        ->order(['organization DESC'])
);

但是,尝试将它用于嵌套关联不起作用:

$this->set('Organizations',
  this->Organizations->find(...)
    ->contain(...)
    ->order(['Organizations.Positions.ended DESC'])
);

Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Organizations.Positions.ended' in 'order clause'

并更改它以引用将被嵌套的字段也不起作用:

//also doesn't work.
$this->set('Organizations',
  $this->Organizations->find(...)
    ->contain(...)
    ->order([Positions.ended DESC])
);

Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Positions.ended' in 'order clause'

在这两种情况下,当 cakephp 执行查询生成的 PDO 语句时,都会产生 sql 错误。

排序选项

同样,根据cookbook,预先加载/关联有'sort'选项: $this->loadModel('Organizations');

$this->set('Organizations',
    $this->Organizations->find('all')
    ->contain([ //eager loading
        'Positions.Skills',
        'Positions' => [
            'sort' => ['Positions.ended'=>'DESC']
        ]
    ])
);

但是,这只对嵌套的 association.Specifically 进行排序,它对嵌套的关联进行排序。它不会通过嵌套关联对整个结果集进行排序(因此,多维排序)。

例如:

组织,组织 C(组织 ID 1),有两个职位:

  1. 位置 5。2012 年结束
  2. 位置 4。2014 年结束

而组织,组织 B(组织 ID 2),有两个职位:

  1. 位置 3 结束于 2013 年
  2. 位置 2 结束于 2015 年

上述代码和数据导致在计算查询时生成以下数组:

Organizations => [
  0 => [
    'organization' => 'Organization A',
    positions => [
      0 => [
        'position' => 'Position 1',
        'ended' => '2016'
      ]
    ]
  ],
  1 => [
    'organization' => 'Organization C',
    'positions' => [
      0 => [
        'position' => 'Position 4',
        'ended' => '2014'
      ],
      1 => [
        'position' => 'Position 5',
        'ended' => '2012'
      ]
    ]
  ],
  2 => [
    'organization' => 'Organization B',
    'positions' => [
      0 => [
        'position' => 'Position 2',
        'ended' => '2015'
      ],
      1 => [
        'position' => 'Position 3',
        'ended' => '2013'
      ]
    ]
  ]
]

其他研究

同样,在我的研究中出现了以下 Whosebug 问题:







 

此外,我知道 PHP 有自己的排序功能,如 sort()multisort();但是,这些只能在查询被评估(通过 foreach)后调用。或者,调用 $organization->toArray() 然后使用 multisort;但是,这必须在视图中完成,会打破关注点分离的 MVC 约定(数据和查询由控制器和模型操作,而不是视图!),并且效率很低,因为它会被调用页面加载时。

那么,如何按嵌套关联对 cakephp 查询进行排序?

或者,更简单地说,我如何 order/sort 查询在评估时生成以下数组:

Organizations => [
  0 => [
    'organization' => 'Organization A',
    'positions' => [
      0 => [
        'position' => 'Position 1',
        'ended' => '2016'
      ]
    ]
  ],
  0 => [
    'organization' => 'Organization B',
    'positions' => [
      0 => [
        'position' => 'Position 2',
        'ended' => '2015'
      ],
      1 => [
        'position' => 'Position 3',
        'ended' => '2013'
      ]
    ]
  ],
  1 => [
    'organization => 'Organization C',
    'positions' => [
      0 => [
        'position' => 'Position 4',
        'ended' => '2014'
       ],
      1 => [
        'position' => 'Position 5',
        'ended' => '2012'
      ]
    ]
  ]
]

背景与背景:

我正在使用 cakephp 3.2 为自己构建一个 [作品集网站][7],以展示我的 Web 开发技能并帮助我寻求开发职业。对于我的简历页面,我使用 嵌套 手风琴来组织大量数据,以模仿招聘人员希望在实际简历中看到的简历风格。因此,我的观点如下:

  1. 遍历顶级视图变量(组织)
    1. 正在呈现组织详细信息
    2. 遍历该组织的职位(仍在 1 内)
      1. 渲染仓位详情
      2. 遍历该职位的相关技能
        1. 使用适当的 link 渲染每个技能以按该技能过滤。

列表项

只有 hasOnebelongsTo 关联正在通过主查询上的联接检索。 hasMany 关联正在单独的查询中检索,因此当您尝试引用 Positions 的字段时出现错误。

你想要的应该很容易解决,在 SQL 水平上,在 PHP 水平上也是如此。

SQL级

在 SQL 级别,您可以加入 Positions,并按计算的 max(Positions.ended) 列排序,例如:

$this->Organizations
    ->find('all')
    ->select(['max_ended' => $this->Organizations->query()->func()->max('Positions.ended')])
    ->select($this->Organizations)
    ->contain([
        'Positions.Skills',
        'Positions' => [
            'sort' => ['Positions.ended' => 'DESC']
        ]
    ])
    ->leftJoinWith('Positions')
    ->order([
        'max_ended' => 'DESC'
    ])
    ->group('Organizations.id');

仅此而已,应该会给您想要的结果。查询将类似于:

SELECT
    MAX(Positions.ended) AS max_ended,
    ... 
FROM
    organizations Organizations     
LEFT JOIN
    positions Positions 
        ON Organizations.id = (
            Positions.organization_id
        )   
GROUP BY
    Organizations.id   
ORDER BY
    max_ended DESC

PHP级

在 PHP 级别上,使用集合也很容易解决(请注意,查询是集合 - 有点),但是只有当您打算检索所有行时才有意义,或者必须无论出于何种原因处理一组无序行......类似于:

$Organizations->map(function ($organization) {
    $organization['positions'] =
        collection($organization['positions'])->sortBy('ended')->toList();
    return $organization;
});
$sorted = $sorted->sortBy(function ($organization) {
    return
        isset($organization['positions'][0]['ended']) ?
            $organization['positions'][0]['ended'] : 0;
});

这也可以在结果格式化程序中实现,所以如果您坚持,事情会发生在控制器或模型级别。

$query->formatResults(function ($results) {
    /* @var $results \Cake\Datasource\ResultSetInterface|\Cake\Collection\CollectionInterface */
    $results = $results->map(function ($row) {
        $row['positions'] =
            collection($row['positions'])->sortBy('ended')->toList();
        return $row;
    });
    return $results->sortBy(function ($row) {
        return isset($row['positions'][0]['ended']) ?
            $row['positions'][0]['ended'] : 0;
    });
});

这对我有用:

$this->Organizations->find('all')
->contain(['Positions' => ['sort' => ['Positions.ended' => 'DESC']]])
->contain('Postions.Skills');