Boolean Wert an Sqlite3 übergeben

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
Juliusvanvern
User
Beiträge: 15
Registriert: Mittwoch 22. Januar 2020, 20:53

Hi :)

vielleicht kann mir jemand helfen.

In einem etwas umfangreicheren Programm, möchte ich ein paar Checkboxen einbauen. Deren Werte sollen in die sqlite3 DB ein- und wieder ausgelesen werden.

Im Moment hänge ich beim einlesen der Werte.

Mein Code auf das nötigste Reduziert.

Code: Alles auswählen

import tkinter as tk
from tkinter import *
import sqlite3

def connect():
    conn=sqlite3.connect('checkdb.db')
    cur=conn.cursor()
    cur.execute("CREATE TABLE IF NOT EXISTs checkdb (\
        check_epos boolean)")
    conn.commit()
    conn.close()

def insert(check_epos):
    conn=sqlite3.connect('checkdb.db')
    cur=conn.cursor()
    cur.execute("INSERT INTO checkdb Values (?)", [check_epos])
    conn.commit()
    conn.close()

connect()

def add_command():
    insert(c1.getboolean())

root = tk.Tk()

root.title('Check Test')

c1 = tk.IntVar()
c1 = tk.Checkbutton(root, text='check mich', variable=c1)
c1.select()
c1.grid(row=0, column=0)

b1 = tk.Button(root, text='Drück mich', command=add_command)
b1.grid(row=1, column=0)

root.mainloop()
Wenn ich über getboolean() den Wert True an die DB übergeben, geht es. getboolean(TRUE). Das heißt es scheint der richtige weg zu sein. In der Datenbank erscheint dann der Wert 1

Wie bekomme ich denn, den Aktuellen Wert aus der Checkbox? Sie hat ja nur zwei Zustände. 0/1 Angehackt oder nicht.

Hier bräuchte ich einen Denkanstoß oder ggf eine Korrektur im Code. :)

Vielen Dank
Benutzeravatar
__blackjack__
User
Beiträge: 13111
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Juliusvanvern: Verwende bessere Namen die beschreiben was der Wert bedeutet der daran gebunden wird. Dann fällt Dir vielleicht auch der Fehler auf. Und das Programm ist wie gezeigt nicht lauffähig weil `getboolean()` ein Argument erwartet.

`getboolean()` hat nichts mit der `Checkbox` zu tun. Das ist eine ”Methode” die es auf jedem Widget gibt und die eine Zeichenkette mit einem Wert den Tcl für ”wahr” oder ”falsch” hält in einen Python-Wert vom Typ `bool` umwandelt. Wenn man diese ”Methode” mit `tkinter.TRUE` aufruft, kommt da immer der Python-Wert `True` bei heraus. Ist also nicht sehr sinnvoll das zu tun.

Weitere Anmerkungen: Sternchen-Importe sind Böse™. Damit holt man sich gerade bei `tkinter` hunderte von Namen ins Modul von denen nur ein kleiner Bruchteil tatsächlich verwendet wird. Und nicht nur Namen die im `tkinter`-Modul definiert werden, sondern auch solche die in dem Modul von woanders her importiert werden. Das macht Programme unübersichtlich und es besteht zudem die Gefahr von Namenskollisionen.

Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm gehört dort nicht hin, schon gar nicht zwischen Funktionsdefinitionen verteilt. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Funktionen und Methoden bekommen alles was sie ausser Konstanten benötigen als Argument(e) übergeben. Geht ja auch nicht anders wenn man keine Variablen mehr auf Modulebene hat. Das bedeutet das jedes nicht-triviale GUI-Programm objektorientierte Programmierung voraussetzt.

Warum `tkinter.IntVar` für einen boole'schen Wert? Was spricht gegen `tkinter.BooleanVar`?

Der Funktionsname `connect()` beschreibt nicht ausreichend was diese Funktion tatsächlich tut.

Der Datenbankentwurf ist unsinnig. Man kann in die Tabelle mehrere Wahrheitswerte speichern, sie dann aber nicht unterscheiden. Welcher soll denn angezeigt werden?

Das Schliessen der Datenbankverbindung sollte man durch ``with`` sicherstellen.

Programmierer vermeiden Daten- und Codewiederholungen. Der Name der Datenbank sollte nur einmal, als Konstante, im Programm stehen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
__blackjack__
User
Beiträge: 13111
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Die meisten Anmerkungen mal auf den bestehenden Code angewendet:

Code: Alles auswählen

#!/usr/bin/env python3
import sqlite3
import tkinter as tk
from functools import partial

DB_FILENAME = "checkdb.db"


def create_table():
    with sqlite3.connect(DB_FILENAME) as connection:
        connection.cursor().execute(
            "CREATE TABLE IF NOT EXISTS checkdb (check_epos BOOLEAN)"
        )
        connection.commit()


def insert(check_epos):
    with sqlite3.connect(DB_FILENAME) as connection:
        connection.cursor().execute(
            "INSERT INTO checkdb VALUES (?)", [check_epos]
        )
        connection.commit()


def add_command(epos_var):
    insert(epos_var.get())


def main():
    create_table()

    root = tk.Tk()
    root.title("Check Test")
    epos_var = tk.BooleanVar()
    check_button = tk.Checkbutton(root, text="Check mich", variable=epos_var)
    check_button.select()
    check_button.grid(row=0, column=0)
    tk.Button(
        root, text="Drück mich", command=partial(add_command, epos_var)
    ).grid(row=1, column=0)

    root.mainloop()


if __name__ == "__main__":
    main()
Zur Frage: Statt `select()` aufzurufen und den Haken damit grundsätzlich zu setzen, müsste man den Wert aus der Datenbank lesen und als Wert bei dem `BooleanVar`-Objekt setzen. Wobei da halt das Problem auftaucht das es beim gegenwärtigen Datenbankentwurf nicht den einen Wert gibt, sondern gar keinen bis beliebig viele Werte. Wie soll denn da der Zustand ermittelt werden?

Der Tabellenname ist auch komisch. Ein Tabellenname beschreibt üblicherweise *einen* Datensatz. Also beispielsweise `person` wenn in der Tabelle Daten zu einer Person pro Datensatz gespeichert werden.

Falls am Ende die gesamte Datenbank nur dazu verwendet wird um *einen* Wahrheitswert zu speichern, dann ist eine relationale Datenbank nicht wirklich das geeignete Format dafür. Da könnte man eine einfache Textdatei für verwenden in die man 1 oder 0 schreibt, je nach dem ob der Haken gesetzt sein soll oder nicht.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Juliusvanvern
User
Beiträge: 15
Registriert: Mittwoch 22. Januar 2020, 20:53

Puh, das ist jetzt viel Input mit einmal. :D Vorab schon einmal vielen Dank. Ich versuche auf alle Anmerkungen einzugehen.
@Juliusvanvern: Verwende bessere Namen die beschreiben was der Wert bedeutet der daran gebunden wird. Dann fällt Dir vielleicht auch der Fehler auf. Und das Programm ist wie gezeigt nicht lauffähig weil `getboolean()` ein Argument erwartet.
Da hast du natürlich recht. Das Skript war nur zum rum probieren gedacht, daher habe ich auf eine Aussagekräftige Benennung verzichtet. Hier habe ich wohl die Funktion von getboolean falsch verstanden.
`getboolean()` hat nichts mit der `Checkbox` zu tun. Das ist eine ”Methode” die es auf jedem Widget gibt und die eine Zeichenkette mit einem Wert den Tcl für ”wahr” oder ”falsch” hält in einen Python-Wert vom Typ `bool` umwandelt. Wenn man diese ”Methode” mit `tkinter.TRUE` aufruft, kommt da immer der Python-Wert `True` bei heraus. Ist also nicht sehr sinnvoll das zu tun.
Ich habe die Funktion falsch verstanden. Ich bin davon ausgegangen, das er den Wert in Form von True/False zurück gibt, was in der DB dann als 0/1 gespeichert wird und ich entsprechend beim Auslesen wieder zurück geben kann.
Weitere Anmerkungen: Sternchen-Importe sind Böse™. Damit holt man sich gerade bei `tkinter` hunderte von Namen ins Modul von denen nur ein kleiner Bruchteil tatsächlich verwendet wird. Und nicht nur Namen die im `tkinter`-Modul definiert werden, sondern auch solche die in dem Modul von woanders her importiert werden. Das macht Programme unübersichtlich und es besteht zudem die Gefahr von Namenskollisionen.
Okay, das hört sich natürlich Plausibel an und werde ich mir merken. Ich habe darüber gar nicht groß nachgedacht, welche Auswirkungen das haben könnte.
Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm gehört dort nicht hin, schon gar nicht zwischen Funktionsdefinitionen verteilt. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Ja in der Tat, aber wie gesagt, ich hatte mir keine große Mühe gegeben, es war rein zum Probieren gewesen. Das eigentliche Programm, wo ich die Funktion benötige ist Ordentlicher gegliedert und DB Abfragen zb ausgelagert.
Warum `tkinter.IntVar` für einen boole'schen Wert? Was spricht gegen `tkinter.BooleanVar`?
Gute Frage, das weiß ich nicht. Mir war nicht klar, das es BooleanVar gibt. :)
Der Funktionsname `connect()` beschreibt nicht ausreichend was diese Funktion tatsächlich tut.
Da gebe ich dir Recht. Den namen werde ich abändern. Hier soll eigentlich nur geprüft werden ob die DB vorhanden ist und wenn nicht soll sich mit entsprechenden Werten erstellt werden.
Der Datenbankentwurf ist unsinnig. Man kann in die Tabelle mehrere Wahrheitswerte speichern, sie dann aber nicht unterscheiden. Welcher soll denn angezeigt werden?
Die Finale DB sieht natürlich nicht so aus. Hier werden am Ende ca 35 Werte enthalten sein (Boolean Werte, Datumsangaben, Text und Zahlen)
Das Schliessen der Datenbankverbindung sollte man durch ``with`` sicherstellen.
Danke, das wusste ich nicht. Ich bin davon ausgegangen das .close() reicht.
Programmierer vermeiden Daten- und Codewiederholungen. Der Name der Datenbank sollte nur einmal, als Konstante, im Programm stehen.
Das stimmt, im Hauptprogramm ist die Benennung strenger. Hier war es nur schnell zu Papier gebracht.
Zur Frage: Statt `select()` aufzurufen und den Haken damit grundsätzlich zu setzen, müsste man den Wert aus der Datenbank lesen und als Wert bei dem `BooleanVar`-Objekt setzen. Wobei da halt das Problem auftaucht das es beim gegenwärtigen Datenbankentwurf nicht den einen Wert gibt, sondern gar keinen bis beliebig viele Werte. Wie soll denn da der Zustand ermittelt werden?
Ich bin mir nicht ganz sicher ob ich die Frage richtig verstehe. Aber vielleicht habe ich glaube noch gar keine Antwort auf das Wie. Wie könnte denn der Zustand ermittelt werden?
Falls am Ende die gesamte Datenbank nur dazu verwendet wird um *einen* Wahrheitswert zu speichern, dann ist eine relationale Datenbank nicht wirklich das geeignete Format dafür. Da könnte man eine einfache Textdatei für verwenden in die man 1 oder 0 schreibt, je nach dem ob der Haken gesetzt sein soll oder nicht.
Leider nein. Die Übergelegte GUI hat mehrere Checkboxen, die für ein schnelles Anhaken von JA/NEIN Fragen herhalten sollen. Theoretisch könnte man das auch mit einer Dropbox machen, die nur den Wert JA / NEIN vorhalten. Aber dann müsste man immer in die Box klicken, ja oder nein auswählen. Einen Haken setzen ist da generell einfacher und es führt beim Anwender eher nicht zur Falschauswahl.
Der Code
Werde ich heute Abend einmal Testen. Vielen Dank für deine ganze Hilfestellung. :)
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Das ist halt das Problem, wenn man sein Programm so weit zusammenstreicht, dass die eigentliche Frage gar nicht mehr klar ist.

Wenn Du mehrere Checkboxen hast, deren Zustand Du speichern willst, dann brauchst Du eine Tabelle checkbox_state, die jeweils eine eindeutige ID für jede Checkbox enthält und den Wert.
Wobei ich mich auch hier frage, ob eine Datenbank das richtige ist, oder ob nicht eine Textdatei als Konfiguration besser wäre.

Kannst Du etwas weiter ausholen und Deine Aufgabe genauer beschreiben?
Juliusvanvern
User
Beiträge: 15
Registriert: Mittwoch 22. Januar 2020, 20:53

Kannst Du etwas weiter ausholen und Deine Aufgabe genauer beschreiben?
Gerne. :)

Ich arbeite Täglich mit einer Excel Tabelle. Über diese habe ich eine VB-Gui gelegt, die alle Informationen mit einmal darstellt. Es ist nun soweit gewachsen, das es wie ein Programm ausschaut und mit dem ich super jeden Tag arbeiten kann. Leider Stößt Excel da an seine Grenzen. Es dauert immer länger bis die Daten geladen oder gespeichert werden.

Die Tabelle enthält die Daten zu meinen Kunden. Vom Datum wann sie angelegt wurden über die letzten Angebote bis hin zu ASP, Website und Kundengespräche. Die Checkboxen sind unter anderen dafür gedacht, welche Art an Informationen zb dem Kunden schon einmal zugekommen sind. Damit keine Doppelten Sachen raus geschickt werden. Und halt noch vieles mehr. Und da dachte ich mir, eine SQL-DB wäre doch schneller und Python sollte keine all zu schwere Sprache sein.

Edit:
Im wesentlichen, wollte ich mit dem Programmschnippsel ganz am Anfang prüfen. Wie bekomme ich den Wert für eine Checkbox ausgelesen und wieder wieder in die Datenbank geschrieben. Da später ja noch ein Primary Key für jeden Eintrag enthalten sein soll, soll ja nicht jedesmal wenn ich auf speichern drücke ein neuer Eintrag erstellt werden. Zb. Kunde mit KdNr: 12345 hat den Primary Key 1, hat einen Eintrag bei Werbung. Das soll aus der Datenbank ausgelesen und entsprechend in der Gui dargestellt werden. Wenn er Werbung bekommen hat, soll ein Haken in die Checkbox gesetzt werden. Wenn ich den Wert ändere, zb den Haken raus nehme und auf speichern drücke, soll der neue Wert unter dem Primary Key 1 zum zugehörigen Kunden geändert werden. Da dachte ich, das würde dann mit cur.execute("UPDATE kunde SET id=?) Funktionieren.
Zuletzt geändert von Juliusvanvern am Donnerstag 23. Januar 2020, 14:52, insgesamt 1-mal geändert.
Benutzeravatar
__blackjack__
User
Beiträge: 13111
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Juliusvanvern: Also brauchst Du nicht nur Ja/Nein-Werte, sondern auch noch auf welchen Kunden die sich beziehen. Beziehungsweise klingt das nicht wirklich nach Ja/Nein-Informationen sondern eher nach Kunden und Informationsarten und eine Tabelle die zuordnet welcher Kunde welche Informationsart bereits bekommen hat. Und wenn der Rest der Excel-Datei auch in einer relationalen Datenbank gespeichert werden soll, dann reden wir von einem deutlich grösseren Datenbankentwurf.

Da sollte man dann auch als erstes mal festhalten das Tabellen in Excel konzeptionell etwas völlig anderes sind als Tabellen in SQL-Datenbanken. Man wird da also nicht für jede Excel-Tabelle eine Datenbanktabelle mit den gleichen Spalten haben. Es sei denn man verwendet Excel-Tabellen tatsächlich wie Tabellen in einer relationalen Datenbank, was eher unwahrscheinlich ist, das man so etwas per Hand macht.

Edit: Und wenn das umfangreicher wird, würde auch schon mal frühzeitig auf SQLAlchemy hinweisen wollen, bevor man da zu viel SQL per Hand zusammenfrickelt.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Juliusvanvern
User
Beiträge: 15
Registriert: Mittwoch 22. Januar 2020, 20:53

Naja, also die Exceltabelle soll abgeschafft werden, aber die SQLDatenbank muss dann entsprechend die selben Inhalte breit halten.

Aktuell sieht mein DB-Entwurf noch so aus. (ich bin mir noch nicht ganz im klaren ob ich die Benennung in Englisch oder Deutsch mache :P )

Code: Alles auswählen

def connect():
    conn=sqlite3.connect('Kundendatenbank_Test\kundendb.db')
    cur=conn.cursor()
    cur.execute("CREATE TABLE IF NOT EXISTs kunde_west (\
        id INTEGER PRIMARY KEY,\
        kd_number integer,\
        kd_name text,\
        kd_email text,\
        kd_anrede text,\
        kd_vorname text,\
        kd_nachname text,\
        kd_rufnummer integer,\
        kd_website text, \
        kd_created date, \#_____________________
        seen_inet boolean, \
        seen_ibau boolean, \ #Das sind die Checkboxen die ausgelesen und wieder upgedatet werden sollen. 
        seen_ipic boolean, \
        seen_epos boolean, \#_______________________
        order_status text, \
        order_date date, \
        werbemittel text, \
        bedarf text, \
        kontakt text, \
        kontakt_art text, \
        termin text, \
        rv text, \
        interesse text, \
        bundesweit text, \
        kommentar text, \
        wdv date, \
        update_date date, \
        aktion1 text, \
        aktion1_datum date, \
        aktion2 text, \
        aktion2_datum date, \
        aktion3 text, \
        aktion3_datum date, \
        angehen text)")
    conn.commit()
    conn.close()
Edit: Und wenn das umfangreicher wird, würde auch schon mal frühzeitig auf SQLAlchemy hinweisen wollen, bevor man da zu viel SQL per Hand zusammenfrickelt.
Das ist eine gute Frage. Ich kenne mich mit Datenbanken nicht so aus und weiß daher nicht recht Welche Hürden mich da erwarten. :D
Benutzeravatar
__blackjack__
User
Beiträge: 13111
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Juliusvanvern: Der Entwurf sieht ja wirklich so aus als wenn da einfach eine Exceltabelle als Datenbanktabelle angelegt wurde. Das `_west` im Tabellennamen riecht komisch. Gibt es denn noch mehr Tabellen mit dem gleichen Aufbau aber anderen Namen? Beispielsweise `kunde_ost`? Dann ist das schon mal falsch, weil da dann Daten im Tabellennamen kodiert sind. Daten gehören aber *in* Tabellen als Spalte. Wenn man dann nur die Kunden West haben will, fragt man die entsprechend ab.

Präfixe die noch mal den Tabellennamen wiederholen gehören nicht in Spaltennamen. Abkürzungen sollte man in Namen in Datenbanken im Grunde genau so wenig verwenden wie bei Namen in Programmen. Am besten benennt man so wie es dann auch im Programm heissen würde, das macht die Verwendung eines ORMs deutlich einfacher wenn man da dann nicht noch mal alle möglichen Namen auf andere Namen abbilden muss.

Telefonnummern sind keine ganzen Zahlen. Führende 0en kann man dann nicht speichern und es können auch noch andere Zeichen in Telefonnummern vorkommen, beispielsweise wenn man eine Nummer mit optionaler internationaler Vorwahl und einer Durchwahl hat, wird das ja oft nach so geschrieben: „+49 (0)30 1234 4567-1“.

Bei den `seen_`-Spalten habe ich den starken verdacht die Enden von den Namen irgendwelche Abkürzungen sind. Das sollte man vermeiden. Und das ist natürlich als Spalten ziemlich starr. Sollte da irgendwann mal etwas dazu kommen oder weg fallen, dann würde ich das wie schon im letzten Beitrag geschrieben in zwei Tabellen heraus ziehen: eine mit Informationsarten, und eine die aufnimmt welcher Kunde welche Informationsart bereits gesehen hat.

Die Bestellungsinformation sieht auch nach einem Kandidaten für eine eigene Tabelle aus. Es sei denn es kann tatsächlich nur eine Bestellung zur gleichen Zeit geben und es sollen tatsächlich keine historischen Daten aufgezeichnet werden. Das gleiche gilt für Termine.

`rv` und `wdv` sind kryptische Abkürzungen. Bei `bundesweit` hätte ich jetzt auf ein BOOLEAN getippt — was ist denn da der Freitext?

Durchnummerierte Spaltennamen sind ähnlich wie durchnummerierte Variablennamen: ein Zeichen das man sich entweder bessere Namen ausdenken will oder die Spalten eigentlich als Datensätze in eine eigene Tabelle gehören.

So einige von den Spalten könnten auch ein NOT NULL vertragen.

Ich würde an Deiner Stelle als allererstes mal klären ob Du das nicht vielleicht als Webanwendung mit Django machen willst. Webanwendung heisst nicht zwangsläufig Server, weil man die ja auch lokal auf einem Rechner laufen lassen kann. In dem Fall müsstest Du Dich nämlich mit dem Django-ORM auseinandersetzen.

In jedem anderen Fall würde ich wie gesagt SqlAlchemy und da auch dessen ORM verwenden und gar nicht erst anfangen SQL als Zeichenketten per Hand zu schreiben.

ORM befreit einen schon mal von der festlegung auf nur ein DBMS, weil man dann auch recht einfach auf PostgreSQL, MariaDB, MySQL, oder andere vom ORM unterstützte DBs umsteigen kann wenn das mal wachsen sollte.

Du hast da einen Pfad mit \ angegeben. Das funktioniert nur auf Windows. Das Programm macht jetzt aber nicht den Eindruck als wenn es sehr Windows-spezifisch ist. Mit dem `pathlib`-Modul kann man das auch platformunabhängig lösen.

\ um Zeilen im Quelltext fortzuführen sind nicht so toll. Die Kommentare machen das beispielsweise kaputt, das geht so nicht. Selbst ein einzelnes Leerzeichen nach einem \ am Zeichenende ist ein Fehler den man nicht so leicht sieht. Für so etwas würde ich entweder ausnutzen das logische Zeilen erst zuende sind wenn alle geöffneten Klammern auch wieder geschlossen sind plus das Zeichenkettenliterale die nur durch „whitespace“ getrennt sind, vom Compiler zu einer Zeichenkette zusammengesetzt werden. Oder man verwendet mehrzeilige Zeichenkettenliterale die in """ (oder ''') eingefasst sind.

Die Aufteilung Deiner Tests sind IMHO nicht gut. Datenhaltung und Benutzerinteraktion sind im Grunde an komplett entgegengesetzen Enden. Informationen speichern und laden, und Informationen dem Benutzer präsentieren und vom Benutzer abfragen sind unabhängig voneinander. Du vermischst hier also auch zwei Fragen und bei beiden hat Dein erstes Beispielprogramm Probleme die man unabhängig voneinander lösen kann/muss. Wenn die beiden Teilprobleme gelöst sind, *dann* kann man die zu einem Programm zusammenfügen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Verwende niemals Abkürzungen, die nicht wirklich geläufig sind und für sich schon das eigentliche Wort dafür abgelöst haben (z.B. SMS). kd_number ist also client_number (in einem Wort sollte man wirklich nicht deutsch und englisch mischen) bei inet ibau ipic epos rv oder wdv bin ich ratlos. Name, Vorname und Nachname scheinen mir redundant. order_date sollte wohl ein TIMESTAMP WITH TIMEZONE sein, aber Sqlite3 unterstützt das nicht wirklich gut, so dass man üblicherweise alles in UTC umrechnet. Damit muß aber Dein Programm sauber arbeiten können. Ich weiß nicht, wie weit das SqlAlchemy inzwischen unterstützt.

Was Du nun hast, ist eine Tabelle die ungefähr in der 1. Normalform vorliegt. Wenn ich so Dinge wie order-Status lese, liegt die Frage nahe, ob jeder Kunde wirklich nur eine einzige Order hat. Auch so Dinge wie termin oder aktion lassen mich fragen, ob es nur einen Termin geben kann, und warum gerade drei Aktionen als Tabellenspalten vorgesehen sind.
Eine sinnvolle Normalisierung würde für jeden dieser Aspekte eigene Tabellen vorsehen.
Juliusvanvern
User
Beiträge: 15
Registriert: Mittwoch 22. Januar 2020, 20:53

Oh man. Ich glaube ich habe jetzt keinen Fehler ausgelassen, den man machen kann. :D
@Juliusvanvern: Der Entwurf sieht ja wirklich so aus als wenn da einfach eine Exceltabelle als Datenbanktabelle angelegt wurde. Das `_west` im Tabellennamen riecht komisch. Gibt es denn noch mehr Tabellen mit dem gleichen Aufbau aber anderen Namen? Beispielsweise `kunde_ost`? Dann ist das schon mal falsch, weil da dann Daten im Tabellennamen kodiert sind. Daten gehören aber *in* Tabellen als Spalte. Wenn man dann nur die Kunden West haben will, fragt man die entsprechend ab.
Ja in der Tat. Ich habe einfach alle Tabellen-Spalten aus der Excel in eine Datenbank übertragen. So in meinem nicht mehr ganz so Jugendlichen Leichtsinn.
Präfixe die noch mal den Tabellennamen wiederholen gehören nicht in Spaltennamen. Abkürzungen sollte man in Namen in Datenbanken im Grunde genau so wenig verwenden wie bei Namen in Programmen. Am besten benennt man so wie es dann auch im Programm heissen würde, das macht die Verwendung eines ORMs deutlich einfacher wenn man da dann nicht noch mal alle möglichen Namen auf andere Namen abbilden muss.
Klingt logisch, werde ich mir merken und umsetzen.
Telefonnummern sind keine ganzen Zahlen. Führende 0en kann man dann nicht speichern und es können auch noch andere Zeichen in Telefonnummern vorkommen, beispielsweise wenn man eine Nummer mit optionaler internationaler Vorwahl und einer Durchwahl hat, wird das ja oft nach so geschrieben: „+49 (0)30 1234 4567-1“.
Sollte ich das dann unter Sqlite3 als TEXT oder BLOB speichern?
Bei den `seen_`-Spalten habe ich den starken verdacht die Enden von den Namen irgendwelche Abkürzungen sind. Das sollte man vermeiden. Und das ist natürlich als Spalten ziemlich starr. Sollte da irgendwann mal etwas dazu kommen oder weg fallen, dann würde ich das wie schon im letzten Beitrag geschrieben in zwei Tabellen heraus ziehen: eine mit Informationsarten, und eine die aufnimmt welcher Kunde welche Informationsart bereits gesehen hat.

Mit einer zweiten Tabelle, die nur Informationsarten bereit hält, hast du eigentlich recht. Dann wäre eine Tabelle mit den Kunden Stammdaten vorgesehen und eine zweite für etwaige weitere Informationen. Klingt gut.
Die Bestellungsinformation sieht auch nach einem Kandidaten für eine eigene Tabelle aus. Es sei denn es kann tatsächlich nur eine Bestellung zur gleichen Zeit geben und es sollen tatsächlich keine historischen Daten aufgezeichnet werden. Das gleiche gilt für Termine.
Die Information der Bestellung, soll mir nur anzeigen, wann tatsächlich die letzte Bestellung gewesen ist. Hier soll dann später noch angezeigt werde, wenn eine gewisse Zeit überschritten ist, dass das Feld dann zb Rot wird. Um bei dem Kunden zb noch einmal anzufragen, ob alles in Ordnung ist usw..
`rv` und `wdv` sind kryptische Abkürzungen. Bei `bundesweit` hätte ich jetzt auf ein BOOLEAN getippt — was ist denn da der Freitext?
Das sind eigentlich nur die Art der Verträge die der Kunde hat. Da es hier mehr als zwei Optionen als Auswahl geben wird, kann man in dem Fall nicht mit 0/1 arbeiten.
Durchnummerierte Spaltennamen sind ähnlich wie durchnummerierte Variablennamen: ein Zeichen das man sich entweder bessere Namen ausdenken will oder die Spalten eigentlich als Datensätze in eine eigene Tabelle gehören.
Ja das stimmt, hier werde ich die Art der Aktion als Benennung vornehmen.
So einige von den Spalten könnten auch ein NOT NULL vertragen.
Das stimmt, Theoretisch wären ja alles außer die Kundenstammdaten "Optionale" Informationen bzw Werte.
Ich würde an Deiner Stelle als allererstes mal klären ob Du das nicht vielleicht als Webanwendung mit Django machen willst. Webanwendung heisst nicht zwangsläufig Server, weil man die ja auch lokal auf einem Rechner laufen lassen kann. In dem Fall müsstest Du Dich nämlich mit dem Django-ORM auseinandersetzen.
Sagt mir erst einmal so nix, müsste ich mir mal anschauen.
In jedem anderen Fall würde ich wie gesagt SqlAlchemy und da auch dessen ORM verwenden und gar nicht erst anfangen SQL als Zeichenketten per Hand zu schreiben.

ORM befreit einen schon mal von der festlegung auf nur ein DBMS, weil man dann auch recht einfach auf PostgreSQL, MariaDB, MySQL, oder andere vom ORM unterstützte DBs umsteigen kann wenn das mal wachsen sollte.
Ja das müsste ich mir auch erst noch anschauen.
Du hast da einen Pfad mit \ angegeben. Das funktioniert nur auf Windows. Das Programm macht jetzt aber nicht den Eindruck als wenn es sehr Windows-spezifisch ist. Mit dem `pathlib`-Modul kann man das auch platformunabhängig lösen.
Danke für den Tipp. Wusste ich nicht. (wie so vieles)
\ um Zeilen im Quelltext fortzuführen sind nicht so toll. Die Kommentare machen das beispielsweise kaputt, das geht so nicht. Selbst ein einzelnes Leerzeichen nach einem \ am Zeichenende ist ein Fehler den man nicht so leicht sieht. Für so etwas würde ich entweder ausnutzen das logische Zeilen erst zuende sind wenn alle geöffneten Klammern auch wieder geschlossen sind plus das Zeichenkettenliterale die nur durch „whitespace“ getrennt sind, vom Compiler zu einer Zeichenkette zusammengesetzt werden. Oder man verwendet mehrzeilige Zeichenkettenliterale die in """ (oder ''') eingefasst sind.
Ja dieser Kommentar steht auch nicht im Code, ich habe nur aufzeigen wollen, wo die Infos zu den Checkboxen rein sollen.
Die Aufteilung Deiner Tests sind IMHO nicht gut. Datenhaltung und Benutzerinteraktion sind im Grunde an komplett entgegengesetzen Enden. Informationen speichern und laden, und Informationen dem Benutzer präsentieren und vom Benutzer abfragen sind unabhängig voneinander. Du vermischst hier also auch zwei Fragen und bei beiden hat Dein erstes Beispielprogramm Probleme die man unabhängig voneinander lösen kann/muss. Wenn die beiden Teilprobleme gelöst sind, *dann* kann man die zu einem Programm zusammenfügen.
Ja das stimmt, sehe ich ein. Mein Beispielprogramm ist einfach zu gekürzt.

@Sirius3 Auch dir Danke für die Infos. Ich denke das ich deine Einwände mit dem oben geschriebenen mit abgedeckt habe.

Noch eine Verständnisfrage um noch einmal auf die Eingangsfrage zurück zu kommen. Wenn ich die Information, ob eine Checkbox angehakt ist oder nicht, in eine Datenbank schreiben möchte, könnte man doch die Information vorher in einen String umwandeln und dann diesen in die DB Schreiben. So das zb nur JA oder NEIN in der DB steht. Und beim auslesen kann man es ja wieder umwandeln. Aktuell mache ich das nämlich in VB auch so.

Code: Alles auswählen

    If CheckBoxIbau = True Then Cells(zeile, 5) = "JA"
    If CheckBoxIbau = False Then Cells(zeile, 5) = "NEIN"
Würde das in dieser Richtung funktionieren und wäre es später nicht besser damit zu arbeiten?

Vorab noch einmal vielen Dank für die ganzen Konstruktiven Vorschläge, ich versuche entsprechend alles umzusetzen. Vielen lieben Dank
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Nein, der Datentyp für einen Wahrheitswert ist bool. Das in einen String zu wandeln, ist nur umständlich und erschwert die Verarbeitung, denn sowohl Python als auch die Datenbank kann mit diesem Datentyp umgehen.
In Deinem Beispiel willst Du ja nur, dass in der Excel-Zelle statt "WAHR" und "FALSCH" "JA" oder "NEIN" steht.
Das schreibt man in VBA übrigens kürzer als:

Code: Alles auswählen

Cells(zeile, 5) = IIf(CheckBoxIbau, "JA", "NEIN")
einfachTobi
User
Beiträge: 491
Registriert: Mittwoch 13. November 2019, 08:38

SQLite3 hat keinen Boolean-Datentyp. Stattdessen werden dort per Konvention Bools als Integer (0 oder 1) abgelegt. So solltest du es auch hier machen, statt mit einem String.
Oh man. Ich glaube ich habe jetzt keinen Fehler ausgelassen, den man machen kann. :D
Das solltest du absolut positiv sehen. So viel wie du in diesem Beitrag über eine sinnvolle Datenbankstruktur gelernt hast, hättest du dir vermutlich sonst in mühsamer Kleinarbeit erarbeiten müssen.
Juliusvanvern
User
Beiträge: 15
Registriert: Mittwoch 22. Januar 2020, 20:53

Das solltest du absolut positiv sehen. So viel wie du in diesem Beitrag über eine sinnvolle Datenbankstruktur gelernt hast, hättest du dir vermutlich sonst in mühsamer Kleinarbeit erarbeiten müssen.
Absolut. Dafür bin ich auch sehr Dankbar. Ich versuche auch entsprechend alles soweit meine Fähigkeiten es zulassen, umzusetzen. ;)
Benutzeravatar
__blackjack__
User
Beiträge: 13111
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Juliusvanvern: BLOB ist für Binärdaten die beliebige Bytes enthalten können. Beispielsweise wenn man eine Bilddatei direkt in der Datenbank speichern möchte. Eine Telefonnummer mit Leer- und Sonderzeichen kann man problemlos als TEXT speichern.

Die letzte Bestellung für einen Kunden kann man auch aus einer Tabelle mit Bestellungen heraussuchen. Und hat dann zusätzlich einen Verlauf über die Bestellungen.

Verschiedene Arten von Verträgen klingt wieder nach Tabelle(n) für Vertragsarten.

@einfachTobi: SQLite ist was Datentypen angeht ja sowieso *sehr* nachlässig, aber man würde das in der DB trotzdem als BOOLEAN angeben und braucht da auch nichts selbst wandeln, denn `bool` ist in Python von `int` abgeleitet und `True` hat den Zahlwert 1 und `False` den Zahlwert 0:

Code: Alles auswählen

In [3]: issubclass(bool, int)                                                   
Out[3]: True

In [4]: isinstance(True, int)                                                   
Out[4]: True

In [5]: isinstance(False, int)                                                  
Out[5]: True

In [6]: True == 1                                                               
Out[6]: True

In [7]: False == 0                                                              
Out[7]: True
Umgekehrt ist 0 in Python im boole'schen Kontext ”falsch” und 1 ist ”wahr”. Da braucht man also nicht wirklich explizit etwas umwandeln, sondern kann `True`/`False` in die DB schreiben und die zurück kommenden 1 und 0 auch einfach als Wahrheitswerte verwenden.

SQLAlchemy macht dann auch tatsächlich eine Umwandlung beim lesen der Wahrheitswerte. Genau wie bei DATE und DATETIME auch in beide Richtungen von und nach `date` und `datetime` aus dem `datetime`-Modul gewandelt wird. IMHO ist der einzige Grund der gegen SQLAlchemy spricht, wenn man stattdessen ein anderes ORM verwendet. :-)
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
einfachTobi
User
Beiträge: 491
Registriert: Mittwoch 13. November 2019, 08:38

Jo, so ist das. Und wie von mir vorgeschlagen sollte der TE das auch so machen. Kann Juliusvanvern da auch nur SQLAlchemy ans Herz legen.
Antworten