如何使用ActiveRecord创建多列IN谓词?

时间:2014-08-18 03:15:38

标签: sql ruby-on-rails ruby postgresql activerecord

我有以下'距离'表:

╔════╦════════════╦════════════╦═════════════════╦═════════════════╦══════════╗
║ id ║ origin_lat ║ origin_lng ║ destination_lat ║ destination_lng ║ distance ║
╠════╬════════════╬════════════╬═════════════════╬═════════════════╬══════════╣
║  1 ║ 1.234567   ║ 2.345678   ║ 3.456789        ║ 4.567890        ║       10 ║
║  2 ║ 5.678901   ║ 6.789012   ║ 7.890123        ║ 8.901234        ║       20 ║
╚════╩════════════╩════════════╩═════════════════╩═════════════════╩══════════╝

问题是,如何在必要时使用ActiveRecord和Arel创建以下SQL查询(由PostgreSQL支持):

SELECT * 
FROM distances
WHERE 
(origin_lat, origin_lng) IN ((1.234567, 2.345678), (5.678901, 6.789012))
AND
(destination_lat, destination_lng) IN ((3.456789, 4.567890), (7.890123, 8.901234));

我试过这个,但它不起作用:

Distance.where('(origin_lat, origin_lng) IN (?) AND (destination_lat, destination_lng) IN (?)', [[1.234567, 2.345678], [5.678901, 6.789012]], [[3.456789, 4.567890], [7.890123, 8.901234]])

它产生了这个:

SELECT "distances".* FROM "distances"  WHERE ((origin_lat, origin_lng) IN ('---
- 1.234567
- 2.345678
','---
- 5.678901
- 6.789012
') AND (destination_lat, destination_lng) IN ('---
- 3.456789
- 4.56789
','---
- 7.890123
- 8.901234
'))

并提出PG::FeatureNotSupported: ERROR: input of anonymous composite types is not implemented

参数的数量是可变的,所以我不能像这样对查询进行硬编码:

Distance.where('(origin_lat, origin_lng) IN ((?,?),(?,?)) AND (destination_lat, destination_lng) IN ((?,?),(?,?))', 1.234567, 2.345678, 5.678901, 6.789012, 3.456789, 4.567890, 7.890123, 8.901234)

我是否需要使用简单的SQL? :/

2 个答案:

答案 0 :(得分:2)

我想我最好的拍摄是建立"其中SQL"字符串我自己,展平并展开参数,所以我创建了这个方法:

class Distance < ActiveRecord::Base
  def self.distance_matrix(origins, destinations)
    return false if origins.empty? || destinations.empty?

    where_sql =  '(origin_lat, origin_lng) IN ('
    where_sql << (['(?, ?)'] * origins.length).join(', ')
    where_sql << ') AND (destination_lat, destination_lng) IN ('
    where_sql << (['(?, ?)'] * destinations.length).join(', ') << ')'

    where(where_sql, *origins.flatten, *destinations.flatten)
  end
end

并称之为:

Distance.distance_matrix([[1.234567, 2.345678], [5.678901, 6.789012]], [[3.456789, 4.567890], [7.890123, 8.901234]])

它有效:D

感谢@BradWerth让我走上正轨,并感谢@muistooshort使代码更具可读性。

答案 1 :(得分:1)

我猜它必须更像这样:

Element.where(&#39;(origin_lat,origin_lng)IN((?,?),(?,?))AND(destination_lat,destination_lng)IN((?,?),(?,?)) &#39;,1.234567,2.345678,5,678901,6.789012,3.456789,4.567890,7.890123,8.901234)