2

我正在使用运行总计来查找加权集的中位数。由于 Hibernate 不支持 FROM 子句中的子选择,这在 sql 中可以正常工作,但在 hql 中则不行。我不能轻易地使用 sql,因为实际代码涉及到 hql 中已经存在的大量动态查询构建。

这是示例表:

score  weight
2      1
5      1
5      1
6      1
7      1
10     2
10     2

总分是 9(我在这个查询之前就知道了)。9/2 = 4.5,所以这个查询应该返回 6 作为加权中值。

这是示例查询:

SET @runtot:=0;
SELECT 
    q1.score
FROM
    (SELECT 
        score, (@runtot:=@runtot + weight) AS rt
    FROM
        tmp_stddev
    ORDER BY score) as q1
WHERE
    q1.rt <= (9 / 2)
ORDER BY q1.score DESC
LIMIT 1;

在子选择中按分数 ASC 排序使我能够继续添加权重,直到达到一半。在外部查询中排序 DESC 使我能够使用 LIMIT 只返回单个结果以获得最佳性能(这里可能有很多数据,所以我真的只想返回一个结果)。

这适用于 SQL,但不适用于 HQL。我可以创建一个自定义方言,我相信它支持在查询中设置用户变量(将其清除为 0 部分将在针对同一连接的单独 sql 查询中)。问题是子选择。

我可以这样做:

SET @runtot:=0;
SET @runtot2:=0;

SELECT
    score,
   (@runtot := @runtot + weight) AS rt
FROM
    tmp_stddev
WHERE (@runtot2 := @runtot2 + weight) <= (9/2)
ORDER BY score;

但这会返回我所有的分数,我真的只想要一个(数据集可能非常大,速度很重要)。

有什么建议可以重新编写它以返回单个结果、快速并采用 hql 可以生成的 sql 形式吗?

更新:根据以下莫斯蒂·莫斯塔乔的建议,以及其他一些研究,这似乎一直有效:

SET @runtot:=0;
SELECT 
    score, weight, @val := score
FROM 
    tmp_stddev
WHERE
    (@runtot := @runtot + weight) <= (9 / 2)
ORDER BY score;

在这里,通过将最后匹配的分数选择到变量中,我可以稍后通过选择它的值在同一连接中使用它,并获取排序列表中的最后一项,这就是我想要的。此外,我还缩小了用户定义变量的读/写范围,当我更改数据时,这似乎是不一致的。

问题:

  • 这是用户定义变量的安全用法吗?我一直在阅读很多关于在同一个语句中读/写它们是多么不安全的文章,但是由于读/写都是 HAVING 子句中单个表达式的一部分,因此不必按顺序评估吗?换句话说,这可靠吗?
  • 我怎样才能让它在 HQL 中工作?如果我使用自定义方言并创建一个执行“@val := score”部分的自定义函数,我会得到一个“无效的过滤器参数名称格式”异常(我认为是因为冒号,但不应该只是Hibernate 从 HQL 到 SQL 的直接传递替换?为什么它会关心有一个冒号?)
  • 有没有我没有考虑过的更好的解决方案?
4

1 回答 1

1

好的,我完全迷失在数学中:)

无论如何,我试图将您的第一个查询变成不使用该FROM子句的东西。这就是我得到的:

SELECT score, (@runtot := @runtot + weight) rt
FROM t, (SELECT @runtot := 0) init
HAVING rt = FLOOR(9 / 2)
ORDER BY score

以这种方式使用having子句确实是一团糟,但它似乎是不需要派生表的唯一方法。唯一的问题是,虽然这会回答这个问题,但它不会帮助你处理小数字段。

现在,解决方案可能并不像将having子句更改为那样简单

HAVING rt <= 9 / 2

检查这个小提琴,看看它是如何影响结果的。当您使用用户定义的变量并且不使用派生表时,这就是您所做的。

要尝试的第二件事是您是否可以在JOIN. 我是说:

SELECT * FROM t
JOIN (
  SELECT id FROM r
) s ON t.id = s.id

这是我能走多远,但可能给了你一些想法来尝试:)

编辑(最后一次尝试):

在以下查询之后,我将不得不请求 SQL 语言的宽恕:

SELECT score
FROM t, (SELECT @runtot := 0.0) init
WHERE (@runtot := @runtot + weight) AND (9 / 2 >= @runtot)
ORDER BY score DESC
LIMIT 1

在这里拉小提琴。

于 2013-10-30T05:05:10.160 回答