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

时间:2020-11-06 10:45:10

标签: sorting cakephp subquery cakephp-3.x

这可能已经在其他地方得到了答案,但是我过去几天一直在寻找答案,却找不到适合我的问题/我理解的答案...

我正在使用CakePHP 3.8.5,目前正在处理在选择中包含子查询的查询。 我有3个表LocationsComputersPrintersLocationsComputersbelongsToManyLocations之间的关系是Printers

因此,我尝试获取以下查询,就数据结果而言,该查询运行良好:

    $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');

因此,我试图在Controller中对$dataQuery进行分页。在我的模型中,我可以单击所有三个列标题,但是只有Area_ID列会被排序。两个子查询列将不排序。即使我也没有收到错误。 查看SQL日志显示,它甚至从未尝试order by这两列...

任何解决此问题/解决此问题的想法都受到高度赞赏!

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

编辑1:

正如@ndm用户指出的那样,我不得不将计算字段放入分页数组的sortWhitelist选项中。 这样做很好,因为我能够按列标题进行排序:

    $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 Server可以读取它:

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

有什么办法解决这个问题吗?

编辑2:

因此,通过newExpr()函数通过pull-request或@fix进行@ndm回答都非常有效。至少要考虑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的大力帮助!绝对是我的支持。

1 个答案:

答案 0 :(得分:0)

如注释中所链接,默认情况下,分页器仅允许对主表中存在的列,其他表(联接)的列或计算列must be explicitly allowed via the sortWhiteList option进行排序。

在生成的窗口函数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');