web-dev-qa-db-ja.com

異なる次元にわたるネストされた集約関数

サーバーセット上のすべてのネットワークインターフェイスのスループットを定期的に記録する表があります。

create table net (
    ts timestamptz not null,
    Host text not null,
    interface text not null,
    recv_bytes bigint not null
);
create index on net (ts);
insert into net (ts, Host, interface, recv_bytes) values
    ('2017-01-01 00:00:00+00', 'a', 'eth0',  500),
    ('2017-01-01 00:00:00+00', 'b', 'eth1', 2000),
    ('2017-01-01 00:00:01+00', 'b', 'eth0', 1000),  -- measurements arrive with some jitter, +1s here
    ('2017-01-01 00:00:02+00', 'c', 'eth0',  100),  -- only present in this interval
    ('2017-01-01 00:00:04+00', 'b', 'eth1', 1900),
    ('2017-01-01 00:00:05+00', 'a', 'eth0',  550),
    ('2017-01-01 00:00:05+00', 'b', 'eth0', 1200),

    ('2017-01-01 00:00:10+00', 'a', 'eth0',  600),
    ('2017-01-01 00:00:10+00', 'b', 'eth0', 1500),
    ('2017-01-01 00:00:11+00', 'b', 'eth1', 1900),
    ('2017-01-01 00:00:15+00', 'a', 'eth0',  600),
    ('2017-01-01 00:00:15+00', 'b', 'eth1', 1400),
    ('2017-01-01 00:00:16+00', 'b', 'eth0', 1400),
    ('2017-01-01 00:00:16+00', 'b', 'eth1', 1700);  -- (b,eth1) appears 3 times in this interval

10秒間隔で平均した、指定された時間範囲のすべてのインターフェースにわたる合計スループットを見つけたいと思います。 (ホスト、インターフェース)ペアに特定の間隔でデータが欠落している場合は、単純に省略できます。これは私が思いついたものです:

select ts_interval, Host, sum(recv_bytes) as recv_bytes
from (
    select
        to_timestamp(floor(extract(Epoch from ts) / 10) * 10) as ts_interval,
        Host,
        interface,
        avg(recv_bytes) as recv_bytes
    from net
    group by ts_interval, Host, interface
) avg_net
where ts_interval >= '2017-01-01 00:00:00+00' and ts_interval < '2017-01-01 00:00:20+00'
group by ts_interval, Host
order by ts_interval, Host;

結果:

      ts_interval       | Host |      recv_bytes
------------------------+------+-----------------------
 2017-01-01 00:00:00+00 | a    |  525.0000000000000000
 2017-01-01 00:00:00+00 | b    | 3050.0000000000000000
 2017-01-01 00:00:00+00 | c    |  100.0000000000000000
 2017-01-01 00:00:10+00 | a    |  600.0000000000000000
 2017-01-01 00:00:10+00 | b    | 3116.6666666666666667

このクエリは冗長すぎるようです。私がやろうとしていることの本質は、一方の次元を合計し、もう一方の次元を平均することです。疑似SQLの場合:

select
    to_timestamp(floor(extract(Epoch from ts) / 10) * 10) as ts_interval,
    Host,
    sum(avg(recv_bytes OVER ts_interval) OVER Host) as recv_bytes
from net
where ts >= '2017-01-01 00:00:00+00' and ts < '2017-01-01 00:00:20+00'
group by ts_interval, Host
order by ts_interval, Host;

これを行うためのより簡潔な方法はありますか?

2
Snowball

クエリ

generate_series() を使用して、結合するタイムラスターを生成できます。

_SELECT g AS ts_interval, Host, sum(recv_bytes) AS recv_bytes
FROM  (
   SELECT g, Host, interface, avg(recv_bytes) AS recv_bytes
   FROM   generate_series(timestamptz '2017-01-01 00:00:00+00'
                        , timestamptz '2017-01-01 00:00:10+00'  -- 10 - only lower bound
                        , interval '10 sec') g
   JOIN   net n ON ts >= g
               AND ts <  g + interval '10 sec'
   GROUP  BY g, Host, interface
   ) sub
GROUP  BY g, Host
ORDER  BY g, Host;
_

同じ結果。冗長ではありませんが、_(ts)_にインデックスがある場合はmuch速くなります。

繰り返しになりますが、重要な点は、 " sargable "述語を使用することです。これも、このようにgenerate_series()なしで取得できます。

_SELECT ts_interval, Host, sum(recv_bytes) as recv_bytes
FROM  (
   SELECT to_timestamp(trunc(extract(Epoch from ts) / 10) * 10) AS ts_interval
        , Host, interface, avg(recv_bytes) as recv_bytes
   FROM   net
   WHERE  ts >= '2017-01-01 00:00:00+00'
   AND    ts <  '2017-01-01 00:00:20+00' -- just make sure to match bounds
   GROUP  BY 1, 2, 3
   ) avg_net
GROUP  BY 1, 2
ORDER  BY 1, 2;
_

マイナーポイント:排他的に正の数では、trunc()floor()と同等で、少し高速です。

「冗長でない」コードを要求したので、序数を使用しています。しかし、それはここでの質問の核心ではありません...

コア質問

1つのクエリレベルで集計関数に対してウィンドウ関数を実行するできる(通常、サブクエリを使用するよりも高速ではありません)。

しかし、その逆ではありません。1つのクエリレベルでウィンドウ関数に対して集計関数を実行することはできません。不可能だ。ウィンドウ関数はSQLでafter集約関数の後に発生します。
関連:

3

インターフェイスの数が限られている場合、それは可能性があります

select
  to_timestamp(floor(extract(Epoch from ts) / 10) * 10) as ts_interval,
  Host,
  coalesce(avg(recv_bytes) filter (where interface = 'eth0'), 0) + 
  coalesce(avg(recv_bytes) filter (where interface = 'eth1'), 0) +
  coalesce(avg(recv_bytes) filter (where interface = 'wlan0'), 0) -- etc
from
  net
group by 1,2
order by 1,2;

しかし、元のクエリよりも本当に優れているかどうかはわかりません。

2
Abelisto