サーバーセット上のすべてのネットワークインターフェイスのスループットを定期的に記録する表があります。
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;
これを行うためのより簡潔な方法はありますか?
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集約関数の後に発生します。
関連:
インターフェイスの数が限られている場合、それは可能性があります
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;
しかし、元のクエリよりも本当に優れているかどうかはわかりません。