2017-10-10 5 views
0

Je tente de créer un backend avec Flask et SQLAlchemy en utilisant une base de données Postgresql. Lors de la configuration du schéma dans models.py, j'ai un objet Carte et un objet Couleur. Il y a cinq couleurs et chaque carte peut être plusieurs couleurs. En tant que tel, j'essaie d'utiliser une relation de plusieurs à plusieurs avec un objet Association, ColorAssociation. Le code models.py pertinente est la suivante:SQLAlchemy Query Listes de relations multiples

class ColorAssociation(db.Model): 
    __tablename__ = 'color_association' 
    card_id = db.Column(db.Integer, db.ForeignKey('cards.id'), primary_key=True) 
    color_id = db.Column(db.Integer, db.ForeignKey('colors.id'), primary_key=True) 
    card = db.relationship("Card", back_populates="colors") 
    color = db.relationship("Color", back_populates="cards") 

class Color(db.Model): 
    __tablename__ = 'colors' 
    id = db.Column(db.Integer, primary_key=True) 
    name = db.Column(db.String()) 
    cards = db.relationship("ColorAssociation", back_populates="color") 

class Card(db.Model): 
    __tablename__ = 'cards' 
    id = db.Column(db.Integer, primary_key=True) 
    name = db.Column(db.String()) 
    colors = db.relationship("ColorAssociation", back_populates="card") 

Le schéma semble fonctionner comme après l'ajout de certaines données, je peux interroger avec succès pour toutes les cartes associé à la couleur avec le nom « Blue », par exemple en utilisant la requête suivante sqlalchemy:

blue = db.session.query(Color).filter(Color.name == 'Blue').all()[0] 
for association in blue.cards: 
    print(association.card) 

Ma question est de savoir comment je demande des cartes de plusieurs couleurs? Par exemple, comment rechercher toutes les cartes associées à des couleurs portant à la fois le nom "Bleu" et "Vert".

Répondre

0

Alors j'ai recréé un environnement de test et j'ai essayé de reproduire le problème que vous rencontrez, le piège le plus courant ici est que sqlalchemy fournit de lourdes charges en arrière-plan. Puisque vous avez placé des associations dans un autre modèle qui doit être joint à la table, vous êtes interrogé lorsque la logique est un peu plus complexe. Donc, par exemple, vous voulez toutes les cartes qui sont bleues ou rouges qui sont un seul doublure db.session.query(Color).filter(or_(color='blue',color='red'))).all().

Mais puisque vous avez demandé que les cartes aient les deux couleurs, nous devons obtenir toutes les cartes uniques et ajouter leurs couleurs respectives. De là, nous filtrons les couleurs que nous aimerions (c'est-à-dire bleu et rouge dans ce cas). Ensuite, assurez-vous que chaque carte a le même nombre exact de couleurs que nous voulons. Ceci est accompli en utilisant la fonctionnalité ayant. Échantillon ci-dessous.

from flask import Flask 
from flask_sqlalchemy import SQLAlchemy 
import random 
from sqlalchemy import and_, func, or_ 

app = Flask(__name__) 
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' 
app.config['SQLALCHEMY_ECHO'] = True 
db = SQLAlchemy(app) 


class ColorAssociation(db.Model): 
    __tablename__ = 'color_association' 
    card_id = db.Column(db.Integer, db.ForeignKey(
     'cards.id'), primary_key=True) 
    color_id = db.Column(db.Integer, db.ForeignKey(
     'colors.id'), primary_key=True) 
    card = db.relationship("Card", back_populates="colors") 
    color = db.relationship("Color", back_populates="cards") 


class Color(db.Model): 
    __tablename__ = 'colors' 
    id = db.Column(db.Integer, primary_key=True) 
    name = db.Column(db.String()) 
    cards = db.relationship("ColorAssociation", back_populates="color") 

    def __repr__(self): 
     return "< %d, %s>" % (self.id, self.name) 


class Card(db.Model): 
    __tablename__ = 'cards' 
    id = db.Column(db.Integer, primary_key=True) 
    name = db.Column(db.String()) 
    colors = db.relationship("ColorAssociation", back_populates="card") 


db.create_all() 
colors_avail = ['blue', 'red', 'green', 'orange', 'pink'] 
for x in colors_avail: 
    col = Color(name=x) 
    db.session.add(col) 
db.session.commit() 

for x in range(0, 20): 
    card = Card(name=str(x) + "card") 
    db.session.add(card) 
    db.session.commit() 
    colors_id = [] 
    for randnum in range(1, 6): 
     rand = random.randint(1, 5) 
     if not rand in colors_id: 
      colors_id.append(rand) 
    for color_id in colors_id: 
     association = ColorAssociation(card_id=card.id, color_id=color_id) 
     db.session.add(association) 
    db.session.commit() 
# Get color ids these are unique in this case 
colors = db.session.query(Color).filter(
    or_(Color.name == 'blue', Color.name == 'red')).all() 

# Create array of these ids 
colors_ids = [color.id for color in colors] 

cards = db.session.query(Card).distinct(Card.id).outerjoin(ColorAssociation). \ 
    filter(ColorAssociation.color_id.in_(colors_ids)).group_by(Card.id).having(
     func.count(ColorAssociation.color_id) == len(colors_ids)).all() 
""" 
This doesn't work 
cards = db.session.query(ColorAssociation). \ 
    filter(ColorAssociation.color_id.in_(colors_ids)).group_by(ColorAssociation.color_id).having(
     func.count(ColorAssociation.color_id) == len(colors_ids)).all() 
""" 
for card in cards: 
    print(card) 
+0

Fonctionne parfaitement, merci d'avoir pris le temps de recréer ça! Juste une question sur la dernière requête que vous faites. Donc la raison pour laquelle j'essaye d'utiliser l'objet Association est donc si je veux filtrer des cartes par certaines couleurs, je n'ai pas besoin de vérifier chaque carte dans le db (il y en aura environ 30 000) contre une couleur, je peux juste sélectionnez la couleur, puis filtrez ses cartes. Est-ce que la dernière requête que vous faites, puisque c'est sur la carte, vérifie chaque carte dans la base de données? Si oui, est-ce inévitable? – gategeek42

+0

Eh bien la façon dont je le ferais est d'avoir l'objet carte avec une clé étrangère de couleur, de cette façon je peux spécifier quelles couleurs je veux. En raison de sql ayant une structure plate. les filtres (où) sont appliqués par ligne et non en groupe. Il n'y a pas moyen de contourner l'interrogation de toutes les cartes, car même les associations de couleurs ont une entrée par carte que vous avez. Et l'itération de 30 000 lignes pour une base de données n'est pas importante même si elle n'est pas correctement indexée. – will7200