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

左外连接按关联值排序产生重复结果的ActiveRecord解决方法

问题

执行LEFT OUTER JOIN查询时得到重复且无序的结果,原因是一件艺术品可能对应多条符合WHERE条件的artwork_colors记录。需求如下:

  • 每件艺术品仅在结果中出现一次
  • 仅选取该艺术品中符合color_id条件且pixel_percent最高的artwork_color记录
  • 以此最高pixel_percent值对艺术品进行排序
  • 必须使用ActiveRecord编写查询,适配现有分页系统

原SQL查询:

# color_ids是与搜索颜色相似的颜色ID集合
# 例如搜索FF0000时,会包含相似的红色色调

SELECT DISTINCT
    "artwork_colors"."pixel_percent",
    artworks.*
FROM
    "artworks"
    LEFT OUTER JOIN "artwork_colors" ON "artwork_colors"."artwork_id" = "artworks"."id"
WHERE
    "artwork_colors"."color_id" IN(106, 108, 119, 120, 128, 133, 156, 160)
ORDER BY
    "artwork_colors"."pixel_percent" DESC
LIMIT 120 OFFSET 0;

原ActiveRecord查询:

artworks
  .includes(:artwork_colors)
  .where('artwork_colors.color_id': color_ids)
  .order(pixel_percent: :desc)
  .select('artworks.*', 'artwork_colors.pixel_percent')
  .distinct

相关模型与表结构:

class Artwork < ApplicationRecord
  has_many :artwork_colors, dependent: :destroy
  has_many :colors, through: :artwork_colors
end

class ArtworkColor < ApplicationRecord
  belongs_to :artwork
  belongs_to :color
end

class Color < ApplicationRecord
  # 这些颜色由图像分析工具Amazon Rekognition从艺术品图像中提取
  has_many :artwork_colors, dependent: :destroy
  has_many :artworks, through: :artwork_colors
end
CREATE TABLE public.artwork_colors (
  id bigint NOT NULL,
  pixel_percent double precision, # 这是期望的排序列
  artwork_id bigint,
  color_id bigint
);

# h s l分别为色相(hue)、饱和度(saturation)、亮度(lightness)(颜色值)
CREATE TABLE public.colors (
  id bigint NOT NULL,
  h double precision,
  s double precision,
  l double precision
);

解决方案

方法一:子查询筛选最高pixel_percent记录

先通过子查询获取每个符合条件的艺术品对应的最高pixel_percent值,再关联原表筛选目标记录,最后排序。

ActiveRecord代码:

# 子查询:获取每个符合color_id条件的艺术品的最高pixel_percent
max_percent_subquery = ArtworkColor
  .select('artwork_id, MAX(pixel_percent) as max_pixel_percent')
  .where(color_id: color_ids)
  .group(:artwork_id)

# 关联子查询,筛选出对应记录并排序
Artwork
  .joins(:artwork_colors)
  .joins("INNER JOIN (#{max_percent_subquery.to_sql}) AS max_pct ON artwork_colors.artwork_id = max_pct.artwork_id AND artwork_colors.pixel_percent = max_pct.max_pixel_percent")
  .where(artwork_colors: { color_id: color_ids })
  .order('max_pct.max_pixel_percent DESC')
  .select('artworks.*, max_pct.max_pixel_percent')
  .distinct

方法二:使用窗口函数(适用于PostgreSQL等支持窗口函数的数据库)

用窗口函数为每个艺术品的符合条件的artwork_colors记录按pixel_percent降序排名,直接取排名第一的记录。

ActiveRecord代码:

# 子查询:为每个艺术品的符合条件的颜色记录排名
ranked_colors_subquery = ArtworkColor
  .select('*, ROW_NUMBER() OVER (PARTITION BY artwork_id ORDER BY pixel_percent DESC) as rn')
  .where(color_id: color_ids)
  .to_sql

# 关联子查询,筛选排名第一的记录并排序
Artwork
  .joins("INNER JOIN (#{ranked_colors_subquery}) AS ranked_acs ON artworks.id = ranked_acs.artwork_id AND ranked_acs.rn = 1")
  .order('ranked_acs.pixel_percent DESC')
  .select('artworks.*, ranked_acs.pixel_percent')

原查询问题说明

原查询的DISTINCT会把artworks.*和artwork_colors.pixel_percent的组合作为去重依据——如果一件艺术品有多条符合条件的artwork_colors记录,每个不同的pixel_percent都会保留,导致艺术品重复出现;同时多条记录的存在也会干扰排序逻辑,出现无序情况。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 21:15:08