2

我正在尝试使用 DBIx::Class 进行周边搜索,但到目前为止还没有成功。

我想生成的 SQL 如下所示:

SELECT 
    zip,
    6371 * ACos( Cos(RADIANS(Lat)) * Cos(RADIANS(USERLAT)) * Cos(RADIANS(USERLNG) - RADIANS(Lng)) + Sin(RADIANS(Lat)) * Sin(RADIANS(USERLAT)) ) AS Distance
FROM 
    geopc
WHERE 
    6371 * ACos( Cos(RADIANS(Lat)) * Cos(RADIANS(USERLAT)) * Cos(RADIANS(USERLNG) - RADIANS(Lng)) + Sin(RADIANS(Lat)) * Sin(RADIANS(USERLAT)) ) <= DISTANCE
ORDER BY 
    Distance

其中 USERLAT、USERLNG 和 DISTANCE 应该是变量,它们将通过 Webrequest 进入。

我的 DBIx::Class 结果:

use utf8;
package MyApp::Models::Schema::Result::Geopc;

use strict;
use warnings;

use base 'DBIx::Class::Core';

__PACKAGE__->table("geopc");

__PACKAGE__->add_columns(
  "id",
  { data_type => "bigint", is_nullable => 0, is_auto_increment => 1 },
  "country",
  { data_type => "varchar", is_nullable => 0, size => 2 },
  "language",
  { data_type => "varchar", is_nullable => 0, size => 2 },
  "iso2",
  { data_type => "varchar", is_nullable => 0, size => 6 },
  "region1",
  { data_type => "varchar", is_nullable => 0, size => 60 },
  "region2",
  { data_type => "varchar", is_nullable => 0, size => 60 },
  "region3",
  { data_type => "varchar", is_nullable => 0, size => 60 },
  "region4",
  { data_type => "varchar", is_nullable => 0, size => 60 },
  "zip",
  { data_type => "varchar", is_nullable => 0, size => 10 },
  "city",
  { data_type => "varchar", is_nullable => 0, size => 60 },
  "area1",
  { data_type => "varchar", is_nullable => 0, size => 80 },
  "area2",
  { data_type => "varchar", is_nullable => 0, size => 80 },
  "lat",
  { data_type => "double precision", is_nullable => 0 },
  "lng",
  { data_type => "double precision", is_nullable => 0 },
  "tz",
  { data_type => "varchar", is_nullable => 0, size => 30 },
  "utc",
  { data_type => "varchar", is_nullable => 0, size => 10 },
  "dst",
  { data_type => "varchar", is_nullable => 0, size => 1 },
);
__PACKAGE__->set_primary_key('id');

我用谷歌搜索,但没有找到处理这个问题的好方法。任何帮助将不胜感激。

我正在使用 MySQL ...

4

3 回答 3

1

我有同样的问题:我有companieswhich belongs_to address,所以一个地址has_many companies- 我需要找到邻居公司,所以,使用Adress模型:

__PACKAGE__->add_columns(
  "id",
  { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
  "country",
  { data_type => "varchar", is_nullable => 0, size => 64 },
  "county",
  { data_type => "varchar", is_nullable => 1, size => 45 },
  "city",
  { data_type => "varchar", is_nullable => 0, size => 64 },
  "street",
  { data_type => "varchar", is_nullable => 0, size => 128 },
  "street_no",
  { data_type => "varchar", is_nullable => 1, size => 24 },
  "apartment_no",
  { data_type => "varchar", is_nullable => 1, size => 24 },
  "extra",
  { data_type => "varchar", is_nullable => 1, size => 128 },
  "lat",
  { data_type => "decimal", is_nullable => 1, size => [10, 7] },
  "long",
  { data_type => "decimal", is_nullable => 1, size => [10, 7] },
);

我已经get_neighbour_companies在该模型中实现了该方法:

sub get_neighbour_companies{
  my ( $self, $args ) = @_;

  my $distance = $args->{distance} // 15;

  my $geo_clause = sprintf('( 6371 * acos( cos( radians(%s) ) * cos( radians( me.lat ) ) * cos( radians( me.`long` ) - radians(%s) ) + sin( radians(%s) ) * sin( radians( me.lat ) ) ) ) AS distance', $self->lat, $self->long, $self->lat );

  my $rs = $self->result_source->schema->resultset('Address')
    ->search_rs(
      {
        'companies.company_type_id' => ( $args->{company_type_id} // 1 ), #defaults to 'orderer' type
      },
      {
        prefetch => { 'companies' => 'address' },
        select => [ 'id', \$geo_clause ],
        as     => [qw/ id distance /],
        having => { distance => { '<=' => $distance } },
        order_by => 'distance',
      }
    );

  my @companies;
  while ( my $address = $rs->next ){
    my @comps = $address->companies()->all;
    next unless @comps;

    foreach my $company ( @comps ) {
        push @companies, {
            company => $company,            
            distance => $address->get_column('distance'),
          };
    }    
  };
  return [ @companies ]; 
}

我这样使用它:

my $customers = $comp->address->get_neighbour_companies({
          distance        => 12,
          company_type_id => 1,
        });

where将是12 公里范围内$customers的列表的数组引用,这也是companies$compcompany

于 2013-03-13T07:39:52.367 回答
1

此类复杂查询的一种解决方案是将它们定义为视图。如果您定义了与它的关系,那么它的优点是可以连接和预取。

另一个将用于计算的“距离”列。'columns' 只是 'select' 和 'as' 参数的组合,它已被证明是一个更强大的 api,可以减少用户错误。请注意,搜索语法来自SQL::Abstract,它提供了一些使用文字 sql的方法。

没有子查询的最佳解决方案是:

my $param = \[
        '6371 * ACos( Cos(RADIANS(Lat)) * Cos(RADIANS(?)) * Cos(RADIANS(?)' .
        ' - RADIANS(Lng)) + Sin(RADIANS(Lat)) * Sin(RADIANS(?)) )'
        [ USERLAT  => $USERLAT ],
        [ USERLNG  => $USERLNG ],
        [ USERLAT  => $USERLAT ],
    ];

my $geopc = $schema->resultset('Result::Geopc')->search({
        $param => { '<=', $distance },
    }, {
        columns => [
           'zip',
           { distance => $param }
        ],
        order_by => $param,
    });
于 2013-03-12T23:28:58.600 回答
0

您可以重写查询以使用如下子查询:

SELECT zip, Distance
FROM (SELECT zip,
        6371 * ACos( Cos(RADIANS(Lat)) * Cos(RADIANS(USERLAT)) * Cos(RADIANS(USERLNG) - RADIANS(Lng)) + Sin(RADIANS(Lat)) * Sin(RADIANS(USERLAT)) )
        AS Distance
     FROM geopc) AS tmp
WHERE Distance <= DISTANCE
ORDER BY Distance

然后像下面这样的东西应该工作:

my $geopc = $schema->resultset('Result::Geopc');

my $subquery = $geopc->search({}, {
    select => [
        'zip',
        \[
            '6371 * ACos( Cos(RADIANS(Lat)) * Cos(RADIANS(?)) * Cos(RADIANS(?)' .
            ' - RADIANS(Lng)) + Sin(RADIANS(Lat)) * Sin(RADIANS(?)) )' .
            ' AS Distance',
            [ USERLAT  => $USERLAT ],
            [ USERLNG  => $USERLNG ],
            [ USERLAT  => $USERLAT ],
        ],
    ],
})->as_query;

my $rs = $geopc->search({
    Distance => { '<=' => $DISTANCE },
}, {
    alias => 'geopc2',
    from => [
        { geopc2 => $subquery },
    ],
    select => [ qw(zip Distance) ],
    order_by => 'Distance',
});

这种方法使用带有占位符和未记录的 ResultSet 属性的文字 SQLfromfrom可以在DBIx::Class 测试套件中找到该属性的一些使用示例。请注意,由于此属性未记录在案,因此未来版本可能不支持它。

于 2013-03-13T12:37:49.703 回答