Caída de columnas PostgreSQL 9.6 y efectos secundarios en funciones SQL con CTE

15

Si tuviera una tabla con 3 columnas, digamos A, B y D, y tuviera que introducir una nueva, diga C para reemplazar la posición actual de D. Usaría el siguiente método:

  1. Introducir 2 nuevas columnas como C y D2.
  2. Copie el contenido de D a D2.
  3. Eliminar D.
  4. Cambiar el nombre de D2 a D.

El nuevo orden sería A, B, C y D.

Pensé que esta era una práctica legítima ya que (hasta ahora) no produjo problemas.

Sin embargo, hoy me encontré con un problema cuando una función que realizaba una declaración en la misma tabla devolvía el siguiente error:

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

Y el siguiente detalle:

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

Intenté reiniciar PostgreSQL, haciendo una VACUUM FULLy finalmente eliminando y volviendo a crear la función como se sugiere aquí y aquí, pero estas soluciones no funcionaron (aparte del hecho de que intentan abordar una situación en la que se ha alterado una tabla del sistema).

Teniendo el lujo de trabajar con una base de datos muy pequeña, la exporté, la eliminé y luego la volví a importar y eso solucionó el problema con mi función.


Era consciente del hecho de que uno no debería perder el tiempo con el orden natural de las columnas modificando las tablas del sistema (ensuciarse las manos pg_attribute, etc.) como se ve aquí:

¿Es posible cambiar el orden natural de las columnas en Postgres?

A juzgar por el error arrojado por mi función, ahora me doy cuenta de que cambiar el orden de las columnas con mi método también es un no-no. ¿Alguien puede arrojar algo de luz sobre por qué lo que estoy haciendo también está mal?


La versión de Postgres es 9.6.0.

Aquí está la función:

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

Realicé el cambio de nombre / reordenamiento en ambas columnas facebook_idy stripe_id(se agregó una nueva columna antes de estas, que es la razón del cambio de nombre, pero esta consulta no la toca).

Tener las columnas en un cierto orden es puramente de interés para el orden. Sin embargo, la razón para hacer esta pregunta es preocupante porque un simple cambio de nombre y eliminación de una columna puede provocar problemas reales para alguien que usa funciones en modo de producción (como me sucedió a mí mismo).

Andy
fuente
Eche un vistazo a ¿Cómo modifico la posición de una columna en una tabla de base de datos PostgreSQL? en Stack Overflow que podría ser útil, aunque en su mayoría sugieren NO reordenar sus columnas.
joanolo

Respuestas:

16

Error probable en 9.6 y 9.6.1

Esto me parece completamente un error ...

No sé por qué sucede, pero puedo confirmar que sucede. Esta es la configuración más simple encontrada que reproduce el problema (en la versión 9.6.0 y 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" ;

Después de esta configuración, la siguiente declaración simplemente funciona

SELECT * FROM __post_users('[email protected]');

En este punto, DEJAMOS una columna:

ALTER TABLE users 
    DROP COLUMN column_that_we_will_drop ;

Este cambio hace que la siguiente declaración genere un error

SELECT * FROM __post_users('[email protected]');

que es lo mismo que mencionó @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('[email protected]');

Dejar caer y recrear la función NO resuelve el problema.
VACÍO COMPLETO (la tabla o la base de datos completa) no resuelve el problema.


El informe de error se pasó a la lista de correo de PostgreSQL apropiada y obtuvimos una respuesta muy rápida :

No puedo reproducir esto en HEAD o punta de rama 9.6. Creo que ya fue reparado por este parche, que entró un poco después de 9.6.1:

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

Pero gracias por el informe!

saludos, tom lane


Versión 9.6.2

En 2017-03-06, puedo confirmar que no puedo reproducir este comportamiento en la versión 9.6.2. Es decir, el error parece haber sido corregido en esta versión.

ACTUALIZAR

Por comentario de @Jana: "Puedo confirmar que el error está presente en 9.6.1 y se corrigió en 9.6.2. La corrección también aparece en el sitio web de publicación de postgres : la consulta de corrección espuria" proporciona un valor para los errores de una columna caída durante INSERTAR o ACTUALIZAR en una tabla con una columna caída "


joanolo
fuente
44
Estoy usando 9.6.0 y @joanolo es correcto, puedo reproducir el error con su método. Si Tom no puede reproducirlo, probablemente fue un error aislado con esta versión específica (y supongo que 9.6.1). Como se menciona en la respuesta, el problema aparece al colocar una columna en una tabla y usar una función con CTE que afecta a esa tabla. Por lo tanto, el problema no es solo con el reordenamiento y eso es a lo que estaba tratando de llegar con mi pregunta. Editaré el título para reflejar esto.
Andy
Puedo confirmar que el error está presente en 9.6.1 y se corrigió en 9.6.2. La corrección también aparece en el sitio web de publicación de Postgres : Corregir errores espurios de "consulta que proporciona un valor para una columna descartada" durante INSERTAR o ACTUALIZAR en una tabla con una columna descartada
Jana
0

Sé que probablemente hayas escuchado esto antes, pero esta es una idea horrible.

  • El ordenamiento lógico solo afecta cosas como SELECT *
  • El efecto del ordenamiento lógico se limita a la apariencia.

Entonces, si no importa en absoluto no lo disuade y reconocemos que solo estamos jugando Photoshop con estructura de filas y obsesionados con la visualización, expliquemos algunas cosas más.

  • El ordenamiento lógico no existe en PostgreSQL
  • El pedido físico tiene beneficios reales (embalaje de mesa)
  • PostgreSQL no le da ningún control sobre el ordenamiento físico después CREATE TABLE(aunque sería una prioridad mucho mayor)

Entonces PostgreSQL es una mala capa de visualización. Además de todo eso, si ALTERbien funciona, no debes templar al dragón. Ambos ALTER TABLE, y la sección CAVEAT hacen mención de esto,

Algunos comandos DDL, actualmente solo TRUNCATEy las formas de reescritura de tablas ALTER TABLE, no son seguros para MVCC. Esto significa que después de las confirmaciones de truncamiento o reescritura, la tabla aparecerá vacía para las transacciones concurrentes, si están utilizando una instantánea tomada antes de que se confirme el comando DDL. Esto solo será un problema para una transacción que no accedió a la tabla en cuestión antes de que comenzara el comando DDL: cualquier transacción que lo haya hecho mantendría al menos un bloqueo de tabla ACCESS SHARE, que bloquearía el comando DDL hasta que se complete esa transacción. Por lo tanto, estos comandos no causarán ninguna inconsistencia aparente en el contenido de la tabla para consultas sucesivas en la tabla de destino, pero podrían causar una inconsistencia visible entre el contenido de la tabla de destino y otras tablas en la base de datos.

Y, si todo eso no es suficiente, y aún así quieres fingir que es una buena idea, más bien una idea horrible. Entonces prueba esto,

  1. Volcar la mesa con pg_dump -t
  2. Reordenar las columnas a mano en el basurero.
  3. Cargue las cosas en una tabla TEMP.
  4. BEGIN una transacción
  5. DROP la vieja mesa por completo,
  6. RENAME la tabla temporal a la tabla de productos.
  7. COMMIT

Si todo eso suena excesivo, tenga en cuenta que la actualización de las filas en la base de datos requiere reescribir las filas (suponiendo que no sean TOSTADAS . Debe analizar los datos y reconstruir el esquema de la tabla, pero de cualquier manera debe reescribir la fila. Si tuviera que hacer esta tarea, así es como lo haría.

Pero, todo esto está hablando en general. Nadie ha reproducido tus resultados.

Has dado un caso de prueba que no podemos ejecutar

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

Y no nos ha dicho la versión exacta en la que se encuentra.

Evan Carroll
fuente
Gracias por la explicación detallada, he editado la pregunta para incluir la versión específica.
Andy
44
El error es reproducible en la versión 9.6.0
ypercubeᵀᴹ
0

Resolví este error haciendo una copia de seguridad y restaurando mi base de datos.

Pasos para Heroku

  • Active el modo de mantenimiento: heroku maintenance:on
  • Base de datos de respaldo: heroku pg:backups:capture
  • Restaurar base de datos: heroku pg:backups:restore
  • Reiniciar la aplicación: heroku restart
  • Apague el modo de mantenimiento: heroku maintenance:off
ma11hew28
fuente
0

Me encontré con este error también. Para aquellos que no quieren hacer una copia de seguridad / restaurar completamente su base de datos. Sepa que simplemente copiar la tabla funciona. Sin embargo, no hay una forma "mágica" de copiar una tabla. Lo hice usando:

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;

Después de eso, aún necesitará recrear manualmente sus índices, claves foráneas y valores predeterminados. Volver a crear la mesa de esta manera hizo que el error desapareciera.

Thibauld
fuente