Löschen von PostgreSQL 9.6-Spalten und Nebenwirkungen auf SQL-Funktionen mit CTEs


15

Wenn ich eine Tabelle mit 3 Spalten hätte - sagen Sie A, B und D - und eine neue einführen müsste - sagen Sie C, um die aktuelle Position von D zu ersetzen. Ich würde die folgende Methode anwenden:

  1. Stellen Sie 2 neue Spalten als C und D2 vor.
  2. Kopieren Sie den Inhalt von D nach D2.
  3. Löschen Sie D.
  4. Benenne D2 in D.

Die neue Reihenfolge wäre A, B, C und D.

Ich hielt dies für eine legitime Praxis, da sie (bisher) keine Probleme verursachte.

Heute bin ich jedoch auf ein Problem gestoßen, als eine Funktion, die eine Anweisung für dieselbe Tabelle ausführt, den folgenden Fehler zurückgab:

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

Und das folgende Detail:

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

Ich habe versucht, PostgreSQL neu zu starten, ein VACUUM FULLauszuführen und schließlich die hier und hier vorgeschlagene Funktion zu löschen und neu zu erstellen , aber diese Lösungen haben nicht funktioniert (abgesehen von der Tatsache, dass sie versuchen, eine Situation zu lösen, in der eine Systemtabelle geändert wurde).

Da ich den Luxus hatte, mit einer sehr kleinen Datenbank zu arbeiten, exportierte ich sie, löschte sie und importierte sie erneut. Damit wurde das Problem mit meiner Funktion behoben .


Mir war bewusst, dass man nicht mit der natürlichen Reihenfolge der Spalten herumspielen sollte, indem man Systemtabellen modifiziert (sich die Hände schmutzig machen pg_attributeusw.), wie hier zu sehen:

Ist es möglich, die natürliche Reihenfolge der Spalten in Postgres zu ändern?

Gemessen an dem Fehler, den meine Funktion verursacht hat, ist mir jetzt klar, dass das Verschieben der Spaltenreihenfolge mit meiner Methode ebenfalls ein Nein-Nein ist. Kann jemand ein bisschen Licht ins Dunkel bringen, warum das, was ich tue, auch falsch ist?


Die Postgres-Version ist 9.6.0.

Hier ist die Funktion:

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

Ich habe die Umbenennung / Neuordnung für beide Spalten facebook_idund vorgenommen stripe_id(vor diesen wurde eine neue Spalte hinzugefügt, die der Grund für die Umbenennung ist, aber von dieser Abfrage nicht berührt wird).

Die Spalten in einer bestimmten Reihenfolge zu haben, ist für die Reihenfolge rein uninteressant. Der Grund für diese Frage liegt jedoch in der Befürchtung, dass ein einfaches Umbenennen und Löschen einer Spalte für jemanden, der Funktionen im Produktionsmodus verwendet (wie es mir selbst passiert ist), echte Probleme verursachen kann.


Werfen Sie einen Blick auf Wie ändere ich die Position einer Spalte in einer PostgreSQL-Datenbanktabelle? on Stack Overflow kann hilfreich sein, schlägt aber meistens vor, die Spalten NICHT neu zu ordnen.
Joanolo

Antworten:


16

Wahrscheinlicher Fehler in 9.6 und 9.6.1

Das sieht für mich komplett nach einem Bug aus ...

Ich weiß nicht, warum es passiert, aber ich kann bestätigen, dass es passiert. Dies ist das am einfachsten zu findende Setup, das das Problem reproduziert (in Version 9.6.0 und 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" ;

Nach diesem Setup funktioniert die nächste Anweisung einfach

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

An dieser Stelle FALLEN wir eine Spalte:

ALTER TABLE users 
    DROP COLUMN column_that_we_will_drop ;

Diese Änderung löst die nächste Anweisung aus, die einen Fehler generiert

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

das ist das gleiche wie von @Andy erwähnt:

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

Das Löschen und Neuerstellen der Funktion löst das Problem NICHT.
VACUUM FULL (die Tabelle oder die gesamte Datenbank) löst das Problem nicht.


Der Fehlerbericht wurde an die entsprechende PostgreSQL-Mailingliste weitergeleitet und wir hatten eine sehr schnelle Antwort :

Ich kann dies in HEAD oder 9.6 Branch Tip nicht reproduzieren. Ich glaube, es wurde bereits durch diesen Patch behoben, der etwas nach 9.6.1 einging:

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

Aber danke für den Bericht!

Grüße, Tom Lane


Version 9.6.2

Am 06.03.2017 kann ich bestätigen, dass ich dieses Verhalten in Version 9.6.2 nicht reproduzieren kann. Das heißt, der Fehler scheint in dieser Version behoben worden zu sein.

AKTUALISIEREN

Per Kommentar von @Jana: "Ich kann bestätigen, dass der Fehler in 9.6.1 vorhanden ist und in 9.6.2 behoben wurde. Die Fehlerbehebung ist auch auf der Website nach der Veröffentlichung von Gres aufgeführt: Fehlerbehebung " Abfrage liefert einen Wert für eine abgelegte Spalte "während INSERT oder UPDATE für eine Tabelle mit einer abgelegten Spalte "



4
Ich benutze 9.6.0 und @joanolo ist korrekt, ich kann den Fehler mit seiner Methode reproduzieren. Wenn Tom es nicht reproduzieren kann, war es wahrscheinlich ein isolierter Fehler mit dieser spezifischen Version (und 9.6.1 nehme ich an?). Wie in der Antwort erwähnt, wird das Problem , wenn Abwurf einer Spalte in einer Tabelle und unter Verwendung einer Funktion mit CTE zu beeinflussen , dass die Tabelle. Das Problem liegt also nicht nur in der Nachbestellung, sondern auch in der Beantwortung meiner Frage. Ich werde den Titel bearbeiten, um dies widerzuspiegeln.
Andy

Ich kann bestätigen, dass der Fehler in 9.6.1 vorhanden ist und in 9.6.2 behoben wurde. Das Update ist auch auf der Postgres-Release-Website aufgeführt : Falsche "Abfrage liefert einen Wert für eine gelöschte Spalte" -Fehler während INSERT oder UPDATE für eine Tabelle mit einer gelöschten Spalte korrigieren
Jana

0

Ich weiß, dass Sie das wahrscheinlich schon einmal gehört haben, aber das ist eine schreckliche Idee.

  • Logische Reihenfolge wirkt sich nur auf Dinge wie SELECT *
  • Die Wirkung der logischen Reihenfolge ist auf das Erscheinungsbild beschränkt.

Wenn Sie also nicht davon abgehalten werden, etwas zu tun , und wir erkennen an, dass wir Photoshop nur mit Zeilenstruktur und Besessenheit über der Anzeige spielen, lassen Sie uns einige weitere Dinge erklären.

  • In PostgreSQL gibt es keine logische Reihenfolge
  • Physische Bestellung hat echte Vorteile (Tischverpackung)
  • PostgreSQL gibt Ihnen keine Kontrolle über die physische Bestellung nach CREATE TABLEentweder (obwohl es eine viel höhere Priorität wäre)

PostgreSQL ist also eine schlechte Anzeigeebene. Auf all das, während es gut ALTERfunktioniert, solltest du den Drachen nicht tempen. Beide ALTER TABLEund der CAVEAT-Abschnitt erwähnen dies,

Einige DDL-Befehle, derzeit nur TRUNCATEund die Formulare zum Umschreiben von Tabellen ALTER TABLE, sind nicht MVCC-sicher. Dies bedeutet, dass nach dem Abschneiden oder erneuten Schreiben die Tabelle für gleichzeitige Transaktionen leer angezeigt wird, wenn sie einen Snapshot verwenden, der vor dem Festschreiben des DDL-Befehls erstellt wurde. Dies ist nur ein Problem für eine Transaktion, die vor dem Start des DDL-Befehls nicht auf die betreffende Tabelle zugegriffen hat. Jede Transaktion, die dies getan hat, hat mindestens eine ACCESS SHARE-Tabellensperre, die den DDL-Befehl blockiert, bis diese Transaktion abgeschlossen ist. Diese Befehle verursachen also keine offensichtlichen Inkonsistenzen im Tabelleninhalt für aufeinanderfolgende Abfragen in der Zieltabelle, aber sie können sichtbare Inkonsistenzen zwischen dem Inhalt der Zieltabelle und anderen Tabellen in der Datenbank verursachen.

Und wenn all das nicht genug ist und Sie trotzdem so tun möchten, als wäre dies eine gute Idee, eher eine schreckliche Idee. Dann probiere das,

  1. Kippen Sie den Tisch mit pg_dump -t
  2. Ordnen Sie die Spalten im Dump von Hand neu an.
  3. Laden Sie das Zeug in eine TEMP-Tabelle.
  4. BEGIN eine Transaktion
  5. DROP die alte Tabelle ganz,
  6. RENAME die temporäre Tabelle zur Prod-Tabelle.
  7. COMMIT

Wenn all dies übermäßig klingt, denken Sie daran, dass für das Aktualisieren von Zeilen in der Datenbank ein erneutes Schreiben der Zeilen erforderlich ist (vorausgesetzt, es handelt sich nicht um TOAST . Sie müssen die Daten analysieren und das Tabellenschema neu erstellen, müssen jedoch in beiden Fällen neu schreiben die Reihe. Wenn ich hatte diese Aufgabe zu tun, das ist , wie ich es tun würde.

Aber das alles spricht im Allgemeinen. Niemand hat Ihre Ergebnisse reproduziert.

Sie haben einen Testfall angegeben, den wir nicht ausführen können

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

Und Sie haben uns nicht die genaue Version mitgeteilt, auf der Sie sich befinden.


Vielen Dank für die ausführliche Erklärung. Ich habe die Frage so bearbeitet, dass sie die spezifische Version enthält.
Andy

4
Der Fehler ist reproduzierbar in Version 9.6.0
ypercubeᵀᴹ

0

Ich habe diesen Fehler umgangen, indem ich meine Datenbank gesichert und wiederhergestellt habe.

Schritte für Heroku

  • Wartungsmodus aktivieren: heroku maintenance:on
  • Backup-Datenbank: heroku pg:backups:capture
  • Datenbank wiederherstellen: heroku pg:backups:restore
  • App neu starten: heroku restart
  • Wartungsmodus deaktivieren: heroku maintenance:off

0

Ich bin auch auf diesen Bug gestoßen. Für diejenigen, die ihre Datenbank nicht vollständig sichern / wiederherstellen möchten. Wisse, dass das einfache Kopieren der Tabelle funktioniert. Es gibt jedoch keine "magische" Möglichkeit, eine Tabelle zu kopieren. Ich habe es gemacht mit:

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;

Danach müssen Sie Ihre Indizes, Fremdschlüssel und Standardeinstellungen immer noch manuell neu erstellen. Durch diese Neuerstellung des Tisches verschwand der Fehler.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.