1. 程式人生 > >SQLAlchemy技術文檔(中文版)-下

SQLAlchemy技術文檔(中文版)-下

span lan sub 映射 可變 使用 自己 lang 優化

10.建立聯系(外鍵)

是時候考慮怎樣映射和查詢一個和Users表關聯的第二張表了。假設我們系統的用戶可以存儲任意數量的email地址。我們需要定義一個新表AddressUser相關聯。

from sqlalchemyimport ForeignKey

from sqlalchemy.orm import relationship, backref
class Address(Base):
__tablename__ = addresses
id= Column(Integer, primary_key=True)
email_address = Column(String, nullable=False)
user_id 
= Column(Integer, ForeignKey(users.id)) user = relationship("User", backref=backref(addresses,order_by=id)) def __repr__(self): return"<Address(email_address=‘%s‘)>"%self.email_address

構造類和外鍵簡單,就不過多贅述。主要說明以下relationship()函數:這個函數告訴ORMAddress類應該和User類連接起來,通過使用addresses.userrelationship()

使用外鍵明確這兩張表的關系。決定Adderess.user屬性是多對一的。relationship()的子函數backref()提供表達反向關系的細節:relationship()對象的集合被User.address引用。多對一的反向關系總是一對多。更多的細節參考Basic RelRational Patterns

這兩個互補關系:Address.userUser.addresses被稱為雙向關系。這是SQLAlchemy ORM的一個非常關鍵的功能。更多關系backref的細節參見Linking Relationships with Backref

假設聲明的方法已經開始使用,relationship()

中和其他類關聯的參數可以通過strings指定。在上文的User類中,一旦所有映射成功,為了產生實際的參數,這些字符串會被當做Python的表達式。下面是一個在User類中創建雙向聯系的例子:

class User(Base):
addresses = relationship("Address", order_by="Address.id", backref="user")

一些知識:

在大多數的外鍵約束(盡管不是所有的)關系數據庫只能鏈接到一個主鍵列,或具有唯一約束的列。

外鍵約束如果是指向多個列的主鍵,並且它本身也具有多列,這種被稱為“復合外鍵”。

外鍵列可以自動更新自己來相應它所引用的行或者列。這被稱為級聯,是一種建立在關系數據庫的功能。

外鍵可以參考自己的表格。這種被稱為“自引”外鍵。

我們需要在數據庫中創建一個addresses表,所以我們會創建另一個元數據,這將會跳過已經創建的表。

11.操作主外鍵關聯的對象

現在我們已經在User類中創建了一個空的addresser集合,可變集合類型,例如setdict,都可以用,但是默認的集合類型是list

jack = User(name=jack, fullname=Jack Bean, password=gjffdd)
jack.addresses
[]

現在可以直接在User對象中添加Address對象。只需要指定一個完整的列表:

jack.addresses = [Address(email_address=[email protected]),Address(email_address=[email protected])]

當使用雙向關系時,元素在一個類中被添加後便會自動在另一個類中添加。這種行為發生在Python的更改事件屬性中而不是用SQL語句:
>>> jack.addresses[1]
<Address(email_address=[email protected])>
>>> jack.addresses[1].user
<User(name=jack, fullname=Jack Bean, password=gjffdd)>

jack提交到數據庫中,再次查詢Jack,(No SQL is yet issued for Jack’s addresses:)這句實在是翻譯不了了,看看代碼就明白是什麽意思:
>>> jack = session.query(User)....
filter_by(name=jack).one()

>>> jack
<User(name=jack,fullname=Jack Bean, password=gjffdd)>


>>>jack.addresses 
[<Address(email_address=[email protected])>,
<Address(email_address=[email protected])>]

當我們訪問uaddresses集合時,SQL會被突然執行,這是一個延遲加載(lazy loading)關系的典型例子。現在addresses集合加載完成並且可以像對待普通列表一樣對其進行操作。以後我們會優化這種加載方式。
12.使用JOINS查詢
現在我們有了兩張表,可以進行更多的查詢操作,特別是怎樣對兩張表同時進行查詢,Wikipediapage on SQL JOIN提供了很詳細的說明,其中一些我們將在這裏說明。之前用Query.filter()時,我們已經用過JOIN了,

filter是一種簡單的隱式join:
>>>for u, a in session.query(User, Address).filter(User.id==Address.user_id).filter(Address.email_address==[email protected]).all():   
    print u
    print a
<User(name=jack,fullname=JackBean, password=gjffdd)>
<Address(email_address=[email protected])>

Query.join()方法會更加簡單:
>>>session.query(User).join(Address)....
    filter(Address.email_address==[email protected])....
    all() 
[<User(name=jack,fullname=JackBean, password=gjffdd)>]

之所以Query.join()知道怎麽join兩張表是因為它們之間只有一個外鍵。如果兩張表中沒有外鍵或者有一個以上的外鍵,當下列幾種形式使用的時候,Query.join()可以表現的更好:
query.join(Address,User.id==Address.user_id)# 明確的條件
query.join(User.addresses)# 指定從左到右的關系
query.join(Address,User.addresses)    #同樣,有明確的目標
query.join(addresses) # 同樣,使用字符串
    outerjoin()和join()用法相同
query.outerjoin(User.addresses)# LEFT OUTER JOIN

12.1使用別名
當在多個表中查詢時,如果同一張表需要被引用好幾次,SQL通常要求對這個表起一個別名,因此,SQL可以區分對這個表進行的其他操作。Query也支持別名的操作。下面我們joinAddress實體兩次,找到同時擁有兩個不同email的用戶:
>>>from sqlalchemy.ormimport aliased
>>>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==[email protected])....
    filter(adalias2.email_address==[email protected]):
...
    print username, email1,
email2      
jack
[email protected] [email protected]

12.1使用子查詢(暫時理解不了啊,多看代碼研究吧:()
from sqlalchemy.sqlimport 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
<User(name=ed,fullname=EdJones, password=f8s7ccs)>
None
<User(name=wendy,fullname=Wendy Williams, password=foobar)>
None
<User(name=mary,fullname=Mary Contrary, password=xxg527)>
None
<User(name=fred,fullname=Fred Flinstone, password=blah)>
None
<User(name=jack,fullname=Jack Bean, password=gjffdd)>
2

12.2從子查詢中選擇實體?
上面的代碼中我們只返回了包含子查詢的一個列的結果。如果想要子查詢映射到一個實體的話,使用aliased()設置一個要映射類的子查詢別名:
>>>
stmt = session.query(Address).\
...
     filter(Address.email_address!= ‘[email protected]‘).\
...
     subquery()
>>>
adalias = aliased(Address, stmt)
#?為什麽有兩個參數?
>>>
for user, address in session.query(User, adalias).\
...
        join(adalias, User.addresses): 
...
    print user
...
    print address
<User(name=‘jack‘,fullname=‘Jack Bean‘, password=‘gjffdd‘)>
<Address(email_address=‘[email protected]‘)>

12.3使用EXISTS(存在?)

如果表達式返回任何行EXISTS為真,這是一個布爾值。它可以用在jions中,也可以用來定位在一個關系表中沒有相應行的情況:

>>>from sqlalchemy.sqlimport exists
>>>
stmt = exists().where(Address.user_id==User.id)
>>>for name, in session.query(User.name).filter(stmt):
    print name
jack

等價於:

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

any()限制行匹配:

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

has()any()一樣在應對多對一關系的情況下(註意“~“意味著”NOT”

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

[]

12.4 常見的關系運算符

== = None 都是用在多對一中,而contains()用在一對多的集合中:

query.filter(Address.user == someuser)
query.filter(User.addresses.contains(someaddress))

Any()(用於集合中):

query.filter(User.addresses.any(Address.email_address == bar))#also takes keyword arguments:
query.filter(User.addresses.any(email_address=bar))

has()(用在標量?不在集合中):

query.filter(Address.user.has(name=ed))

Query.with_parent()(所有關系都適用):

session.query(Address).with_parent(someuser,addresses)

SQLAlchemy技術文檔(中文版)-下