2011-01-06 38 views
5

Tôi là người mới đến SQLAlchemy ORM và tôi đang đấu tranh để thực hiện các truy vấn phức tạp trên nhiều bảng - các truy vấn mà tôi thấy tương đối dễ thực hiện trong Doctrine DQL.Cách truy vấn nhiều bảng trong SQLAlchemy ORM

Tôi có đối tượng dữ liệu của Thành phố thuộc các quốc gia. Một số Thành phố cũng có ID Quận, nhưng không phải tất cả. Cũng như các khóa chính và khóa ngoài cần thiết, mỗi bản ghi cũng có một text_string_id, liên kết tới một bảng TextStrings lưu trữ tên của Thành phố/Hạt/Quốc gia bằng các ngôn ngữ khác nhau. Bảng TextStrings MySQL trông như thế này:

CREATE TABLE IF NOT EXISTS `text_strings` (
    `id` INT UNSIGNED NOT NULL, 
    `language` VARCHAR(2) NOT NULL, 
    `text_string` varchar(255) NOT NULL, 
    PRIMARY KEY (`id`, `language`) 
) 

Tôi muốn xây dựng một mẩu bánh mì cho mỗi thành phố, có dạng:

country_en_name> city_en_name HOẶC

country_en_name> county_en_name> city_en_name,

tùy thuộc vào việc thuộc tính County có được đặt cho thành phố này hay không. Trong Giáo lý, điều này sẽ tương đối đơn giản:

$query = Doctrine_Query::create() 
       ->select('ci.id, CONCAT(cyts.text_string, \'> \', IF(cots.text_string is not null, CONCAT(cots.text_string, \'> \', \'\'), cits.text_string) as city_breadcrumb') 
       ->from('City ci') 
       ->leftJoin('ci.TextString cits') 
       ->leftJoin('ci.Country cy') 
       ->leftJoin('cy.TextString cyts') 
       ->leftJoin('ci.County co') 
       ->leftJoin('co.TextString cots') 
       ->where('cits.language = ?', 'en') 
       ->andWhere('cyts.language = ?', 'en') 
       ->andWhere('(cots.language = ? OR cots.language is null)', 'en'); 

Với SQLAlchemy ORM, tôi đang đấu tranh để đạt được điều tương tự. Tôi tin rằng tôi đã thiết lập các đối tượng chính xác - theo mẫu ví dụ:

class City(Base): 
    __tablename__ = "cities" 

    id = Column(Integer, primary_key=True) 
    country_id = Column(Integer, ForeignKey('countries.id')) 
    text_string_id = Column(Integer, ForeignKey('text_strings.id')) 
    county_id = Column(Integer, ForeignKey('counties.id')) 

    text_strings = relation(TextString, backref=backref('cards', order_by=id)) 
    country = relation(Country, backref=backref('countries', order_by=id)) 
    county = relation(County, backref=backref('counties', order_by=id)) 

Vấn đề của tôi là trong truy vấn - Tôi đã thử nhiều cách khác nhau để tạo đường dẫn nhưng không có gì có vẻ hiệu quả. Một số quan sát:

Có thể sử dụng những thứ như CONCAT và IF nội tuyến trong truy vấn không phải là rất pythonic (thậm chí có thể với ORM?) - vì vậy tôi đã thử thực hiện các hoạt động này bên ngoài SQLAlchemy, trong một vòng lặp Python của Hồ sơ. Tuy nhiên ở đây tôi đã vật lộn để truy cập vào các lĩnh vực riêng lẻ - ví dụ như người truy cập mô hình dường như không đi sâu cấp n, ví dụ: City.counties.text_strings.language không tồn tại.

Tôi cũng đã thử nghiệm với việc sử dụng các bộ - gần nhất tôi đã có để cho nó hoạt động là thông qua việc tách nó ra thành hai truy vấn:

# For cities without a county 
for city, country in session.query(City, Country).\ 
    filter(Country.id == City.country_id).\ 
    filter(City.county_id == None).all(): 

    if city.text_strings.language == 'en': 
    # etc 

# For cities with a county 
for city, county, country in session.query(City, County, Country).\ 
    filter(and_(City.county_id == County.id, City.country_id == Country.id)).all(): 

    if city.text_strings.language == 'en': 
    # etc 

tôi chia nó ra thành hai truy vấn bởi vì tôi không thể tìm hiểu cách làm cho Suit tham gia tùy chọn chỉ trong một truy vấn. Nhưng cách tiếp cận này tất nhiên là khủng khiếp và tệ hơn, truy vấn thứ hai không hoạt động 100% - nó không tham gia tất cả các city.text_strings khác nhau để lọc tiếp theo.

Vì vậy, tôi bị bối rối! Bất kỳ trợ giúp nào bạn có thể cho tôi đặt tôi trên con đường đúng để thực hiện các loại truy vấn phức tạp này trong SQLAlchemy ORM sẽ được đánh giá cao.

Trả lời

5

Ánh xạ cho Suit không hiện diện nhưng dựa trên truy vấn propel tôi giả sử nó có thuộc tính text_strings.

Phần liên quan của tài liệu SQLAlchemy mô tả bí danh với tham gia là tại địa chỉ:

http://www.sqlalchemy.org/docs/orm/tutorial.html#using-aliases

thế hệ của chức năng này là tại địa chỉ:

http://www.sqlalchemy.org/docs/core/tutorial.html#functions

cyts = aliased(TextString) 
cits = aliased(TextString) 
cots = aliased(TextString) 
cy = aliased(Suit) 
co = aliased(Suit) 

session.query(
      City.id, 
      (
       cyts.text_string + \ 
       '> ' + \ 
       func.if_(cots.text_string!=None, cots.text_string + '> ', cits.text_string) 
      ).label('city_breadcrumb') 
      ).\ 
      outerjoin((cits, City.text_strings)).\ 
      outerjoin((cy, City.country)).\ 
      outerjoin((cyts, cy.text_strings)).\ 
      outerjoin((co, City.county))\ 
      outerjoin((cots, co.text_string)).\ 
      filter(cits.langauge=='en').\ 
      filter(cyts.langauge=='en').\ 
      filter(or_(cots.langauge=='en', cots.language==None)) 

mặc dù tôi sẽ nghĩ một mình heck đơn giản hơn rất nhiều để chỉ nói:

city.text_strings.text_string + " > " + city.country.text_strings.text_string + " > " city.county.text_strings.text_string 

Nếu bạn đặt một mô tả về thành phố, Suit:

class City(object): 
    # ... 
    @property 
    def text_string(self): 
     return self.text_strings.text_string 

sau đó bạn có thể nói city.text_string.

+0

Rất cám ơn Mike vì câu trả lời này - Tôi nên biết sử dụng bí danh! Khi tôi nâng cấp SQLAlchemy của mình lên phiên bản mới hơn, mã của bạn hoạt động tốt. Cuối cùng tôi đã chỉnh sửa mã của bạn một chút - Tôi sẽ dán mã của tôi dưới đây như là một câu trả lời riêng biệt trong trường hợp bất cứ ai muốn xem nó. Một điểm cuối cùng: bạn nói, "Tôi sẽ nghĩ rằng nó đơn giản hơn rất nhiều chỉ để nói: city.text_strings.text_string ..." Tôi đã thử làm một cái gì đó như thế này, nhưng cú pháp này dường như không tôn trọng các outerjoins - tức là thuộc tính text_string dành cho ngôn ngữ == 'de' thay vì ngôn ngữ == 'vi'. Tôi không chắc mình có làm gì sai không! –

0

Chỉ để lưu nội dung, đây là mã tôi đã sử dụng. Câu trả lời của Mike (zzzeek) vẫn là câu trả lời chính xác và dứt khoát bởi vì đây chỉ là một sự thích ứng của anh ấy, đó là bước đột phá đối với tôi.

cits = aliased(TextString) 
cyts = aliased(TextString) 
cots = aliased(TextString) 

for (city_id, country_text, county_text, city_text) in \ 
    session.query(City.id, cyts.text_string, cots.text_string, cits.text_string).\ 
    outerjoin((cits, and_(cits.id==City.text_string_id, cits.language=='en'))).\ 
    outerjoin((County, City.county)).\ 
    outerjoin((cots, and_(cots.id==County.text_string_id, cots.language=='en'))).\ 
    outerjoin((Country, City.country)).\ 
    outerjoin((cyts, and_(cyts.id==Country.text_string_id, cyts.language=='en'))): 

    # Python to construct the breadcrumb, checking county_text for None-ness 
Các vấn đề liên quan