13

问题

假设我有这张桌子tab小提琴可用)。

| g | a | b |     v |
---------------------
| 1 | 3 | 5 |   foo |
| 1 | 4 | 7 |   bar |
| 1 | 2 | 9 |   baz |
| 2 | 1 | 1 |   dog |
| 2 | 5 | 2 |   cat |
| 2 | 5 | 3 | horse |
| 2 | 3 | 8 |   pig |

我按 对行进行分组g,并且对于每个组,我想要一个来自 column 的值v。但是,我不想要任何值,但我想要来自带有 maximal 的行的值a,以及来自所有这些的带有 maximal 的行的值b。换句话说,我的结果应该是

| 1 |   bar |
| 2 | horse |

当前解决方案

我知道一个查询来实现这一点:

SELECT grps.g,
(SELECT v FROM tab
 WHERE g = grps.g
 ORDER BY a DESC, b DESC
 LIMIT 1) AS r
FROM (SELECT DISTINCT g FROM tab) grps

问题

但我认为这个查询相当难看。主要是因为它使用了一个依赖子查询,这感觉就像一个真正的性能杀手。所以我想知道这个问题是否有更简单的解决方案。

预期答案

我对这个问题最有可能的答案是 MySQL(或 MariaDB)的某种附加组件或补丁,它确实为此提供了一个功能。但我也欢迎其他有用的灵感。任何没有依赖子查询的工作都可以作为答案。

如果您的解决方案仅适用于单个排序列,即无法区分catand horse,请随意提出该答案,因为我希望它对大多数用例仍然有用。例如,100*a+b一种可能的方法是按两列对上述数据进行排序,同时仍然只使用一个表达式。

我有一些非常老套的解决方案,可能会在一段时间后添加它们,但我会先看看是否有一些不错的新解决方案会先出现。


基准测试结果

由于仅通过查看它们很难比较各种答案,因此我对它们进行了一些基准测试。这是在我自己的桌面上运行的,使用 MySQL 5.1。这些数字不会与任何其他系统进行比较,只能相互比较。如果性能对您的应用程序至关重要,您可能应该使用您的真实数据进行自己的测试。当有新的答案出现时,我可能会将它们添加到我的脚本中,然后重新运行所有测试。

因此,到目前为止,我自己的解决方案似乎并不是那么糟糕,即使使用依赖子查询也是如此。令人惊讶的是,acatt 的解决方案也使用了依赖子查询,因此我认为它的性能要差得多。可能是 MySQL 优化器无法处理的问题。RichardTheKiwi 提出的解决方案似乎也具有良好的整体性能。其他两种解决方案在很大程度上取决于数据的结构。对于许多小团体,xdazz 的方法优于所有其他方法,而 Dems 的解决方案在少数大型团体中表现最好(尽管仍然不是特别好)。

4

4 回答 4

5

这种方式不使用子查询。

SELECT t1.g, t1.v
FROM tab t1
LEFT JOIN tab t2 ON t1.g = t2.g AND (t1.a < t2.a OR (t1.a = t2.a AND t1.b < t2.b))
WHERE t2.g IS NULL

解释:

LEFT JOIN 的工作原理是,当 t1.a 处于最大值时,没有 s2.a 具有更大的值,并且 s2 行的值将为 NULL。

于 2012-10-04T11:52:14.127 回答
5
SELECT g, a, b, v
  FROM (
            SELECT *, 
                   @rn := IF(g = @g, @rn + 1, 1) rn, 
                   @g := g
              FROM (select @g := null, @rn := 0) x, 
                   tab
          ORDER BY g, a desc, b desc, v
       ) X
 WHERE rn = 1;

单程。所有其他解决方案在我看来都是 O(n^2)。

于 2012-10-04T13:19:32.193 回答
1

许多 RDBMS 具有特别适合此问题的结构。MySQL不是其中之一。

这将引导您使用三种基本方法。

  • 使用 EXISTS 和 EXISTS 子句中的相关子查询检查每条记录以查看它是否是您想要的。 (@acatt 的回答,但我知道 MySQL 并不总是很好地优化这一点。(g,a,b)在假设 MySQL 不能很好地做到这一点之前,请确保您有一个复合索引。)

  • 做一个半笛卡尔积来填满同一张支票。任何不加入的记录都是目标记录。如果每个组 ('g') 很大,这会很快降低性能(如果每个唯一值有 10 条记录g,这将产生约 50 条记录并丢弃 49 条。对于 100 的组大小,它会产生约 5000 条记录和丢弃 4999),但它非常适合小团体。(@xdazz 的回答。)

  • 或者使用多个子查询来确定 MAX(a),然后是 MAX(b)...

多个顺序子查询...

SELECT
  yourTable.*
FROM
  (SELECT g,    MAX(a) AS a FROM yourTable GROUP BY g   ) AS searchA
INNER JOIN
  (SELECT g, a, MAX(b) AS b FROM yourTable GROUP BY g, a) AS searchB
    ON  searchA.g = searchB.g
    AND searchA.a = searchB.a
INNER JOIN
  yourTable
    ON  yourTable.g = searchB.g
    AND yourTable.a = searchB.a
    AND yourTable.b = searchB.b

根据 MySQL 优化第二个子查询的方式,这可能比其他选项更高效,也可能不会。然而,对于给定任务,它是最长的(并且可能是最难维护的)代码。

假设所有三个搜索字段都有一个复合索引(g, a, b),我认为它最适合g. 但这应该进行测试。

对于 的小团体g,我会选择@xdazz 的答案。

编辑

还有一种蛮力方法。

  • 创建一个相同的表,但使用 AUTO_INCREMENT 列作为 id。
  • 将您的表插入到这个克隆中,按 g、a、b 排序。
  • 然后可以使用 找到 id SELECT g, MAX(id)
  • 然后可以使用此结果来查找v您需要的值。

这不太可能是最好的方法。如果是这样,这实际上是对 MySQL 优化器处理此类问题的能力的一种否定。

也就是说,每个引擎都有它的弱点。所以,就我个人而言,我会尝试一切,直到我认为我了解 RDBMS 的行为方式并可以做出选择 :)

编辑

使用ROW_NUMBER(). (Oracle、SQL Server、PostGreSQL 等)

SELECT
  *
FROM
(
  SELECT
    ROW_NUMBER() OVER (PARTITION BY g ORDER BY a DESC, b DESC) AS sequence_id,
    *
  FROM
    yourTable
)
  AS data
WHERE
  sequence_id = 1
于 2012-10-04T12:35:37.303 回答
0

这可以使用相关查询来解决:

SELECT g, v
FROM tab t
WHERE NOT EXISTS (
    SELECT 1
    FROM tab
    WHERE g = t.g
        AND a > t.a
        OR (a = t.a AND b > t.b)
    )
于 2012-10-04T12:01:42.760 回答