【问题标题】:DBIx::Class Perimeter SearchDBIx::Class 边界搜索
【发布时间】:2013-03-14 03:29:13
【问题描述】:

我正在尝试使用 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 ...

【问题讨论】:

  • 就个人而言,我会为此使用存储过程。您不能在选择中将参数绑定到函数 arg。
  • 您应该能够使用子选择和未记录的from ResultSet 属性来执行此操作。我可以看看这明天是否有效。

标签: perl dbix-class


【解决方案1】:

我有同样的问题:我有companies 哪个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,
        });

其中$customers 将是$comp 12 公里范围内companies 列表的数组引用,这也是company

【讨论】:

  • 您的解决方案没有为用户提供的值使用绑定值,这可能导致 sql 注入。
  • 我的代码几乎相同。上次我尝试(大约 18 个月前?)SQL::Abstract 无法处理查询 - 'AS'、'HAVING' 和 'ORDER BY' 的组合以及具有多个参数的函数。当然,这现在可能已经改变了。至于可能的 SQL 注入 - 只有距离和纬度/经度被插值,前者很容易清除,其他将来自数据库中的其他行。
  • @abraxxa - 正如@plusplus 所提到的,我在插值中仅使用$self-&gt;lat$self-&gt;long - 它们来自数据库并且类型为DECIMAL - 如果你能想出一些可能那里出错了,请告诉我 - 否则,是的,我不使用或鼓励这种类型的查询构建
【解决方案2】:

此类复杂查询的一种解决方案是将它们定义为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,
    });

【讨论】:

  • 他不能使用视图,因为他不能将参数绑定到USERLAT。这也是我的第一个想法。
  • @jordanm 是的,在这种情况下确实如此,这只是一个一般性建议。
【解决方案3】:

您可以重写您的查询以使用这样的子查询:

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 属性 fromfrom 属性的一些使用示例可以在DBIx::Class test suite 中找到。请注意,由于此属性未记录在案,因此未来版本可能不支持它。

【讨论】:

    猜你喜欢
    • 2013-03-08
    • 1970-01-01
    • 2011-10-29
    • 1970-01-01
    • 2011-03-26
    • 1970-01-01
    • 2011-07-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多