Cómo crear una tabla temporal usando VALUES en PostgreSQL

38

Estoy aprendiendo PostgreSQL y tratando de descubrir cómo crear una tabla temporal o una WITHdeclaración que pueda usarse en lugar de una tabla normal, con fines de depuración.

Miré la documentación de CREATE TABLE y dice que VALUESse puede usar como una consulta, pero no da ningún ejemplo; ¿la documentación para la VALUEScláusula vinculada allí tampoco tiene un ejemplo?

Entonces, escribí una prueba simple de la siguiente manera:

DROP TABLE IF EXISTS lookup;
CREATE TEMP TABLE lookup (
  key integer,
  val numeric
) AS
VALUES (0,-99999), (1,100);

Pero PostgreSQL (9.3) se queja de

error de sintaxis en o cerca de "AS"

Mis preguntas son:

  1. ¿Cómo puedo arreglar la declaración anterior?

  2. ¿Cómo puedo adaptarlo para usarlo en a WITH block?

Gracias por adelantado.

tinlyx
fuente
Traté de responder esta pregunta con algunos consejos más modernos (ya que la respuesta elegida es usar una sintaxis no estandarizada obsoleta
Evan Carroll

Respuestas:

46

EDITAR: Dejo la respuesta original aceptada tal como está, pero tenga en cuenta que la edición a continuación, como lo sugiere a_horse_with_no_name, es el método preferido para crear una tabla temporal usando VALUES.

Si solo desea seleccionar algunos valores, en lugar de simplemente crear una tabla e insertarla, puede hacer algo como:

WITH  vals (k,v) AS (VALUES (0,-9999), (1, 100)) 
SELECT * FROM vals;

Para crear una tabla temporal de manera similar, use:

WITH  vals (k,v) AS (VALUES (0,-9999), (1, 100)) 
SELECT * INTO temporary table temp_table FROM vals;

EDITAR: Como lo señala a_horse_with_no_name, en los documentos dice que CREATE TABLE AS...es funcionalmente similar a SELECT INTO ..., pero que el primero es un superconjunto del último y que SELECT INTOse usa en plpgslq para asignar un valor a una variable temporal, por lo que fallará en Ese caso. Por lo tanto, si bien los ejemplos anteriores son válidos para SQL simple, se CREATE TABLEdebe preferir la forma.

CREATE TEMP TABLE temp_table AS                                     
WITH t (k, v) AS (
 VALUES
 (0::int,-99999::numeric), 
 (1::int,100::numeric)
)
SELECT * FROM t;

Tenga en cuenta, también de los comentarios de a_horse_with_no_name, y en la pregunta original del OP, esto incluye una conversión a los tipos de datos correctos dentro de la lista de valores y utiliza una instrucción CTE (WITH).

Además, como se señala en la respuesta de Evan Carrol, una consulta CTE es una valla de optimización , es decir, el CTE siempre se materializa. Hay muchas buenas razones para usar CTE, pero puede haber un impacto en el rendimiento bastante significativo, si no se usa con cuidado. Sin embargo, hay muchos casos en los que la cerca de optimización puede mejorar el rendimiento, por lo que es algo a tener en cuenta, no evitar a ciegas.

John Powell
fuente
12
de los documentos : " CREATE TABLE AS es funcionalmente similar a SELECT INTO. CREATE TABLE AS es la sintaxis recomendada "
a_horse_with_no_name
La valla de optimización no es necesariamente algo malo. He visto muchas declaraciones que podría sintonizar para correr masivamente más rápido debido a eso.
a_horse_with_no_name
Claro, lo he aclarado también. Yo uso CTE todo el tiempo en un contexto espacial. Si tiene una cláusula where con algo así WHERE ST_Intersects(geom, (SELECT geom FROM sometable)o WHERE ST_Intersects(geom, ST_Buffer(anothergeom, 10), a menudo, el planificador de consultas no usa el índice espacial porque la columna geom ya no se puede modificar. Si crea su área de interés en un CTE inicial, este problema desaparece. También es muy conveniente, si desea utilizar el mismo aoi en varias expresiones adicionales en la misma consulta, lo que no es raro en un contexto SIG.
John Powell
25

create table as necesita una declaración select:

DROP TABLE IF EXISTS lookup;
CREATE TEMP TABLE lookup 
as 
select *
from (
   VALUES 
    (0::int,-99999::numeric), 
    (1::int, 100::numeric)
) as t (key, value);

También puede volver a escribir esto para usar un CTE:

create temp table lookup 
as 
with t (key, value) as (
  values 
    (0::int,-99999::numeric), 
    (1::int,100::numeric)
)
select * from t;
un caballo sin nombre
fuente
1
Gracias por tu comentario. Su enfoque es obviamente mejor por las razones indicadas en los documentos. He editado mi respuesta, aunque casi 5 años tarde.
John Powell
11

El problema son los tipos de datos. Si los elimina, la declaración funcionará:

CREATE TEMP TABLE lookup
  (key, val) AS
VALUES 
  (0, -99999), 
  (1, 100) ;

Puede definir los tipos mediante la conversión de los valores de la primera fila:

CREATE TEMP TABLE lookup 
  (key, val) AS
VALUES 
  (0::bigint, -99999::int), 
  (1, 100) ;
ypercubeᵀᴹ
fuente
3

Realmente no necesita crear una tabla ni usar un CTE, si todo lo que necesita es usar algunos valores en sus consultas. Puedes incluirlos en línea:

SELECT  *
FROM    (VALUES(0::INT, -99999::NUMERIC), (1, 100)) AS lookup(key, val)

Luego puede obtener un producto cartesiano con un CROSS JOIN(donde la otra relación puede ser, por supuesto, una tabla normal, vista, etc.). p.ej:

SELECT  *
FROM    (VALUES(0::int, -99999::numeric), (1, 100)) AS lookup(key, val)
       ,(VALUES('Red'), ('White'), ('Blue')) AS colors(color);

cuyos rendimientos:

key |val    |color |
----|-------|------|
0   |-99999 |Red   |
1   |100    |Red   |
0   |-99999 |White |
1   |100    |White |
0   |-99999 |Blue  |
1   |100    |Blue  |

O JOINlos valores con otra relación (que nuevamente puede ser una tabla, vista, etc.), por ejemplo:

SELECT  *
FROM    (VALUES(0::int, -99999::numeric), (1, 100)) AS lookup(key, val)
  JOIN  (VALUES('Red', 1), ('White', 0), ('Blue', 1)) AS colors(color, lookup_key)
          ON colors.lookup_key = lookup.key;

cuyos rendimientos:

key |val    |color |lookup_key |
----|-------|------|-----------|
1   |100    |Red   |1          |
0   |-99999 |White |0          |
1   |100    |Blue  |1          |
isapir
fuente
Bien, pero la pregunta era "¿cómo crear una tabla temporal con ...?"
ypercubeᵀᴹ
Sí, pero ¿por qué necesitaría una tabla temporal con algunos valores de búsqueda fijos si no fuera por unirla en otra relación? Esta solución resuelve el problema en sí mismo, independientemente de cómo esté redactada la pregunta.
isapir
1
¿Quizás OP simplemente redujo el ejemplo a algo que sería fácil publicar como una pregunta, pero los datos reales tienen miles de valores?
stannius
El OP declaró específicamente el uso de valores, por lo que mi respuesta aún se aplica, ya que eso es exactamente lo que hace
isapir
2

Primero siempre use el estandarizado CREATE TABLE AS, SELECT INTOcomo se sugiere en otras respuestas, ha sido una sintaxis obsoleta durante más de una década. Puedes usarCREATE TABLE AS con un CTE

Si bien muchas respuestas aquí sugieren usar un CTE, eso no es preferible. De hecho, es probable que sea algo más lento. Solo envuélvelo como una mesa.

DROP TABLE IF EXISTS lookup;

CREATE TEMP TABLE lookup(key, value) AS
  VALUES
  (0::int,-99999::numeric),
  (1,100);

Si debe escribir una declaración select, también puede hacerlo (y no necesita un CTE).

CREATE TEMP TABLE lookup(key, value) AS
  SELECT key::int, value::numeric
  FROM ( VALUES
    (0::int,-99999::numeric),
    (1,100)
  ) AS t(key, value);

Un CTE en PostgreSQL fuerza la materialización. Es una valla de optimización. Por esa razón, generalmente no es una buena idea usarlos en cualquier lugar, excepto cuando comprende los costos y sabe que proporciona una mejora en el rendimiento. Puedes ver la desaceleración aquí, por ejemplo,

\timing
CREATE TABLE foo AS
  SELECT * FROM generate_series(1,1e7);
Time: 5699.070 ms

CREATE TABLE foo AS
  WITH t AS ( SELECT * FROM generate_series(1,1e7) ) 
  SELECT * FROM t;
Time: 6484.516 ms
Evan Carroll
fuente
He actualizado la respuesta para reflejar el estándar, y señalo cómo la respuesta aceptada no siempre es equivalente a CREAR TABLA COMO y agregué un comentario sobre la valla de optimización, que es un muy buen punto para mencionar. Los CTE brindan muchas ventajas, pero es cierto que, si se usan a ciegas, pueden conducir a un rendimiento horrible.
John Powell
-2
WITH u AS (
    SELECT * FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) AS account (id,name)
)
SELECT id, name, length(name) from u;
caub
fuente