同じ結果を得るにはさまざまな方法がたくさんあることは明らかですが、あなたの質問は、MySQLの各グループの最後の結果を効率的に取得する方法は何でしょうか。大量のデータを処理していて、MySQLの最新バージョン(5.7.21や8.0.4-rcなど)でもInnoDBを使用していると仮定すると、これを行う効率的な方法がない場合があります。
6,000万行を超えるテーブルでもこれを行う必要がある場合があります。
これらの例では、クエリがデータ内のすべてのグループの結果を見つける必要がある、約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%未満になります)。
このデータはランダムであるため、複数の行に同じrecordedTimestampが含まれる可能性があります。必要なのは、選択されたすべてのグループのリストをgroupIDの順に取得し、各グループの最後に記録されたTimestampを取得することです。同じグループにそのような複数の一致する行がある場合、それらの行の最後に一致する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句がない場合、最後の値は返されたすべての行で同じになります。
ただし、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;
これは永遠にかかっていたので、私はそれを殺さなければなりませんでした。