¿Cómo se usan las variables de script en psql?

133

En MS SQL Server, creo mis scripts para usar variables personalizables:

DECLARE @somevariable int  
SELECT @somevariable = -1

INSERT INTO foo VALUES ( @somevariable )

Luego cambiaré el valor de @somevariableen tiempo de ejecución, dependiendo del valor que desee en la situación particular. Como está en la parte superior del guión, es fácil de ver y recordar.

¿Cómo hago lo mismo con el cliente PostgreSQL psql?

Craig Walker
fuente
55
FWIW, el operador \ set parece estar relacionado con la herramienta de línea de comandos psql, no con el lenguaje por lotes pgsql. Podría estar equivocado.
Daniel Yankowsky
1
¿En qué versión de Postgres estás?
Kuberchaun

Respuestas:

180

Las variables de Postgres se crean mediante el comando \ set, por ejemplo ...

\set myvariable value

... y luego puede ser sustituido, por ejemplo, como ...

SELECT * FROM :myvariable.table1;

... o ...

SELECT * FROM table1 WHERE :myvariable IS NULL;

editar: a partir de psql 9.1, las variables se pueden expandir entre comillas como en:

\set myvariable value 

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

En versiones anteriores del cliente psql:

... Si desea utilizar la variable como valor en una consulta de cadena condicional, como ...

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

... entonces debe incluir las comillas en la variable misma, ya que lo anterior no funcionará. En su lugar, defina su variable como tal ...

\set myvariable 'value'

Sin embargo, si, como yo, te topaste con una situación en la que querías hacer una cadena a partir de una variable existente, encontré que el truco es este ...

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

¡Ahora tiene una variable entre comillas y sin comillas de la misma cadena! Y puedes hacer algo como esto ...

INSERT INTO :myvariable.table1 SELECT * FROM table2 WHERE column1 = :quoted_myvariable;
Crowmagnumb
fuente
66
\setes solo para psqlherramientas, ¡no puede usarlo en procedimientos almacenados!
sorin
66
@SorinSbarnea, el OP preguntó sobre el guión , no sobre el procedimiento
Daniel Serodio
35
Esta respuesta mezcla psqlmetacomandos con comandos \setde PostgreSQL de manera confusa.
Erwin Brandstetter
20
A partir de postgresql 9.1, en psql ahora puede usar: 'variable' para que se cite correctamente como un valor para usted, o: "variable" para usarlo como identificador.
HitScan
9
\set myvariable 'value'no no incluir ninguna cotización dentro de la variable, al contrario de lo que dice esta respuesta. En caso de duda, use \echo :myvariabledentro de psql para mostrar el valor independientemente de cualquier consulta.
Daniel Vérité
61

Una última palabra sobre las variables PSQL:

  1. No se expanden si los encierra entre comillas simples en la instrucción SQL. Por lo tanto, esto no funciona:

    SELECT * FROM foo WHERE bar = ':myvariable'
  2. Para expandirse a un literal de cadena en una instrucción SQL, debe incluir las comillas en el conjunto de variables. Sin embargo, el valor de la variable ya debe estar entre comillas, lo que significa que necesita un segundo conjunto de comillas, y el conjunto interno debe escaparse. Por lo tanto necesitas:

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

    EDITAR : comenzando con PostgreSQL 9.1, puede escribir en su lugar:

    \set myvariable somestring
    SELECT * FROM foo WHERE bar = :'myvariable'
Craig Walker
fuente
12
Saludos para:'myvariable'
Andomar
¡Exactamente lo que estaba buscando!
KeniSteward
47

Puede intentar usar una 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
fuente
De esta manera, es más conveniente cuando usa los mismos valores calculados varias veces en su consulta.
skaurus
2
Contrariamente al informe de Bryce, parece funcionar bien para mí. 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 y la edad de Jack, como se esperaba.
Joshua
2
Para muchos usos, especialmente en el contexto de un marco de aplicación web como Python Flask, esta es la mejor solución para reutilizar valores calculados complejos dentro de una sola consulta.
Will
1
¿Alguien puede sugerir cómo podría funcionar esto en un inserto?
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
33

Específicamente para psql, también puede pasar psqlvariables desde la línea de comando; se pueden pasar con -v. Aquí hay un ejemplo de uso:

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

Tenga en cuenta que los dos puntos no están entre comillas, luego se cita el nombre de la variable. Sintaxis extraña, lo sé. Esto solo funciona en psql; no funcionará en (digamos) PgAdmin-III.

Esta sustitución ocurre durante el procesamiento de entrada en psql, por lo que no puede (por ejemplo) definir una función que use :'filepath'y espere que el valor de :'filepath'cambie de sesión a sesión. Será sustituido una vez, cuando se defina la función, y luego será una constante después de eso. Es útil para los scripts pero no para el tiempo de ejecución.

Craig Ringer
fuente
Las variables psql, por ejemplo: 'filepath', usted señaló: "Tenga en cuenta que los dos puntos no están entre comillas, entonces el nombre de la variable en sí mismo está entre comillas". ¡Gracias! ¡Tú! Ya puse un montón de abolladuras en forma de frente en mi escritorio tratando de hacer que esto funcione, y me salvaste un montón más. Exactamente lo que necesitaba para algunas secuencias de comandos.
Jason
13

FWIW, el verdadero problema era que había incluido un punto y coma al final de mi comando \ set:

\ set owner_password 'la contraseña';

El punto y coma se interpretó como un carácter real en la variable:

\ echo: contraseña_propietario contraseña;

Entonces cuando traté de usarlo:

CREAR PAPEL myrole INICIAR CONTRASEÑA SIN ENCRIPTAR: propietario_contraseña NOINHERIT CREATEDB CREATEROLE VÁLIDO HASTA 'infinito';

...Tengo esto:

CREAR PAPEL myrole INICIAR CONTRASEÑA SIN ENCRIPTAR la contraseña; NOINHERIT CREATEDB CREATEROLE VÁLIDO HASTA 'infinito';

Eso no solo no pudo establecer las comillas alrededor del literal, sino que dividió el comando en 2 partes (la segunda de las cuales no era válida ya que comenzó con "NOINHERIT").

La moraleja de esta historia: las "variables" de PostgreSQL son realmente macros utilizadas en la expansión de texto, no valores verdaderos. Estoy seguro de que es útil, pero al principio es complicado.

Craig Walker
fuente
12

Debe utilizar uno de los lenguajes de procedimiento, como PL / pgSQL, no el lenguaje de proceso SQL. En PL / pgSQL puede usar vars directamente en las declaraciones SQL. Para comillas simples puede usar la función literal de comillas.


fuente
55
No se puede hacer en postgres, pero se puede hacer en la aplicación cliente de PSQL.
Philluminati
1
plpgsql puede (ahora) usarse en postgres (desde la versión 9.0)) postgresql.org/docs/9.0/static/sql-do.html
Jasen
11

postgres (desde la versión 9.0) permite bloques anónimos en cualquiera de los lenguajes de script del lado del servidor compatibles

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

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

Como todo está dentro de una cadena, las variables de cadena externas que se sustituyan deberán escaparse y citarse dos veces. El uso de las cotizaciones en dólares en su lugar no dará una protección total contra la inyección de SQL.

Jasen
fuente
5

Otro enfoque es (ab) usar el mecanismo PostgreSQL GUC para crear variables. Vea esta respuesta previa para detalles y ejemplos.

Declara el GUC en postgresql.conf, luego cambia su valor en tiempo de ejecución con SETcomandos y obtiene su valor con current_setting(...).

No lo recomiendo para uso general, pero podría ser útil en casos limitados como el mencionado en la pregunta vinculada, donde el póster quería una forma de proporcionar el nombre de usuario a nivel de aplicación a los desencadenantes y funciones.

Craig Ringer
fuente
4

Lo resolví con una tabla temporal.

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

De esta manera, tenía una "variable" que podía usar en múltiples consultas, que es única para la sesión. Lo necesitaba para generar "nombres de usuario" únicos sin tener colisiones si importaba usuarios con el mismo nombre de usuario.

geon
fuente
Esta parece ser la única forma de trabajo en herramientas visuales como Heidi SQL.
Altair7852
2

He encontrado esta pregunta y las respuestas extremadamente útiles, pero también confusas. Tuve muchos problemas para que las variables citadas funcionen, así que esta es la forma en que lo hice funcionar:

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

De esta manera puede definir la variable en una declaración. Cuando lo use, se incluirán comillas simples en la variable.

¡NOTA! Cuando puse un comentario después de la variable citada, se absorbió como parte de la variable cuando probé algunos de los métodos en otras respuestas. Eso realmente me estaba jodiendo por un tiempo. Con este método, los comentarios parecen ser tratados como es de esperar.

Nate
fuente
\set deployment_pass 'string_password' ALTER USER :deployment_user WITH PASSWORD :'deployment_pass';
Jasen
\ set no es SQL, es un comando incorporado de psql. No se admiten comentarios sql.
Jasen
2

Realmente extraño esa característica. La única forma de lograr algo similar es usar funciones.

Lo he usado de dos maneras:

  • funciones perl que usan la variable $ _SHARED
  • almacena tus variables en la tabla

Versión 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;

Versión de mesa:

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 es más rápido que perl
  • pg_backend_pid no es la mejor identificación de sesión, considere usar pid combinado con backend_start de pg_stat_activity
  • esta versión de la tabla también es mala porque tienes que borrar esto ocasionalmente (y no eliminar las variables de sesión que funcionan actualmente)
Kaiko Kaur
fuente
1

Variables en psqlchupar. Si desea declarar un número entero, debe ingresar el número entero, luego hacer un retorno de carro, luego finalizar la declaración en punto y coma. Observar:

Digamos que quiero declarar una variable entera my_vare insertarla en una tabla test:

Tabla de ejemplo 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 en esta tabla todavía:

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

Declaramos una variable. ¡Observe cómo está el punto y coma en la siguiente línea!

thedatabase=# \set my_var 999
thedatabase=# ;

Ahora podemos insertar. Tenemos que usar esta extraña " :''" sintaxis de aspecto:

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

¡Funcionó!

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

Explicación:

Entonces ... ¿qué pasa si no tenemos el punto y coma en la siguiente línea? ¿La variable? Echar un vistazo:

Declaramos my_varsin la nueva línea.

thedatabase=# \set my_var 999;

Vamos a seleccionar my_var.

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

¿Que demonios es eso? ¡No es un entero , es una cadena 999; !

thedatabase=# select 999;
 ?column? 
----------
      999
(1 row)
Nombre para mostrar
fuente
55
La razón por la que el punto y coma hace cosas inesperadas por usted es que un punto y coma termina una instrucción SQL, pero está escribiendo un comando psql, \ set, que no es SQL y NO toma un punto y coma de terminación. Poner un punto y coma en la siguiente línea no hará daño, pero no hace absolutamente nada. Es una declaración vacía.
volkerk
1

He publicado una nueva solución para esto en otro hilo .

Utiliza una tabla para almacenar variables y se puede actualizar en cualquier momento. Una función getter estática inmutable se crea dinámicamente (por otra función), activada por la actualización de su tabla. Obtiene un buen almacenamiento de la mesa, además de las velocidades ultrarrápidas de un captador inmutable.

Brev
fuente