具有两个联接的聚合 (MySQL)

Posted

技术标签:

【中文标题】具有两个联接的聚合 (MySQL)【英文标题】:Aggregation with two Joins (MySQL) 【发布时间】:2010-10-08 10:28:29 【问题描述】:

我有一张名为 gallery 的桌子。对于图库中的每一行,图片表中有几行。一张图片属于一个画廊。然后是表投票。每一行都是对某个画廊的赞成或反对。 这是(简化的)结构:

gallery ( gallery_id )
picture ( picture_id, picture_gallery_ref )
vote ( vote_id, vote_value, vote_gallery_ref )

现在我想要一个查询给我以下信息:所有画廊都有自己的数据字段和连接到画廊的图片数量和投票的汇总值。

这是我的查询,但由于多次加入,聚合值不是正确的。 (至少当图片或选票不止一行时。)

SELECT 
  *, SUM( vote_value ) as score, COUNT( picture_id ) AS pictures
FROM 
  gallery
LEFT JOIN 
  vote
  ON gallery_id = vote_gallery_ref
LEFT JOIN 
  picture
  ON gallery_id = picture_gallery_ref
GROUP BY gallery_id

因为我注意到COUNT( DISTINCT picture_id ) 给了我正确数量的图片,所以我尝试了这个:

( SUM( vote_value ) / GREATEST( COUNT( DISTINCT picture_id ), 1 ) ) AS score

在这个例子中是可行的,但是如果一个查询中有更多的连接呢?

只是想知道是否有更好或更“优雅”的方式可以解决这个问题。另外我想知道我的解决方案是特定于 mysql 还是标准 SQL?

【问题讨论】:

您的 GROUP BY 正在使用您未定义的列,我假设您的意思是 gallery_id。 【参考方案1】:

William of Okham 的这句话在这里适用:

Enita non sunt multiplicanda praeter necessitatem

(拉丁语为“实体不得在必要的情况下成倍增加”)。

您应该重新考虑为什么需要在单个查询中完成此操作? 确实,单个查询的开销比多个查询要少,但如果单个查询的性质变得过于复杂,供您开发和 RDBMS 执行,然后运行单独的查询。

【讨论】:

似乎是最好的建议,因为显然没有我不知道的“优雅”解决方案。 呸,现在我很遗憾在下面提出(并测试)我的查询;因为您的问题的最佳答案是无答案 @matt b:展示解决方案的复杂程度是值得的,因此人们可以更有信心地决定运行单独的查询会更简单。 @matt b 您的解决方案可能有效,但它看起来并不比我上面被黑的查询更优雅;)【参考方案2】:

或者只使用子查询...

我不知道这是否是有效的 MySQL 语法,但您也许可以执行以下操作:

SELECT
  gallery.*, a.score, b.pictures
LEFT JOIN
(
  select vote_gallery_ref, sum(vote_value) as score
  from vote
  group by vote_gallery_ref
) a ON gallery_id = vote_gallery_ref
LEFT JOIN 
(
  select picture_gallery_ref, count(picture_id) as pictures
  from picture
  group by picture_gallery_ref
) b ON gallery_id = picture_gallery_ref

【讨论】:

【参考方案3】:

您多久添加/更改一次投票记录?

您多久添加/删除一次图片记录?

您多久对这些总数运行一次此查询?

最好在库表 (total_pictures, total_votes, total_vote_values) 上创建总计字段。

当您在图片表中添加或删除记录时,您也会更新图库表中的总数。这可以使用图片表上的触发器自动更新图库表来完成。也可以使用结合两条 SQL 语句来更新图片表和图库表的事务来完成。当您在图片表上添加记录时,在图库表上增加 total_pictures 字段。当您删除图片表上的记录时,请减少 total_pictures 字段。

添加或删除投票记录或vote_value 更改时类似,您会更新total_votestotal_vote_values 字段。添加记录会增加total_votes 字段并将vote_values 添加到total_vote_values。删除记录会减少 total_votes 字段并从 total_vote_values 中减去 vote_values。更新投票记录上的vote_values 也应该用差异更新total_vote_values(减去旧值,添加新值)。

您的查询现在变得微不足道 - 它只是从图库表中直接查询。但这是以图片和投票表的更复杂更新为代价的。

【讨论】:

【参考方案4】:

正如 Bill Karwin 所说,在一个查询中完成所有这些操作非常难看。

但是,如果你必须这样做,使用聚合数据加入和选择非聚合数据需要加入子查询(过去几年我没有太多使用 SQL,所以我实际上忘记了这个的正确术语) .

假设您的图库表有额外的字段namestate

select g.gallery_id, g.name, g.state, i.num_pictures, j.sum_vote_values
from gallery g
inner join (
  select g.gallery_id, count(p.picture_id) as 'num_pictures'
  from gallery g
  left join picture p on g.gallery_id = p.picture_gallery_ref
  group by g.gallery_id) as i on g.gallery_id = i.gallery_id
left join (
  select g.gallery_id, sum(v.vote_value) as 'sum_vote_values'
  from gallery g
  left join vote v on g.gallery_id = v.vote_gallery_ref
  group by g.gallery_id
) as j on g.gallery_id = j.gallery_id

这将产生一个如下所示的结果集:

gallery_id, name, state, num_pictures, sum_vote_values
1, 'Gallery A', 'NJ', 4, 19
2, 'Gallery B', 'NY', 3, 32
3, 'Empty gallery', 'CT', 0, 

【讨论】:

以上是关于具有两个联接的聚合 (MySQL)的主要内容,如果未能解决你的问题,请参考以下文章

MySQL 使用 MAX 日期更新内部联接

具有相同计数值的两个表之间的内部联接

OBIEE 创建自定义联接以提取两个相同但具有不同数据的列

内部联接选择结果与表

在内部联接中重用 mysql 子查询

MySQL 交叉联接查询上的休眠异常