SQLite: Kann man mit zwei Datenbank-Files arbeiten ?

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
mephisto-online
User
Beiträge: 167
Registriert: Sonntag 29. September 2013, 17:05

In einem Pyside-Programm möchte ich gerne 2 Datenbanken benutzen - eine sehr grosse Ortsdatenbank für lesenden Zugriff und eine Datenbank mit den Haupt-Daten.

Wenn ich folgendes mache, wird der Treiber nicht geladen:

Code: Alles auswählen

        localities_db = QSqlDatabase.addDatabase("QSQLITE", "locs")
        localities_db.setDatabaseName(defs.SQLITE_PATH+'localities.sqlite')
        localities_db.setConnectOptions("QSQLITE_OPEN_READONLY")
        data_db = QSqlDatabase.addDatabase("QSQLITE", "data")
        data_db.setDatabaseName(defs.SQLITE_PATH+'data.sqlite')
Wenn ich es überprüfe,

Code: Alles auswählen

        print('Connections: '+str(self.localities_db.connectionNames()))
erhalte ich:

Code: Alles auswählen

Connections: ['locs']['data']
die Connection lässt sich auch öffnen:

Code: Alles auswählen

        ok = self.localities_db.open()
        print('isOpen: '+str(self.localities_db.isOpen()))
liefert:

Code: Alles auswählen

isOpen: True
Nach einer Query:

Code: Alles auswählen

        ok = villages_model.setQuery('SELECT * FROM villages WHERE name = "%s"' % name)
        if not ok:
            print('ok: '+str(ok))
            print("Query Fehler : " + self.villages_model.lastError().text())
            return
erhalte ich:

Code: Alles auswählen

QSqlQuery::exec: database not open
ok: None
Query Fehler : Driver not loaded Driver not loaded
Wie bekomme ich das mit den zwei Datenbanken denn sonst hin ? Sie müssen nicht unbedingt zur gleichen Zeit geöffnet sein. Geht das nur mit addDatabase und removeDatabase ?
Mit dem normalen QSqlDatabase.addDatabase("QSQLITE") wird der Treiber geladen.
mephisto-online
User
Beiträge: 167
Registriert: Sonntag 29. September 2013, 17:05

Sorry ! Ich hatte ein paar "self" vergessen, aus meinem Code herauszunehmen und die Entstehung der villages-Model-Instanz nicht beschrieben. Das gibt schon wieder Minuspunkte :oops:

Weil der Umgang mit mehreren Datenbank-Instanzen bei Qt, PyQt und Pyside fast gar nicht dokumentiert ist, blieb mir als Anfänger nichts Anderes übrig, als es selbst heraus zu tüfteln. Vielleicht hat ja noch ein Anfänger das Problem. Deshalb hier die Lösung:

Einem QSqlQuery-Objekt muss beim Arbeiten mit mehreren Datenbanken (natürlich) auch das Datenbank-Objekt übergeben werden, was bei nur einer Datenbank nicht unbedingt erforderlich ist, weil die erste (addDatabase-)Session der defaultDatabase zugeordnet wird (laienhaft ausgedrückt).

Hier der funktionierende Code:

Code: Alles auswählen

...
    def search_button_clicked(self):

        self.name = self.ui.ortsNameLineEdit.text()

        ok = self.localities_db.open()

        if not ok:
        # noinspection PyCallByClass
            QtGui.QMessageBox.warning(self, "Fehler", "Problem mit Orte-Datenbank!")
            return

        query = QSqlQuery(self.localities_db)
        query.prepare('SELECT * FROM villages WHERE name = "%s"' % self.name)
        query.exec_()
        self.villages_model.setQuery(query)
...
Und das will hier niemand gewusst haben ? :K Wie auch immer ...
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

mephisto-online hat geschrieben:Weil der Umgang mit mehreren Datenbank-Instanzen bei Qt, PyQt und Pyside fast gar nicht dokumentiert ist, blieb mir als Anfänger nichts Anderes übrig, als es selbst heraus zu tüfteln.
Was heißt denn nicht dokumentiert? In der Dokumentation steht doch ganz klar zu setQuery:
http://qt-project.org/doc/qt-4.8/qsqlquerymodel.html#setQuery-2 hat geschrieben:Executes the query query for the given database connection db. If no database (or an invalid database) is specified, the default connection is used.
Wenn du neue Funktionen verwendest, dann musst du die Dokumentation als erstes lesen, erst dann verwenden ;-)

An deinem Code gibt es auch noch jede Menge zu verbessern:

- "ortsNameLineEdit" ist ein reichlich seltsamer Name. Er entspricht nicht PEP 8 und das "LineEdit" ist ebenfalls überflüssig.
- Du scheinst in deinem Programm Logi, GUI und Daten zu mischen. Das solltest du alles getrennt von einander halten.
- Die Ausgabe des Fehlers solltest du in einer eigenen Methode durchführen. Dann kannst du das Aussehen und die Titel einheitlich machen.
- Deine SQL-Statements sind gefährlich und öffnen Tür und Tor für SQL-Injections. Wenn es schon kein sicherheitsrelevantes Problem ist, dann zumindest ein Fehler im Programm. Was passiert wohl, wenn als Name einfach doppelte Anführungszeichen eingegeben werden? Richtig, dein Programm wird mit einem Fehler abschmieren. Und was Bobby Tables erst anrichten kann... Du darfst *niemals* SQL-Anfragen selbst formatieren und *niemals* *niemals*, wenn die Eingaben auch noch vom Benutzer kommen. Dafür gibt es entsprechende Mechanismen, schau sie dir bei Qt mal an.
- "self.name" scheint irgendwie unnötig zu sein, reicht ein lokaler Name nicht aus?
Das Leben ist wie ein Tennisball.
mephisto-online
User
Beiträge: 167
Registriert: Sonntag 29. September 2013, 17:05

EyDu hat geschrieben:Was heißt denn nicht dokumentiert? In der Dokumentation steht doch ganz klar zu setQuery:
Executes the query query for the given database connection db. If no database (or an invalid database) is specified, the default connection is used.
Habe wohl die überladene Version mit "db=..." übersehen. Echt blöd ! Sollte vielleicht öfter mal ne Pause machen ... wegen der Augen ... :?
EyDu hat geschrieben:An deinem Code gibt es auch noch jede Menge zu verbessern:
Da bin ich mir ganz sicher !
EyDu hat geschrieben:- "ortsNameLineEdit" ist ein reichlich seltsamer Name. Er entspricht nicht PEP 8 und das "LineEdit" ist ebenfalls überflüssig.
pyside-uic liefert erst mal keine keine PEP 8-konforme Namen. Könnte ich aber komplett ändern. Stört mich auch. Da hast du recht !
EyDu hat geschrieben:- Du scheinst in deinem Programm Logi, GUI und Daten zu mischen. Das solltest du alles getrennt von einander halten.
Wie macht man das ? Worauf muss ich da achten ? Eigentlich greift doch alles ineinander !
EyDu hat geschrieben:- Die Ausgabe des Fehlers solltest du in einer eigenen Methode durchführen. Dann kannst du das Aussehen und die Titel einheitlich machen.
Das habe ich auch noch vor. Das war jetzt hier eher "heisse Nadel", ich weiss.
EyDu hat geschrieben:- Deine SQL-Statements sind gefährlich..... Dafür gibt es entsprechende Mechanismen.
Auch da gebe ich mich keinen Illusionen hin: die sind mit Sicherheit noch totaler Mist. Ich fange ja auch gerade erst mal damit an... Deshalb danke für den Link. Mir ging es erst mal darum, dass es überhaupt funktioniert. Soll nicht so bleiben. In einigen Tutorials wird das aber auch genau so vorexerziert. Und wie man mit der Qt-Api die Fehler von Queries abfängt, ist mir auch noch nicht so ganz klar, hab dazu bislang wenig gefunden. Ich weiss nur, wie man das mit der Python-API macht.
EyDu hat geschrieben:- "self.name" scheint irgendwie unnötig zu sein, reicht ein lokaler Name nicht aus?
Den Namen übergebe ich als Parameter an das Widget. In Java habe ich gelernt, solche Parameter lokalen Variablen zuzuweisen und damit zu arbeiten. Oder sind solche Parameter in Python-Klassen "global" ?
BlackJack

@mephisto-online: `self.name` ist ja gerade *nicht* lokal, sondern ein Attribut auf dem Objekt. Also in Java ``this.name``, was ja auch nicht lokal wäre.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

mephisto-online hat geschrieben:
EyDu hat geschrieben:- Du scheinst in deinem Programm Logi, GUI und Daten zu mischen. Das solltest du alles getrennt von einander halten.
Wie macht man das ? Worauf muss ich da achten ? Eigentlich greift doch alles ineinander !
Du musst halt dafür sorgen, dass gerade nicht alles ineinander greift ;-) Prinzipiell muss deine Code so ausgelegt sein, dass du zum Beispiel die ganze GUI wegschmeißen kannst und durch eine ersetzt. Ohne, dass du auch nur ein Stück an der Programmlogik oder Datenbank änderst. Schau dir dazu mal das MVC-Pattern an, das ist für solche Sachen gut geeignet. Du musst es auch nicht vollständig implementieren, bei dir kann man etwas abkürzen. Eine Aufteilung in View und ein zusammengefasster Model/Controller sollten ausreichen.

Wenn man es ganz vereinfach ausdrücken möchte, dann hast du eine Klasse für die GUI (View) und eine für den Controller. In letzterem befindet sich die ganze Logik. Wenn deine GUI nun etwas anzeigen will, dann ruft sie dazu eine Methode des Controllers auf und holt sich von diesem die Daten (oder stößt Berechnungen an etc.). Mehr darf die View auch schon nicht tun: nur Berechnungen für die Anzeige, keinerlei Logik und ganz sicher nicht das Senden von SQL-Statements an den Controller ;-)
Das Leben ist wie ein Tennisball.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Bezüglich PEP 8 würde ich an "A Foolish Consistency is the Hobgoblin of Little Minds" erinnern. PEP 8 schafft bessere Lesbarkeit nicht unbedingt dadurch weil die definierten Konventionen irgendwie optimal sind, sondern weil sie Konsistenz schaffen.

Qt hält sich nicht an PEP 8, schreibt man dann Code der von Qt abhängt, bricht man Konsistenz indem man PEP 8 Namensgebung nutzt, dass schadet der Lesbarkeit eher als dass es hilft. Vorallem dann wenn man von Klassen die aus Qt kommen erbt, was ja durchaus öfter vorkommt und hier auch der Fall zu sein scheint.
BlackJack

Ich handhabe das normalerweise so dass ich bei GUIs mit `mixedCase`-Namen selber auch `mixedCase` für GUI-Objekte und PEP8 für die Programmlogik verwende. Benutze es also als „Feature” um die GUI und die Logik gegeneinander abzugrenzen.
mephisto-online
User
Beiträge: 167
Registriert: Sonntag 29. September 2013, 17:05

@BlackJack
BlackJack hat geschrieben:@mephisto-online: `self.name` ist ja gerade *nicht* lokal, sondern ein Attribut auf dem Objekt. Also in Java ``this.name``, was ja auch nicht lokal wäre.
Das hatte ich auch genau so gedacht. Das sieht so aus:

Code: Alles auswählen

class ChangeLocation(QDialog):

    def __init__(self, localities_db, name):

        super(ChangeLocation, self).__init__()
        self.setWindowModality(Qt.ApplicationModal)
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)

        self.localities_db = localities_db

        self.name = name
        self.ui.ortsNameLineEdit.setText(self.name)
richtig ? Oder kann ich das Attribut "name" auch direkt in der ganzen Klasse verwenden ?
BlackJack hat geschrieben:Ich handhabe das normalerweise so dass ich bei GUIs mit `mixedCase`-Namen selber auch `mixedCase` für GUI-Objekte und PEP8 für die Programmlogik verwende. Benutze es also als „Feature” um die GUI und die Logik gegeneinander abzugrenzen.
Das habe ich mir auch gedacht. Wenn Qt nun mal kein PEP 8 generiert, kann ich es ja auch erst mal so belassen. Und Widget-Instanzen benenne ich dann z.B mit dem Widget-Typ im Namen wie "nameLineEdit", wenn Qt es "lineEdit1" benennt. Vielleich spricht sich PEP 8 ja mal irgendwann bei den PyQt/PySide-Jungs rum...

@DasIch
DasIch hat geschrieben:Bezüglich PEP 8 würde ich an "A Foolish Consistency is the Hobgoblin of Little Minds" erinnern...
Guter Spruch ! Das geht jetzt ein wenig ins Philosophische ! Aber ich persönlich halte PEP 8 für einen genialen Ansatz (wie, nebenbei bemerkt, Python überhaupt :wink: und Qt finde ich auch genial, verglichen mit dem, was ich sonst kenne), wenn ich da an die anderen Sprachen denke, mit denen ich gearbeitet habe und jeder seinen eigenen "Stil" einbringen kann, was nicht unbedingt zu guter Lesbarkeit von fremdem Code beiträgt.
DasIch hat geschrieben:Qt hält sich nicht an PEP 8, schreibt man dann Code der von Qt abhängt, bricht man Konsistenz indem man PEP 8 Namensgebung nutzt, dass schadet der Lesbarkeit eher als dass es hilft. Vorallem dann wenn man von Klassen die aus Qt kommen erbt, was ja durchaus öfter vorkommt und hier auch der Fall zu sein scheint.
Ja, schön ist das nicht, aber da wird man wohl erst mal mit leben müssen.

@EyDu
EyDu hat geschrieben:Prinzipiell muss deine Code so ausgelegt sein, dass du zum Beispiel die ganze GUI wegschmeißen kannst und durch eine ersetzt. Ohne, dass du auch nur ein Stück an der Programmlogik oder Datenbank änderst.
Einfach ne andere Gui drüber setzen, klappt das ? Bei meinen etwas komplexeren Grafiken in meinen beiden letzten Programmen (selbst gezeichnete 2- und 3D-Grafiken, Java/Swing, Java/Android) konnte ich das vergessen. In diese Dinger hatte ich die meiste Arbeit investiert und konnte davon aber fast nichts für Xamarin-C# gebrauchen. Es war weniger Arbeit, es neu zu schreiben in Anlehnung an den vormals investierten "Gehirnschmalz". Die Grafiken (immer auch Teil der Gui) musste ich bislang immer neu schreiben (Pascal->GEM unter Windows/DOS, GEM->Java/Swing, Swing->Xamarin/.NET/mono und jetzt nach Qt)
EyDu hat geschrieben:Schau dir dazu mal das MVC-Pattern an, das ist für solche Sachen gut geeignet. Du musst es auch nicht vollständig implementieren, bei dir kann man etwas abkürzen.
(Klingt ja fast wie MFC ! Igitt, das war vielleicht ein Krampf !) Danke schön ! Habe zwar keine Ahnung, was MVC ist, schaue ich mir aber sicherlich gleich mal an.
EyDu hat geschrieben:Wenn man es ganz vereinfach ausdrücken möchte, dann hast du eine Klasse für die GUI (View) und eine für den Controller. In letzterem befindet sich die ganze Logik... Wenn deine GUI nun etwas anzeigen will, dann ruft sie dazu eine Methode des Controllers auf und holt sich von diesem die Daten (oder stößt Berechnungen an etc.). Mehr darf die View auch schon nicht tun: nur Berechnungen für die Anzeige, keinerlei Logik und ganz sicher nicht das Senden von SQL-Statements an den Controller ;-)
Momentan habe ich eine main, in der ich z.B. Systempfade ermittele und an die MainWindow-Instanz übergebe. In MainWindow habe ich meine zentalen Programm-Daten (Flags, Variablen, eine Liste mit 12 Datenklassen-Instanzen und die DB-Instanzen), die ich jeweils an die von hier aufgerufenen Gui-Instanzen übergebe (Daten rein, ggf. geänderte Daten zurück). Für File- und DB-I/O, Berechnungen, Auswertungen gibt es eigene Klassen(-Module). D.h., ich sollte zwischen main und MainWindow noch eine "Controller"-Klasse schalten ? Hört sich an wie eine gute Idee ! Da dürfte ich dann aber wahrscheinlich keine Qt-Datentypen (zB. QString) verwenden ?
Wie macht man das mit SQL, wenn man Qt-Models (und überhaupt QtSql) verwendet ? Da sind doch die SQL-Statements eingebaut ?
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Ähm, du interpretierst da jetzt ein wenig viel rein. Es geht nicht darum, dass du die GUIs zwischen verschiedenen Sprachen wiederverwenden kannst sondern darum, dass du ein Programm hast und dort die GUI durch eine andere ersetzbar ist. Indem du zum Beispiel Qt durch Wx austauscht.

Stell dir das Problem mal ganz praktisch vor: angenommen du hast ein Proramm, welches du als Desktop-Version anbietet, als App auf einem Smartphone und dann noch ein Web-Interface dazu. Das ist eigentlich gar nichts so Ungwöhnliches. Nun möchtest du irgendwo in den Berechnungen etwas ändern und das willst du natürlich nur an einer einzigen Stelle machen. So wie du gerade programmierst, müsstest du das an drei verschiedenen Stellen tun. Das ist dreimal so viel Arbeit und hat mindestens dreifaches Fehlerpotenzial.

Wahrscheinlich wirst du bei deinem Programm niemals in die Verlegenheit kommen, dass du mehrere GUIs unterstützen musst, trotzdem ist es eine gute Idee Code nach seinen Aufgaben zu teilen. Jede Komponente sollte genau eine Aufgabe habe. Die GUI zeigt Dinge an und nimmt Eingaben entgegen. Mehr nicht. Alles andere sollte auch an anderer Stelle passieren.
mephisto-online hat geschrieben:In MainWindow habe ich meine zentalen Programm-Daten (Flags, Variablen, eine Liste mit 12 Datenklassen-Instanzen und die DB-Instanzen), die ich jeweils an die von hier aufgerufenen Gui-Instanzen übergebe (Daten rein, ggf. geänderte Daten zurück).
Schönes Beispiel, wie es nicht sein sollte ;-)
mephisto-online hat geschrieben:D.h., ich sollte zwischen main und MainWindow noch eine "Controller"-Klasse schalten ? Hört sich an wie eine gute Idee !
Schau dir das MVC-Pattern an und teste es an einem kleinem Beispiel, dann weißt du wie es funktioniert.
mephisto-online hat geschrieben:Da dürfte ich dann aber wahrscheinlich keine Qt-Datentypen (zB. QString) verwenden ? Wie macht man das mit SQL, wenn man Qt-Models (und überhaupt QtSql) verwendet ? Da sind doch die SQL-Statements eingebaut ?
Das kommt darauf an. Qt ist nicht gleichbedeutend mit GUI. Solange du also keine GUI-Sachen in deiner Logik stehen hast, geht das in Ordnung. Du hast dann natürlich immer die Qt-Abhängigkeit. Und wenn du Qt-Objekte auch noch nach außen gibst, dann müssten sich andere GUIs damit rumschlagen. Ich würde es aber realistisch und praktisch betrachten: da du wahrscheinlich niemals wirklich das Toolkit ändern möchtest, würde ich gnadenlos dort Qt verwenden, wo es gerade praktisch ist. Das spart dir sinnlose Arbeit durch einen hypothetischen Falll.
Das Leben ist wie ein Tennisball.
mephisto-online
User
Beiträge: 167
Registriert: Sonntag 29. September 2013, 17:05

EyDu hat geschrieben:Ähm, du interpretierst da jetzt ein wenig viel rein.
Ja, das mag sein. Liegt vielleicht an meinen Erfahrungen, weil ich immer alles neu gemacht habe und noch nie etwas wiederverwenden konnte (bis auf die Algorithmen). War aber auch jedes mal ne andere Sprache (Pascal, C, Java und jetzt Python).
EyDu hat geschrieben:Ich würde es aber realistisch und praktisch betrachten: da du wahrscheinlich niemals wirklich das Toolkit ändern möchtest, würde ich gnadenlos dort Qt verwenden, wo es gerade praktisch ist. Das spart dir sinnlose Arbeit durch einen hypothetischen Falll.
Das denke ich eigentlich auch.
EyDu hat geschrieben:Es geht ... darum, dass du ein Programm hast und dort die GUI durch eine andere ersetzbar ist. Indem du zum Beispiel Qt durch Wx austauscht.
Die Gui ersetzen möchte ich nicht. Warum auch ? Das einzige, was ich berücksichtigen muss ist, dass das Ding auch mal unter Android laufen soll, ich also an die Touch-Bedienung denken muss (z.B. keine Menüs...).
EyDu hat geschrieben:... angenommen du hast ein Proramm, welches du als Desktop-Version anbietet, als App auf einem Smartphone ... Das ist eigentlich gar nichts so Ungwöhnliches. Nun möchtest du irgendwo in den Berechnungen etwas ändern ...
Berechnungen, Auswertungen, File-I/O und DB-I/O laufen mittlerweile in eigenen Klassen in eigenen Modulen.
EyDu hat geschrieben:... trotzdem ist es eine gute Idee Code nach seinen Aufgaben zu teilen. Jede Komponente sollte genau eine Aufgabe habe. Die GUI zeigt Dinge an und nimmt Eingaben entgegen. Mehr nicht. Alles andere sollte auch an anderer Stelle passieren.
Das mache ich mittlerweile ja auch genau so (bis auf den Snippet in meinem Beispiel, wo es um die Datenbank-Frage ging...). Das Programm hat jetzt folgende Module:

Code: Alles auswählen

changelocui.py   debug.py        editdataui.py  main.py        showsingledata2d.py
calculations.py  choosedata.py    definitions.py  fileio.py      mainwindow.py  showsingledata2dui.py
changeloc.py     choosedataui.py  editdata.py     loaddata.py    savedata.py
EyDu hat geschrieben:Schönes Beispiel, wie es nicht sein sollte ;-)
Deshalb werde ich die Datenhaltung ja jetzt auch aus MainWindow herausnehmen und in die main verlagern oder eine eigene Contoller-Klasse machen.
EyDu hat geschrieben:Schau dir das MVC-Pattern an und teste es an einem kleinem Beispiel, dann weißt du wie es funktioniert.
Habe ich mir schon etwas angeschaut. Werde versuchen, das umzusetzen.
Antworten