CakePHP - 使用Paginator Helper的虚拟字段问题

时间:2013-12-05 23:42:19

标签: mysql cakephp pagination alias

我有一个带有lat / long字段的Lis​​tings表。我正在使用Haversine Formula计算原点(33.987339,-81.036819)与每个列表的纬度/经度之间的距离(作为别名/虚拟字段)并返回在原点10英里范围内距离的商家信息。

phpMyAdmin中的以下SQL查询完全返回我期望的内容:

SELECT *, round(3959 * acos(cos(radians(33.987339)) * cos(radians(Listing.lat)) * cos(radians(Listing.long) - radians(-81.036819)) + sin( radians(33.987339)) * sin(radians(Listing.lat)))) 
AS distance, `Listing`.`id` 
FROM `preview_site`.`listings` AS `Listing` 
LEFT JOIN `preview_site`.`users` AS `User` ON (`Listing`.`user_id` = `User`.`id`) 
LEFT JOIN `preview_site`.`categories` AS `Category` ON (`Listing`.`category_id` =   `Category`.`id`) 
LEFT JOIN `preview_site`.`states` AS `State` ON (`Listing`.`state_id` = `State`.`id`) 
WHERE `Listing`.`status` = 'Active' 
HAVING distance < 10 
ORDER BY `distance` ASC LIMIT 20

尝试(以及多种方式失败)获取CakePHP代码以正确生成上述SQL后,我使用this tool从SQL生成以下CakePHP控制器代码(它提供了模型和控制器选项):

$this->Paginator->virtualFields = array(
'distance' => 'round(3959 * acos(cos(radians(33.987339)) * cos(radians(Listing.lat )) * cos(radians(Listing.long) - radians(-81.036819)) + sin(radians(33.987339)) * sin(radians(Listing.lat))))');
$this->Paginator->settings = array(
'fields' => array(
    'Listing.*',
    'Listing.distance',
    'Listing.id',
    'Category.*',
    'State.*',
    'User.*',
),
'joins' => array(

    array(
        'conditions' => array(
            'Listing.user_id = UserJoin.id',
        ),
        'table' => 'users',
        'alias' => 'UserJoin',
        'type' => 'left',
    ),
    array(
        'conditions' => array(
            'Listing.category_id = CatJoin.id',
        ),
        'table' => 'categories',
        'alias' => 'CatJoin',
        'type' => 'left',
    ),
    array(
        'conditions' => array(
            'Listing.state_id = StateJoin.id',
        ),
        'table' => 'states',
        'alias' => 'StateJoin',
        'type' => 'left',
    ),
),
'conditions' => array(
    'Listing.status' => 'Active',
),
'order' => array(
    'distance' => 'asc',
),
'limit' => '5',
'having' => array(
    'distance <' => '10',
),
'contain' => array(
    'User',
    'Category',
    'State',
),
);
$data = $this->Paginator->paginate('Listing');
$this->set('listings', $data);

如果我使用此代码,则会收到以下错误:

Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Listing.distance' in 'field list'

如果我将$ this-&gt; Paginator - &gt; virtualFields更改为$ this-&gt; 列表 - &gt; virtualFields(因为我找不到任何关于Paginator实际上使用了virtualFields方法),我没有得到任何错误,并且分页工作正常,但返回的结果不受距离限制(返回所有列表记录)。以下是带有距离别名的生成SQL的片段:

SELECT `Listing`.*, `Listing`.`id`, `Category`.*, `State`.*, `User`.*, (round(3959 *  acos(cos(radians(33.987339)) * cos(radians(`Listing`.`lat` )) *  cos(radians(`Listing`.`long`) - radians(-81.036819)) + sin(radians(33.987339)) *  sin(radians(`Listing`.`lat`))))) 
AS `Listing__distance` 
FROM `preview_site`.`listings` AS `Listing` 

有没有人对如何使这项工作正确有任何建议?任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:1)

我认为你的问题来自哪里,我相信CakePHP不承认“有”。由于您似乎没有Group By,因此您可以使用常规WHERE并获得相同的结果,在本例中为array('conditions' => array('distance <' => 10))如果您确实有Group By,请参阅以下内容:

CakePHP: How can I use a "HAVING" operation when building queries with find method?

答案 1 :(得分:0)

关于虚拟领域有很多打开的门票。 这可能就是其中之一。

即使你对paginator的初始绑定看起来很糟糕。 您应该将虚拟字段添加到当前模型,因此列出。

$this->Listing->virtualFields['distance'] = ...

对我而言,在我无法轻松使用虚拟字段的情况下,它有助于手动使用别名字段,因此您的订单中包含Listing__distance ASC,或者更重要的是在您的having子句中。 它还将重用已经计算的字段而不是再次执行它(即使我不知道这里是否有速度改进)。请参阅this

另请注意,利用behavior来避免重复其他查询(并保持干燥)可能会更清晰:

$this->Listing->setDistanceAsVirtualField($lag, $lng);

我通常使用条件来限制距离(不需要有,有吗?)。