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

Fel*_*van 5 ruby sql postgresql activerecord ruby-on-rails

我有以下'距离'表:

???????????????????????????????????????????????????????????????????????????????
? 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 ?
???????????????????????????????????????????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

问题是,如何在必要时使用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));
Run Code Online (Sandbox Code Playgroud)

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

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]])
Run Code Online (Sandbox Code Playgroud)

它产生了这个:

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
'))
Run Code Online (Sandbox Code Playgroud)

并加注 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)
Run Code Online (Sandbox Code Playgroud)

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

Fel*_*van 2

我想我最好的办法是自己构建“where 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
Run Code Online (Sandbox Code Playgroud)

并这样称呼它:

Distance.distance_matrix([[1.234567, 2.345678], [5.678901, 6.789012]], [[3.456789, 4.567890], [7.890123, 8.901234]])
Run Code Online (Sandbox Code Playgroud)

它有效:D

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

  • 您可能想使用像 `['(?,?)'] * origins.length` 这样的东西作为 `Array.new(origins.count, '(?, ?)')` 的可能更具可读性的版本。如果您将 [postgres_ext](https://github.com/dockyard/postgres_ext) 与 Rails3 一起使用,您可以将其简化为参数列表中的 `['(?)'] * origins.length` 和 `*origins` 。我想不出任何其他方法可以让 AR 表现得更明智,但 AR 对 SQL 的理解非常有限,所以我倾向于直接直接手动完成。 (2认同)