2013-03-12 45 views
2

我想獲得使用DBIx :: Class的周界搜索,但到目前爲止還沒有成功。DBIx ::類外圍搜索

我想產生這樣看起來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和距離應變量,這會在一個直通的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'); 

我身邊有GOOGLE上搜索,但沒有發現來處理這一個很好的方式。任何幫助將非常感激。

我使用MySQL的...對於這樣複雜的查詢

+0

個人而言,我會爲此使用存儲過程。您不能在select中將參數綁定到函數arg。 – jordanm 2013-03-12 22:56:16

+0

你應該可以通過子查詢和未記錄的'from' ResultSet屬性來做到這一點。我可以看看這是否明天有效。 – nwellnhof 2013-03-13 00:34:59

回答

1

一種解決方案是將其定義爲一個view。如果你定義了它的關係,那麼它的優點是可以加入和預取。

另一個使用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, 
    }); 
+0

他無法使用視圖,因爲他無法將參數綁定到「USERLAT」。這也是我的第一個想法。 – jordanm 2013-03-13 05:15:46

+0

@jordanm是的,在這種情況下是這樣,這只是一個普遍的建議。 – 2013-03-13 19:21:14

1

我有同樣的問題:我有companiesbelongs_toaddress,所以地址has_manycompanies - 我需要找到鄰居的公司,因此,使用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 12公里之內,這也是company

+0

您的解決方案不會爲用戶提供的值導致sql注入使用綁定值。 – 2013-03-13 11:27:52

+1

我有幾乎相同的代碼。我最後一次嘗試(大約18個月前)?SQL :: Abstract無法處理查詢 - 「AS」,「HAVING」和「ORDER BY」的組合以及具有多個參數的函數。當然這現在可能已經改變了。至於可能的SQL注入 - 它只是插入的距離和緯度/長度,前者很容易被忽略,其他的將來自數據庫中的其他行。 – plusplus 2013-03-13 12:43:29

+0

@abraxxa - 作爲@plusplus提到的,我在插值中只使用'$ self-> lat'和'$ self-> long' - 這些來自DB並且是'DECIMAL'類型的 - 如果你能夠確定可能會出現問題,請告訴我 - 否則,是的,我沒有使用或鼓勵這種類型的查詢構建 – 2013-03-13 12:51:35

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屬性的一些使用示例。請注意,由於此屬性未記錄,因此未來版本可能不支持此屬性。