Skript zum Einlegen von Tabellen

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 Python-Profis,

ich bin absoluter Anfänger in Python (in Programmieren allgemein)

Nun muss ich in meinem Praktikum ein Skript schreiben, mit dem man die DB anlegen kann.
Die Daten, die zur Verfügung gestellt sind, haben einen sehr komischen Format: es gibt z. B. eine .GES Datei, die Beschreibung von den Tabellen beinhaltet, eine andere, die die Formate erklärt. Die Tabellen mit deren Attributen müssen auch aus so einer Datei ausgelesen werden. Das alles habe ich irgendwie hinbekommen. Nun stehe ich vor einem Problem, das ich mit eure Hilfe hoffentlich lösen kann:
und zwar mit einer for-Schleife kommt das Programm in ein Verzeichnis, wo alle Files liegen und liesst diese nach einander ein, dabei legt an und befüllt das nach und nach die Tabellen.

Das Problem ist, dass nachdem erste Tabelle angelegt und befüllt ist, und es die zweite Datei bearbeitet, sagt es beim Anlegen/Befüllen:

Error FEHLER: Wert zu lang für Typ character(3)

Exit code: 1

wenn ich aber, das wieder laufen lasse, wird die Tabelle reibungslos angelegt, die nächste aber wieder nicht

ich wusste nicht, wie mein code poste :)

Code: Alles auswählen

#-------------------------------------------------------------------------------
# Name:        module1
# Purpose:
#
# Author:      
#
# Created:     16.10.2012
# Copyright:   (c)2012
# Licence:     <your licence>
#-------------------------------------------------------------------------------
import re, psycopg2, sys

FREE_WHEEL = re.compile
TABLE_START = re.compile(r'00K')
CREATE_ATTR = re.compile(r'00F$')
FILL_ATTR = re.compile(r'00I$')
END = re.compile(r'00E$')
INPUT_R1 = re.compile(r'01([\w\d_]+)')
INPUT_R2 = re.compile(r'02([\w\d_]+)')
INPUT_R3 = re.compile(r'03([\w\d_]+)')
INPUT_R4 = re.compile(r'04([\w\d_]+)')
INPUT_R5 = re.compile(r'05([\w\d_]+)')
INPUT_R6 = re.compile(r'06*([\w\d_]+)')
INPUT_R8 = re.compile(r'08([\w\d_]+)')
INPUT_RNUM = re.compile(r'[0-9][0-9]([\w\d_]*)')


class Attribute(object):
    id=""
    name=""
    key=""
    fill=""
    typ=""
    length=""

    def __init__(self,id,name,key,fill,typ,length):
        self.id=id
        self.name=name
        self.key=key
        self.fill=fill
        self.typ=typ
        self.length=length

  #  def make_attr_list(id,name,key,fill,typ,length):
   #     list=[]
    #    list.append(id,name,key,fill,typ,length)

class Steuerung(object):

    table_name = ""
    number_of_attr = 0
    attribut_id = []
    attribut_name = []
    is_key = []
    be_NULL = []
    attribut_typ = []
    field_length = []
    data_list = []
    p_keys = []
       # print (table_name +" "+ attribut+" "+ data)
    sqlBuffer= ""
    def __init__(self):

        self.state = 'Start'
        print self.state

        self.f = dict({
        'Start':[[TABLE_START,'K-Zustand',None],[INPUT_RNUM,'Error',None],[FILL_ATTR,'Error',None],[CREATE_ATTR,'Error',None]],
        'K-Zustand':[[INPUT_R1,'TABLE_NAME',get_table_name]],
        'TABLE_NAME':[[INPUT_R6,'FULL_NAME',get_table_full_name],[INPUT_RNUM,'TABLE_NAME',None]],
        'FULL_NAME':[[INPUT_R8,'NUMBER_of_ATTR',get_number_of_attr],[INPUT_RNUM,'FULL_NAME',None]],
        'NUMBER_of_ATTR':[[CREATE_ATTR,'F-Zustand',None],[INPUT_RNUM,'NUMBER_of_ATTR',None]],
        'F-Zustand':[[INPUT_R1,'ATTR_ID',get_attr_ID]],
        'ATTR_ID':[[INPUT_R2,'ATTR_NAME',get_attr_name]],
        'ATTR_NAME':[[INPUT_R3,'PRIM_KEY',get_prim_key]],
        'PRIM_KEY':[[INPUT_R4,'FILL_NESS',get_fill_ness]],
        'FILL_NESS':[[INPUT_R5,'FIELD_TYP',get_field_typ]],
        'FIELD_TYP':[[INPUT_R6,'FIELD_LENGTH',get_field_length]],
        'FIELD_LENGTH':[[FILL_ATTR,'I-Zustand',None],[CREATE_ATTR,'F-Zustand',None],[INPUT_RNUM,'FIELD_LENGTH',None]],
        'I-Zustand':[[END,'End',None],[TABLE_START,'K-Zustand',None],[FILL_ATTR,'I-Zustand',None],[INPUT_RNUM,'I-Zustand',feel_attributes]],
        'End':[[TABLE_START,'K-Zustand',None],[INPUT_RNUM,'End',None]]
        })


   # list_a=[]

    def handle_input(self,input):
        typ_1 = ['U','V']

        state,process = self.parseInput(input)
        if(process != None):
          process(input)

        if state == 'Error':
            print('Error!')
        self.state=state

        if process == get_table_name:
            self.table_name = input[2:]
            return self.table_name
        elif process == get_number_of_attr:
            self.number_of_attr = input[2:]
            return self.number_of_attr
        elif process == get_attr_ID:
            self.attribut_id.append(input[2:])
            return self.attribut_id
        elif process == get_attr_name:
            self.attribut_name.append(input[2:])
            return self.attribut_name
        elif process == get_prim_key:
            if input[2:] == '1':
                self.is_key.append('1')
            else:
                self.is_key.append('0')
            return self.is_key
        elif process == get_fill_ness:
            if input[2:] == '0':
                self.be_NULL.append('NOT NULL')
            else:
                self.be_NULL.append(' ')
            return self.be_NULL
        elif process == get_field_typ:
            if input[2:] == 'U' or input[2:] == 'V':
                self.attribut_typ.append('varchar')
            else:
                self.attribut_typ.append('char')
            return self.attribut_typ
        elif process == get_field_length:
            if input[2:]=='':
                self.field_length.append('100')
            else:
                self.field_length.append(input[2:])
            return self.field_length
        elif process == feel_attributes:
            self.data_list.append(input[2:])
            return self.data_list


    def parseInput(self,input):
        for tuple in self.f[self.state]:
                regexp = tuple[0]
                if regexp.match(input):
                    return (tuple[1],tuple[2])
        expected = []
        for tuple in self.f[self.state]:
            expected.append(tuple[0].pattern)
        print "In state "+self.state+": Expected one of"+str(expected)+" but got "+input
        return ('Error',None)

    def isFinished(self):
        return self.state=='End' or self.state=='Error'

    def iterChunks(self, chunk_size):
        res = []
        for item in self.data_list:
            res.append(item)
            if len(res) >= chunk_size:
                yield res
                res = []
        if res:
            yield res  # yield the last, incomplete, portion

    def typ(attribut_typ):
        if attribut_typ == 'U' or attribut_typ == 'V':
            attribut_typ = 'varchar'
        else:
            attribut_typ = 'char'
        return attribut_typ


def get_table_name(input):
    pass

def get_table_full_name(input):
    pass

def get_number_of_attr(input):
    pass

def get_attr_ID(input):
    pass

def get_attr_name(input):
    pass

def get_prim_key(input):
    pass

def get_fill_ness(input):
    pass

def get_field_typ(input):
    pass

def get_field_length(input):
    pass

def feel_attributes(input):
    pass

Code: Alles auswählen

#-------------------------------------------------------------------------------
# Name:        module2
# Purpose:
#
# Author:      
#
# Created:     20.11.2012
# Copyright:   (c) 2012
# Licence:     <your licence>
#-------------------------------------------------------------------------------

from table_sample_sm import Steuerung
from table_sample_sm import Attribute
import psycopg2
import sys, os
import string

def main():
    pass
def quote(string):
    return "'"+string.replace("'","''")+"'"

def replace_all(text, d):
	    for i, j in d.iteritems():
	        text = text.replace(i, j)
	    return text


if __name__ == '__main__':

    m = Steuerung()

    for file in os.listdir('E:\\abdata\\abdamed\\TESTDATEN_ABDAMED\\kkk\\'):
        print file
        infile = open('E:\\abdata\\abdamed\\TESTDATEN_ABDAMED\\kkk\\'+file, 'r')
        for line in infile:
         #   print line
            m.handle_input(line.strip())

            if m.isFinished():
                print "Parsing finished."
                break

        attribute_koplett = []
        attribute_koplett = zip(m.attribut_id,m.attribut_name,m.is_key,m.be_NULL,m.attribut_typ,m.field_length)
        #print "table_name: "+ m.table_name
        #print "there are: "+ str(m.number_of_attr)
        #print attribute_koplett

        attrib =''
        for a in m.attribut_name:
            attrib+=a+','
        #print attrib[:-1]

        #attrib = m.attribut_name
        m.attribut_id = []
        m.attribut_name = []
        m.is_key = []
        m.be_NULL = []
        m.attribut_typ = []
        m.field_length = []

        ucode = {'\\':'', 'A25' : u'\xc4','a25' : u'\xe4', 'O25' : u'\xd6', 'o25' : u'\xf6',
        'U25' : u'\xdc', 'u25' : u'\xfc', 's39': u'\xDF'}

        j = int(m.number_of_attr)
        v = zip(*[iter(m.data_list)]*j)
        #print v


        con = None

        try:

            con = psycopg2.connect(database='postgres', user='postgres')
            cur = con.cursor()

            attributes = ''
            keys = ''

            for i in attribute_koplett:
                attributes+=(i[1])+' '+str(i[4])+'('+str(i[5])+') '+str(i[3])+','
                if str(i[2]) == '1':
                    keys+=str(i[1])+','

            print 'creating table: '+ m.table_name

            cur.execute('CREATE TABLE IF NOT EXISTS '+str(m.table_name)+'('+attributes+'PRIMARY KEY '+'('+keys[:-1]+')'+')')
            for a in v:
                 aaa= ",".join(quote(x) for x in a)
                 txt = replace_all(aaa,ucode)
                 cur.execute ('INSERT INTO '+str(m.table_name)+'('+str(attrib[:-1])+') values (' + txt + ')')
                 #print txt
            con.commit()

        except psycopg2.DatabaseError, e:
            print 'Error %s' % e
            sys.exit(1)


        finally:

            if con:
                con.close()

            infile.close()
            os.remove("E:\\abdata\\abdamed\\TESTDATEN_ABDAMED\\kkk\\"+file)
Wäre super, wenn ihr mir dabei hilft

Grüße Ferdinand
Zuletzt geändert von Anonymous am Montag 26. November 2012, 16:40, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
BlackJack

@fedjan: Du machst das mit der Objektorientierung falsch. Statt Klassenattributen möchtest Du Instanzattribute verwenden. Also all das was in Deinen Klassen am Anfang an die Klasse gebunden wird gehört in die `__init__` und dort an `self` gebunden.

Statt die Attribute von aussen zu ”resetten” solltest Du auch einfach ein neues Parser-Objekt erstellen (der Typ der bei Dir aus mir nicht nachvollziehbaren Gründen `Steuerung` heisst).

`Attribute` wird gar nicht verwendet‽

Vergiss Deine `quote()`-Funktion und verwende Platzhalter für die Werte und übergib die Werte als zusätzliches Argument an `execute()`.

Zusammengehörende Daten sollte man nicht in ”parallelen” Datenstrukturen halten. Diese ganzen Listen in `Steuerung` sollte man durch eine Liste mit Elementen ersetzen, die alle Informationen für jeweils ein Feld enthalten. Dafür war die `Attribute`-Klasse ja wohl auch mal gedacht.
Benutzeravatar
sparrow
User
Beiträge: 4164
Registriert: Freitag 17. April 2009, 10:28

Offensichtlich hat du eine Spalte vom Type char(3) angelegt, also eine Spalte die 3 Buchstaben enthalten darf. Wenn du versuchst etwas einzufügen, das mehr Buchstaben hat, sagt dir postgresql netterweise, dass das nicht geht.

Code: Alles auswählen

postgres=# CREATE TABLE test2 (a char(3));
CREATE TABLE
postgres=# INSERT INTO test2 (a) VALUES ('hu');
INSERT 0 1
postgres=# INSERT INTO test2 (a) VALUES ('h');
INSERT 0 1
postgres=# INSERT INTO test2 (a) VALUES ('ulfa');
FEHLER:  Wert zu lang für Typ character(3)
postgres=#
Edit: Oh, übrigens habe ich im Gegensatz zu BlackJack nicht deine 5 Tapeten Quelltext gelesen, sondern bei der Fehlermeldung aufgehört ;)
fedjan
User
Beiträge: 14
Registriert: Montag 26. November 2012, 16:12

Danke erstmal für eures Reagieren)))

@ sparrow:
das ist mir schon klar, ich es ja beschreiben, dass wenn ich noch mal auf ausführen drücke, dann die Tabelle angelegt, also da ist wo anders ein fehler

@ BlackJack:
du hast absolut recht, was zb die Klasse Attributes angeht, ich habe es im Laufe des Skriptentwicklung 1000 mal andersüberlegt (wie gesagt, ich bin ein richtiger Anfänger)

das mit Attribute resetten: wie kann ich ihm den sagen, das er jetzt ein neues(leeres) objekt erstellen soll?

Danke
fedjan
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

übrigens, ganz gefährlich:
sparrow hat geschrieben:

Code: Alles auswählen

class Steuerung(object):

    table_name = ""
    number_of_attr = 0
    attribut_id = []
    attribut_name = []
    is_key = []
    be_NULL = []
    attribut_typ = []
    field_length = []
    data_list = []
    p_keys = []
Deine ganzen Listen hier werden von allen Steuerung-Objekten geteilt.

Code: Alles auswählen

>>> class Steuerung(object):
...     keys = []
...     def add(self, key):
...         self.keys.append(key)
...     def has_key(self, key):
...         return key in self.keys
... 
>>> s1=Steuerung()
>>> s2=Steuerung()
>>> s1.add('key1')
>>> s2.has_key('key1')
True
BlackJack

@fedjan: Du erstellst ja jetzt einmal vor der Schleife das Objekt. Stattdessen müsstest Du es in der Schleife am Anfang von jedem Durchlauf erstellen.

Bei der Aufteilung des Codes würde ich auch noch die Verarbeitung der Zeilen in die Parser-Klasse verschieben. Und dafür dann eine `classmethod()` anlegen.
fedjan
User
Beiträge: 14
Registriert: Montag 26. November 2012, 16:12

Danke vielmals!!!
Ich probiere es heute aus!!

Grüße
Fedjan
BlackJack

@fedjan: Das neu anlegen des Parsers sollte übrigens das Problem lösen. Um dem auf die Spur zu kommen, hättest Du Dir die Daten mal anschauen können, die da pro Schleifendurchlauf im Hauptprogramm in die Tabelle eingefügt werden sollten. Die Anzahl der Datensätze mit der Anzahl der Daten in den beiden Dateien zu vergleichen hätte wahrscheinlich schon genügt.

Wenn man ein komplexeres Objekt manuell in einen Ausgangszustand zurücksetzt statt einfach ein neues zu verwenden, besteht halt immer die Gefahr, dass man irgend etwas vergisst zurückzusetzen. :-)
fedjan
User
Beiträge: 14
Registriert: Montag 26. November 2012, 16:12

Danke nochmal,

ich habe das Problem gelösst, und zwar müsste ich die Liste von Datensätzen auch zurücksetzen :)
BlackJack

@fedjan: Müsstest Du. Machst Du hoffentlich aber nicht. Wie gesagt, es ist sauberer einfach ein neues Objekt zu verwenden, statt ein komplexes Objekt zurück zu setzen. Für den Programmablauf kommt das vom Aufwand her ungefähr auf's selbe hinaus, nur der Quelltext ist komplizierter.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Hier mal eine Anregung wie Dein __main__ aussehen sollte:

Code: Alles auswählen

import os
if __name__ == '__main__':
    BASEDIR = r'E:\abdata\abdamed\TESTDATEN_ABDAMED\kkk'

    for filename in os.listdir(BASEDIR):
        print filename
        insert_filecontent_into_database(os.path.join(BASEDIR,filename))
Jede Funktion sollte nur Funktionen genau einer Komplexitätsstufe
niedriger aufrufen. insert_filecontent_into_database ist hier die am meisten
abstrahierte Funktion. Sie ruft nur den Parser und eine komplexe Datenbankfunktion auf:

Code: Alles auswählen

def insert_filecontent_into_database(filename):
    with open(filename, 'r') as infile:
        m = Steuerung(infile) # hier bekommt __init__ einen Iterator der Zeilenweise geparst wird.
    print "Parsing finished."

    with Database() as db:
        table = db.create_table(m.table_name, m.attributes)

        for data in m.iter_data():
            table.insert(data)
    os.remove(filename)
Dabei solltest Du darauf achten, dass die einzelnen Funktionen
vollständig getrennt voneinander sind. Wie der Parser "Steuerung"
intern funktioniert ist völlig egal, nur die Schnittstelle ist wirklich festgelegt:
input: irgendwas das Strings produziert (z.B. file-Objekt)
output: Tabellenname, Attribute, Daten
Das Datenbank-Objekt bekommt dagegen Tabellennamen, Attribute, Daten.
Woher sie stammen ist ihm egal.
Vorteile:
Der Parser kann, und sollte auch, unabhängig von der Datenbank getestet
werden. Das solltest Du auch. Dazu schreibst Du ein neues Programm das
nur Testdaten dem Parser füttert und das Ergebnis analysiert. Dabei sollten
alle Fälle des Parsers abgedeckt werden.
Übersichtlichkeit, Verständlichkeit, Erweiterbarkeit, Wiederverwendbarkeit, ...

Grüße
Sirius
fedjan
User
Beiträge: 14
Registriert: Montag 26. November 2012, 16:12

Vielen dank Sirius3!!
Das versuche ich heute umzusetzen!!!

Noch eine kurze Frage:

Es gibt auch Dateien, die UPDATE_Funktion haben:
da ist wieder gleiche Aufbau mit Tabelle, Attribute, dann die Inserts, die neu dazu kommen, das ist alles klar, läuft genau so ab, wie vorher.
Nun kommen noch die Update-Daten in Form:
01123
02drtjutrfdj
09xhjxfj
01258
08dzkj666
usw.

Wenn ich diese einlese (über meinen Zustandsautomat), bekomme ich eine Liste l=['01123','02drtjutrfdj','09xhjxfj','01258','08dzkj666']

Listenelement 01... ist meistens pk, dh ich muss über die liste parsen und diese in Teile zerlegen:
zB: l=[('01123','02drtjutrfdj','09xhjxfj'),('01258','08dzkj666')]

da die Anzahl von Felder, die in jeder Spalte aktualisiert werden, unterschiedlich ist, finde ich keine Lösung, wie ich das machen kann!!!
BlackJack

@fedjan: Du könntest den Zustandsautomaten beziehungsweise die Verarbeitung im Vorhandenen auch erweitern.

Für das erstellen der Tabellen benötigst Du diesen Algorithmus ja eigentlich auch, denn streng genommen könnte es Dir dort passieren, dass die flache Datenliste nicht durch die Anzahl der Attribute in der Tabelle teilbar ist. Wenn ein Wert auch NULL sein darf, muss man ihn beim Einfügen nicht explizit angeben. Das heisst, wenn das robust laufen soll, müsste man nicht nur die Werte speichern, sondern auch zu welcher Spalte der Wert gehört – ich vermute jetzt mal das ist die Bedeutung der Präfix-Zahlen wenn man sich im I-Zustand befindet.

Apropos I-Zustand: Wenn man sich den Zustandsautomaten aus Deinem ersten Beitrag mal anschaut, dann sieht das so aus als wenn innerhalb dieses Zustands Muster die mit '00I' anfangen, einfach ignoriert werden (graue Kanten → Funktion der Kante ist `None`):
Bild
Kann es sein, dass Du an dieser Stelle Information ignorierst, die man prima verwenden könnte um einzelne Datensätze abzugrenzen‽

Den 'Error'-Zustand würde ich rauswerfen. Alles was in eine Sackgasse führt ist ein Fehler. Zudem sind die Kanten auch redundant, denn '00I$' und '00F$' sind in dem allgemeinen Muster '[0-9][0-9]([\w\d_]*)' inbegriffen. Und die Behandlung von dem Zustand ist auch unsinnig. Wenn man in einen Fehler läuft, kann man doch nicht einfach so weiter machen als sei nichts geschehen.

Das Muster für den Übergang von 'FIELD_TYP' zu 'FIELD_LENGTH' sieht mir falsch aus. Man kann es auf '0[\w\d_]{2,}' vereinfachen und das fällt ziemlich aus dem Rahmen wenn man es mit den anderen vergleicht.

Der Zustandsautomat enthält Übergänge mit denen Dein Programm soweit ich das sehe, nicht umgehen kann. Und zwar die von 'I-Zustand' und 'End' zurück zum 'K-Zustand'. Darauf ist Dein Programm nicht vorbereitet. Falls so etwas in den Daten tatsächlich vorkommen kann, musst Du das Programm anpassen. Ansonsten solltest Du die Übergänge rauswerfen.

Die Umsetzung des Automaten ist vorsichtig ausgedrückt sehr eigenartig. Funktionen die alle ein Argument entgegennehmen, aber nichts tun, werden nur zum Vergleichen hergenommen um darauf hin Code auszuführen. Dieser Code gehört in die Funktionen und die gehören als Methoden auf das Parser-Objekt. Dann kann man sich diese ganze lange ``if``/``elif``-Kaskade sparen. Letztlich ist die Zeichenkette redundant wenn man die Funktion selbst als Zustand verwendet.

Grundsätzlich würde ich den Automaten in dieser Form in Frage stellen und an Deiner Stelle ein paar Fragen zum Eingabeformat klären.

1. Fangen alle Zeilen mit einer zweistelligen Zahl an? Dann wäre für mich der erste Verarbeitungsschritt aus den Zeilen Tupel mit (Zahl, Restzeile) zu machen.

2. Fängt jeder Block mit der Zahl 0 an? Dann würde ich die Eingabedaten an diesen Nullzeilen in Listen mit den Blockinhalten aufteilen. Wenn Du auf diese Daten einen Zustandsautomaten loslassen willst, dann hätte der deutlich weniger Zustände, nämlich neben 'Start' und 'End' noch die bei denen '-Zustand' im Namen vorkommt.

3. Gibt es innerhalb eines Blocks mehrfach die gleiche Zahl? Falls nicht, würde ich die Blöcke statt in Listen in Wörterbüchern (`dict`) speichern, weil mindestens bei den Update-Daten ja offenbar Lücken in den Zahlen sind.

Diese Blockdaten würden sich einfacher und vor allem übersichtlicher weiterverarbeiten lassen.
Antworten