【问题标题】:Developing custom functions in Postgres using Rails使用 Rails 在 Postgres 中开发自定义函数
【发布时间】:2014-03-13 17:45:15
【问题描述】:

假设我有一个大的复合公式来计算小部件的质量

quality = 0.4(factory_quality) + 0.3(1/days_since_manufacture) + 0.3(materials_quality)

这三个因素中的每一个都是函数本身,它们需要连接到工厂表,并且可能需要连接到带有材料的物料清单连接表,其中关联的记录是平均的或其他的。

在架构上,您将如何在 Rails 项目中管理它? a) 产生正确的查询和 b) 在 Rails 中管理代码的最佳做法是什么?

目前对于 sql,我在 FROM 语句中使用子查询:

SELECT *,
  (0.4 * factory_quality + 0.3 * (1/days_since_manufacture) + 0.3 * materials_quality) AS quality
FROM (
  SELECT *,
    ((factories.last_inspection_score + factories.variance_score)/2) AS factory_quality,
    (now() - widgets.created_at) AS days_since_manufacture,
    SUM(materials.quality_score) AS materials_quality
  FROM widgets,
  JOIN factories ON widget.factory_id = factories.id
  JOIN bills_of_materials ON widget.id = bills_of_materials.widget_id
  JOIN materials ON bills_of_materials.material_id = materials.id
  GROUP BY widgets.id
) AS widgets;

在 Rails 中,我主要使用 ActiveRecord 来实现:

class Widget < ActiveRecord::Base
  belongs_to :factory
  has_many :bills_of_material
  has_many :materials, through :bills_of_material

  class << self
    def with_quality
      select([
        "widgets.*",
        "(0.4 * factory_quality + 0.3 * (1/days_since_manufacture) + 0.3 * materials_quality) AS quality"
      ].join(",")
      .from("(#{subquery}) AS widgets")
    end
    private
      def subquery
        select([
          "widgets.*",
          "((factories.last_inspection_score + factories.variance_score)/2) AS factory_quality",
          "(now() - widgets.created_at) AS days_since_manufacture",
          "SUM(materials.quality_score) AS materials_quality"
        ].join(","))
        .joins(:factory,:materials)
        .group("widgets.id")
        .to_sql
      end
  end
end

也就是说,我觉得我可以在 Postgres 中将其设为自定义函数,将所有这些 sql 移动到该函数中,迁移它,然后清理 rails 的样子

def with_scores
  select("*,quality_score_func(id) AS quality")
end

或类似的东西,但我觉得通过数据库迁移来管理一个不断发展的公式将是一件很痛苦的事情,更不用说找出公式的当前形式是什么的任务是(也很难测试)。

其他人是如何解决这个问题的?有什么提示或建议吗?

【问题讨论】:

  • 澄清一下,我并不是要避免使用 sql,而且出于性能原因,我相信计算属于数据库。我只是想知道是否有人开发了一种设计模式来清理这段代码。

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


【解决方案1】:

这是我能想到的最不符合 SQL 的方法。我无法真正测试这个,但希望它至少是一个有用的练习。据我了解,如果您使用includes,Rails 会将连接放在一起并在一个查询中急切加载所有相关数据。

# All of these are additional Widget instance methods; you decide if they are private
#
# Example use:
#
#   @widget = Widget.includes(:factory, :materials).find(1)
#   puts @widget.quality_score
# or
#   @widgets = Widget.includes(:factory, :materials).all
#   @widgets.each { |widget| puts widget.quality_score }

# Consider making these weights named constants
def quality_score
  0.4 * factory_quality + \
  0.3 * (1/days_since_manufacture) + \
  0.3 * (materials_quality_score )
end

def days_since_manufacture
  Time.now - created_at
end

def factory_quality
  (factory.last_inspection_score + factory.variance_score)/2
end

def materials_quality_score
  materials.inject(0) {|sum, material| sum + material.quality_score }
end

【讨论】:

  • 感谢您的回答,但我并不想在 Ruby 中做更多的工作,恰恰相反。如果我在 Ruby 中进行计算,是的,代码会更干净,但是为了按质量进行前 5 名之类的查询,我需要实例化所有记录,计算所有记录,在内存中排序,然后丢弃 N - 5。我愿意忍受代码的丑陋以避免对性能造成影响,但我觉得这一定是一个足够普遍的问题,可能有比我做的更丑的方法。
  • 对造成的误解深表歉意。如果是这种情况,我同意定义 Postgres 函数是个坏主意。相反,我想我会定义 Ruby 方法来生成适当的 sn-ps 并将它们组合成您可以发送到 find_by_sql 的东西。我不认为这看起来会好很多,但它允许您将可能从阻塞和处理中改变的计算区分开来。
猜你喜欢
  • 1970-01-01
  • 2014-04-07
  • 1970-01-01
  • 1970-01-01
  • 2010-12-18
  • 2012-07-23
  • 2012-02-08
  • 2020-06-30
  • 2012-10-27
相关资源
最近更新 更多