Wie aktualisiere ich ausgewählte Zeilen mit Werten aus einer CSV-Datei in Postgres?

74

Ich verwende Postgres und möchte eine große Update-Abfrage durchführen, die aus einer CSV-Datei abgerufen wird. Nehmen wir an, ich habe eine Tabelle, die vorhanden ist (id, banana, apple).

Ich möchte ein Update ausführen, das die Bananen und nicht die Äpfel ändert. Jede neue Banane und ihre ID befinden sich in einer CSV-Datei.

Ich habe versucht, mir die Postgres-Site anzusehen, aber die Beispiele bringen mich um.

user519753
quelle
Sie versuchen das nicht aus pgadmin3 heraus, oder? Sie benötigen wahrscheinlich eine Skriptsprache (z. B. Python, ...). Sie müssen auch klarstellen, was Sie unter "Aktualisieren" verstehen. Meine wilde Vermutung ist, dass Ihre CSV-Datei Elemente enthält, die sich möglicherweise in der Datenbank befinden oder nicht, und Sie müssen sie entweder EINFÜGEN oder AKTUALISIEREN - nur wenn es sich um Bananen handelt. Aber bitte klären.

Antworten:

156

COPYdie Datei in eine temporäre Staging-Tabelle und aktualisieren Sie die tatsächliche Tabelle von dort. Mögen:

CREATE TEMP TABLE tmp_x (id int, apple text, banana text); -- but see below

COPY tmp_x FROM '/absolute/path/to/file' (FORMAT csv);

UPDATE tbl
SET    banana = tmp_x.banana
FROM   tmp_x
WHERE  tbl.id = tmp_x.id;

DROP TABLE tmp_x; -- else it is dropped at end of session automatically

Wenn die importierte Tabelle genau mit der zu aktualisierenden Tabelle übereinstimmt, kann dies praktisch sein:

CREATE TEMP TABLE tmp_x AS SELECT * FROM tbl LIMIT 0;

Erstellt eine leere temporäre Tabelle, die ohne Einschränkungen der Struktur der vorhandenen Tabelle entspricht.

Privilegien

SQL COPYerfordert hierfür Superuser-Berechtigungen. ( Das Handbuch ):

COPY Das Benennen einer Datei oder eines Befehls ist nur Datenbank-Superusern gestattet, da damit jede Datei gelesen oder geschrieben werden kann, auf die der Server zugreifen darf.

Der psql Meta-Befehl \copyfunktioniert für jede db Rolle. Das Handbuch:

Führt eine Frontend-Kopie (Client) durch. Dies ist eine Operation, die einen SQL- COPYBefehl ausführt. Statt dass der Server die angegebene Datei liest oder schreibt, liest oder schreibt psql die Datei und leitet die Daten zwischen dem Server und dem lokalen Dateisystem weiter. Dies bedeutet, dass Dateizugriff und Berechtigungen die des lokalen Benutzers und nicht des Servers sind und keine SQL-Superuser-Berechtigungen erforderlich sind.

Der Umfang temporärer Tabellen ist auf eine einzelne Sitzung einer einzelnen Rolle beschränkt, daher muss das oben Gesagte in derselben psql-Sitzung ausgeführt werden:

CREATE TEMP TABLE ...;
\copy tmp_x FROM '/absolute/path/to/file' (FORMAT csv);
UPDATE ...;

Wenn Sie dies in einem Bash-Befehl skripten, müssen Sie alles in einem einzigen psql-Aufruf zusammenfassen. Mögen:

echo 'CREATE TEMP TABLE tmp_x ...; \copy tmp_x FROM ...; UPDATE ...;' | psql

Normalerweise benötigen Sie den Meta-Befehl \\, um zwischen psql-Meta-Befehlen und SQL-Befehlen in psql zu wechseln. Dies ist jedoch \copyeine Ausnahme von dieser Regel. Das Handbuch noch einmal:

Für den \copyMeta-Befehl gelten spezielle Parsing-Regeln . Im Gegensatz zu den meisten anderen Meta-Befehlen wird der gesamte Rest der Zeile immer als Argument von angesehen \copy, und in den Argumenten werden weder Variableninterpolation noch Backquote-Erweiterung durchgeführt.

Große Tische

Wenn die Importtabelle groß ist, kann es sich lohnen, temp_buffersdie Sitzung vorübergehend zu erhöhen (als erstes in der Sitzung):

SET temp_buffers = '500MB';  -- example value

Fügen Sie der temporären Tabelle einen Index hinzu:

CREATE INDEX tmp_x_id_idx ON tmp_x(id);

Und ANALYZEmanuell ausführen , da temporäre Tabellen nicht durch Autovakuum / Autoanalyse abgedeckt werden.

ANALYZE tmp_x;

Verwandte Antworten:

Erwin Brandstetter
quelle
Ja, schön. Ich neige mich immer zu den riesigen Maschinen, wenn die Dinge manchmal so einfach gemacht werden können.
@ user519753: Habe gerade einen neuen Begriff gelernt - und von dem, was ich im Internet sehe, ein "Danke!" ist in Ordnung. :)
Erwin Brandstetter
4
COPY tmp_x FROM '/absolute/path/to/file' (DELIMITER ';', HEADER TRUE, FORMAT CSV)hat besser für mich gearbeitet. Siehe ( postgresql.org/docs/9.1/static/sql-copy.html )
Taper
1
@taper: Normalerweise führe ich COPY ohne Parameter aus. Aber die Frage bezieht sich auf CSV, wie Sie vielleicht bemerkt haben.
Erwin Brandstetter
1
Dies funktionierte nur für mich (Postgres 9.3) nach dem Ersetzen USINGdurch FROMin der UPDATE-Statement
Artm
-1

Sie können den folgenden in Python geschriebenen Code ausprobieren. Die Eingabedatei ist die CSV-Datei, deren Inhalt Sie in der Tabelle aktualisieren möchten. Jede Zeile wird anhand von Kommas aufgeteilt, sodass für jede Zeile Zeile [0] der Wert in der ersten Spalte, Zeile [1] der Wert in der zweiten Spalte usw. ist.

    import csv
    import xlrd
    import os
    import psycopg2
    import django
    from yourapp import settings
    django.setup()
    from yourapp import models


    try:
       conn = psycopg2.connect("host=localhost dbname=prodmealsdb 
       user=postgres password=blank")
       cur = conn.cursor()

       filepath = '/path/to/your/data_to_be_updated.csv'
       ext = os.path.splitext(filepath)[-1].lower()
       if (ext == '.csv'): 
          with open(filepath) as csvfile:
          next(csvfile)
          readCSV = csv.reader(csvfile, delimiter=',')
          for row in readCSV:
              print(row[3],row[5])
              cur.execute("UPDATE your_table SET column_to_be_updated = %s where 
              id = %s", (row[5], row[3]))
              conn.commit()
          conn.close()
          cur.close()

    except (Exception, psycopg2.DatabaseError) as error:
    print(error)
    finally:
    if conn is not None:
      conn.close()
Anupama V Iyengar
quelle