Como você usa variáveis ​​de script no psql?

133

No MS SQL Server, crio meus scripts para usar variáveis ​​personalizáveis:

DECLARE @somevariable int  
SELECT @somevariable = -1

INSERT INTO foo VALUES ( @somevariable )

Depois, alterarei o valor de @somevariableem tempo de execução, dependendo do valor que desejo na situação específica. Como está no topo do script, é fácil ver e lembrar.

Como faço o mesmo com o cliente PostgreSQL psql?

Craig Walker
fonte
5
FWIW, o operador \ set parece estar relacionado à ferramenta de linha de comando psql, não à linguagem de lote pgsql. Eu poderia estar errado.
Daniel Yankowsky
1
Em qual versão do Postgres você está?
Kuberchaun 11/11

Respostas:

180

As variáveis ​​do Postgres são criadas através do comando \ set, por exemplo ...

\set myvariable value

... e pode ser substituído, por exemplo, como ...

SELECT * FROM :myvariable.table1;

... ou ...

SELECT * FROM table1 WHERE :myvariable IS NULL;

edit: A partir do psql 9.1, as variáveis ​​podem ser expandidas entre aspas, como em:

\set myvariable value 

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

Nas versões mais antigas do cliente psql:

... Se você deseja usar a variável como o valor em uma consulta de sequência condicional, como ...

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

... então você precisa incluir as aspas na própria variável, pois as opções acima não funcionarão. Em vez disso, defina sua variável como tal ...

\set myvariable 'value'

No entanto, se, como eu, você se deparou com uma situação em que queria criar uma string a partir de uma variável existente, achei o truque a ser esse ...

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

Agora você tem uma variável entre aspas e não entre aspas da mesma string! E você pode fazer algo assim ....

INSERT INTO :myvariable.table1 SELECT * FROM table2 WHERE column1 = :quoted_myvariable;
crowmagnumb
fonte
66
\seté apenas para psqlferramenta, você não pode usá-lo em procedimentos armazenados!
11552
6
@SorinSbarnea, o OP perguntou sobre script , não sobre procedimento #
Daniel Serodio
35
Esta resposta mistura psqlmeta-comandos \setcom os comandos do PostgreSQL de maneira confusa.
Erwin Brandstetter
20
A partir do postgresql 9.1, no psql agora você pode usar: 'variable' para que seja citado corretamente como um valor para você, ou: "variable" para usá-lo como um identificador.
hitscan
9
\set myvariable 'value'se não incluir qualquer citação dentro da variável, ao contrário do que esta resposta diz. Em caso de dúvida, use \echo :myvariabledentro do psql para exibir o valor independentemente de qualquer consulta.
Daniel Vérité
61

Uma palavra final sobre as variáveis ​​PSQL:

  1. Eles não se expandem se você os colocar entre aspas simples na instrução SQL. Portanto, isso não funciona:

    SELECT * FROM foo WHERE bar = ':myvariable'
  2. Para expandir para uma cadeia literal em uma instrução SQL, você deve incluir as aspas no conjunto de variáveis. No entanto, o valor da variável já deve estar entre aspas, o que significa que você precisa de um segundo conjunto de aspas e o conjunto interno precisa ser escapado. Assim você precisa:

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

    EDIT : começando com o PostgreSQL 9.1, você pode escrever:

    \set myvariable somestring
    SELECT * FROM foo WHERE bar = :'myvariable'
Craig Walker
fonte
12
Cheers for:'myvariable'
Andomar
Exatamente o que eu estava procurando!
precisa saber é o seguinte
47

Você pode tentar usar uma cláusula 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;
skaurus
fonte
Dessa forma, é mais conveniente quando você usa os mesmos valores computados algumas vezes na sua consulta.
Skaurus # 8/13
2
Ao contrário do relatório de Bryce, parece funcionar bem para mim. 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; Ouputs Jack e a idade de Jack, como esperado.
Joshua
2
Para muitos usos, especialmente no contexto de uma estrutura de aplicativos da Web como o Python Flask, esta é a melhor solução para reutilizar valores calculados complexos em uma única consulta.
Will
1
Alguém pode sugerir como isso pode funcionar em uma inserção?
Stoopkid
@Stoopkidcreate table t(x integer); insert into t(x) with sub as (select 999 as num) select num from sub; select * from t;
JL_SO 4/19
33

Especificamente para psql, você também pode passar psqlvariáveis ​​da linha de comando; você pode passá-los com -v. Aqui está um exemplo de uso:

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

Observe que os dois pontos não estão entre aspas, então o nome da variável é auto. Sintaxe estranha, eu sei. Isso funciona apenas no psql; não funcionará (digamos) no PgAdmin-III.

Essa substituição ocorre durante o processamento de entrada no psql, portanto você não pode (digamos) definir uma função que use :'filepath'e espere que o valor :'filepath'mude de sessão para sessão. Será substituído uma vez, quando a função estiver definida, e depois será uma constante depois disso. É útil para scripts, mas não para uso em tempo de execução.

Craig Ringer
fonte
variáveis ​​psql, por exemplo: 'filepath', você apontou: "Observe que os dois pontos não estão entre aspas, então o nome da variável em si é citado." Obrigado! Vocês! Eu já coloquei um monte de dentes em forma de testa em minha mesa tentando fazer esse trabalho, e você me salvou muito mais. Exatamente o que eu precisava para alguns scripts.
Jason
13

FWIW, o verdadeiro problema era que eu incluí um ponto e vírgula no final do meu comando \ set:

\ set owner_password 'thepassword';

O ponto e vírgula foi interpretado como um caractere real na variável:

\ echo: owner_password thepassword;

Então, quando eu tentei usá-lo:

CREATE ROLE myrole LOGIN SENHA NÃO CRIPTOGRAFADA: owner_password NOINHERIT CREATEDB CREATEROLE VÁLIDO ATÉ 'infinito';

...Eu tenho esse:

CREATE ROLE myrole LOGIN SENHA NÃO CRIPTOGRAFIA thepassword; NOINHERIT CRIADOB CREATEROLE VÁLIDO ATÉ 'infinito';

Isso não apenas falhou ao definir as aspas ao redor do literal, mas dividiu o comando em duas partes (a segunda inválida ao iniciar com "NOINHERIT").

A moral desta história: "variáveis" do PostgreSQL são realmente macros usadas na expansão do texto, não valores verdadeiros. Tenho certeza de que isso é útil, mas é complicado no começo.

Craig Walker
fonte
12

Você precisa usar uma das linguagens procedurais, como PL / pgSQL, e não a linguagem proc SQL. No PL / pgSQL, você pode usar vars diretamente nas instruções SQL. Para aspas simples, você pode usar a função literal de aspas.


fonte
5
Não pode ser feito no próprio postgres, mas no aplicativo cliente PSQL.
Philluminati 27/07
1
(agora) o plpgsql pode ser usado no postgres (desde a versão 9.0) postgresql.org/docs/9.0/static/sql-do.html
Jasen
11

O postgres (desde a versão 9.0) permite blocos anônimos em qualquer uma das linguagens de script do lado do servidor suportadas

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

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

Como tudo está dentro de uma sequência, as variáveis ​​externas que estão sendo substituídas precisarão ser escapadas e citadas duas vezes. O uso de cotações em dólares não oferece proteção total contra a injeção de SQL.

Jasen
fonte
5

Outra abordagem é (ab) usar o mecanismo PostgreSQL GUC para criar variáveis. Veja esta resposta anterior para obter detalhes e exemplos.

Você declara o GUC postgresql.conf, depois altera seu valor em tempo de execução com SETcomandos e obtém seu valor comcurrent_setting(...) .

Eu não recomendo isso para uso geral, mas poderia ser útil em casos estreitos como o mencionado na pergunta vinculada, onde o pôster desejava uma maneira de fornecer o nome de usuário no nível do aplicativo para gatilhos e funções.

Craig Ringer
fonte
4

Eu o resolvi com uma tabela temporária.

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

Dessa forma, eu tinha uma "variável" que eu poderia usar em várias consultas, única para a sessão. Eu precisava que ele gerasse "nomes de usuários" exclusivos, embora ainda não tivesse colisões se importasse usuários com o mesmo nome de usuário.

geon
fonte
Essa parece ser a única maneira de trabalhar em ferramentas visuais como o Heidi SQL.
Altair7852
2

Eu achei essa pergunta e as respostas extremamente úteis, mas também confusas. Eu tive muitos problemas para que as variáveis ​​citadas funcionassem, então aqui está a maneira como eu consegui que funcionasse:

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

Dessa forma, você pode definir a variável em uma instrução. Quando você o usa, aspas simples serão incorporadas à variável.

NOTA! Quando coloquei um comentário após a variável citada, ela foi sugada como parte da variável quando tentei alguns dos métodos em outras respostas. Isso realmente estava me ferrando por um tempo. Com esse método, os comentários parecem ser tratados como seria de esperar.

Nate
fonte
\set deployment_pass 'string_password' ALTER USER :deployment_user WITH PASSWORD :'deployment_pass';
Jasen 10/10
\ set não é SQL, é um comando interno psql. Comentários SQL não são suportados.
Jasen 10/10
2

Eu realmente sinto falta desse recurso. A única maneira de conseguir algo semelhante é usar funções.

Eu usei de duas maneiras:

  • funções perl que usam a variável $ _SHARED
  • armazene suas variáveis ​​na tabela

Versão 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;

Versão da tabela:

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';

Notas:

  • plperlu é mais rápido que perl
  • pg_backend_pid não é a melhor identificação de sessão, considere o uso de pid combinado com backend_start em pg_stat_activity
  • esta versão da tabela também é ruim porque é necessário limpar ocasionalmente (e não excluir as variáveis ​​da sessão atualmente em funcionamento)
Kaiko Kaur
fonte
1

Variáveis ​​em psqlchupar. Se você deseja declarar um número inteiro, é necessário inserir o número inteiro, fazer um retorno de carro e encerrar a instrução em ponto e vírgula. Observar:

Digamos que eu queira declarar uma variável inteira my_vare inseri-la em uma tabela test:

Tabela de exemplo 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)

Claramente, nada nesta tabela ainda:

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

Declaramos uma variável. Observe como o ponto e vírgula está na próxima linha!

thedatabase=# \set my_var 999
thedatabase=# ;

Agora podemos inserir. Temos que usar esta :''sintaxe " " estranha :

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

Funcionou!

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

Explicação:

Então ... o que acontece se não tivermos o ponto e vírgula na próxima linha? A variável? Dar uma olhada:

Declaramos my_varsem a nova linha.

thedatabase=# \set my_var 999;

Vamos selecionar my_var.

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

WTF é isso? Não é um número inteiro , é uma string 999; !

thedatabase=# select 999;
 ?column? 
----------
      999
(1 row)
Nome em Exibição
fonte
5
A razão pela qual o ponto e vírgula faz coisas inesperadas para você é que um ponto e vírgula finaliza uma instrução SQL, mas você está digitando um comando psql, \ set, que não é SQL e NÃO leva um ponto e vírgula final. Colocar um ponto e vírgula na próxima linha não vai doer, mas não faz absolutamente nada. É uma afirmação vazia.
volkerk
1

Publiquei uma nova solução para isso em outro tópico .

Ele usa uma tabela para armazenar variáveis ​​e pode ser atualizado a qualquer momento. Uma função getter imutável estática é criada dinamicamente (por outra função), acionada pela atualização em sua tabela. Você obtém um bom armazenamento de mesa, além das velocidades incrivelmente rápidas de um dispositivo imutável.

Brev
fonte