SQL の微調整
少し再フォーマットされた現在のSQLは次のとおりです。
SELECT p.id, g.home, p.user_id, u.username, p.points, p1.points,
ABS(p.points - p1.points) rating_diff
FROM points p
JOIN games g ON p.game_id = p.id
JOIN users u ON p.user_id = u.id
JOIN points p1 ON p.game_id = p1.game_id AND p1.user_id = 1
WHERE u.id != 1
ORDER BY g.id, ABS(p.points - p1.points)
これは、あなたが求めているものに近いはずです。試合日の条件が含まれていません。あなたは言う:
の後に比較が行われ(games.date > now())
ます。
これは、ゲームがまだ将来スケジュールされているときに比較が行われることを意味します (ゲームのタイムスタンプが現在のタイムスタンプよりも後であるため、通常は「後」よりも「ゲームが行われる前」と呼ばれます。<
代わりに、そうする必要があると思います)の>
。
SELECT p.id, g.home, p.user_id, u.username, p.points, a.points,
ABS(p.points - a.points) rating_diff
FROM points AS p
JOIN games AS g ON p.game_id = p.id
JOIN users AS u ON p.user_id = u.id
JOIN points AS a ON p.game_id = a.game_id AND a.user_id = 1
WHERE u.id != 1
AND g.`datetime` < NOW()
ORDER BY g.id, ABS(p.points - a.points)
「admin」レコードには、「p1」ではなく「a」というエイリアスを付けて、1 文字のエイリアスを維持しています。私はしぶしぶ逆ティックを使用datetime
して、それも型であることを考慮して、区切り識別子として扱いました。必要かどうかは定かではありませんが、おそらく必要です。
日付の違いに基づいて余分なポイントを計算していません。その情報を追加のテーブルに記録し、それに適切なものにするのがおそらく最善でしょう。ただ、「2日差でポイント2倍」という構図なので、どういうデザインにすればいいのかわかりにくいですよね。特に、日数差が 0 または 1 の場合はどうすればよいでしょうか。乗数は分数でしょうか。結果は切り上げですか?追加用語はありますか?等。
TDQD — テスト駆動クエリ設計
これで目的のデータが生成されない場合は、TDQD (テスト駆動型クエリ デザイン) モードに入ります。簡単なクエリから始めて、正しい情報が得られることを実証します。次に、それを拡張して、各クエリも機能することを示します。
非管理者ポイント レコード:
SELECT p.id, p.user_id, p.game_id, p.points
FROM points AS p
WHERE p.user_id != 1;
あなたが持っているものと比較してすぐに違いがあることに注意してください。まだユーザー テーブルから選択していないため、その条件を から に変換する必要がありu.id != 1
ますp.user_id != 1
。これは繰り越します。SQL の精度には影響しないはずですが、TDQD は既にわずかに異なるクエリ設計を行っています。
対応する管理レコードを含む非管理ポイント レコード:
SELECT p.id, p.user_id, p.game_id, p.points, a.user_id, a.points
FROM points AS p
JOIN points AS a ON p.game_id = a.game_id AND a.user_id = 1
WHERE p.user_id != 1;
ゲーム情報も選択:
(私は怠け者でdatetime
、バックティックで囲んでいません。)
SELECT p.id, p.user_id, p.game_id, p.points, a.points, g.home, g.datetime
FROM points AS p
JOIN points AS a ON p.game_id = a.game_id AND a.user_id = 1
JOIN games AS g ON p.game_id = g.id
WHERE p.user_id != 1;
過去のゲーム情報のみを選択:
SELECT p.id, p.user_id, p.game_id, p.points, a.points, g.home, g.datetime
FROM points AS p
JOIN points AS a ON p.game_id = a.game_id AND a.user_id = 1
JOIN games AS g ON p.game_id = g.id
WHERE p.user_id != 1
AND g.datetime < NOW();
ユーザー情報を追加します。
SELECT p.id, p.user_id, p.game_id, p.points, a.points, g.home, g.datetime, u.username
FROM points AS p
JOIN points AS a ON p.game_id = a.game_id AND a.user_id = 1
JOIN games AS g ON p.game_id = g.id
JOIN users AS u ON p.user_id = u.id
WHERE p.user_id != 1
AND g.datetime < NOW();
WHERE 条件をu.id != 1
現在の状態に戻すことはできますが、そうしてもメリットはありません。
ポイントカウント
標準 SQL は、INTERVAL DAY TO MINUTE などの INTERVAL タイプをサポートしています。これは経過時間を表し、ポイントのテーブルをエンコードするのが比較的簡単になります。これは MySQL の仮説であることに注意してください — 現実については後で扱います。
CREATE TABLE Extra
(
LoOffsetFromGameTime INTERVAL DAY TO MINUTE NOT NULL,
HiOffsetFromGameTime INTERVAL DAY TO MINUTE NOT NULL,
Points INTEGER NOT NULL,
CHECK(LoOffsetFromGameTime < HiOffSetFromGameTime)
);
INSERT INTO Extra VALUES ('-99 23:59', '-0 00:45', 0);
INSERT INTO Extra VALUES ('-0 00:45', '-0 00:24', 1);
INSERT INTO Extra VALUES ('-0 00:24', '0 00:00', 2);
INSERT INTO Extra VALUES ('0 00:00', '1 00:00', 4);
INSERT INTO Extra VALUES ('1 00:00', '2 00:00', 6);
INSERT INTO Extra VALUES ('2 00:00', '3 00:00', 8);
...
INSERT INTO Extra VALUES ('15 00:00', '99 23:59', 34);
範囲は「開閉」です。低い値は含まれ、高い値は除外されます。これは、BETWEEN AND 演算子を使用できないことを意味します。両方のエンドポイントを含む「open-open」範囲を実装します。
「-99 日」および「+99 日」の値は、必要に応じて増やすことができます。INTERVAL DAY TO MINUTE の日の部分のデフォルトの桁数は 2 ですが、さらに必要な場合はさらに指定できます。以下のクエリは、ゲーム時間とエントリ時間の差が常にテーブルの行の 1 つにあると想定しています。
エクストラポイントも選択
このテーブルを使用できるふりをすると、余分なポイントのクエリは次のようになります。
SELECT p.id, p.user_id, p.game_id, p.points, a.points, g.home, g.datetime, u.username
FROM points AS p
JOIN points AS a ON p.game_id = a.game_id AND a.user_id = 1
JOIN games AS g ON p.game_id = g.id
JOIN users AS u ON p.user_id = u.id
JOIN extra AS x
ON CAST(g.datetime - p.date AS INTERVAL DAY TO MINUTE) >= x.LoOffsetFromGameTime
AND CAST(g.datetime - p.date AS INTERVAL DAY TO MINUTE) < x.HiOffsetFromGameTime
WHERE p.user_id != 1
AND g.datetime < NOW();
表現が繰り返されるのはかなり不器用です。以前はメイン クエリだったものを、値を選択して名前を付けるサブクエリにし、それを Extra テーブルとの結合で使用する必要があります。
SELECT x.*, d.*
FROM extra AS x
JOIN (SELECT p.id, p.user_id, p.game_id, p.usr_points, a.adm_points, g.home,
g.datetime, u.username,
CAST(g.datetime - NOW() AS INTERVAL DAY TO MINUTE) AS Offset
FROM points AS p
JOIN points AS a ON p.game_id = a.game_id AND a.user_id = 1
JOIN games AS g ON p.game_id = g.id
JOIN users AS u ON p.user_id = u.id
WHERE p.user_id != 1
AND g.datetime < NOW()
) AS d
ON d.Offset >= x.LoOffsetFromGameTime
AND d.Offset < x.HiOffsetFromGameTime;
条件を正しく解釈し、何も反転していなければ、必要な情報が得られるはずです。
ただし、MySQL は INTERVAL 型をサポートしていません
「INTERVAL 型をサポートする DBMS に切り替える」という選択肢はないと仮定します。
論理的には、実行時の最も単純なメカニズムは、2 つの日付値の差を適切な整数分として扱うことです。INTERVAL DAY TO MINUTE を代わりに適切な分数としてエンコードできますが、テーブルのデータ エントリはより複雑になります。
CREATE TABLE Extra
(
LoOffsetFromGameTime INTEGER NOT NULL,
HiOffsetFromGameTime INTEGER NOT NULL,
Points INTEGER NOT NULL,
CHECK(LoOffsetFromGameTime < HiOffSetFromGameTime)
);
INSERT INTO Extra VALUES (-2000000, -45, 0);
INSERT INTO Extra VALUES ( -45, -24, 1);
INSERT INTO Extra VALUES ( -24, 0, 2);
INSERT INTO Extra VALUES ( 0, +1440, 4);
INSERT INTO Extra VALUES ( +1440, +2880, 6);
INSERT INTO Extra VALUES ( +2880, +4320, 8);
...
INSERT INTO Extra VALUES ( +21600, +2000000, 34);
便利な「大きな」数として 2,000,000 を使用しています。代わりに ±2,147,483,647 またはその他の適切な範囲を指定できます。
継続的なカバレッジとデータの重複がないことについて、Extra テーブルを検証する必要があることに注意してください。選択する特に適切なオプションがないため、主キーを指定しませんでした。2つのオフセット列の開閉範囲を主キーとして(連続性と非重複の制約付きで)本当に必要ですが、それはSQLで表現できません(または、簡単ではなく、主キー制約としても表現できません)。 .
次に、ゲーム時間と NOW() の差を適切な分数として記述するだけです。実際、秒で作業する方が簡単かもしれませんが、テーブルのデータ入力が少し複雑になります。OTOH、テーブルを頻繁にエンコードすることはありません。便利な表現を使用してそれを秒に変換する手順をいつでも使用するように手配できます。
ただし、TIMESTAMPDIFFはまさに必要なことを行う場合があります。戻り値の型が何であるかの意味を調べていません。選択できる関数は他にもたくさんありますが、そのうちのどこかで、ゲーム時間とユーザーのエントリ時間の差を適切な値として返す便利な式を取得できます。必要な答えをもたらす Extra テーブル。
エクストラポイントの追加 (アップデート 3)
追加するポイントの数はx.points
ifd.usr_points = d.adm_points
であるため、select-list に単純に記述できます。
SELECT CASE WHEN d.usr_points = d.adm_points THEN x.points ELSE 0 END AS added_points,
x.*, d.*
FROM ...