Ruby / Rails使用MySQL中的计算列计算范围内的对象

时间:2012-12-24 21:11:56

标签: mysql ruby-on-rails ruby scope

我无法弄清楚如何恰当地计算范围内的对象。

对于这个例子,让我们假设产品(“skus”)有一个lat和lng,用户也是如此。

我有一个有效的find_by_sql示例:

  def self.with_max_distance(user, distance)
    Sku.find_by_sql(
    <<-eos
      SELECT *,
      ( 3959 * acos( cos( radians(#{user.lat}) ) * cos( radians( skus.lat ) )
      * cos( radians(skus.lng) - radians(#{user.lng})) + sin(radians(#{user.lat}))
      * sin( radians(skus.lat)))) AS distance
      FROM skus
      HAVING distance < #{distance}
    eos
    )
  end

我能够让以下范围部分工作:

  scope :scope_with_max_distance, lambda { |user, distance|
    { :select => "skus.*, ( 3959 * acos( cos( radians(#{user.lat}) ) * cos( radians( skus.lat ) ) * cos( radians(skus.lng) - radians(#{user.lng})) + sin(radians(#{user.lat})) * sin( radians(skus.lat)))) AS distance",
      :having => "distance < #{distance}"
    }
  }

示波器似乎正确地获取了对象,虽然我没有对它进行过广泛的测试,但是如果我将它与其他示波器链接起来似乎也可以工作。

但是,我无法count()对象,这真的很烦人。我知道我可以使用length(),但事实上它不适用于计数让我想知道是否有更好的方法来做到这一点。

1.9.3p125 :048 > Sku.scope_with_max_distance(User.first, 50).limit(10).count
  User Load (0.5ms)  SELECT `users`.* FROM `users` LIMIT 1
   (0.6ms)  SELECT COUNT(count_column) FROM (SELECT 1 AS count_column FROM `skus` HAVING distance < 50 LIMIT 10) subquery_for_count 
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'distance' in 'having clause': SELECT COUNT(count_column) FROM (SELECT  1 AS count_column FROM `skus`  HAVING distance < 50 LIMIT 10) subquery_for_count 

我真的很感激任何建议或想法 - 我尝试了很多不同的方法,但却找不到完全有效的方法。如果问题需要进一步澄清,请告诉我。

1 个答案:

答案 0 :(得分:4)

count将仅使用所需的简单元素替换您的select,以尽可能高效地构建计数。它不会检查其他约束以确保SQL有效。您可以使用您选择的公式替换distance中的having别名以解决此问题。另外,我建议您将having替换为where,以避免不是数据库可移植的隐式分组:

scope :scope_with_max_distance, lambda { |user, distance|
  distance_formula = "(3959 * acos( cos( radians(#{user.lat}) ) * cos( radians( skus.lat ) ) * cos( radians(skus.lng) - radians(#{user.lng})) + sin(radians(#{user.lat})) * sin( radians(skus.lat))))"
  { :select => "skus.*, #{distance_formula} AS distance",
    :where => "#{distance_formula} < #{distance}"
  }
}

最后一件事,你在count之后调用limit(10)这意味着计数永远不会超过10.这本身并不是错误的,但我不确定这是你的意思做。