forwarding to sqlalchemy On Mon, Jul 23, 2018 at 2:13 PM, seaders <seader...@gmail.com> wrote: > Hi Mike, thanks for replying. I've attached the full runner py file. > >> the relationship here names "Competiton" as a "secondary" table which > means it will be aliased (here as competition_1) and you cannot filter > on it in terms of the Competition entity > > Getcha, I wasn't aware of that. > >> If you want to make a > relationship from SourceSport to SourceCompetition that also uses > "Competition" and you also want to filter on Competition ... > > Preferably, if possible, I'd like to have all the options available to me > like with the query. So 1 time, it's filtering on `Competition.key`, > another, it's `SourceCompetition.source_competition_id`, basically like > you'd do with a manual SQL query. The JOIN between the tables > (relationship) should be unchanging, but if I want to do a filter on it, the > data from the JOIN'd tables would be available to me. > >> It looks mostly like SourceCompetition as a joined table > subclass of Competition would be the most natural > > I've not yet created a single subclass, so have no experience with them (If > I had a full handle on them, I might not have ended up here!). > > ---- > > P.S. I've just realised I've mistakenly posted this in sqlalchemy-alembic, > instead of sqlalchemy. Should I delete, and repost there, or are we ok > here? > > > On Monday, July 23, 2018 at 5:30:53 PM UTC+1, seaders wrote: >> >> I've these 5 tables, >> >> Source >> >> Sport 1----* Competition >> >> ________________________________ >> >> Sport 1----1 SourceSport >> >> Competition 1----1 SourceCompetition >> >> ________________________________ >> >> Source 1----* SourceSport >> >> Source 1----* SourceCompetition >> >> ________________________________ >> >> >> >> import logging >> from flask import Flask >> from flask_sqlalchemy import SQLAlchemy >> from sqlalchemy import Column, Integer, String, ForeignKey >> from sqlalchemy.ext.declarative import declared_attr >> from sqlalchemy.orm import relationship, foreign, joinedload, aliased >> from sqlalchemy.sql.elements import and_ >> from typing import List, cast >> >> app = Flask(__name__) >> app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///./test.db' >> app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False >> db = SQLAlchemy(app) >> Model = db.Model >> >> >> class Source(Model): >> id = Column(Integer, primary_key=True) >> key = Column(String(10), unique=True) >> >> >> class Sport(Model): >> id = Column(Integer, primary_key=True) >> name = Column(String(50), unique=True) >> >> >> class Competition(Model): >> id = Column(Integer, primary_key=True) >> key = Column(String(6), unique=True) >> >> sport_id = Column(Integer, ForeignKey(Sport.id)) >> sport = relationship(Sport) >> >> >> class _Sourcer(Model): >> __abstract__ = True >> >> @declared_attr >> def source_id(self): >> return Column(Integer, ForeignKey(Source.id), >> primary_key=True) >> >> @declared_attr >> def source(self): >> return relationship(Source) >> >> >> class SourceSport(_Sourcer): >> sport_id = Column(Integer, ForeignKey(Sport.id), >> primary_key=True) >> sport = relationship(Sport) >> source_sport_id = Column(String(20)) >> >> >> class SourceCompetition(_Sourcer): >> competition_id = Column(Integer, ForeignKey(Competition.id), >> primary_key=True) >> competition = relationship(Competition) >> source_competition_id = Column(String(20)) >> >> >> def create_all(): >> db.create_all() >> >> source = Source(key='main') >> >> sport = Sport(name='American Football') >> ssport = SourceSport(sport=sport, source=source, >> source_sport_id='sAF') >> >> comp = Competition(key='NFL', sport=sport) >> scomp = SourceCompetition( >> competition=comp, source=source, source_competition_id='sNFL') >> >> db.session.add(ssport) >> db.session.add(scomp) >> >> comp = Competition(key='NCAAF', sport=sport) >> scomp = SourceCompetition( >> competition=comp, source=source, source_competition_id='sNCAAF') >> >> db.session.add(scomp) >> >> sport = Sport(name='Basketball') >> ssport = SourceSport(sport=sport, source=source, >> source_sport_id='s2') >> >> comp = Competition(key='NBA', sport=sport) >> scomp = SourceCompetition( >> competition=comp, source=source, source_competition_id='sNBA') >> >> db.session.add(ssport) >> db.session.add(scomp) >> >> comp = Competition(key='NCAAB', sport=sport) >> scomp = SourceCompetition( >> competition=comp, source=source, source_competition_id='sNCAAB') >> >> db.session.add(scomp) >> >> >> And one of the queries I find myself doing often, is getting all the >> SourceCompetion's for SourceSport's. >> >> The "manual" query of this is fairly straight forward, >> >> SourceSport.query >> >> .join(Sport) >> .join(Competition) >> .join(SourceCompetition, >> ((SourceCompetition.source_id == SourceSport.source_id) & >> (SourceCompetition.competition_id == Competition.id)) >> ) >> >> >> Every relationship is set up correctly to join easily enough, until >> SourceCompetition, and then you've to specify the onclause, but again, >> because everything's set up ok, it's easy and straightforward enough. >> >> I can also easily add filters/options to that, >> >> ... >> .contains_eager(SourceSport.sport) >> .filter(Competition.key == 'NFL') >> >> ... >> .filter(Sport.name == 'Basketball') >> >> Example output of the first one, >> >> INFO:sqlalchemy.engine.base.Engine:SELECT sport.id AS sport_id, sport.name >> AS sport_name, source_sport.sport_id AS source_sport_sport_id, >> source_sport.source_sport_id AS source_sport_source_sport_id, >> source_sport.source_id AS source_sport_source_id >> FROM source_sport JOIN sport ON sport.id = source_sport.sport_id JOIN >> competition ON sport.id = competition.sport_id JOIN source_competition ON >> source_competition.source_id = source_sport.source_id AND >> source_competition.competition_id = competition.id >> WHERE competition."key" = ? >> INFO:sqlalchemy.engine.base.Engine:('NBA',) >> >> {'source_id': 1, 'sport_id': 2, 'source_sport_id': 's2', 'sport': {'name': >> 'Basketball', 'id': 2}} >> >> Perfect, now all I want to do, is have the exact same "base" join in a >> relationship, in SourceSport. I have a version working, >> >> class SourceSport(_Sourcer): >> >> ... >> >> source_competitions: List['SourceCompetition'] = relationship( >> lambda: SourceCompetition, >> primaryjoin=lambda: and_( >> SourceSport.sport_id == foreign(Competition.sport_id), >> ), >> secondary=Competition.__table__, >> secondaryjoin=lambda: and_( >> SourceSport.source_id == foreign(SourceCompetition.source_id), >> Competition.id == SourceCompetition.competition_id, >> ), >> innerjoin=True, >> uselist=True) >> >> >> Now, this works correctly (although tbh, I think the complexity of it >> seems too high for what I'm trying to achieve), so I can do queries like, >> >> SourceSport.query >> .options(joinedload(SourceSport.source_competitions)) >> >> ... >> >> SourceSport.query >> .join(SourceCompetition, SourceSport.source_competitions) >> >> >> But the problems come when I try to filter those queries with something >> simple like above, >> >> ... >> >> .filter(Competition.key == 'NFL') >> >> >> Now, when I look at the logs, >> >> INFO:sqlalchemy.engine.base.Engine:SELECT source_sport.sport_id AS >> source_sport_sport_id, source_sport.source_sport_id AS >> source_sport_source_sport_id, source_sport.source_id AS >> source_sport_source_id, source_competition_1.competition_id AS >> source_competition_1_competition_id, >> source_competition_1.source_competition_id AS >> source_competition_1_source_competition_id, source_competition_1.source_id >> AS source_competition_1_source_id >> FROM competition, source_sport JOIN competition AS competition_1 ON >> source_sport.sport_id = competition_1.sport_id JOIN source_competition AS >> source_competition_1 ON source_sport.source_id = >> source_competition_1.source_id AND competition_1.id = >> source_competition_1.competition_id >> WHERE competition."key" = ? >> INFO:sqlalchemy.engine.base.Engine:('NFL',) >> >> {'source_id': 1, 'sport_id': 1, 'source_sport_id': 'sAF', >> 'source_competitions': [{'source_competition_id': 'sNFL', 'source_id': 1, >> 'competition_id': 1}, {'source_competition_id': 'sNCAAF', 'source_id': 1, >> 'competition_id': 2}]} >> >> {'source_id': 1, 'sport_id': 2, 'source_sport_id': 's2', 'sport': {'name': >> 'Basketball', 'id': 2}, 'source_competitions': [{'source_competition_id': >> 'sNBA', 'source_id': 1, 'competition_id': 3}, {'source_competition_id': >> 'sNCAAB', 'source_id': 1, 'competition_id': 4}]} >> >> You can see that the JOIN for Competition is using "competition as >> competition_1", but the WHERE is just using "competition" from the FROM >> table list. This then gives a weird mish-mash of a returned object - not at >> all correct. >> >> I think I've tried just about everything, including a standalone join and >> mapper, but that seemed to be completely wasted work and right back to where >> I started here, I've read the docs, and stepped through a ton of the source, >> but still can't figure what to do. I feel like I'm just throwing spaghetti >> at the wall now, and trying to see will anything stick, and would really >> appreciate some help to figure this out and get it working. >> >> Many, many thanks. > > -- > You received this message because you are subscribed to the Google Groups > "sqlalchemy-alembic" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to sqlalchemy-alembic+unsubscr...@googlegroups.com. > For more options, visit https://groups.google.com/d/optout.
-- SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper http://www.sqlalchemy.org/ To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description. --- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+unsubscr...@googlegroups.com. To post to this group, send email to sqlalchemy@googlegroups.com. Visit this group at https://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout.