我想在以 Postgres 作为数据源的 Grafana 仪表板中显示未安装给定软件包的主机/服务器的数量(例如:“abc”)。
Grafana 中显示的存在“abc”包的单个服务器的示例表:
主机名 | 套餐 | 版本 | 服务器上安装的软件包 |
---|---|---|---|
服务器1 | abc | 10.0.1 | 真实 |
服务器1 | abc | 10.0.2 | 假 |
服务器1 | 套餐2 | 3.1 | 真实 |
服务器1 | 套餐3 | 4.1.1 | 真实 |
服务器2 | 套餐2 | 3.1 | 真实 |
服务器2 | 套餐3 | 4.1.1 | 真实 |
服务器2 | 套餐4 | 10.0.1 | 真实 |
服务器3 | 套餐2 | 3.1 | 真实 |
服务器3 | 套餐3 | 4.1.1 | 真实 |
例如,我有 10,000 台服务器,其中 400 台服务器中未安装软件包“abc”。然后 Grafana 应该显示不存在“abc”版本的服务器的数量。
这是我尝试过的。对于上面的示例,输出应为 2,其中包 abc 不存在。但我没有得到预期的输出(或 2)。
SELECT count(*)
FROM (
SELECT DISTINCT hostname
FROM scanner_table
GROUP BY hostname
HAVING COUNT(CASE WHEN package = 'abc' THEN 1 END) = 0
) AS host_without_abc;
如何解决这个问题?
简单,幼稚,缓慢,第一个实现 - 计算所有主机 - 计算所有带 abc 包的主机 = 不带 abc 包的主机:
SELECT COUNT(*)
FROM (
(
SELECT DISTINCT hostname
FROM scanner_table
)
EXCEPT
(
SELECT DISTINCT hostname
FROM scanner_table
WHERE package = 'abc'
)
)
更成熟的解决方案:与选定的包和计数主机名相同的主机名进行左连接,其中连接为空/空:
SELECT COUNT(DISTINCT a.hostname)
FROM scanner_table AS a
LEFT JOIN scanner_table AS b
ON a.hostname=b.hostname AND b.package = 'abc'
WHERE b.hostname IS NULL
根据未公开的细节,一个或另一个查询更简单/更快。
CREATE TABLE scanner_table (
hostname text NOT NULL -- !
, package text NOT NULL -- !
, version text
, package_installed_on_server bool
);
INSERT INTO scanner_table VALUES
('server1', 'abc' , '10.0.1', true)
, ('server1', 'abc' , '10.0.2', false)
, ('server1', 'package2', '3.1' , true)
, ('server1', 'package3', '4.1.1' , true)
, ('server2', 'package2', '3.1' , true)
, ('server2', 'package3', '4.1.1' , true)
, ('server2', 'package4', '10.0.1', true)
, ('Server3', 'package2', '3.1' , true)
, ('Server3', 'package3', '4.1.1' , true)
;
这个多列索引有助于以下所有查询(如果表足够真空),有些多一些,有些少;
CREATE INDEX scanner_table_hostname_package_idx ON scanner_table (hostname, package);
您应该有一个
hostname
表,每个相关主机一行。
如果您不这样做,请考虑创建一个:
CREATE TABLE hostname (hostname text PRIMARY KEY);
INSERT INTO hostname VALUES
('server1')
, ('server2')
, ('Server3')
;
如果表
hostname
存在则最快。 (总体来说最快。)
SELECT count(*)
FROM hostname h
WHERE NOT EXISTS (
SELECT FROM scanner_table s
WHERE s.hostname = h.hostname
AND s.package = 'abc'
);
没有表
hostname
最快,每个主机名只有很少行。SELECT count(*)
FROM (
SELECT FROM scanner_table -- SELECT list can stay empty
GROUP BY hostname
HAVING bool_and(package <> 'abc') -- assuming column package defined NOT NULL
) sub;
模拟索引跳过扫描速度最快,无需表
hostname
和每个主机名有 many 行。WITH RECURSIVE cte AS (
(
SELECT hostname, package
FROM scanner_table s
ORDER BY hostname, package <> 'abc'
LIMIT 1
)
UNION ALL
(
SELECT s.hostname, s.package
FROM cte c
JOIN scanner_table s ON s.hostname > c.hostname
ORDER BY s.hostname, s.package <> 'abc'
LIMIT 1
)
)
SELECT count(*)
FROM cte
WHERE package <> 'abc';
package <> 'abc'
中的表达式ORDER BY
将包“abc”排序在顶部。为什么?
如果列
package
可以为NULL,则需要对某些查询执行更多操作。