DBIx ::类周界搜索

时间:2013-03-12 22:14:23

标签: perl dbix-class

我正在尝试使用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 ::类结果:

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 ......

3 个答案:

答案 0 :(得分:1)

此类复杂查询的一个解决方案是将它们定义为view。如果您定义与它的关系,那么它具有加入和预取的优势。

另一个使用columns计算“距离”列的人。 'columns'只是'select'和'as'参数的组合,已被证明是一个更强大的api,可以减少用户错误。 请注意,搜索语法来自SQL::Abstract,它提供了一些使用literal 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,
    });

答案 1 :(得分:1)

我遇到了同样的问题:companies belongs_to addresshas_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, });

$customers

我正在使用它:

companies

其中$comp将是company的{​​{1}}列表中的数组引用{{1}},也是{{1}}

答案 2 :(得分: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',
});

此方法使用literal SQL with placeholders和未记录的ResultSet属性from。可以在DBIx::Class test suite中找到from属性的一些用法示例。请注意,由于此属性未记录,因此在将来的版本中可能不支持该属性。