web-dev-qa-db-fra.com

Rejoignez plusieurs tables dans SQLAlchemy / Flask

J'essaie de comprendre la configuration correcte de la requête de jointure dans SQLAlchemy, mais je n'arrive pas à comprendre.

J'ai la configuration de table suivante (simplifiée, j'ai laissé de côté les champs non essentiels):

class Group(db.Model):
    id            = db.Column(db.Integer, primary_key = True)
    number        = db.Column(db.SmallInteger, index = True, unique = True)
    member        = db.relationship('Member', backref = 'groups', lazy = 'dynamic')

class Member(db.Model):
    id            = db.Column(db.Integer, primary_key = True)
    number        = db.Column(db.SmallInteger, index = True)
    groupid       = db.Column(db.Integer, db.ForeignKey('group.id'))
    item          = db.relationship('Item', backref = 'members', lazy = 'dynamic')

class Version(db.Model):
    id           = db.Column(db.Integer, primary_key = True)
    name         = db.Column(db.String(80), index = True)
    items        = db.relationship('Item', backref='versions', lazy='dynamic')  

class Item(db.Model):
    id           = db.Column(db.Integer, primary_key = True)
    member       = db.Column(db.Integer, db.ForeignKey('member.id'))
    version      = db.Column(db.Integer, db.ForeignKey('version.id'))

Les relations sont donc les suivantes:

  • 1: n Membre du groupe
  • 1: n Élément membre
  • Élément de version 1: n

Je voudrais construire une requête en sélectionnant toutes les lignes d'article de la base de données, qui ont une certaine version. Je voudrais ensuite les commander par groupe puis par membre. La sortie utilisant Flask/WTForm devrait ressembler à ceci:

* GroupA
  * MemberA
     * ItemA (version = selected by user)
     * ItemB ( dito )
  * Member B
     * ItemC ( dito )
  ....

J'ai trouvé quelque chose comme la requête suivante, mais je suis sûr que ce n'est pas correct (et inefficace)

   session.query(Item,Member,Group,Version)
    .join(Member).filter(version.id==1)
    .order_by(Group).order_by(Member).all()

Ma première approche intuitive aurait été de créer quelque chose comme

Item.query.join(Member, Item.member==Member.id)
    .filter(Member.versions.name=='MySelection')
    .order_by(Member.number).order_by(Group.number)

mais évidemment, cela ne fonctionne pas du tout. L'opération de jointure sur la table Version ne semble pas produire le type de jointure entre les deux tables que j'attendais. Peut-être que je comprends totalement le concept, mais après avoir lu les tutoriels, cela aurait été logique pour moi.

25
user3347953

Vous trouverez ci-dessous les objets dont vous avez besoin dans une seule requête:

q = (session.query(Group, Member, Item, Version)
        .join(Member)
        .join(Item)
        .join(Version)
        .filter(Version.name == my_version)
        .order_by(Group.number)
        .order_by(Member.number)
        ).all()
print_tree(q)

Cependant, le résultat obtenu sera une liste de tuples (Group, Member, Item, Version). A vous maintenant de l'afficher sous forme d'arborescence. Le code ci-dessous pourrait cependant s'avérer utile:

def print_tree(rows):
    def get_level_diff(row1, row2):
        """ Returns Tuple: (from, to) of different item positions.  """
        if row1 is None: # first row handling
            return (0, len(row2))
        assert len(row1) == len(row2)
        for col in range(len(row1)):
            if row1[col] != row2[col]:
                return (col, len(row2))
        assert False, "should not have duplicates"

    prev_row = None
    for row in rows:
        level = get_level_diff(prev_row, row)
        for l in range(*level):
            print 2 * l * " ", row[l]
            prev_row = row

pdate-1: Si vous êtes prêt à renoncer lazy = 'dynamic' pour les deux premières relations, vous pouvez une requête pour charger un ensemble object network (par opposition aux tuples ci-dessus) avec le code:

q = (session.query(Group)
        .join(Member)
        .join(Item)
        .join(Version)
        # @note: here we are tricking sqlalchemy to think that we loaded all these relationships,
        # even though we filter them out by version. Please use this only to get data and display,
        # but not to continue working with it as if it were a regular UnitOfWork
        .options(
            contains_eager(Group.member).
            contains_eager(Member.items).
            contains_eager(Item.version)
            )
        .filter(Version.name == my_version)
        .order_by(Group.number)
        .order_by(Member.number)
        ).all()

# print tree: easy navigation of relationships
for g in q:
    print "", g
    for m in g.member:
        print 2 * " ", m
        for i in m.items:
            print 4 * " ", i
37
van