【问题标题】:How do you use script variables in psql?如何在 psql 中使用脚本变量?
【发布时间】:2010-09-07 09:48:19
【问题描述】:

在 MS SQL Server 中,我创建脚本以使用可自定义的变量:

DECLARE @somevariable int  
SELECT @somevariable = -1

INSERT INTO foo VALUES ( @somevariable )

然后我将在运行时更改@somevariable 的值,具体取决于我在特定情况下想要的值。由于它位于脚本的顶部,因此很容易查看和记住。

我如何对 PostgreSQL 客户端 psql 做同样的事情?

【问题讨论】:

  • FWIW,\set 运算符 似乎 与 psql 命令行工具有关,而不是与 pgsql 批处理语言有关。我可能是错的。
  • 你使用的是哪个版本的 Postgres?

标签: sql postgresql variables psql


【解决方案1】:

Postgres 变量是通过 \set 命令创建的,例如 ...

\set myvariable value

... 然后可以替换为 ...

SELECT * FROM :myvariable.table1;

...或...

SELECT * FROM table1 WHERE :myvariable IS NULL;

编辑:从 psql 9.1 开始,变量可以用引号展开,如下所示:

\set myvariable value 

SELECT * FROM table1 WHERE column1 = :'myvariable';

在旧版本的 psql 客户端中:

...如果要使用变量作为条件字符串查询中的值,例如...

SELECT * FROM table1 WHERE column1 = ':myvariable';

... 那么您需要在变量本身中包含引号,因为上述方法不起作用。而是这样定义您的变量...

\set myvariable 'value'

但是,如果您像我一样遇到想要从现有变量创建字符串的情况,我发现诀窍是这样...

\set quoted_myvariable '\'' :myvariable '\''

现在你有了同一个字符串的带引号和不带引号的变量!你可以做这样的事情......

INSERT INTO :myvariable.table1 SELECT * FROM table2 WHERE column1 = :quoted_myvariable;

【讨论】:

  • \set 仅适用于psql 工具,不能在存储过程中使用!
  • @SorinSbarnea OP 询问了 script,而不是 procedure
  • 这个答案将psql meta-commands \set 与 PostgreSQL 命令以一种令人困惑的方式混合在一起。
  • 从 postgresql 9.1 开始,在 psql 中,您现在可以使用 :'variable' 将其正确引用为您的值,或者使用 :"variable" 将其用作标识符。
  • \set myvariable 'value' 确实 not 在变量中包含任何引号,这与此答案所说的相反。如有疑问,请在 psql 中使用 \echo :myvariable 以独立于任何查询显示值。
【解决方案2】:

关于 PSQL 变量的最后一句话:

  1. 如果您在 SQL 语句中将它们括在单引号中,它们不会扩展。 因此这不起作用:

    SELECT * FROM foo WHERE bar = ':myvariable'
    
  2. 要在 SQL 语句中扩展为字符串文字,您必须在变量集中包含引号。但是,变量值已经必须用引号括起来,这意味着您需要 second 组引号,并且必须对内部组进行转义。因此你需要:

    \set myvariable '\'somestring\''  
    SELECT * FROM foo WHERE bar = :myvariable
    

    编辑:从 PostgreSQL 9.1 开始,您可以改为:

    \set myvariable somestring
    SELECT * FROM foo WHERE bar = :'myvariable'
    

【讨论】:

    【解决方案3】:

    您可以尝试使用WITH 子句。

    WITH vars AS (SELECT 42 AS answer, 3.14 AS appr_pi)
    SELECT t.*, vars.answer, t.radius*vars.appr_pi
    FROM table AS t, vars;
    

    【讨论】:

    • 当您在查询中多次使用相同的计算值时,这种方式最方便。
    • 与布莱斯的报告相反,它似乎对我来说很好用。 CREATE TABLE test (name VARCHAR, age INT);INSERT INTO test (name, age) VALUES ('Jack', 21), ('Jill', 20);WITH vars AS (SELECT N'Jack' AS name, 21 AS age) SELECT test.* FROM test, vars WHERE test.name = vars.name and test.age = vars.age;输出杰克和杰克的年龄,符合预期。
    • 对于很多用途,尤其是在 Python Flask 等 Web 应用程序框架的上下文中,这是在单个查询中重用复杂计算值的最佳解决方案。
    • 谁能建议这在插入时如何工作?
    • @Stoopkid create table t(x integer); insert into t(x) with sub as (select 999 as num) select num from sub; select * from t;
    【解决方案4】:

    对于psql,你也可以从命令行传递psql变量;您可以使用-v 传递它们。这是一个使用示例:

    $ psql -v filepath=/path/to/my/directory/mydatafile.data regress
    regress=> SELECT :'filepath';
                   ?column?                
    ---------------------------------------
     /path/to/my/directory/mydatafile.data
    (1 row)
    

    请注意,冒号是不加引号的,然后是其自身的变量名。奇怪的语法,我知道。这仅适用于 psql;它在(比如)PgAdmin-III 中不起作用。

    这种替换发生在 psql 中的输入处理期间,因此您不能(比如说)定义一个使用 :'filepath' 的函数并期望 :'filepath' 的值在会话之间发生变化。它会在定义函数时被替换一次,然后将是一个常量。它对脚本很有用,但不适用于运行时。

    【讨论】:

    • psql 变量,例如:'filepath',您指出:“请注意,冒号不被引用,然后变量名其自身被引用。”谢谢!你!我已经在我的桌子上放了一堆额头形状的凹痕来尝试完成这项工作,而你又为我节省了很多。正是我编写脚本所需要的。
    【解决方案5】:

    FWIW,真正的问题是我在 \set 命令的末尾包含了一个分号:

    \set owner_password '密码';

    分号被解释为变量中的实际字符:

    \echo :owner_password 密码;

    所以当我尝试使用它时:

    CREATE ROLE myrole LOGIN UNENCRYPTED PASSWORD :owner_password NOINHERIT CREATEDB CREATEROLE VALID UNTIL 'infinity';

    ...我明白了:

    CREATE ROLE myrole LOGIN UNENCRYPTED PASSWORD thepassword; NOINHERIT CREATEDB CREATEROLE VALID UNTIL 'infinity';

    这不仅没有在文字周围设置引号,而且将命令分成两部分(其中第二部分无效,因为它以“NOINHERIT”开头)。

    这个故事的寓意:PostgreSQL“变量”实际上是用于文本扩展的宏,而不是真正的值。我敢肯定这会派上用场,但一开始会很棘手。

    【讨论】:

      【解决方案6】:

      您需要使用其中一种过程语言,例如 PL/pgSQL,而不是 SQL 过程语言。 在 PL/pgSQL 中,您可以在 SQL 语句中直接使用 vars。 对于单引号,您可以使用引号文字功能。

      【讨论】:

      【解决方案7】:

      postgres(自 9.0 版起)允许使用任何受支持的服务器端脚本语言进行匿名块

      DO '
      DECLARE somevariable int = -1;
      BEGIN
      INSERT INTO foo VALUES ( somevariable );
      END
      ' ;
      

      http://www.postgresql.org/docs/current/static/sql-do.html

      由于所有内容都在字符串中,因此被替换的外部字符串变量需要转义并引用两次。改用美元报价并不能完全防止 SQL 注入。

      【讨论】:

        【解决方案8】:

        另一种方法是(ab)使用 PostgreSQL GUC 机制来创建变量。有关详细信息和示例,请参阅this prior answer

        您在 postgresql.conf 中声明 GUC,然后在运行时使用 SET 命令更改其值并使用 current_setting(...) 获取其值。

        我不建议将其用于一般用途,但它可能在链接问题中提到的狭窄情况下很有用,在这种情况下,发帖人想要一种方法来为触发器和函数提供应用程序级用户名。

        【讨论】:

          【解决方案9】:

          我用临时表解决了这个问题。

          CREATE TEMP TABLE temp_session_variables (
              "sessionSalt" TEXT
          );
          INSERT INTO temp_session_variables ("sessionSalt") VALUES (current_timestamp || RANDOM()::TEXT);
          

          这样,我就有了一个“变量”,我可以在多个查询中使用,这对于会话来说是唯一的。我需要它来生成唯一的“用户名”,同时在导入具有相同用户名的用户时仍然不会发生冲突。

          【讨论】:

          • 这似乎是 Heidi SQL 等可视化工具中唯一的工作方式。
          【解决方案10】:

          我发现这个问题和答案非常有用,但也令人困惑。我在让引用的变量工作时遇到了很多麻烦,所以这是我让它工作的方式:

          \set deployment_user username    -- username
          \set deployment_pass '\'string_password\''
          ALTER USER :deployment_user WITH PASSWORD :deployment_pass;
          

          这样您可以在一个语句中定义变量。使用时,单引号会嵌入到变量中。

          注意!当我在引用的变量之后发表评论时,当我尝试其他答案中的一些方法时,它作为变量的一部分被吸入。这真的让我搞砸了一段时间。使用这种方法,cmets 似乎会按照您的预期进行处理。

          【讨论】:

          • \set deployment_pass 'string_password' ALTER USER :deployment_user WITH PASSWORD :'deployment_pass';
          • \set 不是 SQL,它是 psql 内置命令 不支持 sql cmets。
          【解决方案11】:

          我真的很怀念这个功能。实现类似功能的唯一方法是使用函数。

          我用了两种方式:

          • 使用 $_SHARED 变量的 perl 函数
          • 将变量存储在表中

          Perl 版本:

             CREATE FUNCTION var(name text, val text) RETURNS void AS $$
                  $_SHARED{$_[0]} = $_[1];
             $$ LANGUAGE plperl;
             CREATE FUNCTION var(name text) RETURNS text AS $$
                  return $_SHARED{$_[0]};
             $$ LANGUAGE plperl;
          

          表格版本:

          CREATE TABLE var (
            sess bigint NOT NULL,
            key varchar NOT NULL,
            val varchar,
            CONSTRAINT var_pkey PRIMARY KEY (sess, key)
          );
          CREATE FUNCTION var(key varchar, val anyelement) RETURNS void AS $$
            DELETE FROM var WHERE sess = pg_backend_pid() AND key = $1;
            INSERT INTO var (sess, key, val) VALUES (sessid(), $1, $2::varchar);
          $$ LANGUAGE 'sql';
          
          CREATE FUNCTION var(varname varchar) RETURNS varchar AS $$
            SELECT val FROM var WHERE sess = pg_backend_pid() AND key = $1;
          $$ LANGUAGE 'sql';
          

          注意事项:

          • plperlu 比 perl 快
          • pg_backend_pid 不是最好的会话识别,考虑使用 pid 与 pg_stat_activity 中的 backend_start 结合
          • 这个表版本也很糟糕,因为您必须偶尔清除它(而不是删除当前工作的会话变量)

          【讨论】:

            【解决方案12】:

            psql 中的变量很糟糕。如果你想声明一个整数,你必须输入整数,然后回车,然后用分号结束语句。观察:

            假设我要声明一个整数变量my_var 并将其插入到表中test

            示例表test

            thedatabase=# \d test;
                                     Table "public.test"
             Column |  Type   |                     Modifiers                     
            --------+---------+---------------------------------------------------
             id     | integer | not null default nextval('test_id_seq'::regclass)
            Indexes:
                "test_pkey" PRIMARY KEY, btree (id)
            

            显然,此表中还没有任何内容:

            thedatabase=# select * from test;
             id 
            ----
            (0 rows)
            

            我们声明一个变量。 注意下一行的分号!

            thedatabase=# \set my_var 999
            thedatabase=# ;
            

            现在我们可以插入了。我们必须使用这种看起来很奇怪的“:''”语法:

            thedatabase=# insert into test(id) values (:'my_var');
            INSERT 0 1
            

            成功了!

            thedatabase=# select * from test;
             id  
            -----
             999
            (1 row)
            

            说明:

            那么...如果下一行没有分号会怎样?变量?看看:

            我们声明my_var 没有新行。

            thedatabase=# \set my_var 999;
            

            让我们选择my_var

            thedatabase=# select :'my_var';
             ?column? 
            ----------
             999;
            (1 row)
            

            那是什么鬼?这不是一个整数,它是一个字符串 999;!

            thedatabase=# select 999;
             ?column? 
            ----------
                  999
            (1 row)
            

            【讨论】:

            • 分号给你带来意想不到的结果的原因是分号终止了一条 SQL 语句,但是你输入了一个 psql 命令 \set,它不是 SQL 并且不使用终止分号.在下一行放一个分号不会有什么坏处,但绝对没有任何作用。这是一个空语句。
            • 另外,您不应该对整数值使用:'my_var' 语法。 :my_var 工作得很好。
            【解决方案13】:

            我已经为此on another thread 发布了一个新的解决方案。

            它使用表格来存储变量,并且可以随时更新。静态不可变 getter 函数是动态创建的(由另一个函数),由表的更新触发。您可以获得出色的表存储空间,以及不可变 getter 的极快速度。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-09-14
              • 2013-11-19
              • 1970-01-01
              • 2017-11-09
              • 2018-07-09
              • 2021-08-07
              相关资源
              最近更新 更多