[Sqlite] Alternativer Zugriff auf Objekte ohne Indexangabe

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Benutzeravatar
Dennis89
User
Beiträge: 1376
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

vielen Dank für eure Antworten. Da hätte ich das wieder unnötig kompliziert geschrieben.

Ich antworte erst jetzt, da ich mich erst etwas mit 'classmethod' beschäftigen musste. Aber so sieht das ja jetzt echt ordentlich aus 🙂
Jetzt schaue ich mir die Tage mal an, wie sich die Datenbank am besten aufteilen lässt und dann schaue ich wie weit ich komme.

Grüße und einen schönen Rest-Sonntag
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1376
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo zusammen,

testweise habe ich jetzt eine Datenbank mit zwei Tabellen erstellt und die Klasse 'Kinetics' aus __blackjack__'s Beispielcode wie die Klasse 'Thermodynamics' aufgebaut. Das funktioniert so weit auch.

Ich hatte hier im Forum vor ein paar Wochen auch ein Thema eröffnet, in dem ich gefragt habe, wie ich mit 'tkinter' mehrere Frames von der Logik trenne. Mit diesen Infos versuche ich zur Zeit ein GUI zu schreiben, mit dem ich die Berechnung aus den obenen genannten Klassen ausführen kann. Grundlage dafür ist [url="viewtopic.php?p=408300#p408300"]dieser Code[/code].

Programmablauf:
- Begrüßungsbildschirm mit einem Button, durch den das nächste Frame aufgerufen wird
- Eingabebildschirm mit einem Drop-Down Menü mit allen Maschinentypen, die in der Datenbank in der Spalte "Maschine_type" zu finden sind. Zwei Eingabefelder, einmal zur Eingabe einer Fläche und einmal zur Eingabe einer Gastemperatur. Button für Zurück und für Weiter.
-Ausgabebildschirm: Gibt einen berechneten Wert aus der 'Thermodynamics'-Klasse und einen aus der 'Kinetics'-Klasse aus. Button zum Anfangsbildschirm.

Das Problem ist jetzt nicht mehr wie es zu Beginn dieses Themas war, dennoch beziehe ich mich mal auf folgendes Zitat, weil dass gerade mit meinem Problem zu tun hat:
__deets__ hat geschrieben: Samstag 23. Juli 2022, 09:04 Dein problem hier erfordert nicht, dass du die Werte bei bedarf einliest.
Jetzt muss ich die Werte zwei mal einlesen(?). Ich benötige im "Eingabebildschirm" alle Werte aus der "Maschine_type"-Spalte und erst wenn wenn in dem Drop-Down-Menü eine Maschine ausgewählt wurde, kann ich die benötigten Werte für die Berechnungen auslesen.

Ich habe Probleme das Programm zu strukturieren. Wenn ich dabei bleibe, das ich eine Klasse 'Machine', 'Thermodynamic' und 'Kinetics' habe und für jedes Frame auch eine eigene Klasse, wo erstelle ich eine Instanz der Machine-Klasse? Öffne ich die Datenbankverbindung "schon" in der 'main'-Funktion und übergebe die Verbindung bis ich sie brauche?
Bin ich jetzt auf dem ganz falschen Weg?

Ich poste hier jetzt mal noch den Code wie er gerade in der Entwicklung ist. Mir ist bewusst, dass da Klassen teilweise Argumente haben, die nicht übergeben wurden und das bis jetzt noch gar keine Datenbankverbindung eingebaut ist. Ich habe jetzt beide Codebeispiele hier zusammen gebaut (man könnte auch kopiert sagen) und ich weis nicht wie ich die ordentlich vereine und/oder ob die überhaupt so strukturiert sind, dass sie ordentlich vereinbar sind. Also das hat alles gar nichts mit irgendwelchen "echten" Daten zu tun und der Sinn ist auch "nur" dass ich lerne wie man so etwas macht. Deswegen lösche ich auch bei Bedarf wieder alles und fange noch einmal anders von vorne an, wenn ihr meint das wäre besser.

Code: Alles auswählen

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

DATABASE = "database.db"


class CalculationApp(tk.Frame):
    def __init__(self, master, connection):
        tk.Frame.__init__(self, master)
        self.frames = {}
        home_page = Home(self)
        home_page.grid(row=0, column=0, sticky=tk.NSEW)
        entry_page = Entry(self)
        entry_page.grid(row=0, column=0, sticky=tk.NSEW)
        result_page = Result(self, connection)
        result_page.grid(row=0, column=0, sticky=tk.NSEW)
        self.frames["Home"] = home_page
        self.frames["Entry"] = entry_page
        self.frames["Result"] = result_page
        self.show_page("Home")

    def show_page(self, page_name):
        self.frames[page_name].tkraise()

    def get_page(self, page_name):
        return self.frames[page_name]


class Home(tk.Frame):
    def __init__(self, controller):
        tk.Frame.__init__(self)
        self.controller = controller
        headline = tk.Label(self, text="Hier gehts zur Berechnung")
        headline.grid(row=0, column=1)
        go_to_entry_page = tk.Button(
            self, text=">>", command=partial(self.controller.show_page, "Entry")
        )
        go_to_entry_page.grid(row=1, column=3)


class Entry(tk.Frame):
    def __init__(self, controller, machine_types):
        tk.Frame.__init__(self)
        self.controller = controller
        self.machine_types = machine_types
        tk.Label(self, text="Bitte Maschine auswählen").grid(row=0, column=1)
        machine_choice = tk.OptionMenu(self, "", *self.machine_types)
        machine_choice.grid(row=1, column=0)
        tk.Label(self, text="Bitte Fläche eingeben").grid(row=2, column=1)
        self.area = tk.Entry(self)
        self.area.grid(row=3, column=1)
        tk.Label(self, text="Bitte Gastemperatur eingeben").grid(row=4, column=1)
        self.inlet_gas_temperature = tk.Entry(self)
        self.inlet_gas_temperature.grid(row=5, column=1)
        tk.Button(
            self, text="Berechne", command=partial(self.controller.show_page, "Result")
        ).grid(row=3, column=3)
        tk.Button(
            self, text="<<", command=partial(self.controller.show_page, "Home")
        ).grid(row=3, column=0)

    @classmethod
    def from_database(cls, connection):
        with closing(connection.cursor()) as cursor:
            cursor.execute("SELECT MACHINE_TYPE" " FROM KINETICS")
            machine_types = cursor.fetchall()
            print(machine_types)
        return cls(machine_types)


class Result(tk.Frame):
    def __init__(self, controller, connection):
        tk.Frame.__init__(self)
        self.controller = controller
        self.outlet_gas_temperature = tk.Label(
            self, text=machine.thermodynamic_properties
        )
        self.outlet_gas_temperature.grid(row=0, column=1)
        self.force = tk.Label(self, text=machine.kinetics_properties)
        self.force.grid(row=1, column=1)
        button = tk.Button(
            self, text="Startseite", command=partial(controller.show_page, "Home")
        )
        button.grid(row=2, column=1)


class Kinetics:
    def __init__(self, area, force):
        self.area = area
        self.force = force

    @property
    def pressure(self):
        return self.force / self.area

    @classmethod
    def from_database(cls, connection, machine_type, area):
        with closing(connection.cursor()) as cursor:
            cursor.execute(
                ("SELECT FORCE" " FROM KINETICS" " WHERE machine_type=?"),
                [machine_type],
            )
            force = cursor.fetchone()[0]
        return cls(area, force)


class Thermodynamic:
    def __init__(self, inlet_gas_temperature, volume_in, volume_out):
        self.inlet_gas_temperature = inlet_gas_temperature
        self.volume_in = volume_in
        self.volume_out = volume_out

    @property
    def outlet_gas_temperature(self):
        return self.volume_out * self.inlet_gas_temperature / self.volume_in

    @classmethod
    def from_database(cls, connection, machine_type, inlet_gas_temperature):
        with closing(connection.cursor()) as cursor:
            cursor.execute(
                (
                    "SELECT volume_in, volume_out"
                    " FROM THERMODYNAMICS"
                    " WHERE machine_type=?"
                ),
                [machine_type],
            )
            volume_in, volume_out = cursor.fetchone()
        return cls(inlet_gas_temperature, volume_in, volume_out)


class Machine:
    def __init__(self, thermodynamic_properties, kinetics_properties):
        self.thermodynamic_properties = thermodynamic_properties
        self.kinetics_properties = kinetics_properties

    @classmethod
    def from_database(cls, connection, machine_type, inlet_gas_temperature, area):
        return cls(
            Thermodynamic.from_database(
                connection, machine_type, inlet_gas_temperature
            ),
            Kinetics.from_database(connection, machine_type, area),
        )


def main():
    root = tk.Tk()
    root.title("Berechnungsprogramm")
    app = CalculationApp(root)
    app.mainloop()


if __name__ == "__main__":
    main()
Vielen Danke schon für die Mühe, die ihr aufbringt um das alles durchzulesen!

Achja, wie immer verusche ich erst mal gerne mit euren Hinweisen den Code selbst zu schreiben und wenn ich scheitere freue ich mich über Codebeispiele.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1376
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Abend,

ich habe das jetzt mal mit der Datenbankabfrage so zusammen gefrickelt.

Code: Alles auswählen

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

DATABASE = "database.db"


class CalculationApp(tk.Frame):
    def __init__(self, master, connection):
        tk.Frame.__init__(self, master)
        self.frames = {}
        home_page = Home(self)
        home_page.grid(row=0, column=0, sticky=tk.NSEW)
        entry_page = Entry(self, connection)
        entry_page.grid(row=0, column=0, sticky=tk.NSEW)
        result_page = Result(self)
        result_page.grid(row=0, column=0, sticky=tk.NSEW)
        self.frames["Home"] = home_page
        self.frames["Entry"] = entry_page
        self.frames["Result"] = result_page
        self.show_page("Home")

    def show_page(self, page_name):
        self.frames[page_name].tkraise()

    def get_page(self, page_name):
        return self.frames[page_name]


class Home(tk.Frame):
    def __init__(self, controller):
        tk.Frame.__init__(self)
        self.controller = controller
        headline = tk.Label(self, text="Hier gehts zur Berechnung")
        headline.grid(row=0, column=1)
        go_to_entry_page = tk.Button(
            self, text=">>", command=partial(self.controller.show_page, "Entry")
        )
        go_to_entry_page.grid(row=1, column=3)


class Entry(tk.Frame):
    def __init__(self, controller, connection):
        tk.Frame.__init__(self)
        self.controller = controller
        self.connection = connection
        self.machine_types = self.from_database()
        self.machine_choice = tk.StringVar()
        tk.Label(self, text="Bitte Maschine auswählen").grid(row=0, column=1)
        machine_choice = tk.OptionMenu(self, self.machine_choice, *self.machine_types)
        machine_choice.grid(row=1, column=0)
        tk.Label(self, text="Bitte Fläche eingeben").grid(row=2, column=1)
        self.area = tk.Entry(self)
        self.area.grid(row=3, column=1)
        tk.Label(self, text="Bitte Gastemperatur eingeben").grid(row=4, column=1)
        self.inlet_gas_temperature = tk.Entry(self)
        self.inlet_gas_temperature.grid(row=5, column=1)
        tk.Button(self, text="Berechne", command=self.show_result).grid(row=3, column=3)
        tk.Button(
            self, text="<<", command=partial(self.controller.show_page, "Home")
        ).grid(row=3, column=0)

    def from_database(self):
        with closing(self.connection.cursor()) as cursor:
            cursor.execute("SELECT MACHINE_TYPE" " FROM KINETICS")
            return cursor.fetchone()

    def show_result(self):
        machine = Machine.from_database(
            self.connection,
            self.machine_choice.get(),
            int(self.inlet_gas_temperature.get()),
            int(self.area.get()),
        )
        self.controller.get_page("Result").outlet_gas_temperature.config(
            text=machine.thermodynamic_properties.outlet_gas_temperature
        )
        self.controller.get_page("Result").force.config(
            text=machine.kinetics_properties.pressure
        )
        self.controller.show_page("Result")


class Result(tk.Frame):
    def __init__(self, controller):
        tk.Frame.__init__(self)
        self.controller = controller
        self.outlet_gas_temperature = tk.Label(self, text="")
        self.outlet_gas_temperature.grid(row=0, column=1)
        self.force = tk.Label(self, text="")
        self.force.grid(row=1, column=1)
        button = tk.Button(
            self, text="Startseite", command=partial(controller.show_page, "Home")
        )
        button.grid(row=2, column=1)


class Kinetics:
    def __init__(self, area, force):
        self.area = area
        self.force = force

    @property
    def pressure(self):
        return self.force / self.area

    @classmethod
    def from_database(cls, connection, machine_type, area):
        with closing(connection.cursor()) as cursor:
            cursor.execute(
                (
                    "SELECT FORCE" 
                    " FROM KINETICS" 
                    " WHERE machine_type=?"
                ),
                [machine_type],
            )
            force = cursor.fetchone()[0]
        return cls(area, force)


class Thermodynamic:
    def __init__(self, inlet_gas_temperature, volume_in, volume_out):
        self.inlet_gas_temperature = inlet_gas_temperature
        self.volume_in = volume_in
        self.volume_out = volume_out

    @property
    def outlet_gas_temperature(self):
        return self.volume_out * self.inlet_gas_temperature / self.volume_in

    @classmethod
    def from_database(cls, connection, machine_type, inlet_gas_temperature):
        with closing(connection.cursor()) as cursor:
            cursor.execute(
                (
                    "SELECT volume_in, volume_out"
                    " FROM THERMODYNAMICS"
                    " WHERE machine_type=?"
                ),
                [machine_type],
            )
            volume_in, volume_out = cursor.fetchone()
        return cls(inlet_gas_temperature, volume_in, volume_out)


class Machine:
    def __init__(self, thermodynamic_properties, kinetics_properties):
        self.thermodynamic_properties = thermodynamic_properties
        self.kinetics_properties = kinetics_properties

    @classmethod
    def from_database(cls, connection, machine_type, inlet_gas_temperature, area):
        return cls(
            Thermodynamic.from_database(
                connection, machine_type, inlet_gas_temperature
            ),
            Kinetics.from_database(connection, machine_type, area),
        )


def main():
    with closing(
        sqlite3.connect(DATABASE, detect_types=sqlite3.PARSE_DECLTYPES)
    ) as connection:
        root = tk.Tk()
        root.title("Berechnungsprogramm")
        app = CalculationApp(root, connection)
        app.mainloop()


if __name__ == "__main__":
    main()
Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 18051
Registriert: Sonntag 21. Oktober 2012, 17:20

In Entry.from_database fragst Du einen beliebiegen machine_type ab, weist aber das Ergebnis der Variable machine_types zu. Da passt was nicht. Bei einem besseren Methodennamen, wie z.B. get_machine_type oder get_machine_types wäre es klarer, wo der Fehler liegt.
Benutzeravatar
Dennis89
User
Beiträge: 1376
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen und danke für die Antwort.

Der Fehler wäre mir vielleicht aufgefallen, wenn meine Testdatenbank nicht nur einen Eintrag hätte.

Code: Alles auswählen

class Entry(tk.Frame):
    def __init__(self, controller, connection):
        tk.Frame.__init__(self)
        self.controller = controller
        self.connection = connection
        self.machine_types = self.get_machine_types()
        self.machine_choice = tk.StringVar()
        tk.Label(self, text="Bitte Maschine auswählen").grid(row=0, column=1)
        machine_choice = tk.OptionMenu(self, self.machine_choice, *self.machine_types)
        machine_choice.grid(row=1, column=0)
        tk.Label(self, text="Bitte Fläche eingeben").grid(row=2, column=1)
        self.area = tk.Entry(self)
        self.area.grid(row=3, column=1)
        tk.Label(self, text="Bitte Gastemperatur eingeben").grid(row=4, column=1)
        self.inlet_gas_temperature = tk.Entry(self)
        self.inlet_gas_temperature.grid(row=5, column=1)
        tk.Button(self, text="Berechne", command=self.show_result).grid(row=3, column=3)
        tk.Button(
            self, text="<<", command=partial(self.controller.show_page, "Home")
        ).grid(row=3, column=0)

    def get_machine_types(self):
        with closing(self.connection.cursor()) as cursor:
            cursor.execute("SELECT MACHINE_TYPE FROM KINETICS")
            machine_types = cursor.fetchall()
            return machine_types
Wäre der oben gepostet Code nun ein einigermaßen ordetliches Grundgerüst um ein solches Berechnungsprogramm aufzubauen?

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

Dennis89 hat geschrieben: Freitag 22. Juli 2022, 07:02 Das ergibt:

Code: Alles auswählen

ValueError: invalid literal for int() with base 10: 'Ausgänge'
Ich habe keinen Editor mit dem ich die *.db-Datei öffnen kann. Aber ich habe sie mal mit dem Windows Standard-Editor geöffnet und mal abgesehen von vielen Sonderzeichen finde ich sowas:

Code: Alles auswählen

Maschine VARCHAR(30),
Ausgänge  INTEGER,
Ich fürchte, Du bist da auf eines der ganz grundsätzliche Probleme mit SQLite gestoßen. Im Gegensatz zu richtigen Datenbanken ist das nämlich nicht typsicher und läßt es daher zu, daß String-Werte in Integer-Spalten eingetragen werden. Eine richtige Datenbank würde das nicht zulassen -- und außerdem hat offensichtlich derjenige, der die SQLite-Datei erstellt hat, falsche Werte darin eingetragen, und die Integrität der Daten damit zumindest aufs Spiel gesetzt.

Was Du machen kannst, ist: lies die Datenstruktur aus und mappe die dort aufgeführten Typen anhand der Spaltenname auf "check"-Funktionen. Dann liest Du die Daten mit einem Zeile für Zeile aus, prüfe die Spalten über die Spaltennamen auf die korrekten Datentypen (dabei können Pakete wie "schema", "schemadict" oder "dict-schema-validator" helfen) und wenn die Daten nicht zum Schema passen... naja, ich würde sie vermutlich einfach verwerfen, aber da wirst Du anhand Deiner Daten und der auftretenden Fehler selbst entscheiden müssen, ob das möglich und sinnvoll ist.

Ob eine Aufteilung der Tabelle in mehrere Tabellen möglich und sinnvoll ist, wie es einige der anderen Teilnehmer dieses Threads empfehlen, steht zudem auf einem anderen Blatt. Ich persönlich tendiere zu der Auffassung, daß zusammengehörende Daten in dieselbe Tabelle gehören und diese dann allenfalls zeilenweise aufgeteilt gehört, etwa mit dem Mechanismus, den PostgreSQL als "Tabellenvererbung" oder nur "Vererbung" bezeichnet. In meinem Arbeitsbereich sind Tabellen mit mehreren hundert Spalten allerdings auch keine Seltenheit, und nach unseren Erfahrungen wird die Sache nur unübersichtlicher, wenn man die Daten über mehrere Tabellen verteilt. Definitiv sinnvoll sind allerdings sprechende Spaltennamen und eine ordentliche Dokumentation der Spalteninhalte, PostgreSQL bietet dazu sogar die Möglichkeit, Kommentare direkt in der Datenbank zu hinterlegen.

Andererseits sehe ich, daß bei Dir etwa in der Spalte "Besonderheit" eine Liste mit, nunja, Besonderheiten zu stehen scheint. So etwas kann man je nach Datenbanksystem mit entsprechenden Listendatentypen oder mit JSON händeln, was einige Datenbanken wie PostgreSQL unterstützen, aber das kann dann auf Kosten der Portabilität gehen. Die reine Lehre würde verlangen, daß die jeweiligen Besonderheiten in einer eigenen Tabelle stehen und über eine m:n-Zwischentabelle mit der "Daten"-Tabelle verknüpft werden. Solche Verknüpfungen nennt man "Relationen", daher das "R" in "Relationales Datenbank-Management-System", und den Prozeß, Datenstrukturen für ein solches RDBMS aufzubereiten, nennt man "Normalisierung".

Bei dem kleinen Ausschnitt, den Du gezeigt hast, fehlen mir obendrein eindeutige Primärschlüssel. Das sind in der Regel fortlaufende, ganzzahlige (und häufig vom RDBMS automatisch vergebene bzw. automatisch inkrementierte) und eindeutige Bezeichner für den jeweiligen Datensatz. Man kann zwar auch ohne arbeiten und ja, die kosten ein bisschen Speicherplatz, aber nach unseren Erfahrungen sind es die Mühen und die Aufwände nicht wert, aus Speicherplatzgründen darauf zu verzichten.

Ich bin mir auch nicht ganz sicher, ob Du den Begriff "Index" in diesem Kontext korrekt benutzt. Das Wort "Index" bezeichnet in der Datenbankwelt üblicherweise eine nach Möglichkeit im Arbeitsspeicher vorgehaltene Datenstruktur mit häufig abgefragten Spalten, um den Zugriff auf die Datenbankinhalte zu beschleunigen. Solche Indizes richten sich üblicherweise nach den typischen Zugriffsmustern auf die Datenbank, kosten aber beim Einfügen oder Updaten der Datenbankinhalte ein bisschen mehr Zeit. Manche Datenbank-Admins machen es daher so, daß sie die Indizes für Masseneintragungen oder -Updates deaktivieren und nach deren erfolgreichem Abschluß wieder aktivieren, wodurch die Indizes mit den aktualisierten Daten neu erzeugt werden. Datenbanksysteme wie PostgreSQL führen sogar eigens Statistiken über ihre Indizes, um im Zweifelsfall entscheiden zu können, welche Indizes im Arbeitsspeicher gehalten werden können und welche auf ein Persistierungsmedium ausgelagert werden können.

Bedauerlicherweise sind Deine Informationen zu Deiner "Datenbank" aber ein wenig knapp. Es wäre schön, wenn Du etwas dazu sagen könntest, wie umfangreich Deine Daten sind, zum Beispiel, ob sie komplett in den freien Arbeitsspeicher Deines Rechners passen. Du zeigst auch nur sehr wenige von Deinen Spalten, und außerdem mir ist aufgefallen, daß Deine Tabelle den ziemlich nichtssagenden Namen "daten" hat. Vielleicht möchtest Du an dieser Stelle mit der Vergabe eines sprechenden Tabellennamens ("maschine" vielleicht?) beginnen und dann zunächst zur Datenbereinigung schreiten.

Auch hinsichtlich der erforderlichen Berechnungen wären ein paar Informationen nicht das schlechteste. Möglicherweise ist eine zeilenorientierte Datenbank nicht die beste Wahl für Dein Problem, spaltenorientierte Datenbanken sind insbesondere bei analytischen Queries häufig wesentlich performanter. Was also ist das Ziel Deiner Aufgabe, was soll am Ende dabei herauskommen, und wie sollen die Ergebnisse präsentiert werden? Über eine Webschnittstelle zum Beispiel wären ja etwa auch interaktive Diagramme möglich.

Wenn Deine Daten -- oder: der Teil, den Du davon verarbeiten willst -- in den Arbeitsspeicher Deines Rechners passen, dann ist Pandas sicherlich eine hervorragende Wahl zur Verarbeitung von Massendaten. Wenn Deine Daten und Deine Berechnungen sehr umfangreich sind, kann es sinnvoll sein, die Verarbeitung mit Pandas durch die Verwendung eines Zusatzpaketes namens "pandarallel" zu parallelisieren. Aber glücklich wirst Du mit Pandas erst, wenn Du Deine Daten bereinigt, also in saubere und korrekte Formate gebracht hast.

HTH, YMMV, viel Erfolg!
Benutzeravatar
sparrow
User
Beiträge: 4361
Registriert: Freitag 17. April 2009, 10:28

Ich muss wie immer etwas lachen, wenn jemand der Meinung ist, sqlite wäre keine "richtige" Datenbank :D
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

LukeNukem hat geschrieben: Dienstag 6. September 2022, 16:06 Ich bin mir auch nicht ganz sicher, ob Du den Begriff "Index" in diesem Kontext korrekt benutzt. Das Wort "Index" bezeichnet in der Datenbankwelt üblicherweise eine nach Möglichkeit im Arbeitsspeicher vorgehaltene Datenstruktur mit häufig abgefragten Spalten, um den Zugriff auf die Datenbankinhalte zu beschleunigen. Solche Indizes richten sich üblicherweise nach den typischen Zugriffsmustern auf die Datenbank, kosten aber beim Einfügen oder Updaten der Datenbankinhalte ein bisschen mehr Zeit. Manche Datenbank-Admins machen es daher so, daß sie die Indizes für Masseneintragungen oder -Updates deaktivieren und nach deren erfolgreichem Abschluß wieder aktivieren, wodurch die Indizes mit den aktualisierten Daten neu erzeugt werden. Datenbanksysteme wie PostgreSQL führen sogar eigens Statistiken über ihre Indizes, um im Zweifelsfall entscheiden zu können, welche Indizes im Arbeitsspeicher gehalten werden können und welche auf ein Persistierungsmedium ausgelagert werden können.
Tabellen sind doch auch nach Möglichkeit im Arbeitsspeicher wie Indizes auch. Ich bin auch überrascht was die Statistiken über Indizes angehen soll. Postgres ist doch sehr bekannt dafür eben nicht zu entscheiden was im Arbeitsspeicher gehalten werden soll und was nicht. Die Nutzung von mmap statt direct IO ist doch einer der großen Nachteile die Postgres, im Vergleich zu kommerziellen Lösungen, hat.
Benutzeravatar
Dennis89
User
Beiträge: 1376
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen,

danke für die ausführliche Antwort und sorry das meine Antwort darauf nicht so ausführlich werden kann.

Ja ich habe sehr wenig von der Datenbank gezeigt und bewusst den Namen 'daten' gewählt, weil das nicht meine Daten sind. Es sind Firmendaten und die werde ich nicht einfach so ins Netz stellen. Die gezeigte Tabelle hat im originalen noch mehr Spalten. Aber groß im Sinne vom Speicherplatz ist sie nicht. Ich hatte die mir ausgelesen und in eine Excel-Datei geschrieben und diese Datei hat 64KB.

In der ersten Spalte steht der Maschinentyp, dieser wird in einem GUI ausgewäht. Wenn dann im weiteren Programmablauf Daten dieser Maschine benötigt werden kann ich diese anhand der Auswahl recht einfach(wie ich finde) aus der Datenbank auslesen. Die berechnete Daten werden wieder in einer GUI ausgegeben. Es handelt sich dabei um die Auslegung von Maschinen, dabei werden kinetische, thermodynamische und elektrische Rahmenbedingungen betrachtet. Mittlerweile habe ich auch unterschiedliche Tabellen erstellt, die nach diesen Eigenschaften gegliedert sind und natürlich sprechende Spaltennamen enthalten.
Die Berechnungen sind dementsprechend auch nicht so umfangreich. Man gibt im Prinzip die Kundenanforderungen ein, wählt eine gewünschte Maschine aus und das Programm sagt einem ob die Maschine dafür geeignet ist oder nicht und gibt noch die Berechneten Kennwerte wie beispielsweise die benötigte Drehzahl oder die wirkende Kraft aus.

Es ist schon etwas lustig, dass ausgerechnet gestern eine Antwort auf dieses Thema kam. Weil eigentlich sollte ich an diesem Projekt gar nicht mehr weiter arbeiten, das hat sich jedoch am Montagabend geändert und ich beschäftige mich seit gestern wieder damit.

Aktuell versuche ich in SQLAlchemy einzusteigen, das wird ja öfters von @__blackjack__ empfohlen. Das wird dann wohl noch etwas dauern bis ich sagen kann, ob ich mit der Art von Datenbank, wie ich sie jetzt habe und auch aufgeteilt habe, sinnvoll für die Berechnungen ist. Oder ihr mir, wenn ich damit auf Probleme stoße.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

DasIch hat geschrieben: Dienstag 6. September 2022, 19:37 Tabellen sind doch auch nach Möglichkeit im Arbeitsspeicher wie Indizes auch.
Dieses "nach Möglichkeit" trifft natürlich nur bei Datenmengen zu, die problemlos in den freien Arbeitsspeicher passen -- und das auch nur dann, wenn die Software so konfiguriert ist, daß sie den freien Speicher nutzen darf.
DasIch hat geschrieben: Dienstag 6. September 2022, 19:37 Ich bin auch überrascht was die Statistiken über Indizes angehen soll.
Benutzung, Cache Hits und -Misses... Siehe dazu auch [1] und die erste Antwort unter [2].
DasIch hat geschrieben: Dienstag 6. September 2022, 19:37 Postgres ist doch sehr bekannt dafür eben nicht zu entscheiden was im Arbeitsspeicher gehalten werden soll und was nicht. Die Nutzung von mmap statt direct IO ist doch einer der großen Nachteile die Postgres, im Vergleich zu kommerziellen Lösungen, hat.
Wenn der Arbeitsspeicher eng wird, muß jedes speicherintensive Software entscheiden, welche Strukturen sie im Arbeitsspeicher hält und welche nicht. Da sind RDBMS und natürlich auch PostgreSQL keine Ausnahme.

Ansonsten kenne ich die Diskussionen um DirectIO in PostgreSQL schon länger -- die Einen fordern, PostgreSQL solle unbedingt DIO verwenden, während andere sagen, daß das ein Dirty Hack sei und keine Vorzüge böte. Ich mag dazu keine im Position beziehen. Allerdings scheint die Diskussion dank AIO mittlerweile wieder Fahrt aufzunehmen, siehe dazu diese E-Mail [3] von Andreas Freund.

Nach unseren Erfahrungen und Messungen ist PosgreSQL für unsere (sehr großen) Datenmengen bei denselben Queries genauso schnell oder langsam wie andere RDBMS -- der größte Unterschied, den wir gemessen haben, lag bei unter zwei Prozent. Die große Mehrheit unserer Kunden scheint das ähnlich zu sehen, in den letzten zehn Jahren sind etwa 60 Prozent unserer Kunden zu PostgreSQL migriert und weitere ca. zehn Prozent planen dies in naher Zukunft. Das würden unsere Kunden für einen derart kritischen Teil ihrer Infrastruktur (Echtzeit-Betrugserkennung) sicher nicht tun, wenn die Nichtverwendung von DirectIO in PostgreSQL signifikante Nachteile hätte.

[1] https://www.postgresql.org/docs/current ... stats.html
[2] https://stackoverflow.com/questions/209 ... -in-memory
[3] https://postgrespro.com/list/thread-id/2545530
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

Dennis89 hat geschrieben: Mittwoch 7. September 2022, 06:48 Ja ich habe sehr wenig von der Datenbank gezeigt und bewusst den Namen 'daten' gewählt, weil das nicht meine Daten sind. Es sind Firmendaten und die werde ich nicht einfach so ins Netz stellen. Die gezeigte Tabelle hat im originalen noch mehr Spalten. Aber groß im Sinne vom Speicherplatz ist sie nicht. Ich hatte die mir ausgelesen und in eine Excel-Datei geschrieben und diese Datei hat 64KB.
Da ich nicht mit Excel arbeite, habe ich keine Idee, was das in Bezug auf die Datenmenge aussagt. Soweit ich weiß, speichern moderne Excel-Versionen die Daten in einem komprimierten XML-Format -- und da sich Textdat(ei)en wie diese meistens mit einer Rate von 90 Prozent komprimieren lassen, gehe ich mal davon aus, daß das keine 10 MB an Rohdaten sind. Wenn das stimmt, ist es jedenfalls eine ziemlich überschaubare Datenmenge, und damit erübrigen sich weitere Erwägungen hinsichtlich des Datenspeichersystems zumindest so lange, wie mehrere Prozesse gleichzeitig schreibend auf die Daten zugreifen sollen.
Dennis89 hat geschrieben: Mittwoch 7. September 2022, 06:48 Aktuell versuche ich in SQLAlchemy einzusteigen, das wird ja öfters von @__blackjack__ empfohlen. Das wird dann wohl noch etwas dauern bis ich sagen kann, ob ich mit der Art von Datenbank, wie ich sie jetzt habe und auch aufgeteilt habe, sinnvoll für die Berechnungen ist. Oder ihr mir, wenn ich damit auf Probleme stoße.
SQLAlchemy ist eine großartige Software, insofern schließe ich mich der Empfehlung von __blackjack__ an dieser Stelle ausdrücklich an. Allerdings habe ich die Erfahrung gemacht, daß SQLObject von Ian "Effbot" Bicking bei GUI-Software manchmal angenehmer ist und weniger Konfiguration bedarf. Wenn Du Dich aber nur in einen OR-Mapper einarbeiten möchtest, ist SQLAlchemy vermutlich die bessere Wahl.

Viel Erfolg und Vergnügen bei Deinem Projekt!
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ian Bicking ist nicht Effbot. Das ist Fredrick Lundh. https://www.linkedin.com/in/effbot/

Und SQLObject habe ich mal zu beigetragen, als Oleg das Projekt geleitet hat. Es war deklarativer, weil es die Trennung von DDL und ORM nicht erfordert, aber es war auch ein bisschen knoedelig, und mit SA declarative (wenn ich mich recht erinner) gab's dafuer keinen Grund mehr. Vor allem, weil SA so viel besser dokumentiert und feature-reich ist.
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

__deets__ hat geschrieben: Donnerstag 8. September 2022, 16:48 Ian Bicking ist nicht Effbot. Das ist Fredrick Lundh. https://www.linkedin.com/in/effbot/
Hupsi, Du hast Recht... ich werde alt. Danke für die Richtigstellung.
__deets__ hat geschrieben: Donnerstag 8. September 2022, 16:48 Und SQLObject habe ich mal zu beigetragen,
Cool, vielen Dank!
__deets__ hat geschrieben: Donnerstag 8. September 2022, 16:48 Vor allem, weil SA so viel besser dokumentiert und feature-reich ist.
Ach, ich weiß nicht... die Dokumentation von SQLAlchemy finde ich immer noch unübersichtlich, wenngleich die von SQLObject leider auch nicht immer das Gelbe vom Ei ist.
Antworten