【问题标题】:How to create a multi-column IN predicate with ActiveRecord?如何使用 ActiveRecord 创建多列 IN 谓词?
【发布时间】:2023-04-09 03:28:01
【问题描述】:

我有以下“距离”表:

╔════╦════════════╦════════════╦═════════════════╦═════════════════╦══════════╗
║ 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 吗? :/

【问题讨论】:

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


【解决方案1】:

我想我最好的办法是自己构建“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

然后这样称呼它:

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

它的工作原理:D

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

【讨论】:

  • 您可能希望使用['(?,?)'] * origins.length 之类的内容作为Array.new(origins.count, '(?, ?)') 的可读性更高的版本。如果您将postgres_ext 与Rails3 一起使用,您可以在参数列表中将其简化为['(?)'] * origins.length*origins。我想不出任何其他方法可以使 AR 表现得更合理,但 AR 对 SQL 的理解非常有限,以至于我倾向于直接动手做。
【解决方案2】:

我猜它必须更像这样:

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

【讨论】:

  • 这可行,但参数(1.234567、2.345678 等)不是硬编码的,它们来自数组,就像我发布的那样。会努力完成这项工作,马上回来。
  • 调用#flatten 并喷溅外部数组有效。问题是,参数的数量是可变的。
  • 嗯,第一个参数实际上是一个字符串,所以你可以通过迭代你的数组来构造它,并尽可能多地添加(如果这有意义的话)......
  • 是的,我想这是最好的选择。谢谢,去试试。
猜你喜欢
  • 2017-03-21
  • 2018-02-01
  • 1970-01-01
  • 2011-09-17
  • 1970-01-01
  • 1970-01-01
  • 2017-10-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多