Rekursion im schreiben von Datenbankzeilen vermeiden.

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
Geralonx
User
Beiträge: 10
Registriert: Dienstag 18. August 2020, 09:23

Hey Leute, wie im Title bereits steht, würde ich gerne eine Rekursion vermeiden, aber zuerst möchte ich eure Meinung dazu hören. Ich habe selbst noch nicht allzuviel Erfahrung mit Datenbanken und befinde mich derzeit auf der Spielwiese mit Testdatenbanken wo nichts passieren kann.

Als erstes sei einmal gesagt, der Code macht exakt das was er soll und bisher habe ich keine Probleme damit. Ich weiß, dass das Rekursionslimit bei Python 1000 ist und es ist zum aktuellen Zeitpunkt nicht absehbar, dass dies je erreicht wird. Aber Rekursionen sind ja selten eine gute Idee, weswegen ich euch fragen wollte, wie kann man es besser machen?

Was soll der Code machen?
Die äußerste Funktion "add_new_machine(data)" wird aufgerufen um das unten gezeigte JSON anzunehmen und die einzelnen Teile in die Datenbank zu schreiben. Das gezeigte JSON-File ist natürlich nur ein Beispiel. Wie viele sub_parts jedes einzelne part haben kann weiß ich derzeit nicht, aber theoretisch bietet dieses Format kein Limit.

Als erstes wird ein Objekt der Klasse DbInterface erzeugt. Dies ist meine eigene Klasse, welche ich bereits sehr häufig verwendet habe und bisher super funktioniert hat. DbInterface verwendet das Modul pyodbc für die Datenbankverbindung.

Anschließend wird in dieser Funktion "überprüft" ob es sich um das richtige JSON-File handelt, indem es nach dem 'machine'-Key sucht. Ich weiß, dass das keine Sicherheit ist, aber derzeit reicht, um zu vermeiden, dass andere JSON-Files aus dem Ordner ausversehen dort eingefügt werden.

Von der Session der DbInterface-Klasse wird das cursor-Objekt erzeugt und in die Funktion übergeben, welche die Rekursion beinhaltet.

Das Scirpt fährt eine Rekursive Loop ab, solange eine "sub_part" weitere "sub_parts" enthält.

Meine 1. Frage, was ist eure Meinung dazu? Geht es besser, wenn ja, wie?

Meine 2. Frage:
Ich hab mittels der Erweiterung "; SELECT scope_identity() as id" im SQL-Statement den erzeugen Primary-Key auf die nächste "row" im cursor gesetzt. (Sagt man da so? / Ist das so richtig?)
Anschließend schalte ich mit:

Code: Alles auswählen

cursor.nextset()
for primary_key in cursor:
    print(primary_key)
auf die nächste "row" und kann so den Primary-Key lesen. Was ich nicht verstehe ist, wo dieser Key liegt. Ich hab diesen Teil auch irgendwo im Internet gesehen und er funktioniert, aber ich verstehe nicht ganz warum.

Ist es egal welchen Namen ich für primary_key in der Loop nehme, alles funktioniert.
Meine Annahme ist ja, dadurch, dass ich im SQL-Statement "; SELECT scope_identity() as id" hinzugefügt habe, gibt es als nächste reihe nur dieses eine Element zum auslesen. Leider verstehe ich nicht, wie ich den Wert aus dem cursor direkt auslese ohne die Loop, weil den Wert des Primary-Keys kann ich beim debuggen nirgendwo im cursor-Objekt erkennen. Wäre hilfreich, wenn mir dort jemand was zu sagen könnte.


Mit freundlichen Güßen an alle, Gera

Code: Alles auswählen

machine_struct.json
{
    "meta_data":{
        "user":"...",
        "date":"...",
        "time":"..."
    },
    "machine":{
        "name":"Main",
        "machine_id": 2,
        "parent_id": 0,
        "documents":[
            {
                "title":"Doc 1",
                "reference":"Ref Link"
            },
            {
                "title":"Doc 2",
                "reference":"Ref Link"
            }
        ],
        "sub_parts":[
            {
                "name":"PART-XY",
                "parent_id": null,
                "documents":[
                    {
                        "title":"Doc 1",
                        "reference":"Ref Link"
                    },
                    {
                        "title":"Doc 2",
                        "reference":"Ref Link"
                    }
                ],
                "sub_parts":[
                    {
                        "name":"PATZ-XX",
                        "parent_id": null,
                        "documents":{
                            "doc1":{
                                "title":"Doc 1",
                                "reference":"Ref Link"
                            },
                            "doc2":{
                                "title":"Doc 2",
                                "reference":"Ref Link"
                            }
                        }
                    }
                ]
            },
            {            
                "name":"Part-XZY",
                "parent_id": null,
                "documents":[
                    {
                        "title":"Doc 1",
                        "reference":"Ref Link"
                    },
                    {
                        "title":"Doc 2",
                        "reference":"Ref Link"
                    }
                ]
            }
        ]
    }
}

Code: Alles auswählen

def add_new_machine(json_struct):
    db_config_file_path = PACKAGE_ROOT_DIR + r"\configs\dbconfig.xml"
    database_connector = DbInterface(db_config_file_path)

    # Check if the Main-Key ('machine') is in the json-file. If not, its the wrong structure
    if 'machine' in json_struct:
        with database_connector.session as conn:
            cursor = conn.cursor()
            create_line_and_write_to_db(cursor, json_struct['machine'], parent_id=0)
            conn.commit()
    else:
        print("Wrong JSON-Structure. Cannot find the 'machine'-Key. End of function.")

def create_line_and_write_to_db(cursor, machine_part_element, parent_id):
    # Sample Line (DB Columns)
    # [Machine_ID], [MS_Name], [belongs_to_MS_ID], [MS_BMK], [MS_Barcode], [MS_image], [Serial_No], [built_date]
    db_data = {
        "Machine_ID": None,
        "MS_Name": None,
        "belongs_to_MS_ID": None,
        "MS_BMK": None,
        "MS_Barcode": None,
        "MS_image": None,
        "Serial_No": None,
        "built_date": None
    }
    db_data.update({"MS_Name":machine_part_element['name']})
    db_data.update({"belongs_to_MS_ID":parent_id})

    # TODO: Hier werden noch bedingte Elemente hinzugefügt, derzeit noch nicht Verfügbar

    primary_key_inserted_element = write_line_to_db(cursor, db_data)
    
    if 'sub_parts' in machine_part_element:
        for part in machine_part_element['sub_parts']:
            create_line_and_write_to_db(cursor, part, primary_key_inserted_element)


def write_line_to_db(cursor, data_dict):
    table = 'Machine_Sections_Test'

    # COPY PASTE + ADAPTION #
    # https://stackoverflow.com/questions/9336270/using-a-python-dict-for-a-sql-insert-statement 
    placeholders = ', '.join(['?'] * len(data_dict))
    columns = ', '.join(data_dict.keys())
    sql = "INSERT INTO [dbo].[{table}] ( {cols} ) VALUES ( {vals} ); SELECT scope_identity() as id".format(table=table, cols=columns, vals=placeholders)
    cursor.execute(sql, list(data_dict.values()))
    # ----------------------- #

    cursor.nextset()
    for primary_key in cursor:
        print(primary_key)

    return int(primary_key[0])
    
if __name__ == "__main__":
    json_path = r"somewhere\machine_struct.json"
    with open(json_path, 'r') as jfile:
        data = json.loads(jfile.read())

    add_new_machine(data)
    
Zuletzt geändert von Geralonx am Mittwoch 26. August 2020, 16:09, insgesamt 1-mal geändert.
AMD Ryzen 7 3700X
Gigabyte Nvidia Gefroce RTX 2070 Super Windforce OC
Gigabyte X570 Aorus Elite AMD
HyperX Savage DDR4-2400 2*8192MB

Win10 64 Bit (10.0.19041)
VSCode 1.48
Python 3.8.4 64-Bit
Tensorflow 2.2.0 (GPU)
CUDA 10.1
CUPTI 10.0
cuDNN v8.0.2

Stand: 18.08.2020
Geralonx
User
Beiträge: 10
Registriert: Dienstag 18. August 2020, 09:23

Ja und Sorry für den doppelte Post.
viewtopic.php?f=23&t=49488
Der kann natürlich gelöscht werden. Bin neu hier. Keine Ahnung wie und ob ich das selbst kann?
AMD Ryzen 7 3700X
Gigabyte Nvidia Gefroce RTX 2070 Super Windforce OC
Gigabyte X570 Aorus Elite AMD
HyperX Savage DDR4-2400 2*8192MB

Win10 64 Bit (10.0.19041)
VSCode 1.48
Python 3.8.4 64-Bit
Tensorflow 2.2.0 (GPU)
CUDA 10.1
CUPTI 10.0
cuDNN v8.0.2

Stand: 18.08.2020
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Ob die Datenstruktur aus einer JSON-Datei kommt, oder sonst woher, ist der add_new_machine-Funktion doch völlig egal, also was für eine Struktur ist denn das inhaltlich? Pfade setzt man nicht mit + zusammen. Heutzutage benutzt man pathlib.Path dafür.
In `create_line_and_write_to_db` erzeugst Du erst ein Wörterbuch mit Dummy-Werten, die Du dann umständlich per update eines Ein-Elementigen Wörterbuchs ersetzt. Macht man nicht, da man ja direkt die richtigen Werte beim Erstellen angeben kann.
In `write_line_to_db` erzeugst Du einen generisches SQL-Statement, macht man auch nicht, da Du ja schon die Felder der Tabelle kennst, schreibst Du den Ausdruck direkt.
Und die ganze Datenbankanbindung selbst zu programmieren ist auch unnötig, da mit SQLAlchemy schon ein Packet existiert, das das besser macht als irgendwer von Hand.

Zur Rekursion: Du hast eine Rekursive Datenstruktur, also ist ein rekursives Programm die natürliche Art, soetwas zu lösen.
Geralonx
User
Beiträge: 10
Registriert: Dienstag 18. August 2020, 09:23

@Sirius3
Danke für die Antwort, ja du hast Recht, jetzt sehe ich es auch.

1. Also die Änderung für das Dict sähe dann so aus. Ich hab das Dict erst aus Dumme-Werten erstellt, weil ich erstmal die Keys aus den DB-Spalten aufgeschrieben hatte. Gar nicht dran gedacht die Werte dann direkt zuzuweisen.

2. Ist os noch eine aktuelle Alternative zu 'pathlib.Path'? Bisher habe ich nur mit os gearbeitet. Das Zusammenfügen mit + war nur Testweise, was ich mit der Zeit aus den Augen verloren habe, als ich die anderen Probleme angegangen bin. Wird aber auch geändert.

3. Also ist es in diesem Fall in Ordnung so eine Art der Rekursion? Ich wollte nur mal Fragen, ob es halt einfache und sinnvolle Alternativen gibt, weil man ja immer hört, Rekursionen vermeiden wo es geht.

4. Das generische Statement verwende ich deswegen, weil ich davon ausgehe, dass ich diese Funktion in der Zukunft sehr wahrscheinlich wieder verwenden muss für andere Dicts/Tabellen.

5. Naja selbst Programmiert habe ich die Datenbankverbindung ja nicht. Meine DbInterface-Klasse ist eigentlich nur dazu da, die übergeben Config aufzulösen und die Verbindung herzustellen. Könnte man auch in eine einzelne Funktion auslagern, aber ich habe es vor einiger Zeit (und ich weis inzwischen nicht mehr warum) in diese Klasse eingebettet. Ich verwende pyodbc, sind andere Bibliotheken, wie z.B. SQLALchemy, eher zu empfehlen?

Code: Alles auswählen

def create_line_and_write_to_db(cursor, machine_part_element, parent_id):
    # Sample Line (DB Columns)
    # [Machine_ID], [MS_Name], [belongs_to_MS_ID], [MS_BMK], [MS_Barcode], [MS_image], [Serial_No], [built_date]
    db_data = {
        "Machine_ID": None,
        "MS_Name": machine_part_element['name'],
        "belongs_to_MS_ID": parent_id,
        "MS_BMK": None,
        "MS_Barcode": None,
        "MS_image": None,
        "Serial_No": None,
        "built_date": None
    }
    
    # TODO: Hier werden noch weitere Elemente hinzugefügt, derzeit noch nicht Verfügbar

    primary_key_inserted_element = write_line_to_db(cursor, db_data)
    
    if 'sub_parts' in machine_part_element:
        for part in machine_part_element['sub_parts']:
            create_line_and_write_to_db(cursor, part, primary_key_inserted_element)
AMD Ryzen 7 3700X
Gigabyte Nvidia Gefroce RTX 2070 Super Windforce OC
Gigabyte X570 Aorus Elite AMD
HyperX Savage DDR4-2400 2*8192MB

Win10 64 Bit (10.0.19041)
VSCode 1.48
Python 3.8.4 64-Bit
Tensorflow 2.2.0 (GPU)
CUDA 10.1
CUPTI 10.0
cuDNN v8.0.2

Stand: 18.08.2020
Antworten