diff --git a/app/models/category.rb b/app/models/category.rb
index e19556c2697..455e50cbe8b 100644
--- a/app/models/category.rb
+++ b/app/models/category.rb
@@ -188,11 +188,16 @@ class Category < ActiveRecord::Base
 
     DB.exec <<~SQL
       UPDATE categories c
-         SET topic_count = x.topic_count,
-             post_count = x.post_count
-        FROM (#{topics_with_post_count}) x
+         SET topic_count = COALESCE(x.topic_count, 0),
+             post_count = COALESCE(x.post_count, 0)
+        FROM (
+              SELECT ccc.id as category_id, stats.topic_count, stats.post_count
+              FROM categories ccc
+              LEFT JOIN (#{topics_with_post_count}) stats
+              ON stats.category_id = ccc.id
+             ) x
        WHERE x.category_id = c.id
-         AND (c.topic_count <> x.topic_count OR c.post_count <> x.post_count)
+         AND (c.topic_count <> COALESCE(x.topic_count, 0) OR c.post_count <> COALESCE(x.post_count, 0))
     SQL
 
     # Yes, there are a lot of queries happening below.
diff --git a/spec/models/category_spec.rb b/spec/models/category_spec.rb
index f268c2c7356..fa9cf430104 100644
--- a/spec/models/category_spec.rb
+++ b/spec/models/category_spec.rb
@@ -642,6 +642,22 @@ describe Category do
         expect(@uncategorized.posts_week).to eq(1)
       end
     end
+
+    context 'when there are no topics left' do
+      let!(:topic) { create_post(user: @category.user, category: @category.id).reload.topic }
+
+      it 'can update the topic count to zero' do
+        @category.reload
+        expect(@category.topic_count).to eq(1)
+        expect(@category.topics.count).to eq(2)
+        topic.delete # Delete so the post trash/destroy hook doesn't fire
+
+        Category.update_stats
+        @category.reload
+        expect(@category.topics.count).to eq(1)
+        expect(@category.topic_count).to eq(0)
+      end
+    end
   end
 
   describe "#url" do