2012-11-16 51 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;