我正在开发一个CakePHP 3.7的应用程序。
我们有 3 个具有以下层次结构的数据库表。这些表根据表类正确关联:
regulations
groups
filters
(关联显示在上一个问题中:CakePHP 3 - 未定义关联 - 即使它看起来是)
我可以从所有三个表中获取所有数据,如下所示:
$regulations = TableRegistry::getTableLocator()->get('Regulations');
$data = $regulations->find('all', ['contain' => ['Groups' => ['Filters']]]);
$data = $data->toArray();
filters
表包含>1300 条记录。因此,我正在尝试构建一个功能,该功能在用户向下滚动页面时通过 AJAX 调用逐步加载数据,类似于此处描述的内容:https://makitweb.com/load-content-on-page-scroll-with-jquery-and-ajax/
问题是我需要能够计算返回的行总数。但是,此数据存在于 3 个表中。
如果我这样做debug($data->count());
它将输出 8,因为regulations
表中有 8 行。理想情况下,我需要它在filters
表中返回的行数(~1300 行),这是初始加载中存在的大部分数据的地方。
问题更加复杂,因为此功能允许用户执行搜索。可能是给定的搜索词存在于所有 3 个表中、1 - 2 个表中,或者根本不存在。我不知道正确的方法是尝试计算每个表中返回的行,还是整个行?
我已阅读 Cake 文档中的如何分页关联记录?和按关联数据筛选。
其他信息
问题似乎归结为如何使用 Cake 提供的 ORM 语法编写查询。以下(普通MySQL)查询实际上将执行我想要的操作。假设用户搜索了"石棉":
SELECT
regulations.label AS regulations_label,
groups.label AS groups_label,
filters.label AS filters_label
FROM
groups
JOIN regulations
ON groups.regulation_id = regulations.id
JOIN filters
ON filters.group_id = groups.id
WHERE regulations.label LIKE '%Asbestos%'
OR groups.label LIKE '%Asbestos%'
OR filters.label LIKE '%Asbestos%'
ORDER BY
regulations.id ASC,
groups_label ASC,
filters_label ASC
LIMIT 0,50
假设返回了 203 行。LIMIT
条件意味着我得到了前 50 名。前端上的一些 js(就其工作方式而言并不真正相关)将在用户滚动到页面底部以不同的限制重新运行此查询时进行 ajax 调用(例如LIMIT 51, 100
下一组结果)。
问题似乎是双重的:
如果我使用 Cake 的 ORM 语法编写查询,则输出是一个嵌套数组。相反,如果我用普通 SQL 编写它,它会返回一个只有 3 列的表,其中包含我需要显示的标签。在页面上输出所需数据方面,此结构更容易使用。
第二个 - 也许是更重要的 - 由于 1 中描述的嵌套结构,我看不出如何在 ORM 语法中编写
LIMIT
条件来使其工作。例如,如果我添加了$data->limit(50)
,它只会对regulations
表施加此限制。因此,从本质上讲,搜索适用于regulations
前 50 行的任何关联数据。与我在 MySQL 中编写的LIMIT
条件相反,该条件将考虑整个结果集,其中包括所有 3 个表中的列。
为了进一步阐述第 2 点,假设表包含以下行数:
regulations
: 150groups
: 1000filters
: 5000
如果我使用$data->limit(50)
它仅适用于regulations
表中的 50 行。我需要在搜索给定术语的所有 3 个表中的所有行后应用结果集LIMIT
。
使用查询生成器创建该查询非常简单,如果您希望非 1:1 关联的连接,请使用*JoinWith()
方法而不是contain()
,在本例中为innerJoinWith()
,如下所示:
$query = $GroupsTable
->find()
->select([
'regulations_label' => 'Regulations.label',
'groups_label' => 'Groups.label',
'filters_label' => 'Filters.label',
])
->innerJoinWith('Regulations')
->innerJoinWith('Filters')
->where([
'OR' => [
'Regulations.label LIKE' => '%Asbestos%',
'Groups.label LIKE' => '%Asbestos%',
'Filters.label LIKE' => '%Asbestos%',
],
])
->order([
'Regulations.id' => 'ASC',
'Groups.label' => 'ASC',
'Filters.label' => 'ASC',
]);
参见
- Cookbook> Database Access & ORM> Query Builder> Using innerJoinWith
- Cookbook> Database Access & ORM> Query Builder> Add Joins