Mysql:按子查询中的最大 N 个值排序

Posted

技术标签:

【中文标题】Mysql:按子查询中的最大 N 个值排序【英文标题】:Mysql: Order by max N values from subquery 【发布时间】:2013-07-29 17:20:14 【问题描述】:

我要认输了。

前言:我想用任何 N 来完成这项工作,但为了简单起见,我将 N 设置为 3。

我有一个查询(特别是 mysql),它需要从表中提取数据并根据该表中的前 3 个值进行排序,然后回退到其他排序标准。

所以基本上我有这样的东西:

SELECT tbl.id 
FROM
  tbl1 AS maintable 
  LEFT JOIN 
  tbl2 AS othertable 
  ON
  maintable.id = othertable.id
ORDER BY 
  othertable.timestamp DESC, 
  maintable.timestamp DESC

这是所有基本的教科书内容。但问题是我需要第一个 ORDER BY 子句只获取 othertable.timestamp 中的三个最大值,然后回退到 maintable.timestamp。

此外,对 othertable 执行 LIMIT 3 个子查询并加入它是不行的,因为这需要与应用于 maintable 的任意数量的 WHERE 条件一起工作。

我几乎可以让它与这样的基于用户变量的方法一起工作,但它失败了,因为它没有考虑排序,所以它将采用它找到的前三个其他表值:

ORDER BY 
  (
    IF(othertable.timestamp IS NULL, 0, 
      IF(
        (@rank:=@rank+1) > 3, null, othertable.timestamp
      )
    )
  ) DESC

(语句前有一个@rank:=0)

那么...有什么建议吗?我对这个问题失去了理智。我为此拥有的另一个参数是,由于我只更改现有的(非常复杂的)查询,因此我无法进行包装外部查询。另外,如上所述,我使用的是 MySQL,因此很遗憾,任何使用 ROW_NUMBER 函数的解决方案都无法实现。

在此先感谢大家。

编辑。以下是一些示例数据,其中时间戳简化为更简单的整数,以说明我需要什么:

maintable

id      timestamp
1       100
2       200
3       300
4       400
5       500
6       600

othertable

id     timestamp
4      250
5      350
3      550
1      700

=>

1
3
5
6
4
2

如果出于某种原因我们将 WHERE NOT maintable.id = 5 添加到查询中,我们应该得到以下结果:

1
3
4
6
2

...因为现在 4 是 othertable 中引用此集合的前 3 个值。

如您所见,othertable 中 id 为 4 的行不包含在排序中,因为它是时间戳值降序排列的第四个,因此它会退回到按基本时间戳排序。

现实世界对此的需求是:我在“maintable”中有内容,而“othertable”基本上是具有“特色日期”时间戳的特色内容的标记。我有一个视图,我应该将最后 3 个特色项目浮动到顶部,而列表的其余部分只是一个按时间顺序排列的列表。

【问题讨论】:

我无法获得这张照片。你能展示样本数据和想要的结果吗? 为什么需要处理应用于 maintable 的任意数量的 WHERE 条件会阻止您对 othertable 执行 LIMIT 3 子查询? 不错的博文:xaprb.com/blog/2006/12/07/… Barmar,将示例数据添加到我的帖子中 @MarkBannister,因为如果你限制子查询,你最终会得到任何行的前 3 行。如果您限制了从 maintable 获取的行,那么您很可能会得到错误的 3. 【参考方案1】:

可能是这样的。

SELECT
  id
FROM
  (SELECT 
    tbl.id,
    CASE WHEN othertable.timestamp IS NULL THEN 
      0 
    ELSE 
      @i := @i + 1
    END AS num,
    othertable.timestamp as othertimestamp,
    maintable.timestamp as maintimestamp
  FROM
    tbl1 AS maintable
    CROSS JOIN (select @i := 0) i 
    LEFT JOIN tbl2 AS othertable 
      ON maintable.id = othertable.id
  ORDER BY
    othertable.timestamp DESC) t
ORDER BY
  CASE WHEN num > 0 AND num <= 3 THEN
    othertimestamp
  ELSE
    maintimestamp
  END DESC

【讨论】:

不幸的是,我的场景阻止了这样的方法:“我为此设置的另一个参数是,由于我只更改现有的(非常复杂的)查询,因此我无法执行包装外部查询。”【参考方案2】:

修改后的答案:

select ilv.* from
(select sq.*, @i:=@i+1 rn from
 (select @i := 0) i
  CROSS JOIN 
 (select m.*, o.id o_id, o.timestamp o_t
  from maintable m
  left join othertable o
  on m.id = o.id
  where 1=1
  order by o.timestamp desc) sq
) ilv
order by case when o_t is not null and rn <=3 then rn else 4 end,
         timestamp desc

SQLFiddle here.

修改子查询sq 内的where 1=1 条件以匹配所需的复杂选择条件,并在最终order by 之后添加适当的limit 条件以满足分页要求。

【讨论】:

这不会像问题中明确指出的那样起作用:“另外,对 othertable 执行 LIMIT 3 个子查询并加入它是不行的,因为这需要在应用任意数量的 WHERE 条件下工作到维护表。” 修改后的版本现在与 GolzTrol 的非常相似,但我提出了一些建议,如何根据现有系统对其进行修改。 我想这是唯一的方法,但它仍然使用包装查询,所以很遗憾在这种情况下它不会让我的生活更轻松。我会给你一个努力的投票,但我会留下正确的答案,希望一些魔法仙女会带着一个解开的解决方案到达。我可能只是在这里打一场失败的战斗。不过,谢谢。 @TommiForsström:我能想到的唯一选择是使用主查询来创建一个临时表(如下所示:***.com/questions/5859391/…) - 这样您就可以应用适当的条件而不使用包装询问; 然后,在第二个查询中将临时表连接到其他表。但是,这种方法确实使用 两个 查询。 是的,确实想到了,但这可能只是一个有点可怕的性能危害。另外(有点超出本讨论的范围)这将使我假设查询的所有其他修饰符都已经运行,这意味着主查询已经返回它将返回的所有行。无论如何,谢谢。【参考方案3】:

你可以使用如下的联合查询吗?

(SELECT id,timestamp,1 AS isFeatured FROM tbl2 ORDER BY timestamp DESC LIMIT 3)
UNION ALL
(SELECT id,timestamp,2 AS isFeatured FROM tbl1 WHERE NOT id in (SELECT id from tbl2 ORDER BY timestamp DESC LIMIT 3))
ORDER BY isFeatured,timestamp DESC

这可能有点多余,但它在语义上更接近您所问的问题。这还允许您参数化要返回的特色结果的数量。

【讨论】:

以上是关于Mysql:按子查询中的最大 N 个值排序的主要内容,如果未能解决你的问题,请参考以下文章

如何使用spring MongoTemplate查询子文档,并按子文档的某个字段排序?

使用带有实体框架的动态字段按子记录排序

MySQL------ 子查询

MySQL------ 子查询

06-mysql基础-mysql中的DQL-子查询

MySQL随记 - 子查询