列安全`INSERT INTO t1 SELECT * FROM ...`?

时间:2012-11-16 20:59:05

标签: sql postgresql

有没有办法做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),它会失败,但是,无关紧要,在这种情况下,刷新将无法完成,我们很快就会发现问题。重要的是今天发生的事情:有人改变了视图的两列(相同类型)的顺序,并刷新了插入的虚假数据。

2 个答案:

答案 0 :(得分:1)

您可以查询information_schema.columns以按正确的顺序获取列:

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获取列列表 - 只需从SELECT替换内部information_schema.columns

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

答案 1 :(得分:1)

将此构建到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找到一行,则表示某些内容已关闭。

如果表或视图不存在(或者超出范围 - 不在::regclass且没有模式限定),则转换为search_path会立即引发异常。

如果您还想检查列的数据类型,只需将atttypid添加到USING子句中。

顺便说一句:查询pg_catalog表通常比查询膨胀的视图int information_schema快一个数量级 - information_schema仅适用于SQL标准兼容性和代码的可移植性。由于您正在编写100%Postgres特定代码,因此这两者都不相关。