【问题标题】:Build complex rails SQL构建复杂的 Rails SQL
【发布时间】:2011-10-21 17:39:15
【问题描述】:

我正在使用 postgreSQL。需要使用punchlines_count 获取最近的笑话的范围。我已经用我的范围实现了这一点。我需要知道的是我的妙语计数器不应包括具有警告级别 > 1 的妙语。警告模型具有妙语 ID 和重量,重量 == 警告级别。请帮我建立这个查询。 澄清:Punchline 警告可能是权重 1 或 2,或者可能有 2 个警告,每个权重为 1。如果 warn_level > 1 我不应该将其计入我的范围。谢谢!

我的模型。

class Joke < ActiveRecord::Base
  COLUMNS = self.column_names.map{|c| "jokes.#{c}" }.join(', ') 
  has_many :punchlines, :dependent => :destroy

  scope :recent, :order => 'jokes.created_at DESC'
  scope :recent_jokes_with_punchline_counter, lambda { |limit|
                        select("#{Joke::COLUMNS}, COUNT(punchlines.id) as punchlines_count").
                                             joins(:punchlines).
                                             group(Joke::COLUMNS).limit(limit) }


end

class Punchline < ActiveRecord::Base
  belongs_to :joke
  belongs_to :user
  has_many :warns
end


class Warn < ActiveRecord::Base
  belongs_to :punchline
  belongs_to :user
end

架构:

create_table "jokes", :force => true do |t|
    t.text     "content"
    t.integer  "user_id"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "up_votes",    :default => 0,     :null => false
    t.integer  "down_votes",  :default => 0,     :null => false
    t.string   "cached_slug"
    t.integer  "popularity"
    t.boolean  "anonymous",   :default => false
    t.string   "shorten_url"
  end

  create_table "punchlines", :force => true do |t|
    t.text     "content"
    t.integer  "user_id"
    t.integer  "joke_id"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "up_votes",    :default => 0,     :null => false
    t.integer  "down_votes",  :default => 0,     :null => false
    t.string   "cached_slug"
    t.boolean  "anonymous",   :default => false
  end

  create_table "warns", :force => true do |t|
    t.integer "punchline_id"
    t.integer "user_id"
    t.integer "weight"
  end
end

【问题讨论】:

  • 你为什么不分配赏金?这很糟糕......

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


【解决方案1】:

我认为你最好通过在 punchlines 上创建一个 warn_level 字段来解决这个问题。

就像 ActiveRecord 神奇地为您提供的counter,我们可以做类似的事情。

add_column :punchlines, :warn_level, :integer, :default => 0

class Punchline < ActiveRecord::Base
  def update_warn_level!
    self.update_attribute(:warn_level, self.warns.sum(:weight))
  end
end 

添加警告后,您可以手动调用此方法,或让观察者为您执行此操作。

http://guides.rubyonrails.org/active_record_validations_callbacks.html#observers

class WarnObserver < ActiveRecord::Observer
  def after_create(model)
    if model.punchline
      model.punchline.update_warn_level!
    end
  end
end

# in your application.rb
config.active_record.observers = :warn_observer

有了这个,你的问题就变得简单多了, 我们可以用下面的 sql 做你想做的事。

SELECT jobs.*, (
  SELECT COUNT(*) FROM punchlines
  WHERE punchlines.job_id = jobs.id
  AND punchlines.warn_level <= 1
) AS punchline_count

这可以用 ActiveRecord 来表达

PUNCHLINE_COUNT = <<-SQL
  SELECT COUNT(*) FROM punchlines
  WHERE punchlines.job_id = jobs.id
  AND punchlines.warn_level <= 1
SQL

def with_punchline_count
  select("jobs.*, (#{PUNCHLINE_COUNT}) AS punchline_count")
end

这看起来很乱, 但我认为你给自己设置了一个难题。

希望这对你有用。

注意:您也可以按照类似的方法将 post_count 缓存为列。 但是让我们迭代地处理这个问题。

【讨论】:

  • 这个解决方案存储了违反规范化的冗余信息,并在不需要的地方引入了关系完整性的可能破坏。您只需要一个查询。
  • 每种模式都有其缺点。我只能建议一些解决方案,只要您进行适当的测试,就可以减少不断修改 sql 语句的需要。
【解决方案2】:

您的查询可能如下所示。我填写了您的问题不清楚的地方。

SELECT j.id, j.joke, j.created_at, sum(w.weight) AS warn_level
FROM   jokes j
LEFT   JOIN punchlines p ON p.joke_id = j.id
LEFT   JOIN warns w ON w.punchline_id = p.id
GROUP  BY 1, 2, 3
HAVING COALESCE(sum(w.weight), 0) <= 1
ORDER  BY j.created_at DESC
LIMIT  my_limit;

更多信息后编辑

适配的表名和主键名(ActiveRecord 中自动生成的主键命名为id)。其余的直接命中。 :)

我现在使用LEFT JOIN 来防范jokes 没有punchlinespunchlines 根本没有warns 的情况。因此,HAVING 子句必须使用 COALESCE() 来捕捉这种情况,否则 jokes 将因为没有妙语而被排除在外。
另一方面,也许他们应该 ?没有妙语?开什么玩笑?!

关于 GROUP BY

在 PostgreSQL 9.0 之前,您需要在 GROUP BY 子句中列出 SELECT 的所有未分组列。从 PostgreSQL 9.1 开始,您可以简单地编写 GROUP BY j.id 来覆盖表 jokes 的所有列(id 是主键)。
我在这里写了更多关于该主题的内容:Is it possible to have an SQL query that uses AGG functions in this way?

【讨论】:

  • 非常感谢!您的查询看起来正确,我稍后会检查它。还添加了架构。
  • 这是错误的。如果一个笑话有 5 个妙语,每个都带有 1 个警告,则它不会显示该笑话。我认为 OP 想用 5 条妙语来开这样的玩笑。
  • 我的版本累积了所有妙语的权重。这可能不符合预期。
【解决方案3】:

我认为你的意思是:

select j.id, j.created_at, j.joke, count(helper.joke_id) as punchline_count
from jokes j
left join(
    select pl.joke_id
    from punchlines pl
    left join warns w on w.punchline_id=pl.id
    group by 1
    having colaesce(sum(w.weight),0)<=1
) as helper on helper.joke_id=j.id
group by 1,2,3
order by 2 desc
limit SOME_LIMIT

【讨论】:

  • 你为什么要发布一个几乎与我的答案相同的欺骗,只是没有解释?
  • 我猜你是对的。你的解释更合理。
猜你喜欢
  • 2019-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多