151 lines
4.8 KiB
SQL
151 lines
4.8 KiB
SQL
-- 113 迁移前置兼容修复。
|
||
--
|
||
-- 113 已经进入生产,文件必须保持字节不变。旧快照库中存在两类历史脏数据,
|
||
-- 会让原版 113 在“加新约束/唯一索引”时失败:
|
||
-- 1. sales_daily_facts 仍有旧约束不接受的渠道值;
|
||
-- 2. cost_exceptions 同一范围的旧行会生成重复 scope_key。
|
||
--
|
||
-- 本文件不是迁移,不写 schema_migrations。它只做幂等兼容准备:
|
||
-- - 113 已执行时立即跳过;
|
||
-- - 清理旧库数据,让随后执行的原版 113 可以通过。
|
||
--
|
||
-- 由 deploy/ubuntu-deploy.sh 在数据库迁移前自动执行。
|
||
|
||
DO $preflight$
|
||
DECLARE
|
||
v_has_113 BOOLEAN := FALSE;
|
||
v_sales_has_mode BOOLEAN := FALSE;
|
||
v_scope_duplicates BIGINT := 0;
|
||
v_scope_missing BIGINT := 0;
|
||
BEGIN
|
||
IF to_regclass('public.schema_migrations') IS NOT NULL THEN
|
||
EXECUTE $query$
|
||
SELECT EXISTS (
|
||
SELECT 1
|
||
FROM public.schema_migrations migration
|
||
WHERE to_jsonb(migration)->>'filename' = '113_finance_channel_cutover.sql'
|
||
OR to_jsonb(migration)->>'name' = '113_finance_channel_cutover'
|
||
)
|
||
$query$ INTO v_has_113;
|
||
END IF;
|
||
|
||
IF v_has_113 THEN
|
||
RAISE NOTICE '113 已执行,跳过前置兼容修复';
|
||
RETURN;
|
||
END IF;
|
||
|
||
IF to_regclass('public.sales_daily_facts') IS NOT NULL THEN
|
||
SELECT EXISTS (
|
||
SELECT 1
|
||
FROM pg_attribute
|
||
WHERE attrelid = 'public.sales_daily_facts'::regclass
|
||
AND attname = 'fulfillment_mode'
|
||
AND NOT attisdropped
|
||
) INTO v_sales_has_mode;
|
||
|
||
IF v_sales_has_mode THEN
|
||
EXECUTE $sql$
|
||
ALTER TABLE public.sales_daily_facts
|
||
DROP CONSTRAINT IF EXISTS chk_sales_daily_facts_fulfillment_mode
|
||
$sql$;
|
||
|
||
EXECUTE $sql$
|
||
UPDATE public.sales_daily_facts
|
||
SET fulfillment_mode = CASE
|
||
WHEN fulfillment_mode = 'legacy_platform' THEN 'archived'
|
||
ELSE 'unclassified'
|
||
END
|
||
WHERE fulfillment_mode NOT IN (
|
||
'work_order', 'affiliate_dash', 'third_party_voucher',
|
||
'manual', 'unclassified', 'archived'
|
||
)
|
||
$sql$;
|
||
|
||
-- 迁移前临时使用新旧并集的约束;原版 113 会立即替换成最终约束。
|
||
EXECUTE $sql$
|
||
ALTER TABLE public.sales_daily_facts
|
||
ADD CONSTRAINT chk_sales_daily_facts_fulfillment_mode
|
||
CHECK (
|
||
fulfillment_mode IN (
|
||
'work_order', 'affiliate_dash', 'third_party_voucher',
|
||
'manual', 'unclassified', 'archived'
|
||
)
|
||
)
|
||
$sql$;
|
||
|
||
RAISE NOTICE 'sales_daily_facts 历史渠道值已兼容归一';
|
||
END IF;
|
||
END IF;
|
||
|
||
IF to_regclass('public.cost_exceptions') IS NOT NULL THEN
|
||
EXECUTE $sql$
|
||
ALTER TABLE public.cost_exceptions
|
||
ADD COLUMN IF NOT EXISTS scope_key TEXT,
|
||
ADD COLUMN IF NOT EXISTS item_count INTEGER NOT NULL DEFAULT 1,
|
||
ADD COLUMN IF NOT EXISTS first_seen_at TIMESTAMPTZ,
|
||
ADD COLUMN IF NOT EXISTS last_seen_at TIMESTAMPTZ
|
||
$sql$;
|
||
|
||
EXECUTE $sql$
|
||
WITH duplicate_scope_keys AS (
|
||
SELECT scope_key
|
||
FROM public.cost_exceptions
|
||
WHERE status <> 'resolved'
|
||
AND NULLIF(BTRIM(scope_key), '') IS NOT NULL
|
||
GROUP BY scope_key
|
||
HAVING COUNT(*) > 1
|
||
)
|
||
UPDATE public.cost_exceptions ce
|
||
SET scope_key =
|
||
COALESCE(NULLIF(BTRIM(ce.reason), ''), 'legacy')
|
||
|| '|'
|
||
|| COALESCE(ce.order_item_id::text, ce.subject_id::text, ce.id::text)
|
||
|| '|legacy:'
|
||
|| ce.id,
|
||
first_seen_at = COALESCE(ce.first_seen_at, ce.created_at),
|
||
last_seen_at = COALESCE(ce.last_seen_at, ce.created_at)
|
||
WHERE ce.scope_key IS NULL
|
||
OR BTRIM(ce.scope_key) = ''
|
||
OR ce.first_seen_at IS NULL
|
||
OR ce.last_seen_at IS NULL
|
||
OR (
|
||
ce.status <> 'resolved'
|
||
AND ce.scope_key IN (SELECT scope_key FROM duplicate_scope_keys)
|
||
)
|
||
$sql$;
|
||
|
||
EXECUTE $sql$
|
||
SELECT COUNT(*)
|
||
FROM (
|
||
SELECT scope_key
|
||
FROM public.cost_exceptions
|
||
WHERE status <> 'resolved'
|
||
AND NULLIF(BTRIM(scope_key), '') IS NOT NULL
|
||
GROUP BY scope_key
|
||
HAVING COUNT(*) > 1
|
||
) duplicate_scope_keys
|
||
$sql$ INTO v_scope_duplicates;
|
||
|
||
EXECUTE $sql$
|
||
SELECT COUNT(*)
|
||
FROM public.cost_exceptions
|
||
WHERE scope_key IS NULL OR BTRIM(scope_key) = ''
|
||
$sql$ INTO v_scope_missing;
|
||
|
||
IF v_scope_duplicates > 0 THEN
|
||
RAISE EXCEPTION
|
||
'cost_exceptions 仍有 % 组重复 scope_key,113 无法创建唯一索引',
|
||
v_scope_duplicates;
|
||
END IF;
|
||
|
||
IF v_scope_missing > 0 THEN
|
||
RAISE EXCEPTION
|
||
'cost_exceptions 仍有 % 行 scope_key 为空,113 的唯一索引前置数据不完整',
|
||
v_scope_missing;
|
||
END IF;
|
||
|
||
RAISE NOTICE 'cost_exceptions 历史 scope_key 已保证唯一';
|
||
END IF;
|
||
END
|
||
$preflight$;
|