2010-03-26 18 views
8

所以,我有三个表:如何使用SQLalchemy连接三个表并将所有列保留在其中一个表中?

类defenitions:

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一起标识)订阅的频道。例如,假设我们有四个通道:通道1,通道2,通道3,通道4;通道4,通道4,通道4,一个用户:user1;谁在频道1和频道4上订阅。用户1的查询将返回类似:

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

这是最好的情况的结果,但因为我完全不知道如何完成认购列,我已经不是试图让特定用户id在用户有订阅的行中以及订阅缺失的位置,请将其留空。

我与SQLalchemy atm一起使用的数据库引擎。是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)) 

不过,我会继续寻找更好的解决方案,所以答案仍然很非常欢迎。

+0

你可以添加你的模型/表定义吗?取决于你使用的是声明映射器,普通映射器还是普通表格的语法有点不同。 – Wolph 2010-03-26 16:02:43

+0

完成并更新了问题,除非我弄错了,否则这将是声明映射器。 – jimka 2010-03-26 16:10:59

+0

这就是声明的映射器:) – Wolph 2010-03-26 17:06:45

回答

13

选项1:

Subscription只是许多一对多关系对象,我建议你而不是其建模为如一个单独的类。请参阅Configuring Many-to-Many Relationships文档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个Userchannels被要求时。

选项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完全相同。

+0

是的,我认为我已经阅读了SQLAlchemy手册中有关建模多对多关系(Option-1)的地方,或许我应该看看它更多。虽然我不确定在Option-1的末尾我多么喜欢额外的循环逻辑。 Option-2引入了'case()',它对我来说是新的,它几乎是我想要的,具有一个小细节,它不会与用户表中的sessionId列匹配。 – jimka 2010-03-27 10:08:51

+2

@jimka。 'sessionId'丢失 - 来吧,这是你可以处理的一个小细节,对吗?无论如何,为了完整性,只需在'sessionId'上添加缺少的过滤器即可。 – van 2010-03-27 10:44:49

+0

Offcourse,但它是问题的一部分,所以我相信它应该成为答案的一部分。 – jimka 2010-03-27 11:31:30

1

为了使这个小easyer我已经添加了关系到你的模型,这样你可以做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 
+0

然而,非常优雅的任何()似乎都不起作用,给了我一个错误:AttributeError:'InstrumentedList'对象没有任何属性'any'。 – jimka 2010-03-26 17:44:06

+0

是的,显然它只适用于过滤器。所以......'session.query(Channel).filter(Channel.subscriptions.any())'会起作用。 实际上并不令人感到意外,它应该被选作一个额外的列来运作。像'session.query(Channel.id,Channel.title,Channel.subscriptions.any()。label('subscribed'))''应该可以工作。 – Wolph 2010-03-27 12:00:34

0

不要从用户查询。来自频道的查询。

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

AttributeError:'InstrumentedList'对象在尝试'channel.subscriptions.user'时没有属性'user' – jimka 2010-03-26 18:22:10

+0

是的。自从我使用sqlalchemy以来已经有一段时间了;语法可能不完全正确。正确的语法留给读者练习。 (提示:如果你使用像WoLpH建议的关系会更容易) – jcdyer 2010-03-26 19:45:34

相关问题