Удаление столбцов в PostgreSQL 9.6 и побочные эффекты для функций SQL с CTE

15

Если бы у меня была таблица с 3 столбцами - скажем, A, B и D - и мне пришлось бы ввести новую - скажем, C, чтобы заменить текущую позицию D. Я бы использовал следующий метод:

  1. Введите 2 новых столбца как C и D2.
  2. Скопируйте содержимое D в D2.
  3. Удалить D.
  4. Переименуйте D2 в D.

Новый порядок будет A, B, C и D.

Я думал, что это была законная практика, поскольку (до сих пор) она не вызывала никаких проблем.

Однако сегодня я столкнулся с проблемой, когда функция, выполняющая оператор в той же таблице, возвратила следующую ошибку:

table row type and query-specified row type do not match

И следующая деталь:

Query provides a value for a dropped column at ordinal position 13

Я попытался перезапустить PostgreSQL, сделать VACUUM FULLи, наконец, удалить и заново создать функцию, как предлагалось здесь и здесь, но эти решения не сработали (за исключением того, что они пытаются решить ситуацию, когда системная таблица была изменена).

Имея возможность работать с очень маленькой базой данных, я экспортировал ее, удалил, а затем повторно импортировал, и это устранило проблему с моей функцией.


Я знал о том факте, что не следует возиться с естественным порядком столбцов , изменяя системные таблицы (пачкать руки pg_attributeи т. Д.), Как показано здесь:

Можно ли изменить естественный порядок столбцов в Postgres?

Судя по ошибке, сгенерированной моей функцией, я теперь понимаю, что смещение порядка столбцов с помощью моего метода также является нет-нет. Может ли кто-нибудь пролить свет на то, почему то, что я делаю, тоже неправильно?


Версия Postgres - 9.6.0.

Вот функция:

CREATE OR REPLACE FUNCTION "public"."__post_users" ("facebookid" text, "useremail" text, "username" text) RETURNS TABLE (authentication_code text, id integer, key text, stripe_id text) AS '

-- First, select the user:
WITH select_user AS
(SELECT
users.id
FROM
users
WHERE
useremail = users.email),

-- Second, update the user (if user exists):
update_user AS
(UPDATE
users
SET
authentication_code = GEN_RANDOM_UUID(),
authentication_date = current_timestamp,
facebook_id = facebookid
WHERE EXISTS (SELECT * FROM select_user)
AND
useremail = users.email
RETURNING
users.authentication_code,
users.id,
users.key,
users.stripe_id),

-- Third, insert the user (if user does not exist):
insert_user AS
(INSERT INTO
users (authentication_code, authentication_date, email, key, name, facebook_id)
SELECT
GEN_RANDOM_UUID(),
current_timestamp,
useremail,
GEN_RANDOM_UUID(),
COALESCE(username, SUBSTRING(useremail FROM ''([^@]+)'')),
facebookid
WHERE NOT EXISTS (SELECT * FROM select_user)
RETURNING
users.authentication_code,
users.id,
users.key,
users.stripe_id)

-- Finally, select the authentication code, ID, key and Stripe ID:
SELECT
*
FROM
update_user
UNION ALL
SELECT
*
FROM
insert_user' LANGUAGE "sql" COST 100 ROWS 1
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER

Я выполнил переименование / переупорядочение для обоих столбцов facebook_idи stripe_id(перед ними был добавлен новый столбец, что является причиной переименования, но этот запрос не затрагивает).

Наличие столбцов в определенном порядке просто не представляет интереса для заказа. Однако причина для того, чтобы задать этот вопрос, состоит в том, что простое переименование и удаление столбца может вызвать реальные проблемы для кого-то, использующего функции в производственном режиме (как это случилось со мной).

Энди
источник
Посмотрите, как я могу изменить положение столбца в таблице базы данных PostgreSQL? на переполнение стека, что может быть полезно, хотя в основном они предлагают НЕ менять порядок столбцов.
Жоаноло

Ответы:

16

Вероятная ошибка на 9.6 и 9.6.1

Это полностью похоже на ошибку для меня ...

Я не знаю, почему это происходит, но я могу подтвердить, что это происходит. Это самая простая найденная установка, которая воспроизводит проблему (в версиях 9.6.0 и 9.6.1).

CREATE TABLE users
(
    id SERIAL PRIMARY KEY,
    email TEXT NOT NULL,
    column_that_we_will_drop TEXT
) ;

-- Function that uses the previous table, and that has a CTE
CREATE OR REPLACE FUNCTION __post_users
    (_useremail text) 
RETURNS integer AS
$$
-- Need a CTE to produce the error. A 'constant' one suffices.
WITH something_even_if_useless(a) AS
(
    VALUES (1)
)
UPDATE
    users
SET
    id = id
WHERE 
    -- The CTE needs to be referenced, if the next
    -- condition were not in place, the problem is not reproduced
    EXISTS (SELECT * FROM something_even_if_useless)
    AND email = _useremail
RETURNING
    id
$$
LANGUAGE "sql" ;

После этой настройки следующий оператор просто работает

SELECT * FROM __post_users('a@b.com');

На данный момент мы отбрасываем один столбец:

ALTER TABLE users 
    DROP COLUMN column_that_we_will_drop ;

Это изменение делает следующий оператор для генерации ошибки

SELECT * FROM __post_users('a@b.com');

что так же, как упомянуто @Andy:

ERROR: table row type and query-specified row type do not match
SQL state: 42804
Detail: Query provides a value for a dropped column at ordinal position 3.
Context: SQL function "__post_users" statement 1
    SELECT * FROM __post_users('a@b.com');

Удаление и воссоздание функции НЕ решает проблему.
VACUUM FULL (таблица или вся база данных) не решает проблему.


Сообщение об ошибке было передано в соответствующий список рассылки PostgreSQL, и мы получили очень быстрый ответ :

Я не могу воспроизвести это в HEAD или 9.6. Я полагаю, что он уже был исправлен этим патчем, который появился после 9.6.1:

https://git.postgresql.org/gitweb/?p=postgresql.git&a=commitdiff&h=f4d865f22

Но спасибо за отчет!

С уважением, Том Лейн


Версия 9.6.2

На 2017-03-06 я могу подтвердить, что не могу воспроизвести это поведение в версии 9.6.2. То есть ошибка, кажется, была исправлена ​​в этом выпуске.

ОБНОВИТЬ

Согласно комментарию @Jana: «Я могу подтвердить, что ошибка присутствует в 9.6.1 и была исправлена ​​в 9.6.2. Это исправление также перечислено на веб-сайте постгрес-релиза : Исправление ложного запроса« предоставляет значение для отброшенного столбца »во время ошибок INSERT или UPDATE для таблицы с пропущенным столбцом


joanolo
источник
4
Я использую 9.6.0 и @joanolo правильно, я могу воспроизвести ошибку с его методом. Если Том не может воспроизвести его, возможно, это была отдельная ошибка с этой конкретной версией (и 9.6.1, я полагаю?). Как упоминалось в ответе, проблема возникает при удалении столбца в таблице и использовании функции с CTE, влияющей на эту таблицу. Таким образом, проблема не просто в переупорядочении, а в том, что я пытался решить своим вопросом. Я отредактирую заголовок, чтобы отразить это.
Энди
Я могу подтвердить, что ошибка присутствует в 9.6.1 и была исправлена ​​в 9.6.2. Это исправление также указано на веб-сайте выпуска postgres : исправление ложного запроса «предоставляет значение для пропущенного столбца» при ошибках INSERT или UPDATE в таблице с пропущенным столбцом
Jana
0

Я знаю, что вы, наверное, слышали это раньше, но это ужасная идея.

  • Логическое упорядочение влияет только на такие вещи, как SELECT *
  • Эффект логического упорядочения ограничен внешним видом.

Так что, если ничто не имеет значения, не отговорит вас, и мы признаем, что мы просто играем в Photoshop со структурой строк и зацикливаемся на отображении, давайте объясним еще кое-что.

  • Логический порядок не существует в PostgreSQL
  • Физический заказ имеет реальные преимущества (упаковка таблицы)
  • PostgreSQL не дает вам никакого контроля над физическим упорядочением после этого CREATE TABLE(хотя это будет гораздо более высоким приоритетом)

Так что PostgreSQL - плохой слой отображения. Вдобавок ко всему, хотя и ALTERработает нормально, вы не должны испытывать дракона. Оба ALTER TABLE, и раздел CAVEAT упоминают об этом,

Некоторые команды DDL, в настоящее время только TRUNCATEи формы переписывания таблиц ALTER TABLE, не являются MVCC-безопасными. Это означает, что после фиксации усечения или перезаписи таблица будет отображаться пустой для одновременных транзакций, если они используют снимок, сделанный до принятия команды DDL. Это будет проблемой только для транзакции, которая не обращалась к рассматриваемой таблице до запуска команды DDL - любая сделавшая транзакция содержала бы по крайней мере блокировку таблицы ACCESS SHARE, которая блокировала бы команду DDL до завершения этой транзакции. Таким образом, эти команды не вызовут явного несоответствия в содержании таблицы для последовательных запросов к целевой таблице, но они могут вызвать видимое несоответствие между содержимым целевой таблицы и другими таблицами в базе данных.

И, если всего этого недостаточно, и вы все еще хотите сделать вид, что это хорошая идея, а не ужасная идея. Тогда попробуй это,

  1. Дамп таблицы с pg_dump -t
  2. Изменить порядок столбцов вручную в дампе.
  3. Загрузите материал в таблицу TEMP.
  4. BEGIN сделка
  5. DROP старый стол целиком,
  6. RENAME временная таблица для таблицы продуктов.
  7. COMMIT

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

Но все это говорит в общем. Никто не воспроизвел ваши результаты.

Вы дали тест-кейс, который мы не можем запустить

ERROR:  column users.authentication_code does not exist
LINE 24: users.authentication_code,

И вы не сказали нам точную версию, на которой вы находитесь.

Эван Кэрролл
источник
Спасибо за подробное объяснение, я отредактировал вопрос, включив в него конкретную версию.
Энди
4
Ошибка воспроизводима в версии 9.6.0
ypercubeᵀᴹ
0

Я справился с этой ошибкой, создав резервную копию и восстановив базу данных.

Шаги для Heroku

  • Включить режим обслуживания: heroku maintenance:on
  • Резервное копирование базы данных: heroku pg:backups:capture
  • Восстановить базу данных: heroku pg:backups:restore
  • Перезапустите приложение: heroku restart
  • Отключить режим обслуживания: heroku maintenance:off
ma11hew28
источник
0

Я столкнулся с этой ошибкой тоже. Для тех, кто не хочет полностью резервировать / восстанавливать свои БД. Знайте, что простое копирование таблицы работает. Там нет "волшебный" способ скопировать таблицу, хотя. Я сделал это с помощью:

SELECT * INTO mytable_copy FROM mytable;
ALTER TABLE mytable RENAME TO mytable_backup; -- just in case. you never know
ALTER TABLE mytable_copy RENAME TO mytable;

После этого вам все равно нужно будет вручную воссоздать ваши индексы, внешние ключи и значения по умолчанию. При таком воссоздании таблицы ошибка исчезла.

Thibauld
источник