2009-08-26 15 views
43

Jestem trochę zdezorientowany dziedziczeniem pod sqlalchemy, do tego stopnia, że ​​nie jestem nawet pewien, jakiego rodzaju dziedziczenia (pojedynczy stół, stół połączony, beton) powinienem tu używać. Mam klasy bazowej z pewnymi informacjami, które są wspólne dla podklas i niektóre dane, które są całkowicie oddzielne. Czasami chcę danych ze wszystkich klas, a czasem tylko z podklas. Oto przykład:Dziedziczenie SQLAlchemy

class Building: 
    def __init__(self, x, y): 
     self.x = x 
     self.y = y 

class Commercial(Building): 
    def __init__(self, x, y, business): 
     Building.__init__(self, x, y) 
     self.business = business 

class Residential(Building): 
    def __init__(self, x, y, numResidents): 
     Building.__init__(self, x, y, layer) 
     self.numResidents = numResidents 

Jak przekonwertować to do SQLAlchemy za pomocą deklaratywnego? Jak zatem zapytać, które budynki mieszczą się w zakresie x>5 i y>3? Albo które budynki mieszkalne mają tylko jednego mieszkańca?

Odpowiedz

68

Wybór sposobu reprezentowania dziedziczenia jest głównie kwestią projektowania bazy danych. W celu zapewnienia wydajności dziedziczenie pojedynczych tabel jest zwykle najlepsze. Z punktu widzenia dobrego projektu bazy danych lepsze jest łączone dziedziczenie tabel. Połączone dziedziczenie tabeli umożliwia posiadanie kluczy obcych do podklas wymuszonych przez bazę danych, o wiele łatwiej jest mieć nie-zerowe ograniczenia dla pól podklasy. Dziedziczenie z betonów jest najgorszym z obu światów.

jednym ustawieniu tabela dziedziczenie z deklaratywnych wygląda następująco:

class Building(Base): 
    __tablename__ = 'building' 
    id = Column(Integer, primary_key=True) 
    building_type = Column(String(32), nullable=False) 
    x = Column(Float, nullable=False) 
    y = Column(Float, nullable=False) 
    __mapper_args__ = {'polymorphic_on': building_type} 

class Commercial(Building): 
    __mapper_args__ = {'polymorphic_identity': 'commercial'} 
    business = Column(String(50)) 

class Residential(Building): 
    __mapper_args__ = {'polymorphic_identity': 'residential'} 
    num_residents = Column(Integer) 

Żeby było dołączył tabeli dziedziczenia, trzeba dodać

__tablename__ = 'commercial' 
id = Column(None, ForeignKey('building.id'), primary_key=True) 

do podklasy.

Zapytania są w większości takie same w obu podejściach:

# buildings that are within x>5 and y>3 
session.query(Building).filter((Building.x > 5) & (Building.y > 3)) 
# Residential buildings that have only 1 resident 
session.query(Residential).filter(Residential.num_residents == 1) 

Aby określić, które pola są ładowane można użyć metody query.with_polymorphic().

Najważniejszą rzeczą do rozważenia na temat dziedziczenia w odniesieniu do tworzenia dat, jest to, czy faktycznie potrzebujesz dziedziczenia, czy też możesz zrobić to za pomocą agregacji. Dziedziczenie będzie uciążliwe, jeśli kiedykolwiek będziesz musiał zmienić typ budynku, lub twoje budynki mogą mieć zarówno aspekty komercyjne, jak i mieszkaniowe. W takich przypadkach zazwyczaj lepiej jest traktować aspekty komercyjne i mieszkaniowe jako obiekty powiązane.

+2

Wow, to jest świetna odpowiedź. Dzięki! Porównywałem więc wydajność między jedną a połączonymi opcjami tabeli i stwierdziłem, że drugie zapytanie [filtr (Residential.num_residents == n) .count()] działa ~ 2x szybciej w scenariuszu z jedną tabelą (zgodnie z oczekiwaniami). Jednak z jakiegoś powodu pierwsze zapytanie dotyczące budynku [filter ((Building.x> x) i (Building.y> y)) count()] jest o 10% wolniejsze w przypadku pojedynczej tabeli, ale faktycznie ładuje wszystkie elementy jest dość porównywalne (.all()). – Noah

+0

Aby uzyskać bardziej szczegółowy problem dotyczący połączonego dziedziczenia tabel, zobacz http: // stackoverflow.com/questions/8389606/how-can-a-sqlalchemy-class-inherit-correct-despite-having-a-tricky-foreignkey-r –

+1

Brakuje tylko przykładu użycia konkretnego dziedziczenia tabel - tego, którego szukam naturalnie pomóc :-) Ponieważ jest to stare pytanie, być może konkretne dziedziczenie tabeli zostało dodane po udzieleniu odpowiedzi na to pytanie. – ThatAintWorking

10

Rozwiązanie AntsAasma jest znacznie bardziej eleganckie, ale jeśli zachowujesz definicje klas oddzielnie od definicji tabeli, musisz mapować swoje klasy do tabel za pomocą funkcji mapper. Po zdefiniowaniu swoich klas, trzeba zdefiniować tabele:

building = Table('building', metadata, 
    Column('id', Integer, primary_key=True), 
    Column('x', Integer), 
    Column('y', Integer), 
) 
commercial = Table('commercial', metadata, 
    Column('building_id', Integer, ForeignKey('building.id'), primary_key=True), 
    Column('business', String(50)), 
) 
residential = Table('residential', metadata, 
    Column('building_id', Integer, ForeignKey('building.id'), primary_key=True), 
    Column('numResidents', Integer), 
)

Następnie można odwzorowywać tablice do klas:

mapper(Building, building) 
mapper(Commercial, commercial, inherits=Building, polymorphic_identity='commercial') 
mapper(Residential, residential, inherits=Building, polymorphic_identity='residential')

Następnie współdziałać z klasami dokładnie ten sam sposób mrówki Aasma opisaną .