SQLAlchemy MySQL frage

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
Kalysto
User
Beiträge: 117
Registriert: Freitag 14. April 2017, 15:28

Hallo zusammen,

Ich habe ein paar Fragen.

1. Der Aufbau wie ich den Code erstellt habe ist der so annehmbar oder gibts Fehler ?

Code: Alles auswählen

class Kunde(Base):
    __tablename__ = "customers"
    tabelle_id = Column(Integer, primary_key=True)
    kundennummer = Column(Integer)
    firma = Column(Integer)
    favorit = Column(Integer, default=0)
    benutzername = Column(String(20))
    benutzername_update = Column(String(20), default=None)
    firmenname = Column(String(50))
    vorname = Column(String(15))
    nachname = Column(String(15))
    ort = Column(String(25))
    postleitzahl = Column(Integer)
    kundenprojekte = Column(String(255))
    erstellungsdatum = Column(DateTime, default=f'{date.today()}')
    aktualisierungsdatum = Column(DateTime, default=None)

    def __init__(self, kundennummer, firma, benutzername, firmenname, vorname, nachname, ort, postleitzahl, kundenprojekte):
        self.kundennummer = kundennummer
        self.firma = firma
        self.benutzername = benutzername
        self.firmenname = firmenname
        self.vorname = vorname
        self.nachname = nachname
        self.ort = ort
        self.postleitzahl = postleitzahl
        self.kundenprojekte = kundenprojekte
        
        self.benutzername = "Test Name"
        
        CODE
2. dürfte ich wie hier: `self.benutzername = "Test Name"` gezeigt einfach den Wert abändern oder überschreibe ich somit die Column welche ich zuvor erstellt hatte, sodass meine Einträge nicht mehr "sauber" erstellt werden ?

3. Ist es möglich in dem Bereich wo CODE in der Darstellung steht eine Join abfrage zu erstellen sodass ich mir von einer Anderen Tabelle Daten hier einlesen kann ?

Würde mich wie bekanntlich über Hilfe freuen. :)
Benutzeravatar
__blackjack__
User
Beiträge: 13073
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Kalysto: Ad 1.: Tabellennamen benenne ich in der Einzahl, denn das ist ja eine Entität oder Relation und die beschreibt genau wie die Klasse *einen* Datensatz.

Ich würde im Code alles Englisch benennen, sonst endet das in einem Denglisch-Mix mit der Programmiersprache und anderen Namen. Momentan ist es ja schon so, dass die Tabelle `customers` heisst, die Spalten dieser Tabelle dann aber deutsch benannt sind.

`tabelle_id` würde ich nur `id` nennen.

Bei `firma` dachte ich erst an eine ID und dass das ein Fremdschlüssel sein müsste, aber ich denke eher Du willst da `Boolean` statt `Integer` haben‽ Und dann sollte das eher `ist_firma` oder `is_company` heissen. Wobei ich mich Frage ob man das überhaupt braucht, denn nur Firmen haben einen Firmennamen, also könnte man das doch auch daran koppeln, ob der Firmenname nicht None/NULL ist. `is_company` könnte man als Property definieren. Eventuell auch als Hybrid-Property das auch als Abfragetest in Datenbankabfragen verwendet werden kann. Und `favorit` ist wohl auch ein `Boolean` mit dem Default-Wert `False`.

Insgesamt sollte man da mal schauen was alles nicht `nullable` sein sollte. Im Grunde ja eigentlich erst einmal alles, ausser man hat einen guten Grund das zu erlauben.

`ort` und `postleitzahl` gehören da nicht rein, das kann ja für mehr als einen Kunden gleich sein.

Das Erstellungsdatum sollte keine Zeichenkette als Defaultwert haben, und auch eher keinen festen Wert der *einmal* beim starten des Programms festgelegt wird. Der Wert sollte immer aktuell sein, also etwas aufrufbares, dass das den aktuellen Zeitstempel erzeugt. Und das Datum der letzten Aktualisierung würde ich auf den gleichen Wert setzen. Da None/NULL zu setzen schafft nur unnötig Sonderfälle.

Was ist denn in `kundenprojekte` gespeichert? Das sieht komisch aus.

Eine `__init__()` würde ich nicht selbst schreiben, es gibt ja bereits eine von `Base` die man nicht nur benutzen kann, sondern auch sollte. Insbesondere *muss* man die in einer eigenen `__init__()` ja aufrufen und dann fängt man an viel unnötige Schreibarbeit zu haben.

Ad 2.: Die Attribute auf der Klasse sind andere als die auf jedem einzelnen Exemplar. Die auf der Klasse beschreiben die Attribute die durch die `__init__()` dann später auf den einzelnen Exemplaren zur Verfügung stehen. Also die `__init__()` die bereits besteht, nicht die, die Du da noch zusätzlich geschrieben hast. Was ich wie gesagt sein lassen würde.

Ad 3.: Was soll denn da in der `__init__()` eines Kunden aus anderen Tabellen abgefragt werden? Das würde dann ja nicht auf dem Kunden als Attribut landen‽
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
paddie
User
Beiträge: 101
Registriert: Donnerstag 11. Oktober 2018, 18:09

IMHO sollte Kundenprojekte eine eigene Tabelle werden. Je nach Kunde werden das ja garantiert ein paar mehr ;-).

zu 2. DAS sind Dinge die du später genau dann machst wenn du es brauchst. In deinem Modell hat sowas eher nichts zu suchen.
Kalysto
User
Beiträge: 117
Registriert: Freitag 14. April 2017, 15:28

__blackjack__ hat geschrieben: Samstag 20. Mai 2023, 23:40 @Kalysto: Ad 1.: Tabellennamen benenne ich in der Einzahl, denn das ist ja eine Entität oder Relation und die beschreibt genau wie die Klasse *einen* Datensatz.
war das hierauf bezogen: "`tabelle_id` würde ich nur `id` nennen." ?
werde ich anpassen, danke
__blackjack__ hat geschrieben: Samstag 20. Mai 2023, 23:40 Ich würde im Code alles Englisch benennen, sonst endet das in einem Denglisch-Mix mit der Programmiersprache und anderen Namen. Momentan ist es ja schon so, dass die Tabelle `customers` heisst, die Spalten dieser Tabelle dann aber deutsch benannt sind.
Ja, hmmm das hatte ich zuvor auch gemacht nur ist es dann so das ich teilweise nicht mehr weis was es bedeutet hatte, da mein Englisch nicht gerade das beste ist habe ich dann eben Übersetzter genutzt das dann aber Wiederrum vergessen....
wenn ich nun hergehen würde und würde meine Tabelle `customers` in `Kunden` anpassen wäre das dann okay oder sollte man das alles in Englisch halten wie auch Variablen etc. ?
__blackjack__ hat geschrieben: Samstag 20. Mai 2023, 23:40 Bei `firma` dachte ich erst an eine ID und dass das ein Fremdschlüssel sein müsste, aber ich denke eher Du willst da `Boolean` statt `Integer` haben‽ Und dann sollte das eher `ist_firma` oder `is_company` heissen. Wobei ich mich Frage ob man das überhaupt braucht, denn nur Firmen haben einen Firmennamen, also könnte man das doch auch daran koppeln, ob der Firmenname nicht None/NULL ist. `is_company` könnte man als Property definieren. Eventuell auch als Hybrid-Property das auch als Abfragetest in Datenbankabfragen verwendet werden kann. Und `favorit` ist wohl auch ein `Boolean` mit dem Default-Wert `False`.

Insgesamt sollte man da mal schauen was alles nicht `nullable` sein sollte. Im Grunde ja eigentlich erst einmal alles, ausser man hat einen guten Grund das zu erlauben.
Ja, `firma` Speichert Boolean
Ja, man kann auch hergehen und den `firmenname` auf None/NULL Prüfen ich dachte mir nur das es mit einer direkten abfrage ersichtlicher scheint oder sollte man das nicht machen ?
mit dem Property und Hybrid-Property kann ich so nichts anfangen habe in MariaDB einmal geschaut dort gibt es eine solche Einstellung nicht ?
`favorit` wird bei einem neuen Kunden immer ersteinmal auf False gesetzt, richtig.

Habe ich eig. denn `nullable` ist eig. nur `benutzername_update` und `aktualisierungsdatum` da dies ja bei der Erstellung keine Rolle erst einmal spielt.
__blackjack__ hat geschrieben: Samstag 20. Mai 2023, 23:40 `ort` und `postleitzahl` gehören da nicht rein, das kann ja für mehr als einen Kunden gleich sein.

Das Erstellungsdatum sollte keine Zeichenkette als Defaultwert haben, und auch eher keinen festen Wert der *einmal* beim starten des Programms festgelegt wird. Der Wert sollte immer aktuell sein, also etwas aufrufbares, dass das den aktuellen Zeitstempel erzeugt. Und das Datum der letzten Aktualisierung würde ich auf den gleichen Wert setzen. Da None/NULL zu setzen schafft nur unnötig Sonderfälle.
Wieso gehört das da nicht rein ?
Ich erstelle ja hier keine `SELECT` abfrage damit sondern ein `INSERT INTO` eig. müsste ich meine Klasse so benennen: `Kunden_Eintragung` und dann stimmt das doch ?

Wieso darf Erstellungsdatum keine Zeichenkette als Defaultwert besitzen ? Jedesmal wenn ich doch die Funktion aufrufe um einen Kunden in meine DB einzutragen wird der Wert doch neu gesetzt ?
Was genau meinst du damit: "Da None/NULL zu setzen schafft nur unnötig Sonderfälle." Ich würde `aktualisierungsdatum` erst ein Date geben wenn der Kunde auch aktualisiert wurde sprich ein `UPDATE` durchgeführt wurde ?
__blackjack__ hat geschrieben: Samstag 20. Mai 2023, 23:40 Was ist denn in `kundenprojekte` gespeichert? Das sieht komisch aus.
Das werde ich noch ändern müssen hier werden aktuell "Standard" Projekte eingetragen die Automatisch bei Erstellung des Kundens eingetragen werden anhand einer Tuple: Fertigmontage, Hausrenovierung - Zählerschrank, Hausrenovierung - Unterverteilung, ....
__blackjack__ hat geschrieben: Samstag 20. Mai 2023, 23:40 Eine `__init__()` würde ich nicht selbst schreiben, es gibt ja bereits eine von `Base` die man nicht nur benutzen kann, sondern auch sollte. Insbesondere *muss* man die in einer eigenen `__init__()` ja aufrufen und dann fängt man an viel unnötige Schreibarbeit zu haben.
Das mit der Schreibarbeit ist mir aufgefallen :D
__blackjack__ hat geschrieben: Samstag 20. Mai 2023, 23:40 Ad 2.: Die Attribute auf der Klasse sind andere als die auf jedem einzelnen Exemplar. Die auf der Klasse beschreiben die Attribute die durch die `__init__()` dann später auf den einzelnen Exemplaren zur Verfügung stehen. Also die `__init__()` die bereits besteht, nicht die, die Du da noch zusätzlich geschrieben hast. Was ich wie gesagt sein lassen würde.
Okay, dann werde ich das so nicht machen.
__blackjack__ hat geschrieben: Samstag 20. Mai 2023, 23:40 Ad 3.: Was soll denn da in der `__init__()` eines Kunden aus anderen Tabellen abgefragt werden? Das würde dann ja nicht auf dem Kunden als Attribut landen‽
Ich wollte eig. die PLZ und den Ort in eine Separate Tabelle schreiben und anhand ID's verknüpfen nun war eben mein Gedanke ich rufe dann mit Join etc. die ID des Orts ab sodass ich diese bei Eintragung des Kunden gleich mit in der Tabelle `customers` habe so in etwa:
Ist nun nur so daher geschrieben wie ich es gedacht habe ohne Funktion etc.:

Code: Alles auswählen

class Kunden_Eintragung(Base):
    __tablename__ = "customers"
    tabelle_id = Column(Integer, primary_key=True)
    kundennummer = Column(Integer)
    firma = Column(Integer)
    favorit = Column(Integer, default=False)
    benutzername = Column(String(20))
    benutzername_update = Column(String(20), default=None)
    firmenname = Column(String(50), default=None)
    vorname = Column(String(15), default=None)
    nachname = Column(String(15), default=None)
    # ort = Column(String(25))
    # postleitzahl = Column(Integer)
    kundenprojekte = Column(String(255))
    erstellungsdatum = Column(DateTime, default=f'{date.today()}')
    aktualisierungsdatum = Column(DateTime, default=None)
    
    
    # Nun möchte ich hier gerne Prüfen ob der Ort sowie die PLZ in der Tabelle `hab_noch_keine` bereits vorhanden ist und wenn ja mir deren ID holen bzw. wenn Nein diese Eintragen und somit dann die ID holen
    
    If Ort in `hab_noch_keine`:
    	standort_id = Hier ist die dazugehörige Ort sowie PLZ ID
    else:
    	Hier wird der Ort und die PLZ in die Tabelle `hab_noch_keine` eingetragen und danach
    	standort_id = Hier ist die dazugehörige Ort sowie PLZ ID
    	
stmt = Kunden_Eintragung(
    Kundennummer=1232,
    Firma = False,
    Benutzername = "Max Mustermann",
    Vorname = "Hans",
    Nachname = "Solo",
    Ort = "Opfelhausen",
    Postleitzahl = "55596",
    Kundenprojekte = "Test 1, Test 2, Test 3, Test 4, Test 5",
)
Weis halt nicht ob man das so machen kann und darf oder auch sollte.
hier bin ich auch wieder für Hilfe offen wie man das Lösen sollte bzw. ob das überhaupt so machbar ist denn ich muss ja auf einer andere Tabelle zugreifen können und evtl. eintrage tätigen

oder muss ich zuvor eine andere Klasse/Funktion erstellen die dann erst einmal genau das Prüft und evtl. die Einträge tätigen würde und mir als return die ID liefert ?

@ __blackjack__ vielen dank für deine Mühe!
Benutzeravatar
grubenfox
User
Beiträge: 423
Registriert: Freitag 2. Dezember 2022, 15:49

Kalysto hat geschrieben: Sonntag 21. Mai 2023, 12:17 Ja, hmmm das hatte ich zuvor auch gemacht nur ist es dann so das ich teilweise nicht mehr weis was es bedeutet hatte, da mein Englisch nicht gerade das beste ist habe ich dann eben Übersetzter genutzt das dann aber Wiederrum vergessen....
wenn ich nun hergehen würde und würde meine Tabelle `customers` in `Kunden` anpassen wäre das dann okay oder sollte man das alles in Englisch halten wie auch Variablen etc. ?
So lange das Programm nur im Eigenbedarf verwendet wird oder nur zusammen mit Kollegen aus dem deutschsprachigen Raum, solange nehme ich für den eigenen Kram gerne deutsche Bezeichnungen. Das senkt die Gefahr für so etwas:
`list` ist der Name der Python-List-Klasse und sollte nicht mit eigenen Werten überdeckt werden.
ungemein. Englisch für die Programmiersprache und deutsch für die Begriffe der Anwendung. Schon gibt es fast keine(!) Möglichkeit mehr dass man aus Versehen mit den eigenen Namen irgendwelche Dinge aus der Python-Umgebung überdeckt.
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

@grubenfox: Davon rate ich dringend ab. Das versehentliche Überschreiben von Namen ist gar kein Problem, wenn man vernünftige Namen verwendet. "list" ist ein beschissner Name. "liste" würde es nicht besser machen.

Deutsch hat das Problem, dass die Pluralbildung leider nicht so wunderschön einfach wie im Englischen ist, wo man in der Regel einfach "s" anhängt. Und dann muss man eine Konvention finden oder ständig nachschauen, ob man Umlaute benutzt - oder doch eher nicht.

Ganz klare Empfehlung: Englische Namen. Immer. Auch, weil sich dann der Quellcode holperfrei lesen lässt.
Benutzeravatar
grubenfox
User
Beiträge: 423
Registriert: Freitag 2. Dezember 2022, 15:49

sparrow hat geschrieben: Sonntag 21. Mai 2023, 20:35Und dann muss man eine Konvention finden oder ständig nachschauen, ob man Umlaute benutzt - oder doch eher nicht.
Das Problem gibt es ja leider seit Python 3. In Python 1.x und 2.x (und anderen Sprachen): nur ASCII-Zeichen im Code. Damit läuft das bei mir unter der Konvention "haben wir schon immer so gemacht" und Umlaute und andere Zeichen und Symbole (oder gar Unicode) sind raus aus der Nummer...
Benutzeravatar
__blackjack__
User
Beiträge: 13073
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Kalysto: Mit Einzahl war `customer` statt `customers` gemeint.

Property ist nichts von Datenbanken sondern von Python. Und SQLAlchemy bietet Hybrid-Properties die als normales Property auf den Exemplaren existieren, aber auch als Property auf der Klasse das in SQL umgesetzt wird, so dass man das auch in Abfragen benutzen kann und nicht nur auf Objekten die aus einer Abfrage aus der Datenbank kommen.

In SQL ist jede Spalte ”nullable” sofern man das nicht explizit anders deklariert. Da Du nicht einmal ``nullable=False`` hast, ist alles ausser dem Primärschlüssel ”nullable”.

`ort` und `postleitzahl` gehören da nicht rein weil das redundant, also nicht in Normalform ist. Das hast Du doch selbst auch schon erkannt.

Die Klasse repräsentiert keine `Kunden_Eintragung` sondern einen Kunden. Du erstellst da weder ein ``SELECT`` noch ein ``INSERT INTO`` mit der Klasse, Du beschreibst damit wie ein einzelner Datensatz in der Tabelle auf ein Objekt abgebildet wird. Exemplare davon kannst Du erstellen indem Du ein Objekt aus der Klasse erstellst, oder in dem Du die aus der Datenbank abfragst. Das erste kann zu einem ``INSERT INTO`` führen, falls Du das Objekt zur Session hinzufügst und committest, und die Abfrage führt zu einem ``SELECT``, sofern das Objekt nicht schon in der Session existiert und gecached ist.

Ein Datum ist keine Zeichenkette. Also eigentlich ist das falsch. Wenn die konkrete Datenbank das schluckt, hast Du Glück, da würde ich aber nicht von ausgehen. Und diese Zeichenkette wird *einmal* beim erstellen der Klasse erstellt und danach natürlich nie wieder geändert, solange das Programm läuft.

Wenn Du alle Datensätze haben willst die vor oder nach einem bestimmten Datum aktualisiert wurden und dieses Datum auch NULL/None sein kann, dann musst Du das bei der Abfrage gesondert berücksichtigen. Also nicht nur nach dem Datum filtern, sondern zusätzlich noch mal schauen ob da jetzt die mit keinen Datum drin sein sollen/müssen oder nicht und das auch in der Abfrage ausdrücken.

In der `Kunde`-Klasse hat man eine `standort_id` die ein Fremdschlüssel in eine `Standort`-Klasse/`standort`-Tabelle ist. Und dann definiert man auf beiden Klassen eine entsprechende `relationship()` mit SQLAlchemy und hat dann Attribute die automatisch zu einer Abfrage führen wenn man darauf zugreift (einfachster Fall), oder man kann das auch so konfigurieren, dass zu Kunden automatisch immer die Standort-Daten mit abgefragt werden, also im Hintergrund automatisch ein JOIN bei jeder Abfrage von `Kunden` gemacht wird.

Ich persönlich fange in der Regel erst mit einfachen Beziehungen an, die zu einzelnen Abfragen bei Attributzugriff führen, bis das tatsächlich zu einem messbaren Laufzeitproblem führt. Solange die Datenmengen nicht richtig gross sind/werden, würde ich das sonst als „premature optimization“ sehen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten