entry_timeとvalueの2つの列を持つデータベーステーブルがあるとします。entry_timeはタイムスタンプですが、valueは他のデータ型にすることができます。レコードは比較的一貫しており、およそx分間隔で入力されます。ただし、多くの場合、エントリが作成されない可能性があるため、データに「ギャップ」が生じます。
効率の観点から、クエリで少なくとも時間Y(新旧両方)のこれらのギャップを見つけるための最善の方法は何ですか?
entry_timeとvalueの2つの列を持つデータベーステーブルがあるとします。entry_timeはタイムスタンプですが、valueは他のデータ型にすることができます。レコードは比較的一貫しており、およそx分間隔で入力されます。ただし、多くの場合、エントリが作成されない可能性があるため、データに「ギャップ」が生じます。
効率の観点から、クエリで少なくとも時間Y(新旧両方)のこれらのギャップを見つけるための最善の方法は何ですか?
まず、テーブルのエントリ数を 1 時間ごとにまとめてみましょう。
SELECT CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME) hour,
COUNT(*) samplecount
FROM table
GROUP BY CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME)
ここで、6 分ごと (1 時間に 10 回) に何かをログに記録すると、すべてのサンプル数の値は 10 になります。この式:CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME)
毛むくじゃらに見えますが、分と秒をゼロにすることで、タイムスタンプが発生した時間に単純に切り捨てられます。
これはかなり効率的で、すぐに始められます。ここに示すように、entry_time 列にインデックスを配置して、たとえば昨日のサンプルにクエリを制限できると、非常に効率的です。
SELECT CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME) hour,
COUNT(*) samplecount
FROM table
WHERE entry_time >= CURRENT_DATE - INTERVAL 1 DAY
AND entry_time < CURRENT_DATE
GROUP BY CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME)
しかし、サンプルが欠落している時間全体を検出するのはあまり得意ではありません。また、サンプリングのジッターにも少し敏感です。つまり、1 時間のトップ サンプルが 0.5 秒早い (10:59:30) 場合もあれば、0.5 秒遅い (11:00:30) 場合もあります。したがって、この時間の要約 (または日の要約、または分の要約など) は防弾ではありません。
完全に正しいものを取得するには、自己結合クエリが必要です。それはもう少し毛玉のようなもので、それほど効率的ではありません.
番号付きのサンプルを使用して、このような仮想テーブル (サブクエリ) を作成することから始めましょう。(これは MySQL では厄介な問題です。他の高価な DBMS ではより簡単になります。問題ありません。)
SELECT @sample:=@sample+1 AS entry_num, c.entry_time, c.value
FROM (
SELECT entry_time, value
FROM table
ORDER BY entry_time
) C,
(SELECT @sample:=0) s
この小さな仮想テーブルは、entry_num、entry_time、value を提供します。
次のステップでは、それ自体に結合します。
SELECT one.entry_num, one.entry_time, one.value,
TIMEDIFF(two.value, one.value) interval
FROM (
/* virtual table */
) ONE
JOIN (
/* same virtual table */
) TWO ON (TWO.entry_num - 1 = ONE.entry_num)
これにより、JOIN の ON 句によって管理される、1 つのエントリによって互いにオフセットされた次の 2 つのテーブルが並べられます。
最後に、この表からinterval
しきい値よりも大きい値を選択します。欠落しているサンプルの直前にサンプルの時間があります。
全体的な自己結合クエリはこれです。毛玉だと言ってました。
SELECT one.entry_num, one.entry_time, one.value,
TIMEDIFF(two.value, one.value) interval
FROM (
SELECT @sample:=@sample+1 AS entry_num, c.entry_time, c.value
FROM (
SELECT entry_time, value
FROM table
ORDER BY entry_time
) C,
(SELECT @sample:=0) s
) ONE
JOIN (
SELECT @sample2:=@sample2+1 AS entry_num, c.entry_time, c.value
FROM (
SELECT entry_time, value
FROM table
ORDER BY entry_time
) C,
(SELECT @sample2:=0) s
) TWO ON (TWO.entry_num - 1 = ONE.entry_num)
大きなテーブルで本番環境でこれを行う必要がある場合は、データのサブセットに対して行うことができます。たとえば、過去 2 日間のサンプルに対して毎日行うことができます。これはかなり効率的であり、真夜中に欠落しているサンプルを見落とさないようにすることもできます。これを行うには、小さな行番号付きの仮想テーブルは次のようになります。
SELECT @sample:=@sample+1 AS entry_num, c.entry_time, c.value
FROM (
SELECT entry_time, value
FROM table
ORDER BY entry_time
WHERE entry_time >= CURRENT_DATE - INTERVAL 2 DAY
AND entry_time < CURRENT_DATE /*yesterday but not today*/
) C,
(SELECT @sample:=0) s
これを行う非常に効率的な方法は、カーソルを使用するストアド プロシージャを使用することです。これは他の回答よりもシンプルで効率的だと思います。
このプロシージャは、カーソルを作成し、チェックしている日時レコードを反復処理します。指定した以上のギャップがある場合は、ギャップの開始と終了がテーブルに書き込まれます。
CREATE PROCEDURE findgaps()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE a,b DATETIME;
DECLARE cur CURSOR FOR SELECT dateTimeCol FROM targetTable
ORDER BY dateTimeCol ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
FETCH cur INTO a;
read_loop: LOOP
SET b = a;
FETCH cur INTO a;
IF done THEN
LEAVE read_loop;
END IF;
IF DATEDIFF(a,b) > [range you specify] THEN
INSERT INTO tmp_table (gap_begin, gap_end)
VALUES (a,b);
END IF;
END LOOP;
CLOSE cur;
END;
この場合、「tmp_table」が存在すると想定されます。これは手順で TEMPORARY テーブルとして簡単に定義できますが、この例では省略しました。
MariaDB 10.3.27 でこれを試しているので、この手順が機能しない可能性がありますが、手順の作成中にエラーが発生し、その理由がわかりません! ギャップを見つけたいelectric_use
フィールドで呼び出されたテーブルがあります。 と のフィールドを持つターゲット テーブルを作成しました。Intervaldatetime DATETIME
electric_use_gaps
gap_begin datetime
gap_end datetime
データは 1 時間ごとに取得されますが、5 年間で 1 時間分のデータが欠落しているかどうかを知りたいです。
DELIMITER $$
CREATE PROCEDURE findgaps()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE a,b DATETIME;
DECLARE cur CURSOR FOR SELECT Intervaldatetime FROM electric_use
ORDER BY Intervaldatetime ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
FETCH cur INTO a;
read_loop: LOOP
SET b = a;
FETCH cur INTO a;
IF done THEN
LEAVE read_loop;
END IF;
IF TIMESTAMPDIFF(MINUTE,a,b) > [60] THEN
INSERT INTO electric_use_gaps(gap_begin, gap_end)
VALUES (a,b);
END IF;
END LOOP;
CLOSE cur;
END&&
DELIMITER ;
これはエラーです:
Query: CREATE PROCEDURE findgaps() BEGIN DECLARE done INT DEFAULT FALSE; DECLARE a,b DATETIME; DECLARE cur CURSOR FOR SELECT Intervalda...
Error Code: 1064
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '[60] THEN
INSERT INTO electric_use_gaps(gap_begin, gap_end)
...' at line 16