1

私はこれを最も効率的な方法で行っているとはとても思えないので、plpgsqlここにタグを付けました。これを1,000 の測定システムに対して20 億行で実行する必要があります。

接続が失われたときに以前の値を報告することが多い測定システムがあり、頻繁に、場合によっては長期間にわたって接続が失われます。集計する必要がありますが、その場合は、それがどのくらい繰り返されているかを見て、その情報に基づいてさまざまなフィルターを作成する必要があります。車でmpgを測定しているとしますが、20.1などに移動するよりも1時間20 mpgにとどまっています。詰まったときの精度を評価する必要があります。車が高速道路上にあるときを探すいくつかの代替ルールを配置することもできます。ウィンドウ関数を使用して、車の「状態」を生成し、何かをグループ化することができます。難しい話は抜きにして:

--here's my data, you have different systems, the time of measurement, and the actual measurement
--as well, the raw data has whether or not it's a repeat (hense the included window function
select * into temporary table cumulative_repeat_calculator_data
FROM
    (
    select 
    system_measured, time_of_measurement, measurement, 
    case when 
     measurement = lag(measurement,1) over (partition by system_measured order by time_of_measurement asc) 
     then 1 else 0 end as repeat
    FROM
    (
    SELECT 5 as measurement, 1 as time_of_measurement, 1 as system_measured
    UNION
    SELECT 150 as measurement, 2 as time_of_measurement, 1 as system_measured
    UNION
    SELECT 5 as measurement, 3 as time_of_measurement, 1 as system_measured
    UNION
    SELECT 5 as measurement, 4 as time_of_measurement, 1 as system_measured
    UNION
    SELECT 5 as measurement, 1 as time_of_measurement, 2 as system_measured
    UNION
    SELECT 5 as measurement, 2 as time_of_measurement, 2 as system_measured
    UNION
    SELECT 5 as measurement, 3 as time_of_measurement, 2 as system_measured
    UNION
    SELECT 5 as measurement, 4 as time_of_measurement, 2 as system_measured
    UNION
    SELECT 150 as measurement, 5 as time_of_measurement, 2 as system_measured
    UNION
    SELECT 5 as measurement, 6 as time_of_measurement, 2 as system_measured
    UNION
    SELECT 5 as measurement, 7 as time_of_measurement, 2 as system_measured
    UNION
    SELECT 5 as measurement, 8 as time_of_measurement, 2 as system_measured
    ) as data
) as data;

--unfortunately you can't have window functions within window functions, so I had to break it down into subquery
--what we need is something to partion on, the 'state' of the system if you will, so I ran a running total of the nonrepeats
--this creates a row that stays the same when your data is repeating - aka something you can partition/group on
select * into temporary table cumulative_repeat_calculator_step_1
FROM
    (
    select 
    *,
    sum(case when repeat = 0 then 1 else 0 end) over (partition by system_measured order by time_of_measurement asc) as cumlative_sum_of_nonrepeats_by_system
    from cumulative_repeat_calculator_data
    order by system_measured, time_of_measurement
) as data;

--finally, the query. I didn't bother showing my desired output, because this (finally) got it
--I wanted a sequential count of repeats that restarts when it stops repeating, and starts with the first repeat
--what you can do now is take the average measurement under some condition based on how long it was repeating, for example  
select *, 
case when repeat = 0 then 0
else
row_number() over (partition by cumlative_sum_of_nonrepeats_by_system, system_measured order by time_of_measurement) - 1
end as ordered_repeat
from cumulative_repeat_calculator_step_1
order by system_measured, time_of_measurement

では、巨大なテーブルでこれを実行するには、どのように変更しますか? または、どの代替ツールを使用しますか? これはデータベース内またはデータ挿入プロセス中に行う必要があると思われるため、plpgsql を考えていますが、通常はデータが読み込まれた後にデータを操作します。サブクエリに頼らずにこれを 1 回のスイープで取得する方法はありますか?

別の方法を1 つテストしましたが、それでもサブクエリに依存しており、こちらの方が高速だと思います。その方法では、start_timestamp、end_timestamp、system を使用して「開始と停止」テーブルを作成します。次に、より大きなテーブルに参加し、タイムスタンプがそれらの間にある場合は、その状態にあると分類します。これは、本質的にcumlative_sum_of_nonrepeats_by_system. しかし、これを行うと、数千のデバイスと数千または数百万の「イベント」に対して 1=1 で参加します。そのほうがいいと思いませんか?

4

1 に答える 1

3

テストケース

まず、データを表示するためのより便利な方法-またはさらに良い方法として、sqlfiddleで、次の操作を実行する準備ができています。

CREATE TEMP TABLE data(
   system_measured int
 , time_of_measurement int
 , measurement int
);

INSERT INTO data VALUES
 (1, 1, 5)
,(1, 2, 150)
,(1, 3, 5)
,(1, 4, 5)
,(2, 1, 5)
,(2, 2, 5)
,(2, 3, 5)
,(2, 4, 5)
,(2, 5, 150)
,(2, 6, 5)
,(2, 7, 5)
,(2, 8, 5);

簡略化されたクエリ

不明な点がありますので、上記のみを想定しております。
次に、クエリを簡略化して次のようにします。

WITH x AS (
   SELECT *, CASE WHEN lag(measurement) OVER (PARTITION BY system_measured
                               ORDER BY time_of_measurement) = measurement
                  THEN 0 ELSE 1 END AS step
   FROM   data
   )
   , y AS (
   SELECT *, sum(step) OVER(PARTITION BY system_measured
                            ORDER BY time_of_measurement) AS grp
   FROM   x
   )
SELECT * ,row_number() OVER (PARTITION BY system_measured, grp
                             ORDER BY time_of_measurement) - 1 AS repeat_ct
FROM   y
ORDER  BY system_measured, time_of_measurement;

純粋なSQLを使用することはすべて素晴らしいことですが、plpgsql関数を使用すると、このクエリで少なくとも3回のスキャンが必要な単一のテーブルスキャンで実行できるため、はるかに高速になります。

plpgsql関数でより高速に:

CREATE OR REPLACE FUNCTION x.f_repeat_ct()
  RETURNS TABLE (
    system_measured int
  , time_of_measurement int
  , measurement int, repeat_ct int
  )  LANGUAGE plpgsql AS
$func$
DECLARE
   r    data;     -- table name serves as record type
   r0   data;
BEGIN

-- SET LOCAL work_mem = '1000 MB';  -- uncomment an adapt if needed, see below!

repeat_ct := 0;   -- init

FOR r IN
   SELECT * FROM data d ORDER BY d.system_measured, d.time_of_measurement
LOOP
   IF  r.system_measured = r0.system_measured
       AND r.measurement = r0.measurement THEN
      repeat_ct := repeat_ct + 1;   -- start new array
   ELSE
      repeat_ct := 0;               -- start new count
   END IF;

   RETURN QUERY SELECT r.*, repeat_ct;

   r0 := r;                         -- remember last row
END LOOP;

END
$func$;

電話:

SELECT * FROM x.f_repeat_ct();

この種のplpgsql関数では、常に列名をテーブル修飾してください。修飾されていない場合に優先される出力パラメータと同じ名前を使用するためです。

数十億行

数十億の行がある場合は、この操作を分割することをお勧めします。ここでマニュアルを引用します:

注: 上記のように、関数から戻る前に、結果セット全体の現在の実装RETURN NEXTと保存が行われます。RETURN QUERYつまり、PL / pgSQL関数が非常に大きな結果セットを生成する場合、パフォーマンスが低下する可能性があります。メモリの枯渇を避けるためにデータはディスクに書き込まれますが、結果セット全体が生成されるまで関数自体は返されません。PL / pgSQLの将来のバージョンでは、ユーザーがこの制限のない集合を返す関数を定義できるようになる可能性があります。現在、データがディスクに書き込まれ始めるポイントは、work_mem 構成変数によって制御されます。より大きな結果セットをメモリに格納するのに十分なメモリがある管理者は、このパラメータを増やすことを検討する必要があります。

work_mem一度に1つのシステムの行を計算するか、負荷に対処するのに十分な高さの値を設定することを検討してください。work_memの詳細については、見積もりに記載されているリンクをたどってください。

work_mem1つの方法は、関数でwithに非常に高い値を設定することですSET LOCAL。これは、現在のトランザクションに対してのみ有効です。関数にコメント行を追加しました。サーバーを破壊する可能性があるため、グローバルに非常に高く設定しないでください。マニュアルをお読みください。

于 2012-10-26T02:16:19.033 に答える