You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails+ActiveStorage:Attachment基于Blob校验和去重及统计问题

Rails ActiveStorage 按Blob Checksum去重Attachment并统计数量

我有一个使用ActiveStorage的Rails应用,存在一个Attachment模型,该模型被所有“可附加”模型(如Establishment、Event等)共享。为避免重复上传同一文件,可能存在多个具有相同checksum的Blob。当列出所有附件时需要去除重复项——本质是按关联表active_storage_blobs的checksum字段去重,但最终结果不需要该字段,要保留Attachment表的所有字段。

目前的问题是:用以下SQL能获取去重后的checksum,但改为SELECT DISTINCT active_storage_blobs.checksum, "attachments".*就无法实现去重:

SELECT DISTINCT active_storage_blobs.checksum
FROM ((SELECT "attachments".*
       FROM "attachments"
                INNER JOIN "establishments" ON "attachments"."attachable_id" = "establishments"."id"
                INNER JOIN "establishment_managers"
                           ON "establishments"."id" = "establishment_managers"."establishment_id"
       WHERE "establishment_managers"."manager_id" = 683
         AND "attachments"."attachable_type" = 'Establishment'
       ORDER BY "attachments"."id" ASC)
      UNION
      (SELECT "attachments".*
       FROM "attachments"
                INNER JOIN "events" ON "attachments"."attachable_id" = "events"."id"
                INNER JOIN "establishments" ON "events"."establishment_id" = "establishments"."id"
                INNER JOIN "establishment_managers"
                           ON "establishments"."id" = "establishment_managers"."establishment_id"
       WHERE "establishment_managers"."manager_id" = 683
         AND "attachments"."attachable_type" = 'Event'
       ORDER BY "attachments"."id" ASC)) as attachments
         INNER JOIN "active_storage_attachments" ON "active_storage_attachments"."record_type" = 'Attachment' AND
                                                    "active_storage_attachments"."name" = 'file' AND
                                                    "active_storage_attachments"."record_id" = "attachments"."id"
         INNER JOIN "active_storage_blobs"
                    ON "active_storage_blobs"."id" = "active_storage_attachments"."blob_id"
ORDER BY active_storage_blobs.checksum ASC

另外,使用SELECT DISTINCT ON (active_storage_blobs.checksum) "attachments".*能实现去重,但无法直接用于统计计数。


解决方案

1. 获取去重后的Attachment记录

利用PostgreSQL的DISTINCT ON特性,结合CTE筛选出每个checksum对应的唯一Attachment记录:

WITH filtered_attachments AS (
  SELECT "attachments".*
  FROM (
    SELECT "attachments".*
    FROM "attachments"
    INNER JOIN "establishments" ON "attachments"."attachable_id" = "establishments"."id"
    INNER JOIN "establishment_managers" ON "establishments"."id" = "establishment_managers"."establishment_id"
    WHERE "establishment_managers"."manager_id" = 683
      AND "attachments"."attachable_type" = 'Establishment'
    UNION
    SELECT "attachments".*
    FROM "attachments"
    INNER JOIN "events" ON "attachments"."attachable_id" = "events"."id"
    INNER JOIN "establishments" ON "events"."establishment_id" = "establishments"."id"
    INNER JOIN "establishment_managers" ON "establishments"."id" = "establishment_managers"."establishment_id"
    WHERE "establishment_managers"."manager_id" = 683
      AND "attachments"."attachable_type" = 'Event'
  ) AS attachments
  INNER JOIN "active_storage_attachments" ON 
    "active_storage_attachments"."record_type" = 'Attachment' 
    AND "active_storage_attachments"."name" = 'file' 
    AND "active_storage_attachments"."record_id" = "attachments"."id"
  INNER JOIN "active_storage_blobs" ON "active_storage_blobs"."id" = "active_storage_attachments"."blob_id"
)
SELECT DISTINCT ON (asb.checksum) fa.*
FROM filtered_attachments fa
INNER JOIN "active_storage_attachments" asa ON 
  asa."record_type" = 'Attachment' 
  AND asa."name" = 'file' 
  AND asa."record_id" = fa."id"
INNER JOIN "active_storage_blobs" asb ON asb."id" = asa."blob_id"
ORDER BY asb.checksum, fa.id ASC;

如果用Rails查询语法(更符合框架习惯):

# 先筛选出符合条件且去重后的Attachment ID
unique_attachment_ids = Attachment.joins(<<-SQL)
  INNER JOIN active_storage_attachments asa 
    ON asa.record_type = 'Attachment' 
    AND asa.name = 'file' 
    AND asa.record_id = attachments.id
  INNER JOIN active_storage_blobs asb 
    ON asb.id = asa.blob_id
SQL
.where(<<-SQL, manager_id: 683)
  (
    attachable_type = 'Establishment' 
    AND EXISTS (
      SELECT 1 FROM establishments e 
      INNER JOIN establishment_managers em ON e.id = em.establishment_id 
      WHERE e.id = attachments.attachable_id 
        AND em.manager_id = :manager_id
    )
  ) OR (
    attachable_type = 'Event' 
    AND EXISTS (
      SELECT 1 FROM events ev 
      INNER JOIN establishments e ON ev.establishment_id = e.id 
      INNER JOIN establishment_managers em ON e.id = em.establishment_id 
      WHERE ev.id = attachments.attachable_id 
        AND em.manager_id = :manager_id
    )
  )
SQL
.select("DISTINCT ON (asb.checksum) attachments.id")
.order("asb.checksum, attachments.id ASC")

# 获取完整的Attachment记录
unique_attachments = Attachment.where(id: unique_attachment_ids)

2. 统计去重后的数量

直接统计唯一checksum的数量即可,不需要先查询所有记录:

WITH filtered_attachments AS (
  SELECT "attachments".*
  FROM (
    SELECT "attachments".*
    FROM "attachments"
    INNER JOIN "establishments" ON "attachments"."attachable_id" = "establishments"."id"
    INNER JOIN "establishment_managers" ON "establishments"."id" = "establishment_managers"."establishment_id"
    WHERE "establishment_managers"."manager_id" = 683
      AND "attachments"."attachable_type" = 'Establishment'
    UNION
    SELECT "attachments".*
    FROM "attachments"
    INNER JOIN "events" ON "attachments"."attachable_id" = "events"."id"
    INNER JOIN "establishments" ON "events"."establishment_id" = "establishments"."id"
    INNER JOIN "establishment_managers" ON "establishments"."id" = "establishment_managers"."establishment_id"
    WHERE "establishment_managers"."manager_id" = 683
      AND "attachments"."attachable_type" = 'Event'
  ) AS attachments
  INNER JOIN "active_storage_attachments" ON 
    "active_storage_attachments"."record_type" = 'Attachment' 
    AND "active_storage_attachments"."name" = 'file' 
    AND "active_storage_attachments"."record_id" = "attachments"."id"
  INNER JOIN "active_storage_blobs" ON "active_storage_blobs"."id" = "active_storage_attachments"."blob_id"
)
SELECT COUNT(DISTINCT asb.checksum) AS unique_count
FROM filtered_attachments fa
INNER JOIN "active_storage_attachments" asa ON 
  asa."record_type" = 'Attachment' 
  AND asa."name" = 'file' 
  AND asa."record_id" = fa."id"
INNER JOIN "active_storage_blobs" asb ON asb."id" = asa."blob_id";

用Rails的话直接对之前的unique_attachment_ids计数:

unique_attachments_count = unique_attachment_ids.count

说明

  • DISTINCT ON是PostgreSQL专属语法,必须将去重字段放在ORDER BY的首位,这里按checksum排序后,取每个分组下最早创建的Attachment(按id升序)
  • 统计数量时,COUNT(DISTINCT checksum)直接计算唯一值的数量,效率比查询所有记录再计数更高
  • Rails查询方式避免了硬写复杂SQL,同时保留了框架的可维护性

内容的提问来源于stack exchange,提问作者brcebn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 16:34:59