我有以下用于存储 IP 的表结构(PostgreSQL 11.14):
CREATE TABLE ips (
ip INET
);
INSERT INTO ips VALUES ('10.0.0.4');
INSERT INTO ips VALUES ('10.0.0.0/24');
INSERT INTO ips VALUES ('10.1.0.0/23');
INSERT INTO ips VALUES ('10.1.0.0/27');
我需要知道哪个网络范围重复才能找到重叠的网络条目。
要检测已经存在的重叠,您可以使用
inet <<= inet → boolean
,类似于 @a_horse_with_no_name 建议:演示
CREATE TABLE ips (
id SERIAL PRIMARY KEY,
ip INET );
INSERT INTO ips (ip)
VALUES ('10.0.0.4'),
('10.0.0.0/24'),
('10.1.0.0/23'),
('10.1.0.0/27');
CREATE INDEX ON ips USING gist(ip inet_ops,id);
SELECT
a.id AS id1,
a.ip AS ip1,
b.id AS id2,
b.ip AS ip2
FROM ips AS a
INNER JOIN ips AS b
ON a.ip <<= b.ip
AND a.id<>b.id;
-- id1 | ip1 | id2 | ip2
-------+-------------+-----+-------------
-- 1 | 10.0.0.4 | 2 | 10.0.0.0/24
-- 4 | 10.1.0.0/27 | 3 | 10.1.0.0/23
--(2 rows)
如果您希望防止插入重叠,您可以在该表的排除约束
中使用可交换的
inet && inet → boolean
运算符:demo
CREATE TABLE ips (
ip INET,
CONSTRAINT no_ip_overlaps EXCLUDE USING gist (ip inet_ops WITH &&));
INSERT INTO ips (ip)
VALUES ('10.0.0.4'),
('10.1.0.0/27');
-- You can let the unhandled conflict throw an error
INSERT INTO ips (ip) VALUES ('10.0.0.0/24');
--ERROR: conflicting key value violates exclusion constraint "no_ip_overlaps"
--DETAIL: Key (ip)=(10.0.0.0/24) conflicts with existing key (ip)=(10.0.0.4).
您可以决定在冲突出现时处理它们,要么忽略它们,要么有选择性地处理:
INSERT INTO ips (ip) VALUES ('10.0.0.0/24')
ON CONFLICT ON CONSTRAINT no_ip_overlaps DO NOTHING;
--You might one day decide to keep the bigger network in the overlapping pair:
--right now, only 'do nothing' is supported for conflicts on exclusion constraints
INSERT INTO ips (ip) VALUES ('10.1.0.0/23')
ON CONFLICT ON CONSTRAINT no_ip_overlaps DO UPDATE
SET ip=CASE WHEN ips.ip<<excluded.ip THEN excluded.ip ELSE ips.ip END;
--ERROR: ON CONFLICT DO UPDATE not supported with exclusion constraints
--Until that day you can revert to a MERGE in place of your INSERT
MERGE INTO ips AS present
USING (SELECT '10.1.0.0/23'::inet AS ip) AS incoming
ON (present.ip << incoming.ip)
WHEN MATCHED THEN UPDATE
SET ip=incoming.ip
WHEN NOT MATCHED THEN
INSERT (ip)
VALUES (incoming.ip);
MERGE
最近才被添加到 PostgreSQL 15,在早期版本中,您可以通过 PL/pgSQL upsert 来完成。
我们可以在这里使用
SUBSTRING()
以及正则表达式模式:
WITH cte AS (
SELECT *, COUNT(*) OVER (PARTITION BY SUBSTRING(ip::text FROM '[^/]+')) cnt
FROM ips
)
SELECT *
FROM cte
WHERE cnt > 1;