Software

PostgreSQL 建立分割表

PostgreSQL 分割表建立步驟

1. 建立分區表結構

CREATE TABLE new_table_name (LIKE original_table) 
PARTITION BY RANGE (date_column);

2. 自動建立月份分區

DO $$
DECLARE
    start_date DATE := 'start_date';  -- 例:'2020-01-01'
    end_date DATE := CURRENT_DATE;
    curr_date DATE;
BEGIN
    curr_date := start_date;

    WHILE curr_date < end_date LOOP
        EXECUTE format(
            'CREATE TABLE %s_%s PARTITION OF %s
            FOR VALUES FROM (%L) TO (%L)',
            '[table_name]',
            to_char(curr_date, 'YYYY_MM'),
            '[new_table_name]',
            curr_date,
            curr_date + INTERVAL '1 month'
        );
        curr_date := curr_date + INTERVAL '1 month';
    END LOOP;
END $$;

-- 建立未來分區
CREATE TABLE table_name_future PARTITION OF new_table_name DEFAULT;

3. 遷移數據和替換表

INSERT INTO [new_table_name] SELECT * FROM [original_table];
ALTER TABLE [original_table] RENAME TO [original_table_old];
ALTER TABLE [new_table_name] RENAME TO [original_table];
INSERT INTO new_table_name SELECT * FROM original_table;
ALTER TABLE original_table RENAME TO original_table_old;
ALTER TABLE new_table_name RENAME TO original_table;

自動增加分割表

DO $$
DECLARE
    last_partition_date DATE;
    target_end_date DATE := CURRENT_DATE + INTERVAL '6 months'; -- 預建立未來6個月的分表
    curr_date DATE;
    partition_exists boolean;
BEGIN
    -- 找出最後一個分區的日期(排除 future 分區)
    SELECT to_date(replace(split_part(partition_name, '_', -2) || '_' || 
                          split_part(partition_name, '_', -1), '_', ''), 'YYYYMM')
    INTO last_partition_date
    FROM (
        SELECT inhrelid::regclass::text AS partition_name
        FROM pg_inherits
        WHERE inhparent = 'your_table_name'::regclass
        AND inhrelid::regclass::text NOT LIKE '%future'
        ORDER BY inhrelid::regclass::text DESC
        LIMIT 1
    ) subq;

    -- 設定起始日期為最後一個分區的下一個月
    curr_date := last_partition_date + INTERVAL '1 month';

    WHILE curr_date <= target_end_date LOOP
        -- 檢查分區是否已存在
        SELECT EXISTS (
            SELECT 1
            FROM pg_inherits
            WHERE inhparent = 'your_table_name'::regclass
            AND inhrelid::regclass::text = format('your_table_name_%s', to_char(curr_date, 'YYYY_MM'))::text
        ) INTO partition_exists;

        -- 如果分區不存在,則建立新分區
        IF NOT partition_exists THEN
            EXECUTE format(
                'CREATE TABLE your_table_name_%s PARTITION OF your_table_name
                FOR VALUES FROM (%L) TO (%L)',
                to_char(curr_date, 'YYYY_MM'),
                curr_date,
                curr_date + INTERVAL '1 month'
            );
            RAISE NOTICE '已建立分區 your_table_name_%', to_char(curr_date, 'YYYY_MM');
        END IF;

        curr_date := curr_date + INTERVAL '1 month';
    END LOOP;
END $$;

處理 future 分區中已經存在了日期無法新增問題

-- 1. 首先檢查 future 分區中的數據分布
SELECT date_trunc('month', tr_date) as month,
       count(*) as record_count
FROM your_table_name_future
GROUP BY date_trunc('month', tr_date)
ORDER BY month;

-- 2. 建立臨時表來存儲要移動的數據
CREATE TEMP TABLE temp_future_data AS
SELECT *
FROM your_table_name_future
WHERE tr_date >= '2024-12-01'
  AND tr_date < '2025-01-01';

-- 3. 從 future 分區中刪除這些數據
DELETE FROM your_table_name_future
WHERE tr_date >= '2024-12-01'
  AND tr_date < '2025-01-01';

-- 4. 現在可以建立新的分區
CREATE TABLE your_table_name_2024_12 
PARTITION OF your_table_name
FOR VALUES FROM ('2024-12-01') TO ('2025-01-01');

-- 5. 將臨時表中的數據插入新分區
INSERT INTO your_table_name
SELECT * FROM temp_future_data;

-- 6. 清理臨時表
DROP TABLE temp_future_data;