在函数中使用 psql --set 变量

kbk*_*bkb 2 postgresql psql scripting postgresql-9.5

我想创建一个脚本,该脚本将创建一些自定义命名架构并在其中创建一些表和函数。像这样:

文件example.sh

#!/bin/bash

# this is the only place I want to set the schema name
SCH="ex"

export SCH

export PGPASSWORD="*******"

PSQL="psql \
  -X \
  -U postgres \
  -h localhost \
  --single-transaction \
  --echo-all \
  --set SCH=$SCH \
  --set ON_ERROR_STOP=on "

eval $PSQL "-f ./example.sql"
Run Code Online (Sandbox Code Playgroud)

文件example.sql

DROP SCHEMA IF EXISTS :SCH CASCADE;
CREATE SCHEMA :SCH;

CREATE TABLE :SCH.my_table
(
  id SERIAL PRIMARY KEY,
  my_col text
);

INSERT INTO :SCH.my_table (my_col) VALUES ('abc'), ('def');

CREATE OR REPLACE FUNCTION :SCH.getLast()
RETURNS text AS $$
SELECT my_col FROM :SCH.my_table ORDER BY id DESC LIMIT 1;
$$ LANGUAGE sql STABLE;

SELECT * FROM :SCH.getLast();
Run Code Online (Sandbox Code Playgroud)

它工作正常,直到:

CREATE OR REPLACE FUNCTION :SCH.getLast()
RETURNS text AS $$
SELECT my_col FROM :SCH.my_table ORDER BY id DESC LIMIT 1;
$$ LANGUAGE sql STABLE;
Run Code Online (Sandbox Code Playgroud)

由于函数体是一个文本常量,因此:SCH不会被模式名称替换,我们会得到一个错误:

psql:./example.sql:16: ERROR:  syntax error at or near ":"
LINE 3: SELECT my_col FROM :SCH.my_table ORDER BY id DESC LIMIT 1;
Run Code Online (Sandbox Code Playgroud)

有没有一种巧妙的方法让它发挥作用?

解决方法

这可以通过以下方式完成,但似乎应该有一种更简单的方法:

CREATE OR REPLACE FUNCTION :SCH.makeFunction(schema_name text)
RETURNS VOID AS $body$
BEGIN
    EXECUTE format('CREATE OR REPLACE FUNCTION %1$s.getLast()
    RETURNS text AS $$
    SELECT my_col FROM %1$s.my_table ORDER BY id DESC LIMIT 1;
    $$ LANGUAGE sql STABLE;', schema_name);
END
$body$ LANGUAGE plpgsql VOLATILE;

SELECT :SCH.makeFunction(:'SCH');
Run Code Online (Sandbox Code Playgroud)

dez*_*zso 5

实现此目的的一种有趣方法是使用psql变量来存储整个函数体,例如

\n\n
\\set body \'$$SELECT 1$$\'\n\nCREATE FUNCTION bla() RETURNS integer LANGUAGE SQL AS :c;\n\nSELECT bla();\n bla \n\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n   1\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者,您可以将整个定义传递到变量中,然后运行它:

\n\n
\\set function \'CREATE FUNCTION bla() RETURNS integer LANGUAGE SQL AS $$SELECT 1;$$;\'\n\n:function\n\nSELECT bla();\n bla \n\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n   1\n
Run Code Online (Sandbox Code Playgroud)\n\n

那么,问题就是如何把body放入一个合适的变量中。

\n\n

psql上面的 9.3 开始,有一个\\gset命令可以解决这个问题。我们构建一个查询,生成函数体作为输出,然后将其分配给一个psql变量,并像上面一样使用它(使用format()):

\n\n
SELECT format(\'CREATE OR REPLACE FUNCTION %1$s.getLast()\n    RETURNS text AS $$\n    SELECT my_col FROM %1$s.my_table ORDER BY id DESC LIMIT 1;\n    $$ LANGUAGE sql STABLE;\', \'test\') AS function;\n\n\\gset\n\n:function\n
Run Code Online (Sandbox Code Playgroud)\n\n

并做了。

\n