2

非常に難しい SQL クエリを作成する必要がありますが、自分では処理できません。誰かが私を助けたり、別のアプローチを提案したりできるかもしれません。

私は3つのテーブルを持っています: テーブル構造

そして、次のポイントが必要なページにテーブルを表示する必要があります。

  • すべてのユーザー ポイントを管理者のポイントと比較します (points.user_id=1)
  • ポイントが同額の場合、ポイントが貯まった日付とゲームが開催された日付の差に応じて、ユーザーはポイントを獲得します。ポイントが異なる場合、ポイントは追加されません。
  • 上記の 2 つの日付の差が 2 日、3 日などの場合に、ユーザーが獲得できるポイント数を設定できます (if(games.date - points.date = 2days{points=points*2} など)。
  • 比較は (games.date > now()) 後に行われます

アップデート:

$points = array();

$all_ifo = mysql_query("SELECT p.id, p.user_id, p.game_id, p.points, 
ABS(p.points - a.points) rating_diff, 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();") or die(mysql_error());

while ($info = mysql_fetch_assoc($all_ifo)){
    $points['' . $info['username'] . ''] += $info['rating_diff'];
    echo $info['p.id'];
}
asort($points);

echo "<table class='tabelid'><th>Test</th>";
foreach ($points as $key => $value) {
    $i++;
    echo "<tr><td>".$i.".</td><td style='width:250px;'>" . 
    ucfirst($key) . "</td><td>" . $value . "</td></tr>";
}
echo "</table>";

更新 2:

そのため、ポイントのカウントには別のテーブルが必要であることが提案されました。しかし、それは非常に複雑です:

Game saved in 25-45 min after game start - 1 point
Game saved in 0 - 24 min after game start - 2 points
Game saved in on the same day but before the game - 4 points 
Game saved in 1 day before - 6 points
Game saved in 2 days before - 8 points
..
Game saved in 15 days before - 34 points

後でシステムに簡単に実装するために、テーブルをどのように作成する必要があるかについての提案はありますか? そして、この rating_diff はどのようにして適切なポイント数を取得するのでしょうか?

更新 3:

$points = array();

$all_ifo = mysql_query("SELECT x.*, d.*
  FROM Extra AS x
  JOIN (SELECT p.id, p.user_id, p.game_id, p.points, p.date, a.points adminpoints, g.home,
               g.datetime, u.username, TIMESTAMPDIFF(MINUTE, g.datetime, p.date) 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;") or die(mysql_error());

while ($info = mysql_fetch_assoc($all_ifo)){
    $points['' . $info['username'] . ''] += $info['Offset'];
    echo $info['p.id'];
    }

arsort($points);

echo "<table class='tabelid'><th>Test</th>";
foreach ($points as $key => $value) {
    $i++;
    echo "<tr><td>".$i.".</td><td style='width:250px;'>" . ucfirst($key) . "</td><td>" . $value . "</td></tr>";

}
echo "</table>";

出力については、保存された時間と実際のゲーム時間の時間差を取得します。

Test
1.  User1   1851
2.  User2   -502

しかし、それをエクストラテーブルと比較して適切なポイントを取得する方法がわかりません。もちろん、同じ game_id のポイント (0、1、または 2) が管理者のポイント (0、1、または 2) と同じ場合にのみ、ポイントを追加する必要があります。

4

1 に答える 1

3

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.pointsifd.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 ...
于 2012-06-03T14:50:37.960 に答える