On Tue, May 22, 2018 at 10:06 PM, <chbrn...@gmail.com> wrote: > Figured out what I had wrong, I was trying to use the second table in the > list as the basis, but the order dictates how SQLAlchemy handles it. > > So: > > q = db.session.query(User, Articles, ReadArticles) > should be: > q = db.session.query(Articles, User, ReadArticles)
you can also say: sess.query(User, Articles, ReadArticles).select_from(Articles).outerjoin(ReadArticles, ...).outerjoin(User, ...) > > and > > q = q.filter(User.id == 1).outerjoin(ReadArticles, ReadArticles.user_id == > 1).all() > should be > q = q.filter(User.id == 1).outerjoin(ReadArticles, ReadArticles.article_id > == Articles.id).all() > > to achieve what I was trying: > (<Articles 2>, <User 1>, <ReadArticles 1>) > (<Articles 1>, <User 1>, <ReadArticles 2>) > (<Articles 3>, <User 1>, None) > > > On Tuesday, May 22, 2018 at 6:25:29 PM UTC-5, chbr...@gmail.com wrote: >> >> Simon, >> >> Thank you, I've tried a few things and am still a bit confused. >> >> I've reconfigured the table classes a bit, here is what I'm using >> currently to try to grok this: >> >> class User(db.Model): >> id = db.Column(db.Integer, primary_key=True) >> username = db.Column(db.String(64), index=True, unique=True, >> nullable=False) >> >> >> class Articles(db.Model): >> id = db.Column(db.Integer, primary_key=True) >> article_name = db.Column(db.String(100), unique=True, nullable=False) >> >> >> class ReadArticles(db.Model): >> id = db.Column(db.Integer, primary_key=True) >> user_id = db.Column(db.Integer, db.ForeignKey('user.id'), >> nullable=False) >> article_id = db.Column(db.Integer, db.ForeignKey('articles.id'), >> nullable=False) >> >> I'm confused about two things, if I use this approach: >> >> q = db.session.query(User, Articles, ReadArticles) >> q = q.filter(User.id == 1).outerjoin(ReadArticles, ReadArticles.user_id == >> 1).all() >> >> what's return in q is a set of tuples (what I expected), but I receive >> what seem to be duplicate rows. However many rows in ReadArticles have the >> specified user_id each row that is returned is multiplied that many times. >> So if I have 3 items in Articles, but only two of them match in ReadArticles >> with an article_id matching that particular user_id I will receive two rows >> for each row in Articles. Here is an example of the tuples: >> >> for row in q: >> print(row) >> >> (<User 1>, <Articles 1>, <ReadArticles 1>) >> (<User 1>, <Articles 1>, <ReadArticles 2>) >> (<User 1>, <Articles 2>, <ReadArticles 1>) >> (<User 1>, <Articles 2>, <ReadArticles 2>) >> (<User 1>, <Articles 3>, <ReadArticles 1>) >> (<User 1>, <Articles 3>, <ReadArticles 2>) >> >> Article 3 (Articles.id = 3) does not match in the ReadArticles table, just >> the ones matching id 1 and 2. >> >> Am I missing something in my query that would simply return 1 row for each >> item and None if the user_id and article_id aren't in the same row in >> ReadArticles? >> >> To this end I tried using an and_ but think I don't have the syntax >> correct (well I know I don't, since it throws an error). Here is what I >> tried, perhaps I'm approaching it correctly but don't know the proper way to >> perform this? Would it return the behavior I described or still return >> multiple rows? >> >> Here is the query I'm trying and an abbreviated form of the error: >> >> r = db.session.query(User, Articles, ReadArticles).outerjoin(ReadArticles, >> and_(User.id == ReadArticles.user_id, Articles.id == >> ReadArticles.article_id)).all() >> >> And the error seems to be: 1054, "Unknown column 'articles.id' in 'on >> clause'" (giving me a reference to the SQLAlchemy docs >> http://sqlalche.me/e/2j85 stating simply that I've got an internal error) >> >> Any help (and especially pointers to explanations so I can understand) >> very appreciated! >> Thanks! >> >> On Tuesday, May 1, 2018 at 10:05:04 AM UTC-5, Simon King wrote: >>> >>> On Tue, May 1, 2018 at 2:35 PM, <chbr...@gmail.com> wrote: >>> > I'm using the PrettyPrinted tutorial on Many to Many relationships with >>> > Flask-SQLAlchemy however what I'm trying to figure out he doesn't go >>> > into in >>> > the tutorial and I haven't had much luck in _Essential SQLAlchemy_ or >>> > StackOverflow finding a solution. >>> > >>> > So for his example he has two main tables, Channel and User, and then a >>> > relationship table to create the many to many relationship. >>> > >>> > What I'm trying to do is to use an outerjoin (or whatever would work >>> > for >>> > this solution in SQLAlchemy's ORM) to retrieve a list of all of the >>> > entries >>> > in Channel but filter it so if a certain user id is 'subscribed' (has a >>> > relationship in the relationship table) then it will return that ID as >>> > well, >>> > or a calculated column/alias (however is best in SQLAlchemy's ORM and >>> > most >>> > Pythonic). >>> > >>> > At the moment the only way I'm able to accomplish this requires two >>> > queries, >>> > one for all channels, and one for all the channels that the particular >>> > user >>> > is subscribed to. Then combining them with a comprehension. While the >>> > comprehension is Pythonic the double querying seems like a bad >>> > practice, and >>> > also not very Pythonic. >>> > >>> > I'm using MySQL for the database if that helps, but I'm guessing >>> > whatever >>> > the solution is it will be agnostic to the database due to the ORM? >>> > >>> > Here is the video: https://youtu.be/OvhoYbjtiKc >>> > >>> > So the DB has a Channel table, with an ID and Name, a User table with >>> > ID and >>> > Name, and a 'subs' table (subscriptions) to manage the relationships >>> > that >>> > has a foreign key for the ID in each of the other two tables. >>> > >>> > I've tried several methods using the outerjoin() from SQLAlchemy but it >>> > doesn't seem to limit it in the way I'm trying. >>> > >>> > Here is the join that I've had the most luck with (but again, doesn't >>> > limit >>> > it, it just returns all the channels, then in each channel if you look >>> > at >>> > the User relationships you just see ALL the users subsribed to that >>> > channel, >>> > not just the one I try to search for). Below that is the code for the >>> > tables: >>> > >>> > chan_query = Channel.query.outerjoin(User, User.user_id==1).all() >>> > >>> > which returns all the rows in Channel (desired) but nothing to indicate >>> > if >>> > the selected user is subscribed to the particular row/Channel, just a >>> > list >>> > of ALL the users identified with that row, same for each of them. >>> > >>> > Code for the tables: >>> > --- >>> > >>> > subs = db.Table('subs', >>> > db.Column('user_id', db.Integer, db.ForeignKey('user.user_id')), >>> > db.Column('channel_id', db.Integer, >>> > db.ForeignKey('channel.channel_id')) >>> > ) >>> > >>> > >>> > class User(db.Model): >>> > user_id = db.Column(db.Integer, primary_key=True) >>> > name = db.Column(db.String(20)) >>> > subscriptions = db.relationship('Channel', secondary=subs, >>> > backref=db.backref('subscribers', lazy='dynamic')) >>> > >>> > >>> > class Channel(db.Model): >>> > channel_id = db.Column(db.Integer, primary_key=True) >>> > channel_name = db.Column(db.String(20)) >>> > --- >>> > >>> > Any help is greatly appreciated, feel like I'm just stuck on this and >>> > don't >>> > even know how to search to find the solution :( >>> > >>> >>> You want to return 2 pieces of information in each row: the channel, >>> and a flag that indicates whether the user subscribes. The best way to >>> do that is to use the "session.query()" method. This is the more >>> general version of your "Channel.query" function from >>> Flask-SQLAlchemy. >>> >>> There are probably many different ways of expressing the query you >>> want in SQL. I think of it as needing an inner join between "user" and >>> "subs", with an extra condition restricting it to the user you are >>> interested in. This is then outer-joined to the "channel" table, so >>> that you still get channels that the user hasn't subscribed to. That >>> would look something like this: >>> >>> userjoin = saorm.join( >>> User, subs, >>> sa.and_(subs.c.user_id == User.user_id, >>> User.name == 'joe'), >>> ) >>> q = (session.query(Channel, User.user_id) >>> .outerjoin(userjoin)) >>> >>> ...which produces this SQL (reformatted): >>> >>> SELECT channel.channel_id AS channel_channel_id, >>> channel.channel_name AS channel_channel_name, >>> user.user_id AS user_user_id >>> FROM channel >>> LEFT OUTER JOIN ( >>> user JOIN subs ON subs.user_id = user.user_id AND user.name = ? >>> ) ON channel.channel_id = subs.channel_id >>> >>> >>> Here's a full working example: >>> >>> ############################################################ >>> import sqlalchemy as sa >>> import sqlalchemy.orm as saorm >>> from sqlalchemy.ext.declarative import declarative_base >>> Base = declarative_base() >>> >>> subs = sa.Table( >>> 'subs', Base.metadata, >>> sa.Column('user_id', sa.Integer, sa.ForeignKey('user.user_id')), >>> sa.Column('channel_id', sa.Integer, >>> sa.ForeignKey('channel.channel_id')), >>> ) >>> >>> >>> class User(Base): >>> __tablename__ = 'user' >>> user_id = sa.Column(sa.Integer, primary_key=True) >>> name = sa.Column(sa.String(20)) >>> subscriptions = saorm.relationship( >>> 'Channel', >>> secondary=subs, >>> backref=saorm.backref('subscribers', lazy='dynamic'), >>> ) >>> >>> >>> class Channel(Base): >>> __tablename__ = 'channel' >>> channel_id = sa.Column(sa.Integer, primary_key=True) >>> channel_name = sa.Column(sa.String(20)) >>> >>> >>> if __name__ == '__main__': >>> engine = sa.create_engine('sqlite:///:memory:') >>> Base.metadata.create_all(engine) >>> sessionmaker = saorm.sessionmaker(bind=engine) >>> >>> session = sessionmaker() >>> joe = User(name='joe') >>> bob = User(name='bob') >>> >>> foo = Channel(channel_name='foo') >>> bar = Channel(channel_name='bar') >>> baz = Channel(channel_name='baz') >>> >>> joe.subscriptions = [foo, bar] >>> bob.subscriptions = [bar, baz] >>> >>> session.add_all([joe, bob, foo, bar, baz]) >>> session.commit() >>> >>> userjoin = saorm.join( >>> User, subs, >>> sa.and_(subs.c.user_id == User.user_id, >>> User.name == 'joe'), >>> ) >>> q = (session.query(Channel, User.user_id) >>> .outerjoin(userjoin)) >>> >>> print q >>> >>> for (channel, userid) in q.all(): >>> print channel.channel_name, userid >>> >>> ############################################################ >>> >>> Hope that helps, >>> >>> Simon > > -- > 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. -- 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.