2010-03-26 21 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

相關問題