我正在尝试编写一个脚本,在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 );
还有其他办法吗?
答案 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;
/
答案 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');
报告实际丢弃的表数。 (适应您选择的报告信息。)
使用原始中的信息架构。在这里似乎是正确的选择,但要注意细微的限制:
要在大量并发负载(长事务)下使用,请考虑NOWAIT
option for the LOCK
command并可能从中捕获异常。
ACCESS EXCLUSIVE
与所有模式的锁定冲突(
ACCESS SHARE
,ROW SHARE
,ROW EXCLUSIVE
,SHARE UPDATE
EXCLUSIVE
,SHARE
,SHARE ROW EXCLUSIVE
,EXCLUSIVE
, 和ACCESS EXCLUSIVE
)。这种模式保证了持有人 是以任何方式访问该表的唯一事务。由
ALTER TABLE
,DROP TABLE
,TRUNCATE
,REINDEX
,CLUSTER
和VACUUM 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表达能力作为替代品,我会感到震惊。