明らかに、同じ結果を得るにはさまざまな方法があります。あなたの質問は、MySQL の各グループで最後の結果を得る効率的な方法のようです。膨大な量のデータを扱っていて、最新バージョンの MySQL (5.7.21 や 8.0.4-rc など) でも InnoDB を使用していると想定している場合、これを行う効率的な方法がない可能性があります。
6000 万行を超えるテーブルでこれを行う必要がある場合があります。
これらの例では、クエリでデータ内のすべてのグループの結果を見つける必要がある約 150 万行のデータを使用します。実際のケースでは、多くの場合、約 2,000 のグループからデータを返す必要があります (これは、データの大部分を調べる必要がないという仮説に基づいています)。
次の表を使用します。
CREATE TABLE temperature(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
groupID INT UNSIGNED NOT NULL,
recordedTimestamp TIMESTAMP NOT NULL,
recordedValue INT NOT NULL,
INDEX groupIndex(groupID, recordedTimestamp),
PRIMARY KEY (id)
);
CREATE TEMPORARY TABLE selected_group(id INT UNSIGNED NOT NULL, PRIMARY KEY(id));
温度テーブルには、約 150 万のランダム レコードと 100 の異なるグループが入力されます。selected_group には、これらの 100 個のグループが取り込まれます (この場合、これは通常、すべてのグループで 20% 未満です)。
このデータはランダムであるため、複数の行が同じrecordedTimestampsを持つ可能性があることを意味します。必要なのは、選択したすべてのグループのリストを groupID の順に取得し、各グループの最後のrecordedTimestamp を取得することです。同じグループにそのような一致する行が複数ある場合は、それらの行の最後に一致する id を取得します。
仮説として、MySQL に特別な ORDER BY 句の最後の行から値を返す last() 関数がある場合、次のように簡単に実行できます。
SELECT
last(t1.id) AS id,
t1.groupID,
last(t1.recordedTimestamp) AS recordedTimestamp,
last(t1.recordedValue) AS recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
ORDER BY t1.recordedTimestamp, t1.id
GROUP BY t1.groupID;
この場合、通常の GROUP BY 関数を使用しないため、数 100 行を調べるだけで済みます。これは 0 秒で実行されるため、非常に効率的です。通常、MySQL では GROUP BY 句の後に ORDER BY 句が表示されますが、この ORDER BY 句は last() 関数の ORDER を決定するために使用されます。GROUP BY の後であれば、GROUPS の順序付けになります。GROUP BY 句が存在しない場合、最後の値は返されるすべての行で同じになります。
ただし、MySQL にはこれがありません。そのため、MySQL にあるさまざまなアイデアを見て、どれも効率的でないことを証明しましょう。
例 1
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
SELECT t2.id
FROM temperature t2
WHERE t2.groupID = g.id
ORDER BY t2.recordedTimestamp DESC, t2.id DESC
LIMIT 1
);
これは 3,009,254 行を検査し、5.7.21 では約 0.859 秒、8.0.4-rc ではわずかに長くかかりました。
例 2
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM temperature t1
INNER JOIN (
SELECT max(t2.id) AS id
FROM temperature t2
INNER JOIN (
SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
FROM selected_group g
INNER JOIN temperature t3 ON t3.groupID = g.id
GROUP BY t3.groupID
) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
GROUP BY t2.groupID
) t5 ON t5.id = t1.id;
これは 1,505,331 行を検査し、5.7.21 では約 1.25 秒、8.0.4-rc ではわずかに長くかかりました。
例 3
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM temperature t1
WHERE t1.id IN (
SELECT max(t2.id) AS id
FROM temperature t2
INNER JOIN (
SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
FROM selected_group g
INNER JOIN temperature t3 ON t3.groupID = g.id
GROUP BY t3.groupID
) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
GROUP BY t2.groupID
)
ORDER BY t1.groupID;
これは 3,009,685 行を検査し、5.7.21 では約 1.95 秒、8.0.4-rc ではわずかに長くかかりました。
例 4
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
SELECT max(t2.id)
FROM temperature t2
WHERE t2.groupID = g.id AND t2.recordedTimestamp = (
SELECT max(t3.recordedTimestamp)
FROM temperature t3
WHERE t3.groupID = g.id
)
);
これは 6,137,810 行を検査し、5.7.21 では約 2.2 秒、8.0.4-rc ではわずかに長くかかりました。
例 5
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM (
SELECT
t2.id,
t2.groupID,
t2.recordedTimestamp,
t2.recordedValue,
row_number() OVER (
PARTITION BY t2.groupID ORDER BY t2.recordedTimestamp DESC, t2.id DESC
) AS rowNumber
FROM selected_group g
INNER JOIN temperature t2 ON t2.groupID = g.id
) t1 WHERE t1.rowNumber = 1;
これは 6,017,808 行を検査し、8.0.4-rc で約 4.2 秒かかりました
例 6
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM (
SELECT
last_value(t2.id) OVER w AS id,
t2.groupID,
last_value(t2.recordedTimestamp) OVER w AS recordedTimestamp,
last_value(t2.recordedValue) OVER w AS recordedValue
FROM selected_group g
INNER JOIN temperature t2 ON t2.groupID = g.id
WINDOW w AS (
PARTITION BY t2.groupID
ORDER BY t2.recordedTimestamp, t2.id
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)
) t1
GROUP BY t1.groupID;
これは 6,017,908 行を検査し、8.0.4-rc で約 17.5 秒かかりました
例 7
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
LEFT JOIN temperature t2
ON t2.groupID = g.id
AND (
t2.recordedTimestamp > t1.recordedTimestamp
OR (t2.recordedTimestamp = t1.recordedTimestamp AND t2.id > t1.id)
)
WHERE t2.id IS NULL
ORDER BY t1.groupID;
これは永遠にかかっていたので、私はそれを殺さなければなりませんでした。