Вариант-1:
Subscription
- это просто объект отношения «многие ко многим», и я бы посоветовал вам смоделировать его как таковой, а не как отдельный класс. См. Настройка отношений «многие ко многим» Документация SQLAlchemy/declarative
.
Ваша модель с тестовым кодом становится:
from sqlalchemy import create_engine, Column, Integer, DateTime, String, ForeignKey, Table
from sqlalchemy.orm import relation, scoped_session, sessionmaker, eagerload
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('sqlite:///:memory:', echo=True)
session = scoped_session(sessionmaker(bind=engine, autoflush=True))
Base = declarative_base()
t_subscription = Table('subscription', Base.metadata,
Column('userId', Integer, ForeignKey('user.id')),
Column('channelId', Integer, ForeignKey('channel.id')),
)
class Channel(Base):
__tablename__ = 'channel'
id = Column(Integer, primary_key = True)
title = Column(String)
description = Column(String)
link = Column(String)
pubDate = Column(DateTime)
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key = True)
username = Column(String)
password = Column(String)
sessionId = Column(String)
channels = relation("Channel", secondary=t_subscription)
# NOTE: no need for this class
# class Subscription(Base):
# ...
Base.metadata.create_all(engine)
# ######################
# Add test data
c1 = Channel()
c1.title = 'channel-1'
c2 = Channel()
c2.title = 'channel-2'
c3 = Channel()
c3.title = 'channel-3'
c4 = Channel()
c4.title = 'channel-4'
session.add(c1)
session.add(c2)
session.add(c3)
session.add(c4)
u1 = User()
u1.username ='user1'
session.add(u1)
u1.channels.append(c1)
u1.channels.append(c3)
u2 = User()
u2.username ='user2'
session.add(u2)
u2.channels.append(c2)
session.commit()
# ######################
# clean the session and test the code
session.expunge_all()
# retrieve all (I assume those are not that many)
channels = session.query(Channel).all()
# get subscription info for the user
#q = session.query(User)
# use eagerload(...) so that all 'subscription' table data is loaded with the user itself, and not as a separate query
q = session.query(User).options(eagerload(User.channels))
for u in q.all():
for c in channels:
print (c.id, c.title, (c in u.channels))
, который выдает следующий вывод:
(1, u'channel-1', True)
(2, u'channel-2', False)
(3, u'channel-3', True)
(4, u'channel-4', False)
(1, u'channel-1', False)
(2, u'channel-2', True)
(3, u'channel-3', False)
(4, u'channel-4', False)
Обратите внимание на использование eagerload
, которое будет выдавать только 1 оператор SELECT вместо 1 для каждого User
при запросе channels
.
Вариант-2:
Но если вы хотите сохранить свою модель и просто создать запрос SA, который даст вам столбцы, как вы просите, следующий запрос должен выполнить эту работу:
from sqlalchemy import and_
from sqlalchemy.sql.expression import case
#...
q = (session.query(#User.username,
Channel.id, Channel.title,
case([(Subscription.channelId == None, False)], else_=True)
).outerjoin((Subscription,
and_(Subscription.userId==User.id,
Subscription.channelId==Channel.id))
)
)
# optionally filter by user
q = q.filter(User.id == uid()) # assuming uid() is the function that provides user.id
q = q.filter(User.sessionId == id()) # assuming uid() is the function that provides user.sessionId
res = q.all()
for r in res:
print r
Вывод абсолютно такой же, как в варианте 1 выше.