Logfile in eine MSSQL Datenbank einlesen

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Das mit dem 'list()', war ein sehr guter tip. Die testweise Ausgabe funktioniert jetzt.

Allerdings habe ich gerade ein Problem mit pymssql.

folgender Code liefert keine Fehlermeldung, legt aber auch keine Tabelle an.

Code: Alles auswählen

#!/usr/bin/env python3

import os
import re
import sys
import pymssql
from contextlib import closing


conn = pymssql.connect(
        server='localhost',
        user='sa',
        password='xxx',
        database='F1'
        )

with closing(conn.cursor()) as cursor:
    cursor.execute("""
        CREATE TABLE main(
        zeit DATETIME,
        prom VARCHAR(8),
        message VARCHAR(14),
        did INTEGER,
        rufnummer VARCHAR(12),
        site VARCHAR(10)
        )
        """)
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Muss man eine Transaktion commiten?
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

vielleicht lese ich das auch falsch, allerdings wird hier:
sowas ähnliches gemacht, Transaktion in doppel quotes gesetzt.

Wenn ich

Code: Alles auswählen

with closing(conn.cursor()) as cursor:
    cursor.execute(
        'CREATE TABLE main(zeit DATETIME, prom VARCHAR(8), message VARCHAR(14), did INTEGER, rufnummer VARCHAR(12), site VARCHAR(10))')
verwende gibt es auch keine Tabelle, Fehlermeldung und auch im syslog wird kein zugriff auf die Datenbank registriert.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Mit folgendem Code klappt es auf anhieb.

Code: Alles auswählen

conn = _mssql.connect(
        server='localhost',
        user='sa',
        password='xxx',
        database='F1'
        )

conn.execute_non_query('CREATE TABLE main(zeit DATETIME, prom VARCHAR(8), message VARCHAR(14), did INTEGER, rufnummer VARCHAR(12), site VARCHAR(10))')

Wie kann ich prüfen ob pymssql richtig läuft?
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Die Dokumentation sieht die Verwendung des Contextmanagers wohl etwas anders vor:

Code: Alles auswählen

with pymssql.connect(server, user, password, "tempdb") as conn:
    with conn.cursor(as_dict=True) as cursor:
        ...
Und in dem von dir verlinkten Beispiel gibt es ein commit.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Ok, jetzt verstehe ich, was du mit, "commiten", meinst,......hoffentlich. :-)
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Code: Alles auswählen

cursor = conn.cursor()
cursor.execute('CREATE TABLE main(zeit DATETIME, prom VARCHAR(8), message VARCHAR(14), did INTEGER, rufnummer VARCHAR(12), site VARCHAR(10))')
conn.commit()
conn.close()
so gehts schon mal.

Danke für den Hinweis, Sparrow.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Aktuell ist mein Zwischenstand, dass alle relevanten Einträge in die Tabelle eingetragen werden und leider bei jeder Ausführung erneut.

Das soll doch durch "try and except" und "isinstance()" verhindert werden, oder?
Komme aktuell durch suchen nicht weiter.

Von meinem denken her, würde ich mit einer Abfrage die Zeilen der Tabelle in Strings einlesen und sie dann mit den match lines vergleichen.
Da allerdings täglich ca. 5 bis 8 tausend Einträge in die Datenbank kommen werden, wird das auf dauer sicherlich sehr Hardware intensiv.
Welche Möglichkeiten gibt es noch?

Kann mir das bitte nochmal jemand genauer erläurtern?

Code: Alles auswählen

#!/usr/bin/env python3
#
import os
import sys
import re
from collections import namedtuple
from contextlib import closing
from datetime import datetime as DateTime

import pymssql

LOG_PATH = '/var/log/ESI/ESI2'
LOG_FILENAME_TEMPLATE = os.path.join(LOG_PATH, 'F1log_{}.txt')

COMMON_PATTERN = r'^(\d+) (?P<zeit>\d{2}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) '
MAIN_RE = re.compile(COMMON_PATTERN + r'(?P<prom>\d{8}) (?P<message>.{14}) .+ N00 (?P<did>\d{4}) (?P<rufnummer>\d{8,12})? .+ ?SITE=(?P<site>\d{8,10})')
ERROR_RE = re.compile(COMMON_PATTERN + r'ISDN Fehlanruf C.00 (?P<did>\d{4}) (?P<rufnummer>\d{8,12})? ?Diag:(?P<message>[A-Z ]{14,18})')

Main = namedtuple('Main', 'zeit prom message did rufnummer site')
Error = namedtuple('Error', 'zeit prom message did rufnummer site')

server = 'localhost'
user = 'sa'
password = 'xxx'
database = 'F1'

def create_tables(connection):
    with closing(connection.cursor()) as cursor:
        cursor.execute(
                'CREATE TABLE main(zeit DATETIME, prom VARCHAR(8),'
                'message VARCHAR(18), did INTEGER, rufnummer VARCHAR(12)'
                'site VARCHAR(10))'
                )
    
def get_common(match):
    zeit = DateTime.strptime(match.group('zeit'), '%d/%m/%y %H:%M:%S')
    did = int(match.group('did'))
    rufnummer = match.group('rufnummer')
    return (zeit, did, rufnummer)

def parse_log(lines):
    for line in  lines:
        match = MAIN_RE.match(line)
        if match:
            zeit, did, rufnummer = get_common(match)
            message = match.group('message')
            prom = match.group('prom')
            site = match.group('site')
            yield Main(zeit, prom, message, did, rufnummer, site)
        else:
            match = ERROR_RE.match(line)
            if match:
                zeit, did, rufnummer = get_common(match)
                prom = None 
                site = None
                message = match.group('message')
                yield Error(zeit, prom, message, did, rufnummer, site)

def insert_entries(connection, entries):
    with closing(connection.cursor()) as cursor:
        for entry in entries:
            if isinstance(entry, Main):
                cursor.execute(
                        'INSERT INTO main (zeit, prom, message, did, rufnummer, site)'
                        'VALUES (%s, %s, %s, %s, %s, %s)',
                        entry
                        )
            elif isinstance(entry, Error):
                cursor.execute(
                        'INSERT INTO main (zeit, prom, message, did, rufnummer, site)'
                        'VALUES (%s, %s, %s, %s, %s, %s)',
                        entry
                        )

def main():
    log_filename = LOG_FILENAME_TEMPLATE.format(sys.argv[1])
    with open (log_filename) as lines:
        connection = pymssql.connect(
                server, user, password, database
                )
        with closing(connection):
            try:
                insert_entries(connection, parse_log(lines))
            except:
                connection.rollback()
                raise
            else:
                connection.commit()

main()
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Warum sollte isinstance das verhindern? isinstance dient der Unterscheidung in welcher Tabelle ein Eintrag gemacht wird. Da isinstance aber keine Eigenschaften der Objekte beruecksichtigt jenseits ihrer Klasse, verhindert es auch keine Mehrfachanlage. Und warum sollte das try/except daran etwas aendern? Das wuerde - wenn es greift - den *GESAMTEN* Prozess des eintragens von Daten rueckgaengig machen. Ebenfalls nicht pro Eintrag irgendetwas pruefen.

Deine Tabellen enthalten keine Kriterien, nach denen die Eindeutigkeit eines Eintrags bestimmt wird. Also meckert die Datenbank auch nicht, wenn es zu einem Doppeleintrag kommt. Wodurch bestimmt sich fuer dich, ob ein Eintrag schonmal vorgekommen ist?
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Deine Tabellen enthalten keine Kriterien, nach denen die Eindeutigkeit eines Eintrags bestimmt wird. Also meckert die Datenbank auch nicht, wenn es zu einem Doppeleintrag kommt. Wodurch bestimmt sich fuer dich, ob ein Eintrag schonmal vorgekommen ist?
Wenn exakt die gleiche zeile schon in der Datenbank vorkommt, also jede Zelle der Zeile schon mal eingetragen wurde.
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dann musst du das so definieren, indem du einen UNIQUE-Constraint einfuehrst, der alle Spalten umfasst. Danach solltest du zumindest einen Fehler bekommen, wenn du versuchst, die gleiche Zeile zweimal anzulegen. Und dann geht's weiter, du musst dein Skript so umschreiben, dass es entweder alles zeilenweise einfueght, committet oder zurueckrollt, oder du versuchst dich an Mechanismen wie sie hier https://stackoverflow.com/questions/209 ... -not-exist beschrieben werden.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Also müsste man jeder Zeile das Attribut, "unique" geben, damit es eine Fehlermeldung gibt, auf grund derer man dann die eintragung dieser Zeile skippen kann?
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@fredvonfat: nein. Du mußt alle Felder zusammen einen UNIQUE-Constraint geben, weil nur alle Felder zusammen eindeutig sein müssen.

Inzwischen hast Du es ja geschafft, dass Fehlerzeilen und normale Zeilen das gleiche Schema teilen, dann ist aber eine Unterscheidung zwischen Error und Main unsinnig.

Wenn man garantieren kann, dass nachträglich keine weiteren Einträge hinzukommen, wäre das einfachste Kriterium das Datum, so dass man alle Einträge skippen kann, die älter sind als der neuste Eintrag in der Datenbank.
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

nzwischen hast Du es ja geschafft, dass Fehlerzeilen und normale Zeilen das gleiche Schema teilen, dann ist aber eine Unterscheidung zwischen Error und Main unsinnig.
Ja, daran hatte ich auch schon gedacht.

Würde es reichen, wenn ich "def insert_entries" anpasse? In "def parse_log" benötige ich die Unterscheidung ja noch.
Wenn man garantieren kann, dass nachträglich keine weiteren Einträge hinzukommen, wäre das einfachste Kriterium das Datum, so dass man alle Einträge skippen kann, die älter sind als der neuste Eintrag in der Datenbank.
Leider reicht das nicht aus, da der Zeitstempel durchaus gleich sein kann, allerdings ist dann mindestens eines der anderen Felder anders.

Auszuschließen ist, dass Zeit und Rufnummer bei zwei aufeinander folgenden Einträgen gleich ist.
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dann eben der UNIQUE-constraint ueber die notwendigen Spalten. https://stackoverflow.com/questions/347 ... le-columns
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Nach einigem Experimentieren stellt sich heraus, dass es tatsächlich sämtliche Konstellationen gibt, so dass ich die komplette Zeile auswerten muss....

Bei nachfolgenden Code bekomme ich
102, b"Incorrect syntax near 'LIMIT'.DB-Lib error message 20018,

Code: Alles auswählen

with pymssql.connect(server, user, password, database) as conn:
    with conn.cursor(as_dict=True) as cursor:
        cursor.execute('SELECT * FROM main ORDER BY zeit LIMIT 1')
        for row in cursor:
            print("%s %s %s %s %s %s" % 
                    (
                        row['zeit'],
                        row['prom'],
                        row['message'],
                        row['did'],
                        row['rufnummer'],
                        row['site']))
                        
ohne Limit geht es...

Gibt es da eine alternative zu?
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wofuer soll das denn gut sein, das limit? Wenn du einen UNIQUE-Constraint hast, fragst du einfach alle Spalten ab, und dann kann genau eine oder keine Zeile zurueckkommen.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@fredvonfat: was versuchst Du denn da mit dem SELECT zu erreichen?
fredvonfat
User
Beiträge: 51
Registriert: Mittwoch 12. September 2018, 10:00
Wohnort: Berlin

Den letzten Eintrag in der DB mit den logfile lines vergleichen.
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Warum den letzten? Ich denke das reicht nicht, sondern es koennen mehrere pro Zeitpunkt sein? Hast du vorher doch noch extra ausgefuehrt?

Und wenn dich nur der neueste interessiert, sollte eigentlich MAX(zeit) klappen. Das gibt dann nur eine Zeile als Ergebnis.
Antworten