SAWarning: Multiple rows returned with uselist=False

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
MoonKid
User
Beiträge: 105
Registriert: Mittwoch 10. Dezember 2014, 16:24

Ich versuche den unten stehenden Fehler bzw. die Warnung zu interpretieren.

Es handelt sich um ein bestehendes Objekt (t), bei welchem der ForeignKey bereits gesetzt ist.
Diesen ändere ich hier nun, mit einer einfachen Zuweiseung (self._m = m) und bekomme dann diese Warnung.

Hier auszugsweise die Deklaration der Klassen.

Code: Alles auswählen

class TrainingUnitData(_Base):
    __tablename__ = 'TrainingUnit'

    __oid = sa.Column('oid', sa.Integer, primary_key=True)
    # ...
    __machine_fk = sa.Column('machine', sa.Integer, sa.ForeignKey('Machine.oid'))
    __machine = sao.relationship("MachineData", backref=sao.backref("TrainingUnitData", uselist=False))

    def SetMachine(self, machine):
        self.__machine = machine



class MachineData(_Base):
    __tablename__ = 'Machine'

    __oid = sa.Column('oid', sa.Integer, primary_key=True)
    # ...
Es ist das erste Mal, dass ich foreign keys verwende. Vermutlich verstehe ich hier etwas falsch.
Bin mir auch nicht sicher, ob ich '__machine_fk' überhaupt benötige. Dort steht nur die 'oid' von der Maschine drin.

Code: Alles auswählen

>>> t.SetMachine(m)
/usr/lib/python3/dist-packages/sqlalchemy/orm/strategies.py:524: SAWarning: Multiple rows returned with uselist=False for lazily-loaded attribute 'MachineData.TrainingUnitData' 
  return self._emit_lazyload(session, state, ident_key, passive)
Zuletzt geändert von Anonymous am Sonntag 12. April 2015, 09:46, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
BlackJack

@MoonKid: Das sieht so aus als wenn Du diese Maschine bei mehr als einer Training-Unit gesetzt hast, beim `backref()` aber steht das es nur ein Objekt gibt wenn man das Attribut auf der Maschine abruft.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@MoonKid: jede Maschine kann nur eine Trainings-Unit haben. Da Maschine, die Du also neu zuweist, hat schon eine und müßte jetzt eine zweite bekommen, was Du aber wegen uselist=False nicht haben möchtest.

__oid und __machine_fk haben mindestens einen, __machine sogar zwei Unterstriche zu viel, womit SetMachine, das übrigens auch noch eine falsche Schreibweise hat, überflüssig wird.
MoonKid
User
Beiträge: 105
Registriert: Mittwoch 10. Dezember 2014, 16:24

Sirius3 hat geschrieben:__oid und __machine_fk haben mindestens einen, __machine sogar zwei Unterstriche zu viel, womit SetMachine, das übrigens auch noch eine falsche Schreibweise hat, überflüssig wird.
Kann dir nicht ganz folgen. Das Thema mit nur einem Unterstrich hatten wir ja schon - das ist mir klar.
Aber warum soll '__machine' gar keinen haben? Und was ist an 'SetMachine()' falsch?
Kannst du bitte einen Vorschlag machen?
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@MoonKid: Durch Deine Methode SetMachine hast Du machine öffentlich gemacht, also kannst Du auch direkt auf das Attribut machine zugreifen, wobei es wahrscheinlich viel öfter Lesezugriffe geben wird, die noch komplizierter wären, wenn machine so versteckt wird, wie Du es gemacht hast.
BlackJack

Mich wundert in dem Zusammenhang ja eher das `MachineData`-Objekte ein Attribut `TrainingUnitData` haben und das nicht `__TrainingUnitData` heisst und für den Zugriff keine `GetTrainingUnitData()`-Methode auf `MachineData`-Objekten existiert. ;-) SCNR
MoonKid
User
Beiträge: 105
Registriert: Mittwoch 10. Dezember 2014, 16:24

BlackJack hat geschrieben:Mich wundert in dem Zusammenhang ja eher das `MachineData`-Objekte ein Attribut `TrainingUnitData` haben und das nicht `__TrainingUnitData` heisst
Bin jetzt völlig verwirrt. Vielleicht verstehe ich dich falsch, aber "MachineData" hat kein kein Attribut vom Typ "TrainingUnitData".
BlackJack

@MoonKid: Doch das hat es. Das legst Du in Zeile 7 fest. Was denkst Du denn was `backref` macht?
MoonKid
User
Beiträge: 105
Registriert: Mittwoch 10. Dezember 2014, 16:24

Sirius3 hat geschrieben:@MoonKid: Durch Deine Methode SetMachine hast Du machine öffentlich gemacht, also kannst Du auch direkt auf das Attribut machine zugreifen, wobei es wahrscheinlich viel öfter Lesezugriffe geben wird, die noch komplizierter wären, wenn machine so versteckt wird, wie Du es gemacht hast.
Nur noch mal, um zu sehen, ob ich es richtig verstehe:
Das Foreign-Key Attribut darf ich mit '_' verstecken, weil ich es von Außen eh nicht benötige. Korrekt?
Auf 'machine' allerdings greife ich hin und wieder, deswegen muss ich es (nach Python-Konvention) auch nicht verstecken. Korrekt?

Klingt sinnvoll.
MoonKid
User
Beiträge: 105
Registriert: Mittwoch 10. Dezember 2014, 16:24

BlackJack hat geschrieben:@MoonKid: Doch das hat es. Das legst Du in Zeile 7 fest. Was denkst Du denn was `backref` macht?
Faszinierend...
"denken" tue ich da viel, aber verstanden hab ich die SQLA-Doku nur bedingt in diesem Punkt. Selbst das eigentlich gute Tutorial war an diesem Punkt (für mich) nur mäßig verständlich.

Aber vielleicht ist diese Praxissituation eine gute Gelegenheit für mich, das Prinzip dahinter besser zu verstehen. Brauch nur Zeit und Ruhe...

Also '_machine_fk' interessiert mich eigentlich gar nicht. Ich ging davon aus, dass SQLA das für seine internen Prozesse/Logiken benötigt.
ich brauche 'machine' bzw. eine Referenz auf das konkrete Objekt. Eigentlich ist mir beim jetztigen Code schon nicht klar, woher SQLA diese Referenz eigentlich herbekommt.

btw:
Den aktuellen Datenbestand hab ich übrigens nicht mit desem Code angelegt, sondern mehr oder weniger manuell mit LibreOffice Base als FrontEnd.
BlackJack

@MoonKid: Wenn Du auf das `machine`-Attribut zugreifst dann fragt SA beim ersten Zugriff den Datensatz dazu von der Datenbank ab und erzeugt ein entsprechendes Objekt. Du hast ja in Zeile 7 gesagt das `machine` eine Beziehung zu `MachineData` ist. Und da es nur einen Fremdschlüssel in `TrainingUnitData` zu `MachineData` gibt, ist auch klar welcher Fremdschlüssel dazu benutzt werden muss. Wenn man mehr als eine Verbindung zur einer anderen Tabelle erstellen will, müsste man explizit angeben welchen Fremdschlüssel man verwenden möchte.
MoonKid
User
Beiträge: 105
Registriert: Mittwoch 10. Dezember 2014, 16:24

Many-to-One

So haut es hin. Hab einfach zuviel Objektorientierung von SQLA erwartet und die relationale Ebene nicht berücksichtigt.
Danke für eure Hilfe.
BlackJack

@MoonKid: Das verstehe ich nicht, das Problem ist doch nicht OOP sondern das Du `backref()` verwendet hast ohne zu wissen was das bedeutet, insbesondere dort das Argument `uselist`. *Du* hast gesagt es soll eine 1:1 Beziehung sein und SA hat halt gewarnt das diese Einschränkung durch eine Operation verletzt wurde. Eine Warnung weil man das an der Stelle ja noch beheben kann in dem man bei dem anderen `TrainingUnitData` das Maschinen-Attribut auf `None` setzen könnte und dann wäre alles wieder in Ordnung. Bei einer Abfrage in dem Zustand hätte es dagegen einen Fehler gegeben. Aber Du wusstest ja nicht einmal das es das Attribut gibt und man es abfragen kann.
MoonKid
User
Beiträge: 105
Registriert: Mittwoch 10. Dezember 2014, 16:24

BlackJack hat geschrieben:@MoonKid: Das verstehe ich nicht, das Problem ist doch nicht OOP sondern das Du `backref()` verwendet hast
Schon ok, hab nur laut gedacht. Problem war, dass ich im Paradigma der OOP an das Problem herangeangen bin. In der OOP braucht man sich keine Gedanken über keys zu machen. Auch Referenzen werden ganz anders formuliert. Ein "one to many" gibt es dort in der Form eigentlich nicht.
Was ich hier habe wäre in der OO eine "unidirektionale zu-eins-Beziehung". Der fachlich exakte Wortlaut ist mir leider nicht geläufig. Ich drücke sowas mit UML-Diagrammen aus.

Wäre ich im relationalen Paradigma rangegangen, hätte ich das Problem auch anders gesehen, woanders gesucht und anders analysiert. Das wollte ich damit ausdrücken.

Muss mir in Zukunft einfach mehr vor Augen halten, das SQLA auch nur ein Wrapper ist und mir nicht alles abnimmt.
Bin ja bei der Kombi aus PostgreSQL und SQLA gelandet, weil mir für Python3 bisher keine brauchebare freie und echte Objekteorientierte Datenbank bekannt ist. (ZOP ist keine!)
BlackJack

@MoonKid: Der einzige Unterschied ist doch das man beim relationalen Modell automatisch beide Richtungen bekommt und damit auch bei den Objekten mit `backref` leicht bekommen kann. Und wenn man das macht kann man auch bei der ”anderen” Seite angeben das die Kardinalität 1 und nicht n sein soll. Das hast Du gemacht in dem Du gesagt hast das die Rückrichtung keine ”Liste” benutzen soll.

Sich so etwas mit Properties bei einer reinen OOP-Lösung zu basteln halte ich auch nicht für so abwegig.

Ein „one to many” wäre ein Attribut das eine Liste oder ein anderer Container-Typ auf der „one”-Seite mit den Objekten der „many”-Seite. Also das gibt's in OOP schon.
Antworten