使用纯SQL更新Postgres Counter Cache

时间:2015-10-06 00:11:50

标签: mysql ruby-on-rails ruby postgresql ruby-on-rails-4

我需要向具有大约400万行的数据库添加计数器缓存。我这样做的正常方法就是这样的迁移:

class AddClicksCounterCacheToPosts < ActiveRecord::Migration
  def change
    add_column :posts, :clicks_count, :integer
    Post.find_each { |post| Post.reset_counters(post.id, :clicks) }
  end
end

然而这太慢了。 I came across a way在纯SQL中执行此操作,但看起来它是为MySQL编写的,我似乎无法让它为Postgres工作。这是我正在尝试的:

class AddClicksCounterCacheToPosts < ActiveRecord::Migration
  def change
    add_column :posts, :clicks_count, :integer
    execute <<-eos
      update posts, (select
                      id as post_id, coalesce(count, 0) as count
                    from posts left join
                      (select post_id, count(id) as count from clicks group by post_id) as count_table
                    on
                      posts.id = count_table.post_id) as count_table
      set
        posts.clicks_count = count_table.count
      where
        posts.id = count_table.post_id
    eos
  end
end

这是我得到的错误:

ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR:  syntax error at or near ","

我很确定在postgres中允许使用逗号,但说实话,我不会写太多原始的postgres,所以我不确定。

知道如何将其转换为Postgres吗?

2 个答案:

答案 0 :(得分:1)

从MySQL到Postgres的自动翻译(无需评估原始查询的正确性):

update posts
set posts.clicks_count = count_table.count
from (
    select id as post_id, coalesce(count, 0) as count
    from posts
    left join (
        select post_id, count(id) as count
        from clicks group by post_id) as count_table
    on posts.id = count_table.post_id) as count_table
where
    posts.id = count_table.post_id;

答案 1 :(得分:1)

使用适当的UPDATE syntax,在Postgres中看起来就像这样:

UPDATE posts p
SET    clicks_count = ct.ct
FROM  (
   SELECT po.id, COALESCE(c.ct, 0) AS ct
   FROM   posts po
   LEFT   JOIN  (
      SELECT post_id, count(*) AS ct
      FROM   clicks
      GROUP  BY 1
      ) c ON c.post_id = po.id
   ) ct
WHERE  p.id = ct.id
AND    p.clicks_count <> ct.ct;  -- avoid empty update

我添加了另一个条件AND p.clicks_count <> ct.ct以避免空更新。详细说明:

改为运行这两个查询可能会更快:

UPDATE posts p
SET    clicks_count = p.ct
FROM  (
   SELECT post_id, count(*) AS ct
   FROM   clicks
   GROUP  BY 1
   ) ct
WHERE  p.id = p.category_id
AND    clicks_count <> p.ct;

UPDATE posts p
SET    clicks_count = 0
WHERE  NOT EXISTS (SELECT 1 FROM clicks WHERE post_id = p.id)
AND    p.clicks_count <> 0;