web-dev-qa-db-fra.com

PostgreSQL 9.6 Column baisse et effets secondaires sur les fonctions SQL avec des CTES

Si j'avais une table avec 3 colonnes - disons A, B et D - et je devais introduire un nouveau - dire c pour remplacer la position actuelle de D. J'utiliserais la méthode suivante:

  1. Introduisez 2 nouvelles colonnes comme C et D2.
  2. Copiez le contenu de D à D2.
  3. Supprimer D.
  4. Renommer D2 à D.

La nouvelle commande serait A, B, C et D.

Je pensais que c'était une pratique légitime que (jusqu'à présent), cela n'a produit aucun problème.

Cependant, aujourd'hui, je suis tombé sur un problème lorsqu'une fonction effectuant une déclaration sur la même table a renvoyé l'erreur suivante:

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

Et les détails suivants:

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

J'ai essayé de redémarrer PostgreSQL, faire un VACUUM FULL et enfin la suppression et la ré-création de la fonction comme suggéré ici et ici Mais ces solutions n'ont pas fonctionné (en dehors du fait qu'ils essaient de s'attaquer à une situation où un système la table a été modifiée).

Avoir le luxe de travailler avec une très petite base de données que j'ai exportée, l'a supprimé, puis la ré-importée et que corrige le problème avec ma fonction.


J'étais conscient du fait que l'on ne devrait pas gâcher avec l'ordre naturel des colonnes en modifiant les tables système (amener les mains sales avec pg_attribute, etc.) comme on le voit ici:

est-il possible de changer l'ordre naturel des colonnes dans Postgres?

Juger par l'erreur jeté par ma fonction, je me rends compte que le déplacement de l'ordre des colonnes avec ma méthode est également un non-non. Quelqu'un peut-il briller de la lumière sur la raison pour laquelle ce que je fais est aussi faux?


La version Postgres est 9.6.0.

Voici la fonction:

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

J'ai effectué le renommage/la réorganisation des deux colonnes facebook_id et stripe_id (une nouvelle colonne a été ajoutée avant celles-ci, qui est la raison du renommage, mais n'est pas touchée par cette requête).

Avoir les colonnes dans un certain ordre est purement à l'insu pour la commande. Toutefois, la raison de la poser de cette question est incluse selon laquelle une simple renommée et la suppression d'une colonne peut déclencher de véritables problèmes pour quelqu'un à l'aide de fonctions en mode de production (comme cela est arrivé à moi-même).

15
Andy

Bug probable sur 9.6 et 9.6.1

Cela ressemble complètement à un bug pour moi ...

Je ne sais pas pourquoi cela se passe, mais je peux confirmer que cela se produit. C'est la configuration la plus simple que l'on reproduit le problème (version 9.6.0 et 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" ;

Après cette configuration, la prochaine déclaration fonctionne simplement

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

À ce stade, nous déposons une colonne:

ALTER TABLE users 
    DROP COLUMN column_that_we_will_drop ;

Ce changement fait la déclaration suivante pour générer une erreur

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

quel est le même que mentionné par @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]');

Laisser tomber et recréer la fonction ne résout pas le problème.
Vacuum Full (la table ou la base de données entière) ne résout pas le problème.


Le rapport de bogue a été transmis à la liste de diffusion postgreSQL appropriée et nous avons eu un une réponse très rapide :

Je ne peux pas reproduire cela dans HEAD ou 9,6 pointe de succursale. Je crois que c'était déjà réparé par ce patch, qui est allé un peu après 9,6.1:

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

Mais merci pour le rapport!

cordialement, Tom Lane


Version 9.6.2

Le 2017-03-06, je peux confirmer que je ne peux pas reproduire ce comportement sur la version 9.6.2. C'est-à-dire que le bogue semble avoir été corrigé sur cette version.

METTRE À JOUR

Par commentaire de @jana: "Je peux confirmer que le bogue est présent en 9.6.1 et a été corrigé en 9.6.2. Le correctif est également répertorié sur Site Web de publication Postgres : correction de la "requête parasite fournit une valeur pour une colonne abandonnée" Erreurs lors de l'insertion ou de la mise à jour sur une table avec une colonne abandonnée "


16
joanolo

Je sais que vous avez probablement entendu cela auparavant, mais c'est une idée horrible.

  • Les commandes logiques n'effectuent que des choses comme SELECT *
  • L'effet de la commande logique est limité à l'apparence.

Donc, si qui ne comporte pas du tout ne vous dissuade pas et que nous reconnaissons que nous jouons simplement Photoshop avec une structure de rangée et obsédé par l'affichage, expliquons d'autres choses.

  • Les commandes logiques n'existent pas dans PostgreSQL
  • Le commandement physique a de réels avantages (emballage de table)
  • PostgreSQL ne vous donne aucun contrôle sur la commande physique après CREATE TABLE (bien que ce soit un beaucoup Priorité plus élevée)

Donc, PostgreSQL est une mauvaise couche d'affichage. Sur tout cela, alors que ALTER fonctionne bien, vous ne devriez pas tempérer le dragon. Les deux ALTER TABLE , et la section de mise en garde en fait mention de ceci,

Certaines commandes DDL, actuellement uniquement TRUNCATE et les formulaires de réécriture de la table de ALTER TABLE, ne sont pas MVCC-Safe. Cela signifie qu'après la troncature ou la réécriture commettre, la table apparaîtra vide aux transactions simultanées, s'ils utilisent un instantané pris avant que la commande DDL engagée. Cela ne constituera qu'un problème pour une transaction qui n'a pas accédé à la table en question avant le début de la commande DDL - Toute transaction qui l'a fait disposerait d'au moins une serrure de la table d'actions d'accès, qui bloquerait la commande DDL jusqu'à ce que cette transaction soit terminée. Donc, ces commandes ne proviendront aucune incohérence apparente dans le contenu de la table pour des requêtes successives sur la table cible, mais elles pourraient entraîner une incohérence visible entre le contenu de la table cible et d'autres tables de la base de données.

Et si tout ce qui ne suffit pas, et que vous voulez toujours prétendre que c'est une bonne idée plutôt une idée horrible. Ensuite, essayez ceci,

  1. Dump la table avec pg_dump -t
  2. Réorganiser les colonnes à la main dans le décharge.
  3. Charger les choses dans une table Temp.
  4. BEGIN une transaction
  5. DROP l'ancienne table entièrement,
  6. RENAME la table TEMP à la table Prod.
  7. COMMIT

Si tout cela semble excessive, gardez à l'esprit que la mise à jour de la mise à jour des lignes de la base de données nécessite de réécrire les lignes (en supposant qu'ils pas [~ # ~] Toast [ ~ # ~] . Vous devez analyser les données et le schéma de table reconstruit, mais de toute façon, vous devez réécrire la ligne. Si je avait Pour faire cette tâche, c'est comme ça que je le ferais.

Mais tout cela parle généralement. Personne n'a reproduit vos résultats.

Vous avez donné un cas de test que nous ne pouvons pas courir

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

Et vous ne nous avez pas dit la version exacte Version Vous êtes activé.

0
Evan Carroll

J'ai eu autour de ce bogue en sauvegardant et à la restauration de ma base de données.

Étapes pour Heroku

  • Activer le mode de maintenance: heroku maintenance:on
  • Base de données de sauvegarde: heroku pg:backups:capture
  • Restaurer la base de données: heroku pg:backups:restore
  • Application de redémarrage: heroku restart
  • Désactiver le mode de maintenance: heroku maintenance:off
0
ma11hew28

J'ai aussi rencontré ce bug. Pour ceux qui ne veulent pas complètement sauvegarder/restaurer leur DB. Sachez que simplement copier la table fonctionne. Il n'y a pas de moyen "magique" de copier une table. Je l'ai fait en utilisant:

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;

Après cela, vous aurez toujours besoin de recréer manuellement vos index, clés étrangères et par défaut. Recréer le tableau comme celui-ci fait disparaître le bogue.

0
Thibauld