2012-11-16 106 views
4

有沒有辦法做INSERT INTO t1 SELECT * FROM...,使它失敗如果列名不重合?列安全的INSERT INTO t1 SELECT * FROM ...`

我在使用Postgresql 9.x列名不提前知道。

動機:我在做由(相當標準)的物化視圖的週期性刷新PL/pgSQL的過程:

CREATE OR REPLACE FUNCTION matview_refresh(name) RETURNS void AS 
$BODY$ 
DECLARE 
    matview ALIAS FOR $1; 
    entry matviews%ROWTYPE; 
BEGIN 
    SELECT * INTO entry FROM matviews WHERE mv_name = matview; 
    IF NOT FOUND THEN 
     RAISE EXCEPTION 'Materialized view % does not exist.', matview; 
    END IF; 

    EXECUTE 'TRUNCATE TABLE ' || matview; 
    EXECUTE 'INSERT INTO ' || matview || ' SELECT * FROM ' || entry.v_name; 

    UPDATE matviews SET last_refresh=CURRENT_TIMESTAMP WHERE mv_name=matview; 
    RETURN; 
END 

我寧願一個TRUNCATE後跟一個SELECT * INTO而不是DROP/CREATE因爲它看起來更加輕便和併發友好。如果有人從視圖中添加/刪除列(然後我會執行DROP/CREATE),那麼它會失敗,但這並不重要,在這種情況下刷新不會完成,我們很快就會發現問題。重要的是今天發生的事情:有人改變了視圖(相同類型)的兩列的順序,刷新插入了僞造數據。

+0

您是否無法使用postgresql指定列列表? ('INSERT INTO t1(col1,col2,...)SELECT *') – Kermit

+1

我想他希望它可以用於任何表,只要它們具有相同的列。 – ThiefMaster

+0

@njk:列名不提前知道 – leonbloy

回答

1

SELECT INTO cols array_to_string(array_agg(column_name::text), ',') 
FROM (
    SELECT column_name 
    FROM information_schema.columns 
    WHERE table_name = 'matview' 
    ORDER BY ordinal_position 
) AS x; 
EXECUTE 'INSERT INTO ' || matview || ' SELECT ' || cols || ' FROM ' || entry.v_name; 

您可以直接從pg_attribute裏得到列的列表只是更換內SELECTinformation_schema.columns將此構建到您的plpgsql函數中,以驗證視圖和表格完全相同的列名稱相同:

IF EXISTS (
    SELECT 1 
    FROM (
     SELECT * 
     FROM pg_attribute 
     WHERE attrelid = matview::regclass 
     AND attisdropped = FALSE 
     AND attnum > 0 
     ) t 
    FULL OUTER JOIN (
     SELECT * 
     FROM pg_attribute 
     WHERE attrelid = entry.v_name::regclass 
     AND attisdropped = FALSE 
     AND attnum > 0 
     ) v USING (attnum, attname) -- atttypid to check for type, too 
    WHERE t.attname IS NULL 
    OR v.attname IS NULL 
    ) THEN 
    RAISE EXCEPTION 'Mismatch between table and view!'; 
END IF; 

對於列名稱列表之間的任何不匹配,FULL OUTER JOIN將爲NULL值添加一行。因此,如果EXISTS找到一行,則表示關閉。

如果表或視圖不存在(或超出範圍 - 不在search_path中且不符合模式限定),則立即引發異常立即引發異常。

如果您還想檢查列的數據類型,只需將atttypid添加到USING子句。

順便說一下:查詢pg_catalog表比查詢臃腫視圖的速度要快一個數量級int information_schema - information_schema僅適用於SQL標準兼容性和代碼的可移植性。由於您正在編寫100%Postgres特定的代碼,因此在這裏無關。

+0

完美。我添加了'atttypid'進行類型檢查。 – leonbloy

1

可以查詢INFORMATION_SCHEMA.COLUMNS得到正確的順序列: - :

SELECT attname AS column_name 
FROM pg_attribute 
WHERE attrelid = 'matview'::regclass AND attisdropped = false 
ORDER BY attnum;