0

5 日間のデータ (日はルーチンの引数) から 1 日の各分ごとの平均値を計算し、結果を別のテーブルに挿入する SQL ルーチンを作成しました。かなり長いので、最適化する方法があるかどうか疑問に思っていました。

平均を計算するために必要な値はすべて同じテーブルSiteReadingにあるため、同じ分の5つの値を取得しますが、異なる日からそれらの日のテーブルのサブセットに参加して、時間と分が一致し、次に値は同じ行になります。次に、各行に 5 つの値を追加し、そこから新しいテーブルを作成して、それらの平均を格納するベースライン テーブルに挿入します。

ルーチンは次のとおりです。

CREATE PROCEDURE 'calc_baseline` (IN `input_site_id` int, IN `day1` varchar(12), IN `day2` varchar(12), IN `day3` varchar(12), IN `day4` varchar(12), IN `day5` varchar(12))
BEGIN

insert into Baseline
SELECT 
    site_id,
    contract_id,
    temp_time as timestamp,
    (sr1value + sr2value + sr3value + sr4value + sr5value) / 5 as value,
    programme
FROM
    (SELECT 
        distinct concat(cast(hour(temp_time) as char), ':', cast(minute(temp_time) as char)) as hourminute,            
            SR.site_id as site_id,
            value as sr1value,
            temp_time,
            S.contract_id as contract_id,
            programme
    FROM
        SiteReading SR
    join Site S ON SR.site_id = S.site_id
    join Contract C ON S.contract_id = C.contract_id
    where
        temp_time like 'day1%'
            and SR.site_id = input_site_id) sr1
        join
    (SELECT 
        concat(cast(hour(temp_time) as char), ':', cast(minute(temp_time) as char)) as hourminute,
            value as sr2value
    FROM
        SiteReading
    where
        temp_time like 'day2%'
            and site_id = input_site_id) sr2 ON sr1.hourminute = sr2.hourminute
        join
    (SELECT 
        concat(cast(hour(temp_time) as char), ':', cast(minute(temp_time) as char)) as hourminute,
            value as sr3value
    FROM
        SiteReading
    where
        temp_time like 'day3%'
            and site_id = input_site_id) sr3 ON sr1.hourminute = sr3.hourminute
        join
    (SELECT 
        concat(cast(hour(temp_time) as char), ':', cast(minute(temp_time) as char)) as hourminute,
            value as sr4value
    FROM
        SiteReading
    where
        temp_time like 'day4%'
            and site_id = input_site_id) sr4 ON sr1.hourminute = sr4.hourminute
        join
    (SELECT 
        concat(cast(hour(temp_time) as char), ':', cast(minute(temp_time) as char)) as hourminute,
            value as sr5value
    FROM
        SiteReading
    where
        temp_time like 'day5%'
            and site_id = input_site_id) sr5 ON sr1.hourminute = sr5.hourminute
limit 1440;

END//

DELIMITER ;

読み取りおよび書き込みに関連するテーブルは次のとおりです。

- サイトリーディング:

CREATE TABLE `SiteReading` (
  `site_id` int(11) NOT NULL,
  `contract_id` int(11) DEFAULT NULL,
  `temp_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `value` int(11) NOT NULL,
  PRIMARY KEY (`site_id`,`temp_time`),
  KEY `site_id` (`site_id`),
  KEY `contract_id` (`contract_id`),
  CONSTRAINT `SiteReading_ibfk_1` FOREIGN KEY (`site_id`) REFERENCES `Site` (`site_id`),
  CONSTRAINT `SiteReading_ibfk_3` FOREIGN KEY (`contract_id`) REFERENCES `Contract` (`contract_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8$$

- ベースライン:

CREATE TABLE `Baseline` (
  `site_id` int(11) NOT NULL,
  `contract_id` int(11) NOT NULL,
  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `value` int(11) NOT NULL,
  `programme` int(11) NOT NULL,
  PRIMARY KEY (`site_id`,`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8$$
  1. 各行で同じベースラインに格納する追加の値 (site_id、contract_id、programme) を取得する必要があるため、別の方法で挿入ステートメントを実行する必要があるのではないかと考えていました。問題は、ベースライン テーブルのすべての列を null にすることはできないということです。

  2. 誰かがこの手順について他のコメントを持っているかもしれません.ON DUPLICATE KEY UPDATEやその他のルーチン関連のものなど、このルーチンに他のパラメータを定義する必要がありますか?

ありがとう。

4

2 に答える 2

1
SELECT 
  t1.site_id,
  t1.contract_id,
  t1.temp_time,
  AVG(t2.value)
FROM 
  SiteReading AS t1
LEFT JOIN 
  SiteReading AS t2
ON 
  t1.site_id = t2.site_id 
  AND t2.datetime BETWEEN startdate AND enddate
  AND HOUR(t1.temp_time) = HOUR(t2.temp_time) 
  AND MINUTE(t1.temp_time) = MINUTE(t2.temp_time)
WHERE 
  t1.temp_time BETWEEN startdate AND enddate
GROUP BY
  t1.site_id,
  t1.contract_id,
  t1.temp_time

まったくテストされていませんが、このようなものがより役立つ場合があります。私が行った最適化:

  1. 一致する時間間隔で単一の自己結合を使用します。
  2. 平均集計で group by を使用する
  3. 最初のテーブルを 5 日間 (開始日、終了日) の間のサブセットに制限する
  4. サイトまたは契約に参加していません。ベースラインにはこれらのテーブルの外部キーがあるため、これらのテーブルから余分なデータを取得する必要はありません (プログラムはこれらのテーブルの 1 つから来たと仮定しています)。

1. ベースラインに格納する追加の値 (site_id、contract_id、programme) を各行で同じにする必要があるため、別の方法で挿入ステートメントを実行する必要があるのではないかと考えていました。問題は、ベースライン テーブルのすべての列を null にすることはできないということです。

#4を参照

2. 誰かがこの手順について何か他のコメントを持っているかもしれません - ON DUPLICATE KEY UPDATE やその他のルーチン関連のもののような、このルーチンの他のパラメータを定義する必要がありますか?

ここであなたの要件を完全に理解しているかどうかはわかりません. 長期間にわたって複数の 5 日間のベースラインを収集していますか? もしそうなら、なぜ何かを更新する必要があるのか​​ わかりません。temp_time の一部が重複している場合 (つまり、5 日間で 5 日ごとよりも頻繁にプロシージャを実行する場合)、重複キーを防ぐために、プロシージャをいつ実行したかを識別するベースライン主キーの一部として一意の ID またはタイムスタンプを保持できます。 temp_time で。

編集

あなたの日々は連続していないかもしれないことがわかりました。その場合、次の行を変更します。

AND t2.datetime BETWEEN startdate AND enddate

t1.temp_time BETWEEN startdate AND enddate

に:

AND DATE(t2.datetime) IN (day1, day2, day3, day4, day5)

DATE(t1.temp_time) IN (day1, day2, day3, day4, day5)

ただし、WHERE 句と ON 条件でサイト読み取りの完全なテーブル スキャンを実行する必要があるため、これは問題を引き起こします。これを回避するには、データセットを保存する前に時間間隔を正規化することを検討してください。たとえば、1 日に 24*60 の読み取り値が取得される場合、各 temp_time 間隔は 1 から 1440 までの int で表すことができ、各日は 1 から 365 (366 閏年) までの int で表すことができます。次に、代わりに where 句と join 句でこれらの値を使用します。

于 2012-12-21T21:21:42.343 に答える