@wildplasser 提供的有趣但不是很有用的解决方案。我创建脚本来插入示例数据:
WITH param AS (
SELECT 8 AS max
), maxarray AS (
SELECT array_agg(i) as ma FROM (SELECT generate_series(1, max) as i FROM param) as i
), pre AS (
SELECT
*
FROM (
SELECT
*, CASE WHEN (id >> mbit) & 1 = 1 THEN ma[mbit + 1] END AS item_id
FROM (
SELECT *,
generate_series(0, array_upper(ma, 1) - 1) as mbit
FROM (
SELECT *,
generate_series(1,(2^max - 1)::int8) AS id
FROM param, maxarray
) AS pre1
) AS pre2
) AS pre3
WHERE item_id IS NOT NULL
), ins_item AS (
INSERT INTO theitem (item_id, item_name) SELECT i, i::text FROM generate_series(1, (SELECT max FROM param)) as i RETURNING *
), ins_set AS (
INSERT INTO theset (set_id, set_name)
SELECT id, id::text FROM generate_series(1, (SELECT 2^max - 1 FROM param)::int8) as id
RETURNING *
), ins_set_item AS (
INSERT INTO set_item (set_id, item_id)
SELECT id, item_id FROM pre WHERE (SELECT count(*) FROM ins_item) > 0 AND (SELECT count(*) FROM ins_set) > 0
RETURNING *
)
SELECT
'sets', count(*)
FROM ins_set
UNION ALL
SELECT
'items', count(*)
FROM ins_item
UNION ALL
SELECT
'sets_items', count(*)
FROM ins_set_item
;
当我用 8(set_item 为 1024 - 2^8 行)调用它时,它运行 21 秒。太糟糕了。当我关闭触发器时,它花费了不到 1 毫秒。
我的建议
在这种情况下使用数组非常有趣。不幸的是,PostgreSQL 不支持数组的外键,但它可以由 TRIGGER 完成。我删除set_item 表并为theset 添加items int[] 字段:
-- The data
CREATE TABLE theitem (
item_id integer NOT NULL PRIMARY KEY
, item_name text UNIQUE
);
CREATE TABLE theset (
set_id INTEGER NOT NULL PRIMARY KEY
, set_name text UNIQUE
, items integer[] UNIQUE NOT NULL
);
CREATE INDEX i1 ON theset USING gin (items);
CREATE OR REPLACE FUNCTION check_item_CU() RETURNS TRIGGER AS $sql$
BEGIN
IF (SELECT count(*) > 0 FROM unnest(NEW.items) AS u LEFT JOIN theitem ON (item_id = u) WHERE item_id IS NULL) THEN
RETURN NULL;
END IF;
NEW.items = ARRAY(SELECT unnest(NEW.items) ORDER BY 1);
RETURN NEW;
END;
$sql$ LANGUAGE plpgsql;
CREATE TRIGGER check_item_CU BEFORE INSERT OR UPDATE ON theset FOR EACH ROW EXECUTE PROCEDURE check_item_CU();
CREATE OR REPLACE FUNCTION check_item_UD() RETURNS TRIGGER AS $sql$
BEGIN
IF (TG_OP = 'DELETE' OR TG_OP = 'UPDATE' AND NEW.item_id != OLD.item_id) AND (SELECT count(*) > 0 FROM theset WHERE OLD.item_id = ANY(items)) THEN
RAISE EXCEPTION 'item_id % still used', OLD.item_id;
RETURN NULL;
END IF;
RETURN NEW;
END;
$sql$ LANGUAGE plpgsql;
CREATE TRIGGER check_item_UD BEFORE DELETE OR UPDATE ON theitem FOR EACH ROW EXECUTE PROCEDURE check_item_UD();
WITH param AS (
SELECT 10 AS max
), maxarray AS (
SELECT array_agg(i) as ma FROM (SELECT generate_series(1, max) as i FROM param) as i
), pre AS (
SELECT
*
FROM (
SELECT
*, CASE WHEN (id >> mbit) & 1 = 1 THEN ma[mbit + 1] END AS item_id
FROM (
SELECT *,
generate_series(0, array_upper(ma, 1) - 1) as mbit
FROM (
SELECT *,
generate_series(1,(2^max - 1)::int8) AS id
FROM param, maxarray
) AS pre1
) AS pre2
) AS pre3
WHERE item_id IS NOT NULL
), pre_arr AS (
SELECT id, array_agg(item_id) AS items
FROM pre
GROUP BY 1
), ins_item AS (
INSERT INTO theitem (item_id, item_name) SELECT i, i::text FROM generate_series(1, (SELECT max FROM param)) as i RETURNING *
), ins_set AS (
INSERT INTO theset (set_id, set_name, items)
SELECT id, id::text, items FROM pre_arr WHERE (SELECT count(*) FROM ins_item) > 0
RETURNING *
)
SELECT
'sets', count(*)
FROM ins_set
UNION ALL
SELECT
'items', count(*)
FROM ins_item
;
这个变种运行不到 1 毫秒