检查表是否在运行时为空

时间:2014-11-15 08:08:24

标签: sql postgresql stored-procedures plpgsql dynamic-sql

我正在尝试编写一个脚本,在Postgres数据库中删除一些过时的表。我想确保表在丢弃之前是空的。我还希望脚本可以保存在我们的迁移脚本中,即使在实际删除这些表之后也可以安全运行。

有我的剧本:

CREATE OR REPLACE FUNCTION __execute(TEXT) RETURNS VOID AS $$
  BEGIN EXECUTE $1; END;
$$ LANGUAGE plpgsql STRICT;

CREATE OR REPLACE FUNCTION __table_exists(TEXT, TEXT) RETURNS bool as $$
SELECT exists(SELECT 1 FROM information_schema.tables WHERE (table_schema, table_name, table_type) = ($1, $2, 'BASE TABLE'));
$$ language sql STRICT;

CREATE OR REPLACE FUNCTION __table_is_empty(TEXT) RETURNS bool as $$
  SELECT not exists(SELECT 1 FROM  $1 ); 
$$ language sql STRICT;

-- Start migration here

SELECT __execute($$
   DROP TABLE oldtable1;
$$)
WHERE __table_exists('public', 'oldtable1')
 AND __table_is_empty('oldtable1');

-- drop auxilary functions here

最后我得到了:

ERROR:  syntax error at or near "$1"
LINE 11:   SELECT not exists(SELECT 1 FROM  $1 );

还有其他办法吗?

3 个答案:

答案 0 :(得分:3)

如果要在Postgres函数中传递表名作为参数,则必须使用EXECUTE 例如:

CREATE OR REPLACE FUNCTION __table_is_empty(param character varying) 
RETURNS bool
AS $$
DECLARE
 v int;
BEGIN
      EXECUTE 'select 1 WHERE EXISTS( SELECT 1 FROM ' || quote_ident(param) || ' ) '
            INTO v;
      IF v THEN return false; ELSE return true; END IF;
END;
$$ LANGUAGE plpgsql;
/

演示:http://sqlfiddle.com/#!12/09cb0/1

答案 1 :(得分:3)

不,不,不。出于很多原因。

@kordirko已经指出了错误消息的直接原因:在纯SQL中,变量只能用于而不能用于关键字或< EM>标识符。您可以使用dynamic SQL进行修复,但仍然无法使您的代码正确。

您正在应用其他编程语言中的编程范例。使用PL / pgSQL,将代码分成多个独立的微小子功能是非常效率低下的。相比之下,开销很大。

你的实际通话也是定时炸弹。 WHERE子句中的表达式以任何顺序执行 ,因此这可能会也可能不会引发不存在的表名称的异常:

WHERE __table_exists('public', 'oldtable1')
 AND __table_is_empty('oldtable1');

...将回滚整个交易。

最后,你对比赛条件完全开放。与@Frank already commented一样,并发事务可以使用表,在这种情况下,打开锁可能会阻止您删除表的尝试。也可能导致死锁(系统通过回滚除一个竞争事务之外的所有事件来解决)。自己拿出独占锁之前检查表是否(仍然)为空。

正常功能

可安全并发使用。它需要一组表名(以及可选的模式名),并且只删除未以任何方式锁定的现有空表:

CREATE OR REPLACE FUNCTION f_drop_tables(_tbls text[] = '{}'
                                       , _schema text = 'public'
                                       , OUT drop_ct int)  AS
$func$
DECLARE
   _tbl   text;                             -- loop var
   _empty bool;                             -- for empty check
BEGIN
   drop_ct := 0;                            -- init!
   FOR _tbl IN 
      SELECT quote_ident(table_schema) || '.'
          || quote_ident(table_name)        -- qualified & escaped table name
      FROM   information_schema.tables
      WHERE  table_schema = _schema
      AND    table_type   = 'BASE TABLE'
      AND    table_name   = ANY(_tbls)
   LOOP
      EXECUTE 'SELECT NOT EXISTS (SELECT 1 FROM ' || _tbl || ')'
      INTO _empty;                          -- check first, only lock if empty

      IF _empty THEN
         EXECUTE 'LOCK TABLE ' || _tbl;     -- now table is ripe for the plucking

         EXECUTE 'SELECT NOT EXISTS (SELECT 1 FROM ' || _tbl || ')'
         INTO _empty;                       -- recheck after lock

         IF _empty THEN
            EXECUTE 'DROP TABLE ' || _tbl;  -- go in for the kill
            drop_ct := drop_ct + 1;         -- count tables actually dropped
         END IF;
      END IF;
   END LOOP;
END
$func$ LANGUAGE plpgsql STRICT;

呼叫:

SELECT f_drop_tables('{foo1,foo2,foo3,foo4}');

使用与默认&#39; public&#39;:

不同的架构进行呼叫
SELECT f_drop_tables('{foo1,foo2,foo3,foo4}', 'my_schema');

重点

  

ACCESS EXCLUSIVE

     

与所有模式的锁定冲突(ACCESS SHAREROW SHAREROW EXCLUSIVE,   SHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVE,   和ACCESS EXCLUSIVE)。这种模式保证了持有人   是以任何方式访问该表的唯一事务。

     

ALTER TABLEDROP TABLETRUNCATEREINDEXCLUSTERVACUUM FULL命令获取。这也是    LOCK TABLE语句的默认锁定模式,未明确指定模式

大胆强调我的。

答案 2 :(得分:-2)

我在DB2上工作,所以我无法确定这是否适用于Postgres数据库,但请尝试这样做:

select case when count(*) > 0 then True else False end from $1

......取代:

SELECT not exists(SELECT 1 FROM  $1 )

如果Postgres没有CASE / END表达能力,如果它没有某种类似的IF / THEN / ELSE表达能力作为替代品,我会感到震惊。