flask, sqlalchemy, many-to-one, distinct()

Django, Flask, Bottle, WSGI, CGI…
Antworten
beetronic
User
Beiträge: 33
Registriert: Mittwoch 2. Mai 2007, 10:23

Hi,

Ich entwickle eine WebApp mit Flask, als ORM Layer benutze ich SQLAlchemy.

Flask==0.9
Flask-SQLAlchemy==0.16
SQLAlchemy==0.8.0b2

Ich habe zwei einfache Tabellen. Zwischen Büchern und Kategorien besteht eine n:1 Beziehung, mehrere Bücher können einer Kategorie zugeordnet sein.

Code: Alles auswählen

class Category(db.Model):
    __tablename__ = "categories"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    description = db.Column(db.String)

class Book(db.Model):
    __tablename__ = "books"

    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String)
    publisher = db.Column(db.String)
    
    category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
    category = db.relationship('Book')
Ich würde jetzt gerne mit der Query Methode distinct() eine Liste aller zur Zeit zugeordneten Kategorien erhalten. Allerdings geht das nicht so wie ich mir das vorstelle.

das funktioniert:

distinct_publishers = db.session.query(Immo.publisher).distinct().all()

... und liefert eine Liste von NamedTuples zurück.

das funktioniert nicht:

distinct_categories = db.session.query(Immo.category).distinct().all()

... liefert eine Liste von NamedTuples zurück, in denen allerdings nur True/False drin steht.

[(False), (True)]

Ich hätte stattdessen NamedTuples mit Category Objekten erwartet.

das funktioniert wieder:

distinct_category_ids = db.session.query(Immo.category_id).distinct().all()

ist aber nicht ganz was ich möchte, da es mir nur die IDs zurückliefert und ich dann erst noch die Objekte mir holen müsste.

Hat jemand einen Tip, was ich anders machen muss?

Dank + Gruss, beetronic
BlackJack

@beetronic: `categorie` ist ja kein Datenbankfeld, das kann man deshalb so auch nicht abfragen. Von SQL ausgehend würde ich die Kategorien abfragen mit einem Filter der auf Existenz (mindestens) eines Buches prüft.

Bei `relationship()` 'Book' anzugeben ist übrigens komisch. Eigentlich hätte ich hier `relationship()` auf der `Category` erwartet und zwar so etwas:

Code: Alles auswählen

    books = db.relationship('Book', backref='category')
Und dann kein `category` auf dem `Book` denn das wird von dem `relationship()` auf `Category` automatisch als Rückveweis angelegt.
beetronic
User
Beiträge: 33
Registriert: Mittwoch 2. Mai 2007, 10:23

BlackJack hat geschrieben:@beetronic: `categorie` ist ja kein Datenbankfeld, das kann man deshalb so auch nicht abfragen.
Aber ich kann doch z.B. auch sowas machen, das liefert mir auch ein Category Objekt zurück.

Code: Alles auswählen

Book.query.first().category
BlackJack hat geschrieben: Bei `relationship()` 'Book' anzugeben ist übrigens komisch. Eigentlich hätte ich hier `relationship()` auf der `Category` erwartet und zwar so etwas:

Code: Alles auswählen

    books = db.relationship('Book', backref='category')
Und dann kein `category` auf dem `Book` denn das wird von dem `relationship()` auf `Category` automatisch als Rückveweis angelegt.
Ich hatte es so gemacht, weil es hier so beschrieben wird http://docs.sqlalchemy.org/en/rel_0_9/o ... any-to-one Deine Variante funktioniert aber auch, das Ergebnis ist allerdings das selbe.
BlackJack

@beetronic: Bei ``Book.query.first().category`` ist die Abfrage bei `first()` zuende, das heisst da wird SQL ausgeführt um das erste Buch aus der Datenbank zu holen und zu deinem Programm in den Arbeitsspeicher zu laden. Auf diesem `Book`-Exemplar wird dann das `category`-Attribut abgefragt was zu einer weiteren, getrennten Datenbankabfrage führt — sofern man die Modelle nicht so konfiguriert hat, dass `Category`-Objekte immer gleich mit den Büchern zusammen abgefragt werden und/oder das entsprechende `Category`-Objekt schon abgefragt wurde und bereits lokal im Cache vorliegt.

Bei der verlinkten Dokumentation wird es so gemacht wie ich das beschrieben habe. `Parent` ist die `Category` und `Child` entspricht dem `Book`. Du hast das anders gemacht. Und IMHO falsch, auch wenn das richtige heraus kommen mag, sieht es komisch aus auf `Book` eine `relationship()` zu 'Book' zu deklarieren die aber eigentlich `Book` und `Category` in Beziehung setzt. Das hat mich beim lesen komplett verwirrt.
beetronic
User
Beiträge: 33
Registriert: Mittwoch 2. Mai 2007, 10:23

BlackJack hat geschrieben: Bei der verlinkten Dokumentation wird es so gemacht wie ich das beschrieben habe. `Parent` ist die `Category` und `Child` entspricht dem `Book`. Du hast das anders gemacht. Und IMHO falsch, auch wenn das richtige heraus kommen mag, sieht es komisch aus auf `Book` eine `relationship()` zu 'Book' zu deklarieren die aber eigentlich `Book` und `Category` in Beziehung setzt. Das hat mich beim lesen komplett verwirrt.
Hi BlackJack! Ok, Asche auf mein Haupt. Ich habe mir meinen Beispielcode nochmal angesehen, und musste festellen, dass der falsch ist. Es macht natürlich keinen Sinn, dass die Book Klasse sich selbst referenziert. Es muss natürlich heissen ..

Code: Alles auswählen

category = db.relationship('Category')
Mit dieser Änderung entspricht mein Beispiel dann dem der Doku, ich habe nur die Klassen in anderer Reihenfolge definiert, zuerst die Child Klasse (Category) und dann die Parent Klasse (Book).

Und nun nochmal zurück zu meiner eigentlichen Frage, warum kann ich nicht folgendes machen?

Code: Alles auswählen

db.session.query(Book.category).distinct()
BlackJack

@beetronic: Na erstens weil es keine echte Datenbankspalte ist und dann ist es immer noch falsch herum. So hast Du mehrere Kategorien für ein Buch, aber nur ein Buch pro Kategorie. Im Beispiel in der Dokumentation ist es anders herum definiert.
beetronic
User
Beiträge: 33
Registriert: Mittwoch 2. Mai 2007, 10:23

BlackJack hat geschrieben:@beetronic: Na erstens weil es keine echte Datenbankspalte ist und dann ist es immer noch falsch herum. So hast Du mehrere Kategorien für ein Buch, aber nur ein Buch pro Kategorie. Im Beispiel in der Dokumentation ist es anders herum definiert.
Sorry, aber ich glaube jetzt bringst Du was durcheinander. Bei meinem Bsp. verweist jedes Buch (Parent) auf genau eine Kategorie (Child). Diese Relation wird in der Spalte category_id gespeichert. dort kann man pro Buch ja auch nur eine ID abspeichern. Von der anderen Seite betrachtet kann natürlich eine einzelne Kategorie von vielen Büchern referenziert werden. Deswegen many-to-one aus Sicht des Parent Objekts.
BlackJack

@beetronic: Es ist genau umgekehrt: `Book` entspricht `Child` aus dem Beispiel und `Category` entspricht `Parent` aus dem Beispiel in der Dokumentation. Ein `Parent` kann viele Kinder haben, genau so wie eine `Category` viele Bücher haben kann. Umgekehrt kann ein `Child` aus dem Beispiel nur ein Elternobjekt haben, so wie ein `Book` nur einer Kategorie zugeordnet werden kann.
Antworten