在 CakePHP 3 中对子查询列进行分页排序

Paginate sort on subquery column in CakePHP 3

这可能已经在其他地方得到了回答,但过去几天我一直在寻找答案,但找不到适合我的答案 problem/that 我明白了...

我正在使用 CakePHP 3.8.5,目前正在处理一个包含 select 中的子查询的查询。 我有 3 个表 LocationsComputersPrintersLocationsComputers 处于 belongsToMany 关系,以及 LocationsPrinters.

所以我正在尝试获取以下查询,就数据结果而言,它运行良好:

    $computersQuery = $this->Computers->find();
    $computersQuery->select([$computersQuery->func()->count('*')])
        ->where(function (QueryExpression $exp) {
            return $exp
                ->notEq('Computers.Unwanted_Software', '')
                ->equalFields('Computers.Area_ID', 'Locations.Area_ID');
        });
        
    $printersQuery = $this->Printers->find();
    $printersQuery->select($printersQuery->func()->count('*'))
        ->where(function (QueryExpression $exp) {           
            return $exp
                ->eq('Printers.WHQL', 0)
                ->equalFields('Printers.Area_ID', 'Locations.Area_ID');
        });
    
    $dataQuery = $this->Locations->find();
    $dataQuery->select(['Locations.Area_ID',
            'Unwanted_Software' => $computersQuery,
            'Printers_Not_WHQL_Compatible' => $printersQuery])
        ->group('Locations.Area_ID');

所以我试图在我的控制器中对 $dataQuery 进行分页。在我的模型中,我可以单击所有三列 headers 但只有 Area_ID 列会被排序。两个子查询列不会排序。即使我没有收到错误。 查看 SQL-log 表明它甚至从未尝试 order by 这两列...

非常感谢任何解决此问题的想法 this/work!

如果您需要有关我的代码的更多信息,请在下面发表评论。

编辑 1:

正如用户@ndm 指出的那样,我必须将计算字段放入分页数组的 sortWhitelist 选项中。 这样做效果很好,因为我能够按 headers:

列进行排序
    $this->paginate = [
        'limit' => '100',
        'sortWhitelist' => [
            'Locations.Area_ID',
            'Unwanted_Software',
            'Printers_Not_WHQL_Compatible'
        ],
        'order' => ['Locations.Area_ID' => 'ASC']
    ]

但是接下来的问题出现了。试图按 Printers_Not_WHQL_Compatible 列排序。生成的 SQL 代码有一个小问题(顺便说一句,我使用的是 SQL Server 2008):

    SELECT * 

    FROM (SELECT Locations.Area_ID AS [Locations__Area_ID], 
    (SELECT (COUNT(*)) FROM computers Computers WHERE (...)) AS [Unwanted_Software], 
    (SELECT (COUNT(*)) FROM printers Printers WHERE (...)) AS [Printers_Not_WHQL_Compatible], 
    (ROW_NUMBER() OVER (ORDER BY SELECT (COUNT(*)) FROM printers Printers WHERE (...) asc, Locations.Area_ID ASC)) AS [_cake_page_rownum_] FROM locations_Views Locations GROUP BY Locations.Area_ID ) _cake_paging_ 

    WHERE _cake_paging_._cake_page_rownum_ <= 100

这表示生成的 SQL 代码。问题是在 de Order By 语句中,我的子查询周围没有括号。它应该看起来像这样,以便 SQL 服务器可以读取它:

    ... ORDER BY ( SELECT (COUNT(*)) FROM printers Printers WHERE (...) ) asc, ...

有什么解决办法吗?

编辑 2:

所以 @ndm 使用 pull-request 或通过 newExpr() 函数进行修复的答案都很好。至少关于 Order By.

中子查询的括号

遗憾的是已经运行进入下一个问题。生成的 SQL(这对两种解决方案都很重要!)有点“刷新”Order By 中整个查询的参数输入,这意味着它为 Where 放置过滤器参数通过参数 :c0 重新启动的子句。您可以在以下查询中看到:

SELECT * 

FROM (SELECT Locations.Area_ID AS [Locations__Area_ID], 
(SELECT (COUNT(*)) 
    FROM computers Computers 
    WHERE (Computers.Unwanted_Software != :c0 
    AND Computers.Area_ID = (Locations.Area_ID)
    )
) AS [Unwanted_Software], 
(SELECT (COUNT(*)) 
    FROM printers Printers 
    WHERE (Printers.WHQL = :c1 
    AND Printers.Area_ID = (Locations.Area_ID))
) AS [Printers_Not_WHQL_Compatible], 
(ROW_NUMBER() OVER (ORDER BY 
    (SELECT (COUNT(*)) 
        FROM printers Printers 
        WHERE (Printers.WHQL = :c0 
        AND Printers.Area_ID = (Locations.Area_ID))) asc,
    Locations.Area_ID ASC)
) AS [_cake_page_rownum_] 

FROM locations_Views Locations 

GROUP BY Locations.Area_ID ) _cake_paging_ 

WHERE _cake_paging_._cake_page_rownum_ <= 100

我认为这不是预期的结果。我个人可能可以通过避免直接传递参数来解决这个问题(我不必处理具体的外部用户输入)。仍然认为应该检查一下。

感谢@ndm 的大力帮助!绝对是我的赞成票。

如评论中所链接,默认情况下,分页器仅允许对存在于主 table 中的列、其他 table 的列(连接)或计算列 .

生成的 window 函数中缺少括号 SQL 看起来像一个错误,the SQL Server query translator 不检查 order 中定义的表达式是否子句是一个查询,需要将生成的 SQL 括在括号中。

我已经推送了 3.94.1 的可能修复:

https://github.com/cakephp/cakephp/pull/15165
https://github.com/cakephp/cakephp/pull/15164

如果您现在无法升级,可能的解决方法是将您的子查询包裹在额外的表达式中,编译时应将内部查询表达式包裹在括号中:

$dataQuery
    ->select([
        'Locations.Area_ID',
        'Unwanted_Software' => $dataQuery->newExpr($computersQuery),
        'Printers_Not_WHQL_Compatible' => $dataQuery->newExpr($printersQuery)
    ])
    ->group('Locations.Area_ID');