2

現在の合計を使用して、加重セットの中央値を見つけています。これは SQL では正常に機能しますが、Hibernate が FROM 句でサブセレクトをサポートしていないため、hql では機能しません。実際のコードには、hql に既に配置されている多くの動的クエリ構築が含まれているため、簡単に SQL にドロップすることはできません。

サンプルテーブルは次のとおりです。

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 を使用して、最高のパフォーマンスを得るために 1 つの結果だけを返すことができます (ここには大量のデータが存在する可能性があるため、実際には 1 つの結果のみを返したいと考えています)。

これは 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;

しかし、これはすべてのスコアを返します。実際には 1 つだけが必要です (データセットは非常に大きくなる可能性があり、速度が重要です)。

これを書き直して単一の結果を返し、高速にし、hql が生成できる sql の形式にする方法について何か提案はありますか?

更新: 以下の Mosty Mostacho の提案とその他の調査に基づいて、これは一貫して機能するようです。

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

ここで、最後に一致したスコアを変数に選択することで、後でその値を選択して同じ接続で使用し、並べ替えリストの最後の項目を取得できます。これが私が望むものです。また、ユーザー定義変数の読み取り/書き込みの範囲を縮小しました。これは、データを変更したときに一貫性がないように見えました。

質問:

  • これはユーザー定義変数の安全な使用法ですか? 同じステートメントでそれらを読み書きすることがどれほど安全でないかについて多くのことを読んできましたが、読み書きは両方とも HAVING 句の単一の式の一部であるため、これは順番に評価する必要はありませんか? 言い換えれば、これは信頼できますか?
  • これを HQL で動作させるにはどうすればよいですか? カスタム方言を使用して、「@val := スコア」部分を実行するカスタム関数を作成すると、「無効なフィルタ パラメータ名形式」という例外が発生します (コロンが原因だと思いますが、それだけではいけません)。 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

このフィドルをチェックして、結果がどのように混乱するかを確認してください。それは、ユーザー定義変数をいじり、派生テーブルを使用しないときに作成するものです。

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 に答える