2017-09-03 4 views
0

J'essaie de comprendre s'il est possible de faire quelque chose avec Sqlalchemy, ou si j'y pense de la mauvaise façon. À titre d'exemple, que j'ai deux (ce ne sont que des exemples) classes:Python sqlalchemy relation dynamique

class Customer(db.Model): 
    __tablename__ = 'customer' 
    id = Column(Integer, primary_key=True) 
    name = Column(String) 
    addresses = relationship('Address') 

class Address(db.Model): 
    __tablename__ = 'address' 
    if = Column(Integer, primary_key=True) 
    address = Column(String) 
    home = Column(Boolean) 
    customer_id = Column(Integer, ForeignKey('customer.id')) 

Et plus tard, je veux effectuer une requête qui obtient le client et juste leur domicile. Est-il possible de le faire avec quelque chose comme ceci:

db.session.query(Customer).join(Address, Address.home == True) 

ci-dessus Est-ce que d'affiner/restreindre la jonction de sorte que les résultats seraient seulement obtenir l'adresse de la maison?

Merci à l'avance, Doug

Répondre

0

Ouais, c'est tout à fait possible, si vous voudriez probablement du code comme:

# if you know the customer's database id... 
# get the first address in the database for the given id that says it's for home 
home_address = db.session.query(Address).filter_by(customer_id=customer_id_here, home=True).first() 

Au lieu d'avoir une valeur booléenne pour la maison, vous pouvez essayer un 'type' rangée à la place, en utilisant une énumération. Cela vous permettrait de choisir facilement une adresse pour des lieux comme le travail, plutôt que simplement un choix binaire de «soit cette adresse est pour la maison ou non». Mise à jour: Vous pouvez également utiliser l'argument mot-clé back_populates avec l'appel de relation, donc si vous avez une instance d'adresse (appelée a), vous pouvez obtenir le client avec quelque chose comme a.customer (qui est l'instance de la classe Customer cette adresse est associée à).

+0

Merci pour la réponse. J'aurais dû être plus clair sur ma question, les deux classes ne sont que des exemples que j'utilise pour illustrer le problème. Les classes d'exemple modélisent une relation un-2-plusieurs, avec ForeignKey désignant la jointure. Si elle effectue une requête simple pour obtenir un client, la collection de relations d'adresses contient également toutes les adresses de ce client. Mais ce que je veux, c'est remplacer ou ajouter aux critères de jointure afin que je puisse interroger pour obtenir un client, mais la collection de relations n'a qu'un seul élément, l'adresse du domicile. –

1

En cas de doute si une construction de requête est ce que vous voulez, essayez de l'imprimer:

In [29]: db.session.query(Customer).join(Address, Address.home == True) 
Out[29]: <sqlalchemy.orm.query.Query at 0x7f14fa651e80> 

In [30]: print(_) 
SELECT customer.id AS customer_id, customer.name AS customer_name 
FROM customer JOIN address ON address.home = true 

Il est clair que ce n'est pas ce que vous voulez. Chaque client est joint à chaque adresse qui est une adresse de domicile. En raison de la façon dont les entités sont traitées, cela peut ne pas être évident au premier abord. Les lignes dupliquées par client sont ignorées et vous obtenez le résultat d'entités client distinctes, même si la requête sous-jacente était erronée. La requête ignore également simplement les adresses jointes lors de la formation des résultats.

La solution la plus simple serait d'interroger juste pour tuples clients et adresses des critères requis:

db.session.query(Customer, Address).\ 
    join(Address).\ 
    filter(Address.home) 

Vous pouvez également faire quelque chose comme ça

db.session.query(Customer).\ 
    join(Address, (Customer.id == Address.customer_id) & Address.home).\ 
    options(contains_eager(Customer.addresses)) 

mais je recommande vivement contre . Vous vous mentiriez sur ce que contient la collection de relations et cela pourrait se retourner contre vous à un moment donné. Au lieu de cela, vous devez ajouter une nouvelle relation biunivoque au client avec le custom join condition:

class Customer(db.Model): 
    ... 
    home_address = relationship(
     'Address', uselist=False, 
     primaryjoin='and_(Customer.id == Address.customer_id, Address.home)') 

et vous pouvez ensuite utiliser une charge impatient rejoint

db.session.query(Customer).options(joinedload(Customer.home_address)) 
+0

@ write-on Je vais répondre à votre commentaire que vous avez proposé comme une modification ici: "C'est dans un appel de fonction, où home_only est un paramètre de cette fonction, donc je peux obtenir toutes les adresses de clients ou seulement le domicile. vous expliquez ce que vous êtes réticent à cela (puisque c'est proche de votre exemple)? " - Je serais réticent à implémenter un tel double rôle pour la relation simplement parce que cela pourrait revenir vous hanter, bien que cela puisse sembler improbable maintenant. Dans le futur, les objets de cet appel de fonction pourraient se retrouver quelque part où vous ou quelqu'un d'autre s'attendrait à ce que la collection contienne toutes les adresses –

+0

... et comme il existe une approche plus simple pour gérer une telle relation "maison" avec une autre Dans une relation, il y a peu à gagner à ajouter de la complexité au système en créant un tel cas particulier où la collection d'adresses pourrait contenir toutes les adresses ou seulement la maison. Mais comme toujours, ymmv, et si la solution fonctionne pour vous, qui suis-je pour discuter. –

+0

Merci encore llja, pour l'instant ça marche pour moi. La définition du modèle avec le priaryjoin simple me fait un client avec toutes les adresses. Mais, si je modifie la requête au moment de l'exécution pour ajouter le filtre pour la maison, je reçois un client avec juste une adresse (de domicile). :) –