Dubletten in MySQL verhindern

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
h0rnung
User
Beiträge: 46
Registriert: Mittwoch 28. Mai 2014, 11:41

Servus zusammen,

viele kennen mich schon mit meinem etwas dilettantischen Versuch Online Feeds zu streamen und in eine MySQL DB zu schreiben. So weit klappt alles ganz gut bis auf das Entfernen der Doubletten. Dieses kann problemlos nachträglich gemacht werden, ich hätte aber gerne einen Code der überflüssige Doubletten direkt erkennt und gar nicht er in die DB Tabelle einfügt.

Ausprobiert habe ich bisher folgendes im Create Table Statement:

(1) LSE_CommentNr integer, UNIQUE( LSE_CommentNr)

sowie

(2) LSE_CommentNr integer UNIQUE ON CONFLICT IGNORE,

Bei Variante (1) bekomme ich folgenden Fehlercode:

Code: Alles auswählen

Traceback (most recent call last):
File "C:/Python27/MySQL_finalversion/RSS_common_FV_LSE_v1.py", line 50, in <module>
main()
File "C:/Python27/MySQL_finalversion/RSS_common_FV_LSE_v1.py", line 45, in main
cur.execute("""INSERT INTO feeddata_lse_nodub(LSE_UnixTimesstamp, LSE_Timestamp, LSE_Source, LSE_Title, LSE_Text, LSE_Link, LSE_Epic, LSE_CommentNr, LSE_Author) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)""",item)
File "C:\Python27\lib\site-packages\MySQLdb\cursors.py", line 205, in execute
self.errorhandler(self, exc, value)
File "C:\Python27\lib\site-packages\MySQLdb\connections.py", line 36, in defaulterrorhandler
raise errorclass, errorvalue
IntegrityError: (1062, "Duplicate entry 'Mon, 14 Jul 2014 10:15:53 GMT-9385236' for key 'LSE_Timestamp'")
Bei Variante (2) bekomme ich folgenden Fehlercode:

Code: Alles auswählen

Warning (from warnings module):
  File "C:\Python27\MySQL_finalversion\RSS_common_FV_LSE_v1.py", line 16
    cur.execute("DROP TABLE IF EXISTS feeddata_lse_nodub")
Warning: Unknown table 'sentimentanalysis_unicode.feeddata_lse_nodub'

Traceback (most recent call last):
  File "C:\Python27\MySQL_finalversion\RSS_common_FV_LSE_v1.py", line 20, in <module>
    cur.execute(sql_lse)
  File "C:\Python27\lib\site-packages\MySQLdb\cursors.py", line 205, in execute
    self.errorhandler(self, exc, value)
  File "C:\Python27\lib\site-packages\MySQLdb\connections.py", line 36, in defaulterrorhandler
    raise errorclass, errorvalue
ProgrammingError: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CONFLICT IGNORE, LSE_Author varchar(255))' at line 1")
Mein kompletter Code sieht wie folgt aus:

Code: Alles auswählen

import feedparser
import MySQLdb
import time
from cookielib import CookieJar

db = MySQLdb.connect(host="localhost", # your host, usually localhost
                     user="root", # your username - SELECT * FROM mysql.user
                     passwd="****", # your password
                     db="sentimentanalysis_unicode",
                     charset="utf8") # name of the data base

cur = db.cursor()
cur.execute("SET NAMES utf8")
cur.execute("SET CHARACTER SET utf8")
cur.execute("SET character_set_connection=utf8")
cur.execute("DROP TABLE IF EXISTS feeddata_lse_nodub")

sql_lse = """CREATE TABLE feeddata_lse_nodub(LSE_ID INT NOT NULL AUTO_INCREMENT,PRIMARY KEY(LSE_ID), LSE_UnixTimesstamp integer, LSE_Timestamp varchar(255), LSE_Source varchar(255), LSE_Title varchar(255), LSE_Text TEXT,  LSE_Link varchar(255), LSE_Epic varchar(255), LSE_CommentNr integer, LSE_Author varchar(255))"""

cur.execute(sql_lse)

def feed_load_lse(feed_lse):
    return [(time.time(),
             entry.published,
             'lse',
             entry.title,
             entry.summary,
             entry.link,
             (entry.link.split('ShareTicker=')[1]).split('&post')[0],
             entry.link.split('post=')[1],
             entry.author)
            for entry
            in feedparser.parse(feed_lse).entries]
     
def main():
    feed_url_lse = "http://www.lse.co.uk/chat/recent/"
    try: feed_lse = feed_load_lse(feed_url_lse)
    except IndexError: error = 'ERROR'
    try:
        for item in feed_lse:
            cur.execute("""INSERT INTO feeddata_lse(LSE_UnixTimesstamp, LSE_Timestamp, LSE_Source, LSE_Title, LSE_Text, LSE_Link, LSE_Epic, LSE_CommentNr, LSE_Author) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)""",item)
        db.commit()
        print feed_lse[0][1]
    except: print error

if __name__ == "__main__":
    while True:
        main()
        time.sleep(10)
Wie immer herzlichen Dank :) und Grüße aus London

Ps: Ich weiss, dass meine split Befehle wie auch die Namensgebung meiner Spalten nicht optimal sind. Wer da Hinweise hat - ich freue mich über jede PN - dies soll aber nicht Teil der Fragestellung sein :)
BlackJack

@h0rnung: (2) ist so ganz einfach nicht möglich. Was in der MySQL-Dokumentation von `CREATE` hat Dich denn dazu verleitet?
h0rnung
User
Beiträge: 46
Registriert: Mittwoch 28. Mai 2014, 11:41

@BlackJack: Hab (2) als losgeloestes SQL Statement in google gefunden. Dass das nicht funktioniert, war mir nach der Error Nachricht schon fast klar.
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

h0rnung hat geschrieben:Bei Variante (1) bekomme ich folgenden Fehlercode:

Code: Alles auswählen

[...]
IntegrityError: (1062, "Duplicate entry 'Mon, 14 Jul 2014 10:15:53 GMT-9385236' for key 'LSE_Timestamp'")
Das ist doch genau das was du möchtest, oder? Der einzutragende Eintrag wäre doppelt und du bekommst daher einen IntegrityError. Den Fehler kannst du jetzt gezielt abfangen und ignorieren oder protokollieren.
BlackJack

@/me: h0rnung möchte viele Datensätze mit `executemany()` einfügen und das Doubletten dabei einfach ignoriert, also nicht eingefügt werden. Sonst müsste man eine Schleife schreiben und alle Datensätze einzeln einfügen.
h0rnung
User
Beiträge: 46
Registriert: Mittwoch 28. Mai 2014, 11:41

Also mir fliegt jetzt leider noch ein weiterer Fehler um die Ohren: Sobald ich UNIQUE(LSE_CommentNr) dem Create Statement hinzufüge erhalte ich diesen Fehlercode:

Code: Alles auswählen

Traceback (most recent call last):
  File "C:\Python27\MySQL_finalversion\RSS_common_FV_LSE.py", line 49, in <module>
    main()
  File "C:\Python27\MySQL_finalversion\RSS_common_FV_LSE.py", line 45, in main
    except: print error
UnboundLocalError: local variable 'error' referenced before assignment
Dieser erscheint nicht, wenn ich UNIQUE(LSE_CommentNr) aus dem SQL Statement ausschliesse.

Zum eigentlichen Thema

Ich kann sowohl /me Vorschlag verstehen wie auch BlackJacks Einwand. Ich hätte jetzt etwas in dieser Richtung verrucht:

Code: Alles auswählen

    try:
        for item in feed_lse:
            try: cur.execute("""INSERT INTO feeddata_lse(LSE_UnixTimesstamp, LSE_Timestamp, LSE_Source, LSE_Title, LSE_Text, LSE_Link, LSE_Epic, LSE_CommentNr, LSE_Author) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)""",item)
            except IntegrityError: ....
        db.commit()
        print feed_lse[0][1]
    except: print error

... konnte ich aber wegen des oberen Fehler leider nicht ausprobieren!

Grüße und tausend Dank für die Hilfe!
h0rnung
User
Beiträge: 46
Registriert: Mittwoch 28. Mai 2014, 11:41

Code: Alles auswählen

def main():
    feed_url_lse = "http://www.lse.co.uk/chat/recent/"
    try: feed_lse = feed_load_lse(feed_url_lse)
    except IndexError: print 'IndexError'
    for item in feed_lse:
        try: cur.execute("""INSERT INTO feeddata_lse(LSE_UnixTimesstamp, LSE_Timestamp, LSE_Source, LSE_Title, LSE_Text, LSE_Link, LSE_Epic, LSE_CommentNr, LSE_Author) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)""",item)
        except: print 'IntegrityError'
    db.commit()

if __name__ == "__main__":
    while True:
        main()
        print'done'
        time.sleep(10)
.... ich glaube das ist die Lösung in meinem Fall. Was meint ihr? Gibt es eine besser Möglichkeit (vor allem weil die RowID (=Key) auch an die Dubletten vergeben wird und nach der Löschung für das 100. Kommentar bereits RowID 1700 vergeben wird)? Seht ihr eine bessere Möglichkeit als zick mal 'Integrity Error' auszugeben?

Grüße und danke!
Sirius3
User
Beiträge: 17746
Registriert: Sonntag 21. Oktober 2012, 17:20

@h0rnung: angeblich gibt es in "INSERT IGNORE ...", das alle Fehler ignoriert. Damit vereinfacht sich der Code zu:

Code: Alles auswählen

def main():
    feed_url_lse = "http://www.lse.co.uk/chat/recent/"
    try:
        feed_lse = feed_load_lse(feed_url_lse)
    except IndexError:
        print 'IndexError'
    cur.execute_many("""INSERT IGNORE INTO feeddata_lse(LSE_UnixTimesstamp, LSE_Timestamp, LSE_Source, LSE_Title, LSE_Text, LSE_Link, LSE_Epic, LSE_CommentNr, LSE_Author) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)""", feed_lse)
    db.commit()
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

h0rnung hat geschrieben:Also mir fliegt jetzt leider noch ein weiterer Fehler um die Ohren: Sobald ich UNIQUE(LSE_CommentNr) dem Create Statement hinzufüge erhalte ich diesen Fehlercode:

Code: Alles auswählen

Traceback (most recent call last):
  File "C:\Python27\MySQL_finalversion\RSS_common_FV_LSE.py", line 49, in <module>
    main()
  File "C:\Python27\MySQL_finalversion\RSS_common_FV_LSE.py", line 45, in main
    except: print error
UnboundLocalError: local variable 'error' referenced before assignment
Die Fehlermeldung ist doch recht eindeutig: Du versuchst den Wert von "error" auszugeben, error wurde aber nirgends definiert. Die Meldung der Exception wird nicht magisch an "error" gebunden, dass musst du schon selber machen.
h0rnung hat geschrieben:Gibt es eine besser Möglichkeit (vor allem weil die RowID (=Key) auch an die Dubletten vergeben wird und nach der Löschung für das 100. Kommentar bereits RowID 1700 vergeben wird)? Seht ihr eine bessere Möglichkeit als zick mal 'Integrity Error' auszugeben?
Klar, du hast ja die zweitschlechteste aller Varianten gewählt. Schlimmer wäre nur noch ein wegwerfen mittels pass ;-) Gib doch einfach den dazugehörigen Traceback aus oder, noch besser, verwende gleich das logging-Modul. Du fängst übrigens wieder alle möglichen Ausnahmen ab, beschränke dich auf die möglichen Fehler von der Datenbank. Du verdeckst dir sonst eigene Fehler.
Das Leben ist wie ein Tennisball.
h0rnung
User
Beiträge: 46
Registriert: Mittwoch 28. Mai 2014, 11:41

Wie immer gilt GANZ HERZLICHEN DANK FUER DIE TOLLE UNTERSTÜTZUNG

Mein Aktueller Code (@Sirius: Habe deinen Vorschlag jetzt nicht umgesetzt weil es so aktuell ganz gut klappt und ich auch einen Information darüber habe wieviele Dubletten pro Ladegang auftauchen) sieht so aus:

Code: Alles auswählen

def main():
    feed_url_iii = "http://www.iii.co.uk/site_wide_discussions/site_wide_rss2.epl"
    dub = 0
    try: feed_iii = feed_load_iii(feed_url_iii)
    except IndexError:
        print'IndexError'
    try:
        for item in feed_iii:
            try:cur.execute("""INSERT INTO feeddata_iii(III_UnixTimesstamp, III_Timestamp, III_Source, III_Title, III_Text, III_Link, III_Epic, III_CommentNr, III_Author) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)""",item)
            #INSERT IGNORE INTO
            except: dub = dub+1
        db.commit()
        print'Dublicates deleted:'
        print dub
    except:
        print 'ERROR OCCURED'
@EyDu ... den Fehler mit dem Error habe ich auch direkt selbst entfernt (sehr noobig :) ). Weiterhin 2 Fragen an dich:

1.) Was ist das loggin Modul?
2.) Wie gebe ich den Traceback aus?

Grüße und Danke!
BlackJack

@h0rnung: Du musst Dir echt mehr Gedanken um Fehlerbehandlung machen, vor allem wann es besser ist das einfach sein zu lassen. Wenn im ersten ``try``/``except`` ein `IndexError` auftritt, dann wird `feed_iii` nichts zugewiesen, der Name existiert dann also gar nicht. Womit zwangsläufig die ``for``-Schleife in Zeile 8 nicht funktionieren kann. Wenn man einen Fehler behandelt, dann sollte man schon darauf achten das dadurch nicht unweigerlich Folgefehler entstehen. Eine Fehlerbehandlung die daraus besteht einfach nur etwas auszugeben und dann so weiter zu machen als sei der Fehler nie passiert ist nur selten eine angemessene Behandlung eines Fehlers. Tipp: Zu ``try``/``except`` kann man auch einen ``else``-Zweig hinzufügen.

Und ``except`` ohne konkrete Ausnahmen anzugeben die behandelt werden sollen ist immer noch keine gute Idee. Das behandelt *alle* Ausnahmen. Auch solche mit denen man nicht rechnet, die zum Beispiel durch Programmierfehler entstehen, und die man durch solche Ausnahmebehandlungen dann nur sehr schwer finden kann, weil man sie nicht zu sehen bekommt. Nicht alles was bei dem ``INSERT`` schief gehen kann muss eine Doublette sein. Du zählst aber einfach alles als eine solche, statt sicherzustellen dass tatsächlich nur Integritätsfehler gezählt werden.

Und ein 'ERROR OCCURED' hilft auch nicht besonders wenn man wissen will *was* denn da für ein Fehler aufgetreten ist.

Das `logging`-Modul ist Bestandteil der Python-Standardbibliothek und dokumentiert.
h0rnung
User
Beiträge: 46
Registriert: Mittwoch 28. Mai 2014, 11:41

@BJ ... danke für die Antwort. Das mit den Fehlern ist absolutes Neuland für mich und daher stell ich mich etwas an.

Ich habe versucht

Code: Alles auswählen

except IntegrityError:
zu verwenden. Obzwar der IntegrityError im Traceback stand hat dieser Code aber nicht funktioniert. Eine Alternative hat google mir leider nicht liefern können, kannst du das vllt?

Grüße und danke!
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

h0rnung hat geschrieben:Obzwar der IntegrityError im Traceback stand hat dieser Code aber nicht funktioniert.
Was hat nicht funktioniert? Hat der Rechner zu brennen angefangen? Oder wurdest du nach Wittenberg in ins Jahr 1517 teleportiert, wo Luther gerade das Kapitel 9.12.5 aus der MySQL-Doku an die Tür der Schlosskirche nagelt?
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@h0rnung: Exceptions fliegen nicht einfach so im luftleeren Raum herum. Das gilt nur für Builtin-Exceptions, auf die man halt - ähnlich wie auf Builtin-Funktionen - jederzeit Zugriff hat. In allen anderen Fällen muss man aber schauen, in welchem Modul die spezielle Exception definiert wurde und sie dann über das Modul ansprechen. So wie man das bei jeglichen "Nicht-Builtin-Namen" ja auch macht.
Antworten