Datenbank abfragen mit OOP

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
Gerhardus
User
Beiträge: 42
Registriert: Dienstag 31. Dezember 2013, 01:59

Hallo,
habe eine Anwendung fertiggestellt, die mir diverse Listen aus einer Access DB erstellt. Funktioniert alles bestens(Dank eurer Hilfe).
Nun möchte ich zu Übungszwecken diese auf OOP umschreiben. Die Tkinter Klassen funktionieren, aber ich schaffe es trotz stundenlanger Googlesuche nicht einen simplen Code als Klasse und die Abfragen auszugeben.
z.B.:
import pypyodbc
erg=""
def Auswahl():
conn=pypyodbc.win_connect_mdb('D:\\Test.mdb')
sql="SELECT name FROM Mitglieder WHERE ID < 200" # update, insert....
cursor=conn.cursor()
cursor.execute(sql)
res=cursor.fetchall()
return res
erg=Auswahl()
print(erg)

Wie würde der Code als Klasse ausschauen? Speziell die Auswertung habe ich nicht zusammengebracht.
(missing 1 required positional argument:war eine häufige Fehlermeldung.)

Danke im Voraus
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Bitte die Code-Tags benutzen. So laesst sich das schwer lesen. Und dann zum Code passender Fehlermeldungen. Nicht eine ungefaehre Umschreibung von irgendwelchen Fehlermeldungen, die irgendwann mal bei irgendeinem Stand aufgetreten sind.

Die Vorbelegung von erg ist ueberfluessig, und der Name unnoetig verkuerzt. Auswahl sollte auswahl heissen - Funktionen, Methoden und Variablen werden in Python klein geschrieben.

Fuer das praesentierte Problem ist eine Klasse nicht sinnvoll. Dazu muesstest du schon etwas mehr ausholen, was das System so als Ganzes koennen soll.
Benutzeravatar
__blackjack__
User
Beiträge: 14000
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Gerhardus: OOP lernen heisst auch zu lernen wann man kein OOP verwendet. Oder anders: Wenn man irgendwas in Klassen stopft was da so gar nicht rein gehört, dann hat man nichts über OOP gelernt, ausser halt wie man es falsch macht. Klassen sind kein Selbstzweck, die müssen schon Sinn machen.

Ich würde da eher über SQLAlchemy nachdenken um handgeschriebenes SQL los zu werden. Und je nach dem was da genau gemacht werden soll, kann man da dann mit dem ORM von SQLAlchemy vielleicht auch sinnvoll Klassen einsetzen, zumindest als einfache Datenklassen.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Gerhardus
User
Beiträge: 42
Registriert: Dienstag 31. Dezember 2013, 01:59

Danke für eure Antworten.
Ich habe eine GUI Anwendung mit tkinter geschrieben, die mir aus einer mdb Datenbank, verschiedene
Listen als exe erstellt mit und am Bildschirm und als pdf mit reportlab ausgibt.
Ich wollte lediglich versuchen, mein Programm in OOP umzuschreiben, was mir bei den Datenabfragen aber
Schwierigkeiten bereitet. Da es scheinbar gar nicht sinnvoll ist, dies in Klassen darzusellen, werde ich es bei den Datenabfragen sein lassen.
Mich als Hobbyist, hätte nur Iinterressiert wie man mein simples Beispiel richtig in einer Klasse darstellt und das Ergebnis ausgibt.

Was bei den vielen einfachen OOP Beispielen leicht verständlich ist, will mir bei den Db's nicht in den Kopf gehen.

Danke
Gerhardus
Gerhardus
User
Beiträge: 42
Registriert: Dienstag 31. Dezember 2013, 01:59

Hallo,
leider lässt mich ein ungelöstes Problem keine Ruhe, daher nochmals zu meiner Anfrage.
Ich habe auf Stackoverflow ein passendes Beispiel gefunden.
https://stackoverflow.com/questions/380 ... in-a-class
das ich auf meine DB abgewandelt habe.

Code: Alles auswählen

import pypyodbc

class csql():
    
    dbc='D:\\Test.mdb'
  
    def __init__(self):
        db=pypyodbc.win_connect_mdb(*self.dbc)
        self.cursor=db.cursor()

    def query(self,sql):
        self.cursor.execute(sql)
        return self.cursor.fetchone()
Wenn ich mit sql1=csql eine Instanz anlege, und mit
res=sql1.query("SELECT name FROM Mitglieder") ein Ergebnis anzeigen möchte, fehlt natürlich 1 Parameter.
Es braucht vorm String das self(csql) das ausserhalb der Klasse nicht bekannt ist. Gibt es dafür eine Lösung?
Oder funktioniert das mit diesem Code einfach nicht. Es würde mich vom Verständnis her interressieren,
egal ob es jetzt sinnvoll ist oder ob es mit eiener anderen DB leichter geht, da ich meine aktuellen Daten aus einer mdb datei bekomme.
Danke!
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn du auf stackoverflow nach code suchst, dann empfiehlt es sich NICHT den in der Frage gezeigten zu nehmen. Schließlich hat der ein Problem. Sondern wenn den, der in den Antworten steht.

Und was kann dein Code besser, als sich einfach den cursor zu merken? Der kann dann schon alles, was du brauchst. Inklusive keinen, einen oder mehrere Parameter zu unterstützen.
Benutzeravatar
__blackjack__
User
Beiträge: 14000
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Gerhardus: Da fehlt kein Parameter, Du hast nur keine ”Instanz” angelegt sondern die Klasse `csql` zusätzlich an den Namen `sql1` gebunden. Klassen rufen sich genau wie Funktionen und Methoden nicht selbst auf, man muss das *tun*. Da fehlen Klammern.

Du solltest echt mal an Deiner Namensgebung arbeiten. `sql1` fängt ja schon mit dem nummerieren an bevor das überhaupt ”nötig” wird. Man nummeriert aber keine Namen. Und `sql` ist auch falsch, denn letztlich ist das ein verpacktes `Cursor`-Objekt.

Das die Klasse von StackOverflow nicht wirklich sinnvoll ist hat __deets__ ja bereits gesagt. Zusätzlich ist auch dort der Klassenname nicht gut, entspricht in der Schreibweise nicht den Konventionen, und das `dbc` ein Klassenattribut ist, macht das ganze unnötig unflexibel.

Was auch nicht „best practice“ ist, ist das ”zusammenschweissen” von Datenbankverbindung und Cursor auf diese Weise ist, denn die haben in der Regel deutlich unterschiedliche Lebenszyklen. Die Verbindung besteht normalerweise die ganze Zeit in der (potentiell) etwas mit der Datenbank gemacht wird, während Cursor-Objekte nur für einzelne Abfragen oder zumindest ”units of work” bestehen und danach geschlossen/verworfen werden. Ach ja, schliessen kann man die Datenbankverbindung auch nicht mehr, denn es gibt keinen offiziellen Weg die aus dem `Cursor`-Objekt heraus zu bekommen.

Hatte ich eigentlich schon mal SQLAlchemy erwähnt? 😎
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Gerhardus
User
Beiträge: 42
Registriert: Dienstag 31. Dezember 2013, 01:59

Sorry, aber jetzt bin ich so schlau wie vorher. Der Code vom Link war eine Antwort, allerdings ohne haken und
die Klasse meldet keinen Fehler, jedoch nach Instanzierung kommt eine Fehlermeldung,weil er einen Parameter für self erwartet.
Was soll ich hier eingeben?
Und was kann dein Code besser, als sich einfach den cursor zu merken? Der kann dann schon alles, was du brauchst. Inklusive keinen, einen oder mehrere Parameter zu unterstützen.
Was ist falsch oder richtig?
:?:
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du denkst du legst eine Instanz an mit

Code: Alles auswählen

sql1=csql
Das ist falsch. Du legst keine Instanz an. Du bindest nur die KLASSE auch an den Namen sql1. Du musst da schon noch Klammern hinter packen.

Und dann ist die ganze Klasse konzeptioneller Murks, weil sie die Lebenszeit eines Cursors mit der einer Verbindung gleichsetzt. Und wenn das sinnvoll waere, dann gaebe es erst gar nicht zwei verschiedene Objekte. Gibt es aber. Warum hat __blackjack__ ja schon ausgefuehrt.
Benutzeravatar
__blackjack__
User
Beiträge: 14000
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Um noch mal SQLAlchemy anzuregen. Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
from sqlalchemy import create_engine, MetaData, select


def main():
    engine = create_engine("access+pyodbc://@your_dsn")
    metadata = MetaData(engine)
    metadata.reflect()
    mitglied_table = metadata.tables["mitglied"]
    print(
        select([mitglied_table.c.name])
        .where(mitglied_table.c.id < 200)
        .execute()
        .fetchall()
    )


if __name__ == "__main__":
    main()
Und natürlich wäre auch das ORM einen Blick wert, wenn das Sinn macht.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Gerhardus
User
Beiträge: 42
Registriert: Dienstag 31. Dezember 2013, 01:59

Danke _blackjack_ ,
sql1=csql()
das war's. Jetzt funktionierts!
Deine Erklärung bezüglich Instanzierung war sehr hilfreich.
Bezüglich sqlalchemy schaue ich mir das als Neugierde an, aber in meinem Fall bin ich leider
an die alte access DB gebunden, welche ca. seit 20-25 Jahren besteht (Klubintern), mit der mit einem Teil der Daten
damals Ranglisten erstellt habe. Zuerst mit Excel, dann VB dann nur in Access und jetzt mit Python.
Ich habe da ca. 10 DB Abfragen mit SELECT, löschen, updaten, insert und count, daher wollte
ich wissen ob mit Klassen das ganze kürzer und strukturieter wird.

Danke an euch beiden

Gerhardus
Benutzeravatar
__blackjack__
User
Beiträge: 14000
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Gerhardus: SQLAlchemy einen Access-Dialekt zu verpassen geht mit Installation von `sqlalchemy-access`. Im Code habe ich ja schon eine "access+pyodbc://…"-URL angedeutet die man dann verwendet.

Und wahrscheinlich würde hier auch das ORM Sinn machen. Und letztlich wäre der Code dann wahrscheinlich auch automatisch von der konkreten DB unabhängig. Man könnte also wenn das läuft beispielsweise von Access auf SQLite, MySQL, oder PostgreSQL umsteigen ohne viel am Code ändern zu müssen. Im günstigsten Fall gar nichts ausser der URL zum Verbinden mit der Datenbank.

Mit dem ORM könnte das beispielsweise so aussehen (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
from sqlalchemy import Column, create_engine, INTEGER, VARCHAR
from sqlalchemy.orm import Session
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class Mitglied(Base):
    __tablename__ = "mitglied"

    id = Column(INTEGER, primary_key=True)
    name = Column(VARCHAR(200), nullable=False, unique=True)
    ...


def main():
    session = Session(create_engine("access+pyodbc://@your_dsn"))
    for row in session.query([Mitglied.name]).filter(Mitglied.id < 200):
        print(row.name)


if __name__ == "__main__":
    main()
Man kann auch beim ORM mit „reflection“ arbeiten, also die Informationen über die Tabellen aus der DB auslesen lassen, aber dafür braucht man dann ja schon für die Definition der Klassen eine Datenbankverbindung, das ist also nicht so toll auf Modulebene. Ausserdem hat man dann nicht die Freiheit schlecht benannte oder nicht den Konventionen entsprechende Namen aus der Datenbank auf der Klasse anzupassen. Und wenn die Informationen (auch) im Programm stehen, kann man SQLAlchemy auch verwenden um daraus in einer leeren Datenbank neue Tabellen anzulegen.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Gerhardus
User
Beiträge: 42
Registriert: Dienstag 31. Dezember 2013, 01:59

Danke für die Hinweise.
Sqlalchemy und ORM, beides bislang für mich unbekannt, aber mit programmieren beschäftige ich mich erst wieder seit
Corona. Deine codes werde ich ausprobieren und mir beides anschauen und Tante Google benutzen.. Aber wenn es nicht zu kompliziert ist
wäre es vielleicht eine Alternative.

LG Gerhardus
Benutzeravatar
noisefloor
User
Beiträge: 4173
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Aber wenn es nicht zu kompliziert ist wäre es vielleicht eine Alternative.
SQLAlchemy hat - wie jedes ORM - schon eine gewissen Lernkurve. Die Doku von SQLAlchemy ist recht umfangreich, aber gut strukturiert und es gibt auch Sektionen für Einsteiger. Ein bisschen Zeit musst du da schon investieren, aber dafür bekommst du dann als "Belohnng" die von __blackjack__ genannten Vorteile zurück.

Gruß, noisefloor
Gerhardus
User
Beiträge: 42
Registriert: Dienstag 31. Dezember 2013, 01:59

@ _blackjack_ Ich habe es tatsächlich hingekriegt mit sqlalchemy und deinem Code ohne Klassen eine Verbindung herzustellen, trotzdem ich schon mehrmals aufgegeben hatte. Keine Ahnung bis heute was eine DSN ist. Habe mir eine verpasst und einen 32 bit Driver gewählt (Access 64 bit vers.) da meine Python V. 3.8 32 bit ist Nur die Bedeutung von .c. in

Code: Alles auswählen

 select([mitglied_table.c.name])
ist mir nicht klar.
Danke
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Steht für column.
Gerhardus
User
Beiträge: 42
Registriert: Dienstag 31. Dezember 2013, 01:59

Danke _deets_,
aber ein Problem steht noch an, gehört wahrscheinlich nicz´t hierher, aber ich versuch es trotzdem.
Bisher mit Idle ohne debugger gearbeitet, jetz aktuelle Ver. von Visual Studio Code imit deutschem Lang. Pack installiert,
aber er bleibt generell beim Haltepunkt nicht stehen.

Code: Alles auswählen

"stopOnEntry": true
gesetzt, mehrmals installiert, Videos
geschaut google befragt, nichts hat genützt. Hat wer eine Lösung, bevor ich ihn runterschmeisse?

Gruß Gerhardus
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Habe ich keine Ahnung von, ich benutze den Debugger nur in der Kommandozeile per breakpoint()-Aufruf. Das reicht fuer mich. Aber ich bin sicher, VSCode kann man so hinbiegen, musst du nur woanders fragen. Oder wen anderes.
Antworten