If exists Abfrage in SQLAlchemy

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
adicllong
User
Beiträge: 3
Registriert: Dienstag 14. Juni 2022, 12:11

Ich hänge gerade an einer SQLite Abfrage mit SQLAlchemy die mit if exists die Tabelle auf eine ID prüfen soll, beim Nichtvorhandensein einen Datensatz mit bestimmten Werten erstellen soll, ist die ID bereits vorhanden werden andere Werte eingetragen. Ich habe die Dokumentation auf das if exists Konstrukt durchsucht und keinen passenden Eintrag gefunden.
Der Eintrag sollte etwa folgendermassen aussehen:

Code: Alles auswählen

  res = session.execute(select(db.Software.id).where(db.Software.name == content['name'])) ## db ist der Alias des Moduls in welchem sich die ORM Klassen befinden, content['name'] kommt aus einer Liste über die iteriert wird.
            for row in res:
                id = row.id
                if exists(select(db.Status.software_id).where (db.Status.software_id == id)):
                    ## do this
                else:
                    newEntry = db.SoftwareStatus(software_id = id)
                    session.add(newEntry)
                session.commit()
Die Fehlermeldung lautet ´: TypeError: Boolean value of this clause is not defined' und wird von der if exists Zeile geworfen. Sicherlich gäbe es andere Wege zu prüfen ob eine ID zurückgegeben wird, ich würde aber gern herausfinden wie ich es mit if exists hinbekomme.
Hilfe wäre 'highly appreciated' 8)
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Du hast in dem ``if`` ein `ClauseElement` und das wehrt sich, zurecht, dagegen in einen Wahrheitswert umgewandelt zu werden. Was Du da machst ist quasi eine SQL-Abfrage zu erstellen, aber die dann nicht gegen die Datenbank auszuführen, sondern die Abfrage und nicht deren Ergebnis zu fragen ob sie wahr ist oder nicht.

Code: Alles auswählen

In [76]: bool(sa.exists())                                                      
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-76-42b5f571bf4c> in <module>
----> 1 bool(sa.exists())

/usr/local/lib/python3.6/dist-packages/sqlalchemy/sql/elements.py in __bool__(self)
    485 
    486     def __bool__(self):
--> 487         raise TypeError("Boolean value of this clause is not defined")
    488 
    489     __nonzero__ = __bool__

TypeError: Boolean value of this clause is not defined
Ich finde das aber auch ein bisschen komisch wie Du die Anfragen erstellst, statt `Session.query()` zu verwenden wenn das ORM-Klassen sind.

Mein ”Lieblingsthema”: Namen. Keine kryptischen Abkürzungen. `res`? Das ist wohl eher `rows`. `newEntry` hält sich nicht an die Namenskonventionen (klein_mit_unterstrichen) und auch hier ist das `entry` etwas generisch. Und man muss das auch gar nicht zwingend an einen Namen binden.

Ungetestet:

Code: Alles auswählen

            #
            # db ist der Alias des Moduls in welchem sich die ORM Klassen
            # befinden, content['name'] kommt aus einer Liste über die iteriert
            # wird.
            #
            rows = (
                session.query(db.Software.id)
                .filter_by(name=content["name"])
                .all()
            )
            for row in rows:
                if (
                    session.query(db.Status)
                    .filter_by(software_id=row.id)
                    .exists()
                    .scalar()
                ):
                    # do this
                    ...
                else:
                    session.add(db.SoftwareStatus(software_id=row.id))

                session.commit()
Nachtrag: Wenn man grundsätzlich die Existenz einer Status-ID zu jeder Software-ID prüft und die Status-ID nicht NULL sein kann, dann könnte man auch gleich in der ersten Abfrage einen LEFT OUTER JOIN machen. Ungetestet:

Code: Alles auswählen

            for software_id, status_id in (
                session.query(db.Software.id, db.Status.id)
                .outerjoin(db.Status)
                .filter_by(name=content["name"])
                .all()
            ):
                if status_id is not None:
                    # do this
                    ...
                else:
                    session.add(db.SoftwareStatus(software_id=software_id))
                
                session.commit()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Wenn `content` tatsächlich daher kommt dass du eine Schleife in der Form `for content in ...:` hast, wäre es empfehlenswert den Code auch da zu ändern so dass du nur einen Query ausführst, statt einen pro Iteration. An der Stelle kann man sich vielleicht von __blackjack__'s Nachtrag inspirieren lassen.

Das ist deswegen empfehlenswert weil jede Interaktion über das Netzwerk zwangsläufig viel Zeit in Anspruch nimmt, deswegen ist es besser man verpackt so viele Informationen wie möglich in eine Nachricht, statt die Informationen über viele Interaktionen zu verteilen.
adicllong
User
Beiträge: 3
Registriert: Dienstag 14. Juni 2022, 12:11

@DasIch - ich finde es auch sehr imperformant, ich soll es aber erstmal so schreiben, wobei das nicht der Originale Quelltext ist, es ging mir einfach nur um die Verwendung des if exists (da ist mir auch ein Fehler unterlaufen, ich will natürlich nicht die id als Rückgabewert haben, sondern das gesamte Objekt. Habe es aber dann aber doch einfach mit row[0] geprüft.
adicllong
User
Beiträge: 3
Registriert: Dienstag 14. Juni 2022, 12:11

Ich muss doch nochmal nachhaken da das nur auf den ersten Blick geklappt hat - in den meistens Fällen kann ich nicht gegen result[0] == None prüfen, es führt nicht nur dem gewünschten Ergebnis. Ich habe nun nochmal eine Weile dagesessen und auch viel gegoggelt, aber bei den meisten Antworten wird Pandas oder etwas andres genutzt was aber bei mir der Fall ist. Wenn man mal das SQLAlchemy aussen vor lässt, wie prüfe ich ob das 'ChunkedIteratorResult', welches man in jedem Fall erhält, kein Resultat ergibt, iterieren kann man nicht, und auch so weiss ich nicht gegen was ausser None ich prüfen kann, da es kein resultat[0] gibt kann ich auch nicht prüfen.
Weleche Methode kann ich hier verwenden ?
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@adicllong: Ich bin mir jetzt nicht sicher in welchem Zusammenhang das jetzt gebraucht wird. Ergebnisse haben ja einen Haufen Methoden und je nach dem wie viele Datensätze Du da erwartest und ob die nur ein Element enthalten oder mehrere will man da `scalar_one()`, `scalar_one_or_none()` oder `scalar()`; oder `one()`, `first()`, oder `all()` aufrufen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Antworten