Update Table (python + postgresql)

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
fedjan
User
Beiträge: 14
Registriert: Montag 26. November 2012, 16:12

Hallo zusammen,

ich habe ein Problem. Ich habe einen Skript geschrieben, der eine Tabelle update-tet:
Ausschnitt:

Code: Alles auswählen

while(string.find(line, '00U') > -1):
                sqlStatementWhere = 'WHERE '
                attrib_spalte = 1
                kc = 0
                while kc < keysCounter:
                    kc      += 1
                    line    = infile.readline()
                    keyNr   = (int(line[:2])-1)   # first element has index '0'
                    #print keyNr
                    keyVal  = line[2:]
                    keyVal_D_1 = replace_all(keyVal, ucode)
                    keyName = attribute_koplett[keyNr][attrib_spalte]
                    keyName_D_1 = replace_all(keyName, ucode)
                    sqlStatementWhere += (keyName_D_1 + " = '" + keyVal_D_1+"'")

                #print sqlStatementWhere

                while(string.find(line, '00U') < 0) and (string.find(line, '00E') < 0) :
                    line    = infile.readline()
                    keyNr   = (int(line[:2])-1)
                    keyVal  = line[2:]
                    keyVal_D_2 = replace_all(keyVal, ucode)
                    if keyNr != -1:
                        keyName = attribute_koplett[keyNr][attrib_spalte]
                        keyName_D_2 =replace_all(keyName, ucode)
                        #print keyName_D
                        sqlStatement = ("UPDATE "+m.table_name+" SET " + keyName_D_2 + " = '"+keyVal_D_2+"' " + sqlStatementWhere)
                        print sqlStatement
                        cur.execute (sqlStatement)
                        con.commit()
Die Ausgabe von sqlStatement sieht so aus:

Code: Alles auswählen

UPDATE ADF_MED SET Kurzname = 'Schwarzwald-Apotheke (Schonach)
' WHERE Key_ADF = '29530
'
UPDATE ADF_MED SET Telefon = '07722/9648980
' WHERE Key_ADF = '29530
'
Die Tabelle wird aber nicht aktualisiert. Kann mir jemand sagen, woiran das liegen kann?? Wegen 'Hochkommas' vielleicht? Wie kriege ich die ans Ende der Zeile?

Danke
Fedjan
Zuletzt geändert von Anonymous am Freitag 14. Dezember 2012, 11:54, insgesamt 1-mal geändert.
Grund: Code-Formatierung korrigiert
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Du solltest Zeichenketten nicht mit + verbinden, sondern mit Python-Methoden formatieren. Dann ist das auch mit dem Hochkomma kein Problem, denn im Augenblick sieht das verdächtig so aus, als wenn da ein Zeilenumbruch in der Zeichenkette ist, und damit wird natürlich der Datensatz nicht gefunden.

Eigentlich solltest du sogar die Methoden der Schnittstelle verwenden, um Parameter zu setzen, um SQL-Injections zu vermeiden. Allerdings funktioniert das nur bei Parametern, nicht um Sachen wie den Tabellennamen, die sollten eigentlich nie variabel sein.
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

sparrow hat geschrieben:Allerdings funktioniert das nur bei Parametern, nicht um Sachen wie den Tabellennamen, die sollten eigentlich nie variabel sein.
Du hast völlig recht. Wenn man variable Tabellennamen verwendet macht man entweder Datenbank-Metaprogrammierung oder einen Fehler.

Hier sieht das nach zweiterem aus.
fedjan
User
Beiträge: 14
Registriert: Montag 26. November 2012, 16:12

Hallo
danke fürs schnelle Reagieren.

Das Problem ist, dass ich einen Skript für alle Tabelle (ca.50) nutzen will, deshalb verwende ich variablen Tabellename.

Heisst es jetzt, dass ich eine Methode für "sqlStatement" nutzen soll? wie gesagt, ich bin nicht so stark in programmieren))

Grüße
Fedjan
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Du solltest dir meinen Link anschauen und erst einmal die Zeichenketten wie dort beschrieben formatieren. Offensichtlich stimmt ja etwas mit dem Statement nicht, dass du an die Datenbank schickst. Nämlich der Zeilenumbruch in der Zeichenkette für die Where-Bedingung.
Wahrscheinlich sind diese Zeilenumbrüche bereits in den Ausgangsdaten. Liest du das aus einer Datei und nimmst den Zeilemumbruch mit? .strip() hilft das, und überflüssige Leerzeichen am Anfang und Ende der Zeichenkette, zu entfernen.

Update kann mehrere Felder je Datensatz updaten.
UPDATE table SET feld1=xxx, feld2=xxx, feld3=xxx WHERE xxx;
Zuletzt geändert von sparrow am Freitag 14. Dezember 2012, 10:13, insgesamt 1-mal geändert.
fedjan
User
Beiträge: 14
Registriert: Montag 26. November 2012, 16:12

das man alle Updates in einem statement machen kann, weiss ich -> ich habe es aber wegen besonderer Datenstruktur, die ich habe so gelöst))) ob es so richtif und schön ist, ist andere Frage

mit .strip() hat es jetzt tatsächlich geklappt, die Datensätze wurden aktualisiert.

Vielen Dank!!!!!!!!!!
BlackJack

@fedjan: Du solltest trotzdem die Werte nicht selbst in die Zeichenkette hinein formatieren. Selbst wenn man SQL-Injection nahezu ausschliessen kann, können immer noch normale Daten dabei sein, die zu einem Syntaxfehler in der SQL-Anweisung führen oder andere Probleme verursachen. Da scheinen ja zum Beispiel (Firmen)Namen in den Daten zu sein. "Peterchen's Dorfapotheke" würde beispielsweise ein Problem darstellen.

Das `string`-Modul sollte man nun seit sehr vielen Jahren schon nicht mehr für Funktionen verwenden, die es auch als Methoden auf Zeichenketten gibt. Ausserdem ist `find()` hier die falsche Wahl. Die Methode sollte man nur verwenden, wenn man tatsächlich den Index wissen möchte. Selbst dafür würde ich `index()` bevorzugen. Falls man nur wissen möchte ob eine Zeichenkette in einer anderen enthalten ist oder nicht, dann ist der ``in``- beziehungsweise ``not in``-Operator einfacher geschrieben und für den Leser auch offensichtlicher. Aber wenn ich Dein Datenformat noch richtig im Kopf habe, möchtest Du gar nicht wissen ob die Zeichenketten *irgendwo* vorkommen, sondern die stehen *immer* am Anfang der Zeile. Um das zu testen haben Zeichenketten eine eigene Methode: `startswith()`.

Die ``while``-Schleife mit `kc` in der Bedingung ist ziemlich eindeutig eine ``for``-Schleife.

Die `readline()`-Methode würde ich grundsätzlich meiden. Dateiobjekte sind iterierbar und haben eine `next()`-Methode die grundsätzlich das gleiche Ergebnis liefert. Allerdings sind die beiden Wege nicht zwingend kompatibel, dass heisst man sollte `readline()` und `next()` nicht mischen, da können Daten bei ”verschluckt” werden. Zumindest in CPython sollte `next()` effizienter sein und man kann das Dateiobjekt als ”iterable” verwenden. Zum Beispiel um in der `kc`-Schleife den unnötigen Zähler los zu werden in dem man direkt von der Datei mit `itertools.islice()` die gewünschte Anzahl von Zeilen abfragt.

Ich würde mal raten, dass diese Schleife bisher bei Dir noch nie mehr als einmal durchlaufen wurde. Dann produziert sie nämlich eine syntaktisch falsche 'WHERE'-Bedingung, denn die Einzelbedingungen müssten mit 'AND' verknüpft werden.

Letztlich ist der Code viel zu „direkt” und zu „kurzsichtig” oder „greedy”. Statt erst einmal die Daten aus der Eingabe zu extrahieren und zu sammeln, wird immer sofort versucht mit jedem kleinen Stück eine Ausgabe zu basteln. Das führt hier zu einem grossen, unübersichtlichen „Klumpen” Quelltext mit unnötigen Wiederholungen und einer gewissen Ineffizienz. Zum Beispiel das für einen Datensatz mehrere Spaltenänderungen einzeln ausgeführt und `commit()`\et werden. Und das Verarbeiten und aufteilen einer Zeile wird zumindest sehr ähnlich in dem gezeigten Quelltext zwei mal gemacht. Ich vermute mal dass das so ähnlich noch öfter an anderen Stellen im Quelltext wiederholt wird. So etwas gehört in eine Funktion ausgelagert. Beziehungsweise habe ich in einem anderen Thread ja schon mal die Vermutung geäussert, dass die Zeilen ganz grundsätzlich alle so aufgebaut sein könnten, dass am Anfang eine zweistellige Zahl steht. Dann sollte man das auch als allererstes und an *einer* Stelle im Quelltext verarbeiten.

Es gibt für meinen Geschmack relativ viele unnötige Klammern um Ausdrücke in dem Quelltext.

Ich lande dann bei dem Quelltext ungefähr bei so etwas (was immer noch kein gut organisierter Code ist):

Code: Alles auswählen

from itertools import chain, islice

ATTRIB_SPALTE = 1

# ...

def parse_line(line):
    return int(line[:2]), line[2:]


def get_attribute_name(attributes, index):
    return attributes[index - 1][ATTRIB_SPALTE]

# ...

            while line.startswith('00U'):
                where_conditions = list()
                for line in islice(infile, keysCounter):
                    index, value = parse_line(line)
                    name = get_attribute_name(attribute_koplett, index)
                    where_conditions.append((name, value))
                
                new_values = list()
                while not line.startswith(('00U', '00E')):
                    line = infile.next()
                    index, value = parse_line(line)
                    if index:
                        name = get_attribute_name(attribute_koplett, index)
                        new_values.append((name, value))
            
                sql = 'UPDATE {0} SET {1} WHERE {3}'.format(
                    m.table_name,
                    ', '.join(
                        '{0} = ?'.format(replace_all(n, ucode))
                        for n, _ in new_values
                    ),
                    ' AND '.join(
                        '{0} = ?'.format(replace_all(n, ucode))
                        for n, _ in where_conditions
                    )
                )
                all_values = chain.from_iterable(
                    (replace_all(v, ucode) for _, v in xs)
                    for xs in [new_values, where_conditions]
                )
                cursor.execute(sql, all_values)
                connection.commit()
Die Einrücktiefe der ersten ``while``-Schleife lässt allerdings Böses ahnen — nämlich dass Du den ganzen komplexen Zustandsautomaten aus dem Skript zum Einlegen von Tabellen-Thema jetzt in eine riesige Funktion gesteckt hast die *alles* macht. Also Eingabe, Verarbeitung, und Ausgabe in einem. Das ist so gar nicht das was man eigentlich möchte um den Überblick nicht zu verlieren. Das Programm könnte sicher auch von objektorientierter Programmierung profitieren, anstelle von verschachtelten Datenstrukturen mit magischen Indizes auf Werte in Listen.
fedjan
User
Beiträge: 14
Registriert: Montag 26. November 2012, 16:12

Hallo und danke euch allen

Grüße Fedjan
Antworten