SQLAlchemy —— 多表查詢

1. join 查詢

假設(shè)這樣一個(gè)業(yè)務(wù)場(chǎng)景殿漠,知道一個(gè)郵箱地址,要查詢這個(gè)地址所屬的用戶佩捞,第一個(gè)辦法是用連接多個(gè) filter() 來查詢绞幌。

for u, a in session.query(User, Address).\
    filter(User.id==Address.user_id).\
    filter(Address.email_address=='jack@google.com').\
    all():

    print(u)
    print(a)

# 執(zhí)行結(jié)果
jack
jack@google.com

更簡(jiǎn)便的方法是使用 join() 方法:

u =  session.query(User).join(Address).\
    filter(Address.email_address=='jack@google.com').\
    one()

print(u)

# 執(zhí)行結(jié)果
jack

Query.join() 知道如何在 UserAddress 之間進(jìn)行連接,因?yàn)槲覀冊(cè)O(shè)定了外鍵一忱。假如我們沒有指定外鍵莲蜘,比如這樣:

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    fullname = Column(String(50))
    password = Column(String(12))


class Address(Base):
    __tablename__ = 'addresses'
    id = Column(Integer, primary_key=True)
    email_address = Column(String, nullable=False)
    user_id = Column(Integer)

我們可以用下面方法來讓 join 生效:

query.join(Address, User.id==Address.user_id)    # explicit condition
query.join(User.addresses)                       # specify relationship from left to right
query.join(Address, User.addresses)              # same, with explicit target
query.join('addresses')                          # same, using a string

例子:

session.query(User).\
    join(Address, User.id==Address.user_id).\
    filter(Address.email_address=='jack@google.com').all()




2. 子查詢(subquery)

現(xiàn)在需要查詢每個(gè)用戶所擁有的郵箱地址數(shù)量,思路是先對(duì) addresses 表按用戶 ID 分組掀潮,統(tǒng)計(jì)各組數(shù)量菇夸,這樣我們得到一張新表琼富;然后用 JOIN 連接新表和 users 兩個(gè)表仪吧,在這里,我們應(yīng)該使用 LEFT OUTER JOIN鞠眉,因?yàn)槭褂?INTER JOIN 所得出的新表只包含兩表的交集薯鼠。

from sqlalchemy.sql import func

stmt = session.query(Address.user_id, func.count('*').\
    label('address_count')).\
    group_by(Address.user_id).subquery()

for u, count in session.query(User, stmt.c.address_count).\
    outerjoin(stmt, User.id==stmt.c.user_id).order_by(User.id):
    print(u, count)

# 執(zhí)行結(jié)果
ed None
wendy None
mary None
fred None
jack 2

如果上面的暫時(shí)看不懂,我們先來看看第一個(gè) stmt 的情況械蹋。

from sqlalchemy.sql import func

stmt = session.query(Address.user_id, func.count('*').\
    label('address_count')).\
    group_by(Address.user_id).all()

for i in stmt:
    print(i)

# 執(zhí)行結(jié)果
(5, 2)

可以理解成 group_by() 方法生成了一張新的表出皇,該表有兩列,第一列是 user_id 哗戈,第二列是該 user_id 所擁有的 addresses 的數(shù)量郊艘,這個(gè)值由 func() 跟著的方法產(chǎn)生,我們可以使用 c() 方法來訪問這個(gè)值唯咬。

from sqlalchemy.sql import func

stmt = session.query(Address.user_id, func.count('*').\
    label('address_count')).\
    group_by(Address.user_id).subquery()

q = session.query(User, stmt.c.address_count).\
    outerjoin(stmt, User.id==stmt.c.user_id).order_by(User.id).all()

for i in q:
    print(i)

# 執(zhí)行結(jié)果
(ed, None)
(wendy, None)
(mary, None)
(fred, None)
(jack, 2)

如果不用 outerjoin() 而使用 join()纱注,就等于使用 SQL 中的 INTER JOIN,所得出的表只為兩者交集胆胰,不會(huì)包含 None 值的列狞贱。

from sqlalchemy.sql import func

stmt = session.query(Address.user_id, func.count('*').\
    label('address_count')).\
    group_by(Address.user_id).subquery()

q = session.query(User, stmt.c.address_count).\
    join(stmt, User.id==stmt.c.user_id).order_by(User.id).all()

for i in q:
    print(i)

# 執(zhí)行結(jié)果
(jack, 2)




3.使用別名(aliased)

SQLAlchemy 使用 aliased() 方法表示別名,當(dāng)我們需要把同一張表連接多次的時(shí)候蜀涨,常常需要用到別名瞎嬉。

from sqlalchemy.orm import aliased

# 把 Address 表分別設(shè)置別名
adalias1 = aliased(Address)
adalias2 = aliased(Address)

for username, email1, email2 in \
    session.query(User.name, adalias1.email_address, adalias2.email_address).\
    join(adalias1, User.addresses).\
    join(adalias2, User.addresses).\
    filter(adalias1.email_address=='jack@google.com').\
    filter(adalias2.email_address=='j25@yahoo.com'):
    
    print(username, email1, email2)

# 執(zhí)行結(jié)果
jack jack@google.com j25@yahoo.com

上述代碼查詢同時(shí)擁有兩個(gè)名為:"jack@google.com" 和 "j25@yahoo.com" 郵箱地址的用戶。

別名也可以在子查詢里使用:

from sqlalchemy.orm import aliased

stmt = session.query(Address).\
    filter(Address.email_address != 'j25@yahoo.com').\
    subquery()

adalias = aliased(Address, stmt)

for user, address in session.query(User, adalias).\
    join(adalias, User.addresses):

    print(user)
    print(address)

# 執(zhí)行結(jié)果
jack
jack@google.com




4. EXISTS 關(guān)鍵字

EXISTS 關(guān)鍵字可以在某些場(chǎng)景替代 JOIN 的使用厚柳。

from sqlalchemy.sql import exists

stmt = exists().where(Address.user_id==User.id)

for name, in session.query(User.name).filter(stmt):
    print(name)

# 執(zhí)行結(jié)果
jack

使用 any() 方法也能得到同意的效果:

for name, in session.query(User.name).\
    filter(User.addresses.any()):
    
    print(name)

使用 any() 方法時(shí)也可加上查詢條件:

for name, in session.query(User.name).\
    filter(User.addresses.any(Address.email_address.like('%google%'))):
    print(name)

使用 has() 方法也能起到 JOIN 的作用:

session.query(Address).filter(~Address.user.has(User.name=='jack')).all()

注意:這里的 ~ 符號(hào)是 “不” 的意思氧枣。




關(guān)系運(yùn)算符

1. 等于、不等于
query = session.query(Address)
jack = session.query(User).filter(User.name == 'jack').one()

# 篩選 user 為 jack 的郵箱
query.filter(Address.user == jack)

# 篩選 user 不為 jack 的郵箱
query.filter(Address.user != jack)
2. 為空别垮、不為空
# 篩選 user 為空的郵箱
query.filter(Address.user == None)

# 篩選 user 不為空的郵箱
query.filter(Address.user != None)
3. 包含
query = session.query(User)
address = session.query(Address).filter(Address.id == 1).one()

# 篩選包含某地址的用戶
query.filter(User.addresses.contains(address))
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末便监,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子宰闰,更是在濱河造成了極大的恐慌茬贵,老刑警劉巖簿透,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異解藻,居然都是意外死亡老充,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門螟左,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啡浊,“玉大人,你說我怎么就攤上這事胶背∠锵” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵钳吟,是天一觀的道長廷粒。 經(jīng)常有香客問我,道長红且,這世上最難降的妖魔是什么坝茎? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮暇番,結(jié)果婚禮上嗤放,老公的妹妹穿的比我還像新娘。我一直安慰自己壁酬,他們只是感情好次酌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著舆乔,像睡著了一般岳服。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蜕煌,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天派阱,我揣著相機(jī)與錄音,去河邊找鬼斜纪。 笑死贫母,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的盒刚。 我是一名探鬼主播腺劣,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼因块!你這毒婦竟也來了橘原?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎趾断,沒想到半個(gè)月后拒名,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芋酌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年增显,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脐帝。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡同云,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出堵腹,到底是詐尸還是另有隱情炸站,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布疚顷,位于F島的核電站旱易,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏荡含。R本人自食惡果不足惜咒唆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望释液。 院中可真熱鬧,春花似錦装处、人聲如沸误债。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寝蹈。三九已至,卻和暖如春登淘,著一層夾襖步出監(jiān)牢的瞬間箫老,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國打工黔州, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留耍鬓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓流妻,卻偏偏與公主長得像牲蜀,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绅这,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容