Как я присоединяюсь к трем таблицам с SQLalchemy и сохраняющий все столбцы в одной из таблиц?

Так, у меня есть три таблицы:

Определения класса:

engine = create_engine('sqlite://test.db', echo=False)
SQLSession = sessionmaker(bind=engine)
Base = declarative_base()

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)

class Subscription(Base):
    __tablename__ = 'subscription'

    userId = Column(Integer, ForeignKey('user.id'), primary_key=True)
    channelId = Column(Integer, ForeignKey('channel.id'), primary_key=True)

Примечание: Я знаю, что user.username должен быть уникальным, должен зафиксировать это, и я не уверен, почему SQLalchemy создает некоторые названия строки с двойными кавычками.

И я пытаюсь придумать способ получить все каналы, а также признак на том, на каких каналах у одного конкретного пользователя (определенный user.sessionId вместе с user.id) есть подписка.

Например, скажите, что у нас есть четыре канала: channel1, channel2, channel3, channel4; пользователь: user1; у кого есть подписка на channel1 и channel4. Запрос для user1 возвратил бы что-то как:

channel.id | channel.title | subscribed
---------------------------------------
1            channel1        True
2            channel2        False
3            channel3        False
4            channel4        True

Это - результат лучшего случая, но так как у меня нет абсолютно никакой подсказки как, как выполнить подписанный столбец, я вместо этого пытался получить конкретный пользовательский идентификатор в строках, где у пользователя есть подписка и где подписка отсутствует, просто оставьте его незаполненный.

Механизм базы данных, который я использую вместе с банкоматом SQLalchemy. sqlite3

Я царапал голову по этому в течение двух дней теперь, у меня нет проблемы при объединении все три посредством таблицы подписки, но затем всех каналов, где у пользователя нет подписки, опущен.

Я надеюсь, что мне удалось описать мою проблему достаточно, заранее спасибо.

Править: Управляемый для решения этого в немного неуклюжем способе включить подзапрос:

# What a messy SQL query!
stmt = query(Subscription).filter_by(userId = uid()).join((User, Subscription.userId == User.id)).filter_by(sessionId = id()).subquery()
subs = aliased(Subscription, stmt)
results = query(Channel.id, Channel.title, subs.userId).outerjoin((subs, subs.channelId == Channel.id))

Однако я буду продолжать искать более изящное решение, таким образом, ответы будут все еще очень одобрены.

8
задан 27 March 2010 в 10:13
поделиться

3 ответа

Option-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, когда запрашиваются каналы.

Вариант-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 выше.

13
ответ дан 5 December 2019 в 12:09
поделиться

Чтобы сделать это немного проще, я добавил отношения к вашей модели, таким образом, вы можете просто сделать user.subscriptions, чтобы получить все подписки.

engine = create_engine('sqlite://test.db', echo=False)
SQLSession = sessionmaker(bind=engine)
Base = declarative_base()

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)

class Subscription(Base):
    __tablename__ = 'subscription'

    userId = Column(Integer, ForeignKey('user.id'), primary_key=True)
    user = relationship(User, primaryjoin=userId == User.id, backref='subscriptions')
    channelId = Column(Integer, ForeignKey('channel.id'), primary_key=True)
    channel = relationship(channel, primaryjoin=channelId == channel.id, backref='subscriptions')

results = session.query(
    Channel.id,
    Channel.title,
    Channel.subscriptions.any().label('subscribed'),
)

for channel in results:
    print channel.id, channel.title, channel.subscribed
1
ответ дан 5 December 2019 в 12:09
поделиться

Не запрашивать у пользователя. Запрос от канала.

user = query(User).filter_by(id=1).one()
for channel in query(Channel).all():
    print channel.id, channel.title, user in channel.subscriptions.user

Таким образом вы получите все каналы, а не только те, которые связаны с данным пользователем.

0
ответ дан 5 December 2019 в 12:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: