2012-06-05 37 views
7

Tôi muốn tạo thuộc tính ánh xạ của đối tượng được điền từ bảng khác.SQLAlchemy thuộc tính khai báo từ tham gia (thuộc tính duy nhất, không phải toàn bộ đối tượng)

Sử dụng ví dụ tài liệu SQLAlchemy, tôi muốn thực hiện một lĩnh vực user_name tồn tại trên lớp Địa chỉ như vậy mà nó có thể được cả hai dễ dàng truy vấn và dễ dàng truy cập (mà không có một chuyến du lịch vòng hai cơ sở dữ liệu)

Ví dụ, tôi muốn để có thể truy vấn và lọc theo user_nameAddress.query.filter(Address.user_name == 'wcdolphin').first() và cũng truy cập vào user_name thuộc tính của tất cả các đối tượng Địa chỉ, mà không bị phạt hiệu suất, và có nó đúng cách kiên trì viết như được mong đợi của một thuộc tính trong __tablename__

class User(Base): 
    __tablename__ = 'users' 

    id = Column(Integer, primary_key=True) 
    name = Column(String(50)) 
    addresses = relation("Address", backref="user") 

class Address(Base): 
    __tablename__ = 'addresses' 

    id = Column(Integer, primary_key=True) 
    email = Column(String(50)) 
    user_name = Column(Integer, ForeignKey('users.name'))#This line is wrong 

Làm cách nào để thực hiện việc này?

Tôi thấy tài liệu tương đối khó hiểu, vì nó dường như không phù hợp với hầu hết các ví dụ, đặc biệt là các ví dụ Flask-SQLAlchemy.

+0

Chính xác là sai với tài liệu? [Sqlalchemy tutorial] (http://docs.sqlalchemy.org/en/rel_0_7/orm/tutorial.html) bao gồm mọi thứ cho các tác vụ phổ biến. Và các khái niệm nâng cao được tìm thấy trong phần còn lại của tài liệu. – schlamar

Trả lời

6

Bạn có thể thực hiện việc này với một số join trên đối tượng truy vấn, không cần chỉ định trực tiếp thuộc tính này. Vì vậy, mô hình của bạn sẽ trông như thế:

from sqlalchemy import create_engine, Column, Integer, String, ForeignKey 
from sqlalchemy.orm import sessionmaker, relation 
from sqlalchemy.ext.declarative import declarative_base 

Base = declarative_base() 
engine = create_engine('sqlite:///') 
Session = sessionmaker(bind=engine) 

class User(Base): 
    __tablename__ = 'users' 
    id = Column(Integer, primary_key=True) 
    name = Column(String(50)) 
    addresses = relation("Address", backref="user") 

class Address(Base): 
    __tablename__ = 'addresses' 
    id = Column(Integer, primary_key=True) 
    email = Column(String(50)) 
    user_id = Column(Integer, ForeignKey("users.id")) 


Base.metadata.create_all(engine) 

Một truy vấn sau khi địa chỉ bằng cách lọc tên người dùng trông giống như:

>>> session = Session() 
>>> session.add(Address(user=User(name='test'))) 
>>> session.query(Address).join(User).filter(User.name == 'test').first() 
<__main__.Address object at 0x02DB3730> 

Sửa: Như bạn có thể truy cập trực tiếp người sử dụng từ một đối tượng địa chỉ, có không cần trực tiếp tham chiếu thuộc tính cho lớp Địa chỉ:

>>> a = session.query(Address).join(User).filter(User.name == 'test').first() 
>>> a.user.name 
'test' 
+0

Trong khi phần nào đúng, đây không phải là ý định của tôi, vì tôi không thể truy cập thuộc tính user_name của 'Địa chỉ' như tôi đã mô tả trong câu hỏi của mình. Khi viết điều này trong PHP/ORM bằng tay tùy chỉnh, nó là một trường hợp đơn giản để tạo SQL một cách chính xác để chọn User.name từ User .__ table__ và lưu trữ thuộc tính, SQLAlchemy phải hỗ trợ kịch bản này? –

+0

@wcdolphin Đã cập nhật câu trả lời. – schlamar

+0

Rực rỡ, chìa khóa ở đây là phép thuật xảy ra khi bạn tạo backref, nó thực sự 'biết' để cung cấp cho Địa chỉ một đối tượng người dùng từ đó để truy vấn id. –

1

Nếu bạn thực sự muốn Địa chỉ có phiên bản SQL được bật "User.name" mà không cần tham gia một cách rõ ràng, bạn cần phải sử dụng truy vấn con tương quan. Điều này sẽ làm việc trong mọi trường hợp nhưng có xu hướng không hiệu quả ở phía cơ sở dữ liệu (đặc biệt là với MySQL), vì vậy có thể có một hình phạt hiệu suất ở phía SQL so với sử dụng JOIN thường xuyên. Chạy một số xét nghiệm EXPLAIN có thể giúp phân tích mức độ ảnh hưởng của nó.

Ví dụ khác về column_property tương ứng() là http://docs.sqlalchemy.org/en/latest/orm/mapped_sql_expr.html#using-column-property.

Đối với sự kiện "đặt", truy vấn phụ tương ứng đại diện cho thuộc tính chỉ đọc, nhưng sự kiện có thể được sử dụng để chặn thay đổi và áp dụng chúng cho hàng Người dùng chính. Hai phương pháp tiếp cận này được trình bày bên dưới, một phương pháp sử dụng cơ chế bản đồ nhận dạng thông thường, sẽ chịu tải trọng của hàng Người dùng nếu chưa có, hàng khác phát ra UPDATE trực tiếp tới hàng:

from sqlalchemy import * 
from sqlalchemy.orm import * 
from sqlalchemy.ext.declarative import declarative_base 

Base= declarative_base() 

class User(Base): 
    __tablename__ = 'users' 

    id = Column(Integer, primary_key=True) 
    name = Column(String(50)) 
    addresses = relation("Address", backref="user") 

class Address(Base): 
    __tablename__ = 'addresses' 

    id = Column(Integer, primary_key=True) 
    user_id = Column(Integer, ForeignKey('users.id')) 
    email = Column(String(50)) 

Address.user_name = column_property(select([User.name]).where(User.id==Address.id)) 

from sqlalchemy import event 
@event.listens_for(Address.user_name, "set") 
def _set_address_user_name(target, value, oldvalue, initiator): 
    # use ORM identity map + flush 
    target.user.name = value 

    # use direct UPDATE 
    #object_session(target).query(User).with_parent(target).update({'name':value}) 

e = create_engine("sqlite://", echo=True) 
Base.metadata.create_all(e) 

s = Session(e) 
s.add_all([ 
    User(name='u1', addresses=[Address(email='e1'), Address(email='e2')]) 
]) 
s.commit() 

a1 = s.query(Address).filter(Address.user_name=="u1").first() 
assert a1.user_name == "u1" 

a1.user_name = 'u2' 
s.commit() 

a1 = s.query(Address).filter(Address.user_name=="u2").first() 
assert a1.user_name == "u2" 
+0

Có lợi ích gì khi sử dụng phương pháp này không?Một thuộc tính Python chuẩn có vẻ đơn giản hơn (xem câu trả lời của tôi). Hoặc tôi đang thiếu một cái gì đó? (Truy cập nó như là 'address.user.name' là IMO là phiên bản pythonic nhất, mặc dù.) – schlamar

+0

cũng anh ta muốn có thể truy vấn với nó mà không cần gọi join(). Mô hình này là một trong những tôi đã nhấn mạnh rất nhiều trong những ngày đầu của SQLA, và nó vẫn có liên quan nếu bạn cần một cái gì đó giống như một "tổng số đếm". Nhưng để có được một thuộc tính liên quan, vâng không quá nhiều. SQLA thực sự muốn bạn đánh vần hình học của các truy vấn. – zzzeek

Các vấn đề liên quan