Как создать временную таблицу, используя VALUES в PostgreSQL

38

Я изучаю PostgreSQL и пытаюсь выяснить, как создать временную таблицу или WITHобъявление, которое можно использовать вместо обычной таблицы для целей отладки.

Я посмотрел документацию по CREATE TABLE, и там написано, что она VALUESможет быть использована в качестве запроса, но не приводится пример; документация дляVALUES оговоренного в нем предложения также не имеет примера?

Итак, я написал простой тест следующим образом:

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

Но PostgreSQL (9.3) жалуется на

синтаксическая ошибка в или около "AS"

Мои вопросы:

  1. Как я могу исправить заявление выше?

  2. Как я могу адаптировать его для использования в WITH block?

Заранее спасибо.

tinlyx
источник
Я попытался ответить на этот вопрос с некоторыми более современными советами (так как выбранный ответ использует устаревший нестандартизированный синтаксис) dba.stackexchange.com/a/201575/2639
Эван Кэрролл

Ответы:

46

РЕДАКТИРОВАТЬ: я оставляю исходный принятый ответ, как есть, но, пожалуйста, обратите внимание, что редактирование ниже, как предложено a_horse_with_no_name, является предпочтительным методом для создания временной таблицы с использованием VALUES.

Если вы просто хотите выбрать одно из значений, а не просто создать таблицу и вставить в нее, вы можете сделать что-то вроде:

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

Чтобы создать временную таблицу аналогичным образом, используйте:

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

РЕДАКТИРОВАТЬ: Как указано a_horse_with_no_name, в документах говорится, что CREATE TABLE AS...он функционально похож SELECT INTO ..., но первый является надмножеством последнего и SELECT INTOиспользуется в plpgslq для присвоения значения временной переменной - поэтому он потерпит неудачу в тот случай. Поэтому, хотя приведенные выше примеры действительны для простого SQL, CREATE TABLEформа должна быть предпочтительной.

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

Обратите внимание, что также из комментариев a_horse_with_no_name и в исходном вопросе OP, он включает приведение к правильным типам данных в списке значений и использует оператор CTE (WITH).

Кроме того, как указывалось в ответе Эвана Кэррола, запрос CTE является забором оптимизации , т. Е. CTE всегда материализуется. Есть много веских причин для использования CTE, но может быть весьма значительное снижение производительности, если не использовать его осторожно. Однако во многих случаях барьер оптимизации может фактически повысить производительность, поэтому об этом следует помнить, а не слепо избегать.

Джон Пауэлл
источник
12
из документов : « CREATE TABLE AS функционально аналогичен SELECT INTO. CREATE TABLE AS - это рекомендуемый синтаксис »
a_horse_with_no_name
Забор оптимизации не обязательно плохая вещь. Я видел много заявлений, из-за которых я мог настроиться так, чтобы работать намного быстрее.
a_horse_with_no_name
Конечно, я тоже это уточнил. Я использую CTE все время в пространственном контексте. Если у вас есть предложение where с чем-то вроде WHERE ST_Intersects(geom, (SELECT geom FROM sometable)или WHERE ST_Intersects(geom, ST_Buffer(anothergeom, 10)тогда, часто планировщик запросов не использует пространственный индекс, потому что столбец geom больше не может использоваться. Если вы создаете интересующую вас область в начальном CTE, эта проблема исчезнет. Это также очень удобно, если вы хотите использовать один и тот же aoi в нескольких последующих выражениях в одном и том же запросе, что не редкость в контексте ГИС.
Джон Пауэлл
25

create table as требуется оператор выбора:

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

Вы также можете переписать это, чтобы использовать CTE:

create temp table lookup 
as 
with t (key, value) as (
  values 
    (0::int,-99999::numeric), 
    (1::int,100::numeric)
)
select * from t;
a_horse_with_no_name
источник
1
Спасибо за ваш комментарий. Вы подходите, очевидно, лучше по причинам, изложенным в документах. Я отредактировал свой ответ, хотя и почти на 5 лет позже.
Джон Пауэлл
11

Проблема в типах данных. Если вы удалите их, заявление будет работать:

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

Вы можете определить типы, приведя значения к первой строке:

CREATE TEMP TABLE lookup 
  (key, val) AS
VALUES 
  (0::bigint, -99999::int), 
  (1, 100) ;
ypercubeᵀᴹ
источник
3

Вам действительно не нужно ни создавать таблицу, ни использовать CTE, если все, что вам нужно, это использовать несколько значений в ваших запросах. Вы можете включить их:

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

Затем вы можете получить декартово произведение с CROSS JOIN(где другие отношения могут быть, конечно, обычным столом, представлением и т. Д.). например:

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

который дает:

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

Или JOINзначения с другим отношением (которое снова может быть обычной таблицей, представлением и т. Д.), Например:

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;

который дает:

key |val    |color |lookup_key |
----|-------|------|-----------|
1   |100    |Red   |1          |
0   |-99999 |White |0          |
1   |100    |Blue  |1          |
isapir
источник
ОК, но вопрос был "как создать временную таблицу с ...?"
ypercubeᵀᴹ
Да, но зачем вам нужна временная таблица с несколькими фиксированными значениями поиска, если не для объединения ее в другом отношении? Это решение решает саму проблему независимо от того, как сформулирован вопрос.
Исапир
1
Может быть, OP просто свел пример к тому, что было бы легко опубликовать в виде вопроса, но реальные данные имеют тысячи значений?
Станниус
ОП специально указывал, используя значения, поэтому мой ответ по-прежнему применим, поскольку именно это он и делает
isapir
2

Сначала всегда используйте стандартизированный CREATE TABLE AS, SELECT INTOкак предлагалось в других ответах, является устаревшим синтаксисом уже более десяти лет. Вы можете использоватьCREATE TABLE AS с CTE

Хотя многие ответы здесь предлагают использовать CTE, это не является предпочтительным. На самом деле, это, вероятно, несколько медленнее. Просто оберните это как стол.

DROP TABLE IF EXISTS lookup;

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

Если вам нужно написать оператор выбора, вы тоже можете это сделать (и вам не нужен 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);

CTE в PostgreSQL форсирует материализацию. Это забор оптимизации. По этой причине, как правило, не рекомендуется использовать их где-либо, кроме случаев, когда вы понимаете затраты и знаете, как обеспечить повышение производительности. Вы можете увидеть замедление здесь, например,

\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
Эван Кэрролл
источник
Я обновил ответ, чтобы отразить стандарт, и указал, что принятый ответ не всегда эквивалентен CREATE TABLE AS, и добавил комментарий к забору оптимизации, который является очень хорошим моментом для обсуждения. CTE имеют много преимуществ, но, если их использовать вслепую, это может привести к ужасной производительности.
Джон Пауэлл
-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
источник