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,  <[email protected] <javascript:>> 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 [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to