ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SQLAlchemy 시작하기 - Part 2
    Python & Flask & Django 2017. 2. 1. 20:01





    리스트와 Scalars 반환하기


    Query 객체의 all(), one(), first() 메소드는 즉시 SQL을 호출하고 non-iterator 값을 반환한다. all()은 리스트를 반환한다.


    query = session.query(User).filter(User.name.like('%air')).order_by(User.id)

    query.all()

    # [<User('haruair', 'JohnJeong123', '1234')>, <User('wendy', 'Wendy Willams', 'fobar')>]



    first()는 첫째를 리밋으로 설정해 scalar로 가져온다.

    query.first()

    #<User('haruair', 'JohnJeong123', '1234')>



    one()은 모든 행을 참조해 식별자를 값으로 가지고 있지 않거나 여러 행이 동일한 값을 가지고 있는 경우 에러를 만든다.

    from sqlalchemy.orm.exc import MultipleResultsFound

    try:

        user = query.one()

    except MultipleResultFound, e:

        print e

    from sqlalchemy.org.exc import NoResultFound

    try:

        user = query.filter(User.id == 99).one()

    except NoResultFound, e:

        print e



    문자로 된 SQL 사용하기


    문자열을 Query와 함께 유연하게 쓸 수 있다. 대부분 메소드는 문자열을 수용한다. 예를 들면 filter()order_by()에서 쓸 수 있다.


    for user in session.query(User).\

            filter("id<224").\

            order_by("id").all():

        print user.name


    연결된 파라미터에서는 콜론을 이용한, 더 세세한 문자열 기반의 SQL를 사용할 수 있다. 값을 사용할 때 param() 메소드를 이용한다.


    session.query(User).filter("id<:value and name=:name").\

        param(value=1234, name='fred').order_by(User.id).one()


    문자열 기반의 일반적인 쿼리를 사용하고 싶다면 from_statement()를 쓴다. 컬럼들은 매퍼에서 선언된 것과 동일하게 써야한다.


    session.query("id", "name", "thenumber12").\

        from_statement("SELECT id, name, 12 as "

                "thenumber12 FROM user WHERE name=:name").\

        pram(name='haruair').all()




    문자열 SQL의 장단점 


    Query로 생성해서 쓰는건 sqlalchemy의 이점인데 그렇게 쓰지 않으면 당연히 안좋아지는 부분이 있다. 직접 쓰면 특정하게 자기가 필요한 결과물을 쉽게 만들어낼 수 있겠지만 Query는 더 이상 SQL 구조에서 아무 의미가 없어지고 새로운 문맥으로 접근할 수 있도록 변환하는 능력이 상실된다.


    예를들어 User 객체를 선택하고 name 컬럼으로 정렬하는데 name 이란 문자열을 쓸 수 있다. 


    q = session.query(User.id, User.name)

    q.order_by("name").all()


    지금은 문제가 없다. Query 를 쓰기 전에 뭔가 멋진 방식을 사용해야 할 때가 있다. 예를 들면 아래처럼 from_self()같은 고급 메소드를 사용해, 사용자 이름의 길이가 다른 경우를 비교할 때가 있다.


    from sqlalchemy import func

    ua = alias(User)

    q = q.from_self(User.id, User.name, ua.name).\

        filter(User.name < ua.name).\

        filter(func.length(ua.name) != func.length(User.name))


    Query는 서브쿼리에서 불러온 것처럼 나타나는데 User는 내부와 외부 양쪽에서 불러오게 된다. 이제 Query 에게 name 으로 정렬하라고 명령하면 어떤 name 을 기준으로 정렬할지 코드로는 예측할 수 없게 된다. 이 경우에는 바깥과 상관없이 aliased된 User를 기준으로 정렬된다.


    q.order_by("name").all()

    # [(3, u'fred', 'u'haruair'), (4, u'haruair', u'mary'), (2, u'mary', u'wendy'), (3, u'fred', u'wendy')]


    User.name 또는 ua.name 같이 SQL 요소를 직접 쓰면 Query가 알 수 있을 만큼 충분한 정보를 제공하기 때문에 어떤 name을 기준으로 정렬해야할지 명확하게 판단하게 도니다.

    그래서 아래 두가지와 같은 차이를 볼 수 있다.


    q.order_by(ua.name).all()

    # [(3, u'fred', 'u'haruair'), (4, u'haruair', u'mary'), (2, u'mary', u'wendy'), (3, u'fred', u'wendy')]


    q.order_by(User.name).all()

    # [(3, u'fred', 'u'haruair'), (3, u'fred', u'wendy'), (4, u'haruair', u'mary'), (2, u'mary', u'wendy')]




    숫자세기


    Query 는 count()라는 숫자를 세는 편리한 메소드를 포함한다. 


    session.query(User).filter(User.name.like('har%')).count()


    count()는 몇개의 행이 반환될지 알려준다. 위 코드로 생성되는 SQL을 살펴보면, SQLAlchemy는 항상 어던 쿼리가 오더라도 거기서 행의 수를 센다. SELECT count(*) FROM table 하면 단순해지지만 최근 버전의 SQLAlchemy는 정확한 SQL로 명시적으로 판단할 수 있는 경우 추측해서 처리하지 않는다.


    숫자를 세야 할 필요가 있는 경우에는 func.count()로 명시적으로 작성하면 된다.


    from sqlalchemy import func

    session.query(func.count(User.name), User.name).group_by(User.name).all()

    # [(1, u'fred'), (1, 'u'JohnJeong'), (1, u'haruair') ]


    SELECT count(*) FROM table 만 사용하고 싶다면


    session.query(func.count('*')).select_from(User).scalar()


    User의 Primary key를 사용하면 select_from 없이 사용할 수 있다.


    session.query(func.count(User.id)).scalar()




    관계(relartionship) 만들기


    이제 User와 관계된, 두번째 테이블을 만들 것이다. 계정당 여러 개의 이메일 주소를 저장할 수 있게 만들 것이다. users 테이블과 연결되는, 일대다 테이블이므로 테이블 이름을 addresses라고 정하고 전에 작성했던 것처럼 Declarative로 address 클래스를 작성한다. 


    from sqlalchemy import 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))


    위 클래스는 ForeignKey를 어떻게 만드는지 보여준다. Column에 직접 넣은 지시자는 이 칼럼의 내용이 대상된 컬럼을 따르도록 만든다. 이 점이 관계 데이터베이스의 주요 특징 중 하나인데 풀과 같은 역할을 해, 연결되지 않은 테이블 사이를 잘 붙여준다. 위에서 작성한 ForeignKey는 addresses.user_id 컬럼이 users.id 컬럼을 따르도록 만든다. 


    두번째 지시자인 relationship() 은 ORM에게 Address 클래스 자체가 User 클래스에 연결되어 있다는 사실을 Address.user 속성을 이요해 알 수 있게 해준다. relationship()은 외래키 연결에서 두 테이블 사이에 Address.use로 다대일 관계임을 결정한다. 


    덧붙여 relationship() 내에서 호출하는 backref()는 역으로 클래스를 이용할 수 있도록, 즉 Address 객체에서 User를 참조할 수 있도록 User.addresses 를 구현한다.  다대일 관계의 반대측은 항상 일대다의 관계이기 때문이다 자세한건 기본 관계 패턴 문서를 참고.


    Address.user와 User.addresses 의 관계는 양방향 관계(bidirectional relationship)로 SQLAlchemy ORM의 주요 특징이다. Backref로 관계 연결하기에서 backref에 대한 자세한 정보를 확인할 수 있다.


    relationship() 을 원격 클래스를 객체가 아닌 문자열로 연결하는 것에 대해 Declarative 시스템에서 사용하는 것으로 문제가 될 수 있지 않나 생각해볼 수 있다. 전부 맵핑이 완료된 경우, 이런 문자열은 파이썬 표현처럼 다뤄지며 실제 아규먼트를 처리하기 위해 사용된다. 위의 경우에선 User 클래스가 그렇다. 이런 이름들은 이것이 만들어지는 동안에만 허용되고 모든 클래스 이름은 기본적으로 선언될 때 사용이 가능해진다. (주. 클래스의 선언이 순차적으로 진행되기 때문에 클래스 선언 이전엔 에러가 나므로 이런 방식을 사용하는 거승로 보인다.)


    아래는 동일하게 "addresses/user" 양방향 관계를 User 대신 Address로 선언한 모습이다.


    class User(Base):

        # ...

        addresses = relationship("Address", order_by="Address.id", backref="user")


    상세한 내용은 relationship()를 참고


    이건 알고 계시나요?  

    - 대부분의 관계형 데이터베이스에선 외래키 제약이 primary key 컬럼이나 Unique 칼럼에만 가능하다.

    - 다중 칼럼 primary key 에서의 외래키 제약은 스스로 다중 칼럼을 가지는데 이를 합성 외래키(composite foreign key)라고 한다. 이 또한 이 컬럼의 서브셋을 레퍼런스로 가질 수 있다.

    - 외래키 컬럼은 연결된 칼럼이나 행의 변화에 자동으로 그들 스스로를 업데이트 한다. 이걸 CASCADE referential action이라고 하는데 관계형 데이터베이스에 내장된 함수다. 

    - 외래키는 스스로의 테이블을 참고할 수 있다. 이걸 자기참조(self-referential) 외래키라고 한다.

    - 외래키에 대해 더 알고 싶다면 위키피디아 외래키 항목을 참고.


    addresses 테이블을 데이터베이스에 생성해야 하므로 metadata로부터 새로운 CREATE를 발행한다. 이미 생성된 테이블을 생략하고 생성한다.


    Base.metadata.create_all(engine) 




    관계된 객체 써먹기


    이제 User를 만들면 빈 address 콜렉션이 나타난다. 딕셔너리나 set같은 다양한 컬렉션이 있는데 기본으로 컬렉션은 파이썬의 리스트다.


    jack = User('jack, 'Jack Bean', sadfjklas')

    jack.addresses # [   ]빈 리스트를 반환


    자유롭게 Adress 객체를 User객체에 넣을 수 있따. 그냥 리스트 사용법과 똑같다.


    jack.addresses = [

        Address(email_address="test@test.com"),

        Address(email_address="test2@test.com")]


    양방향 관계인 경우 자동으로 양쪽으로 접근할 수 있게 된다. 별도의 SQL 없이 양쪽에 on-change events로 동작한다.


    jack.addresses[1]            # <Addresses(emali_address='test@test.com'):>

    jack.addresses[1].user    # <User('jack', 'Jack Bean', 'sadfjklas') >


    데이터베이스에 저장해 보자. User인 Jack Bean을 저장하면 두 Address도 알아서 cascading으로 저장된다.


    session.add(jack)

    session.commit()


    Jack을 쿼리해서 다시 불러보자. 이렇게 Query하면 아직 주소들은 SQL을 호출하지 않은 상태다. 


    jack = session.query(User).\

    filter_by(name='jack').one()

    jack         # <User('jack', 'Jack Bean', 'sadfjklas')>


    하지만 addresses 컬렉션을 호출하는 순간 SQL이 만들어진다. 


    jack.addresses

    # [<Addresss(email_addresss='test@test.com')>, <Address(email_address="test2@test.com')>]


    이렇게 뒤늦게 SQL로 불러오는걸 게으른 불러오기 관계(lazy loading relationship)라고 한다. 이 addresses 는 이제 불러와 평범한 리스트처럼 동작한다. 이렇게 컬렉션을 불러오는 방법을 최적화하는 방법은 나중에 살펴본다. 




    Join과 함께 쿼리하기 


    두 테이블이 있는데 Query의 기능으로 양 테이블을 한번에 가져오는 방법을 살펴볼 것이다. SQL JOIN에 대해 join 하는 방법과 여러가지 좋은 설명이 위키피다에 있으니 참고.


    간단하게 User와 Address 두 테이블을 오나전 조인하는 방법은 Query.filter()로 관계 있는 두 컬럼이 동일한 경우를 찾으면 된다.


    for u, a in session.query(User, Address).\

        filter(User.id == Address=user_id).\

        filter(Address.email_address=='test@test.com').\

        all():

        print u, a

    # <User('jack', 'Jack Bean', 'adfjklas')> <Address('test@test.com')>


    반면 진짜 SQL JOIN 문법을 쓰려면 Query.join()을 쓴다.


    session.query(User).join(Address).\

        filter(Address.email_address=='test@test.com').\

        all()

    # [<User('jack', 'Jack Bean', 'sadfjklas')>]


    Query.join()은 User와 Address 사이에 있는 하나의 외래키를 기준으로 join 한다. 만약 외래키가 없거나 여러개라면 Query.join() 아래같은 방식을 써야한다.


    query.join(Address. User.id==Addresss.user_id)     # 정확한 상태를 적어줌

    query.join(User.addressses)                                     # 명확한 관계 표기 (좌에서 우로)

    query.join(Address, User.addresses)                        # 동일, 명확하게 목표를 정해준

    query.join('addresses')                                              # 동일, 문자열 이용



    외부 join은 outerjoin()을 쓴다.

    query.outerjoin(User.addresses)        # left outer join


    join()이 궁금하면 문서를 참고하자. 어떤 SQL에서든 매우 중요한 기능이다.




    별칭(aliases) 사용하기


    여러 테이블을 쿼리하면 같은 테이블을 여러개 불러와야 할 때가 있는데 그럴 때 동일 테이블명에 별칭(alias)를 지정해 달느 테이블과 문제를 일으키지 않도록 해야한다. Query는 별칭으로 된 녀석들도 잘 알아서 처리해준다. 아래의 코드는 Address  엔티티를 두번 조인해서 한 행에 두 이메일 주소를 가져오도록 하는 예시다.


    from sqlalchemy.orm import 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=="test@test.com').\ 

        filter(adalias2.email_address=="test2@test.com"):

        print username, email1, email2




    서브쿼리 사용하기 


    Query는 서브쿼리 만들 때에도 유용하다. User 객체가 몇개의 Address를 가지고 있는지 알고 싶을 때 서브쿼리는 유용하다. SQL을 만드는 방식으로 생각하면 주소 목록의 수를 사용자 ID를 기준으로 묶은 후 (group by), User 와 join하면 된다. 이 상황에선 LEFT OUTER JOIN이 사용자의 모든 주소를 가져오므로 적합하다. 


    SELECT users.*, adr_count.address_count

    FROM users

    LEFT OUTER JOIN(

            SELECT user_id, count(*) AS address_count

            FROM  address GROUP BY user_id

        ) AS adr_count

        ON users.id = adr_count.user_id


    Query를 사용하면 명령문을 안에서 밖으로 빼내듯 쓸 수 있다. 명령문 접근자는 일반적인 Query를 통해 SQL 표현을 나타내는 명령문을 생성해 반환한다. 이건 select()를 쓰는 거소가 비슷한데 자세한건 SQL 표현 언어 튜터리얼 문서를 참고.


    from sqlalchemy.sql import func

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

        group_by(Address.user_id).subquery()


    func 키워드는 SQL 함수를 만들고 subquery() 메소드는 별칭을 이용해 다른 query에 포함할 수 있는 SELECT 명령문의 형태로 반환해준다. (query.statement.alias()를 줄인 것)


    이렇게 만든 서브쿼리는 Table 처럼 동작한다. 아래 코드를 잘 모르겠다면 튜토리얼 앞부분에서 Table 을 어떻게 다뤘는지 살펴보면 도움이 된다. 여기서는 컬럼에 접근할 때 table.c.컬럼명 으로 접근했떤, 그 방법처럼 사용하낟. 


    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('wendy', 'Wendy Williams', 'foobar')> None

    # <User('mary', 'Mary Contrary', 'xxg527')> None

    # <User('fred', 'Fred Flinstone', 'blar')> None

    # <User('haruair', 'Edward Kim', '1234')> None

    # <User('jack', 'Jack Bean', 'sadfjklas')> 2



    서브쿼리서 엔티티 선택하기
    위에서는 서브쿼리서 컬럼을 가져와서 결과를 만들었다. 만약 서브쿼리가 엔티티를 선택하기 위한 맵이라면 aliased() 로 매핑된 클래스를 서브쿼리를 활용할 수 있다.


    stmt = session.query(Address).\

            filter(Address.email_address != 'test@test.com').\

            subquery()

    adalias = aliased(Address, stmt)

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

            join(adalias, User.addresses):

        print user, address

    # <User('jack', 'Jack Bean', 'sadfjklas'> <Address('test@test.com')>




    EXISTS 사용하기 
    SQL에서 EXISTS 키워드는 불린 연산자로 조건에 맞는 행이 있으면 True 를 반환한다. 이건 많은 시나리오에서 join을 위해 쓰는데, join에서 관계 테이블서 적합한 값이 없는 행을 처리하는데에도 유용하다. 


    외부 EXISTS는 이런 방식으로 할 수 있다.


    from sqlalchemy.sql import exists 

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

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

        print name

    # jack 


    Query의 기능 중 몇가지 연산자에서는 EXISTS를 자동으로 사용하낟. 위 같은 경우는 User.addresses 관계에 any()를 사용하면 가능하다.


    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_addresss.like('%gmail%'))):

        print name

    # jack 


    has()도 any()와 동일한 기능을 하는데 대신 다대일 관계에서 사용한다. (~연산자는 NOT 이란 뜻이다.)


    session.query(Address).\

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

    # []  




    일반 관계 연산자


    관계 (relationship)에서 사용할 수 있는 모든 연산자인데 각각 API 문서에서 더 자세한 내용을 볼 수있다.


    __eq__() 다대일에서의 equals 비교

    query.filter(Address.user == someuser)


    __ne__() 다대일에서의 not equals 비교

    query.filter(Addresss.user != someuser)


    IS NULL 다대일 비교(__eq__())

    query.filter(Addresss.user == None)


    contains() 일대다 컬렉션에서 사용

    query.filter(User.addresses.contains(someaddress))


    any() 컬렉션에서 사용

    query.filter(User.addresses.any(Address.email_address == 'bar'))

    # 키워드, 아규먼트도 받음

    query.filter(User.addresses.any(email_address='bar'))


    has() scalar 레퍼런스에서 사용

    query.filter(Address.user.has(name='eq'))


    Query.with_parent() 어떤 관계서든 사용

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




    선행 로딩 (Eager Loading)


    lazy loading의 반대 개념으로 User.addresses 를 User 호출할 때 바로 불러오도록 하는 방법이다. eager loading으로 바로 불러오면 쿼리 호출의 수를 줄일 수 있다.

    SQLAlchemy는 자동화와 사용자정의 기준을 포함해 3가지 타입의 선행 로딩(eager loading)를 제공한다. 3가지 모두 query options로 제어하는데 Query에 불러올 때 Query.options() 메소드를 통해 쓸 수 있다.



    서브쿼리 로딩

    선행로딩하도록 User.addresses 에 표기하는 방법이다. orm.subqueryload()를 이용해서 서브쿼리를 불러올 때 한번에 연계해 불러오도록 처리한다. 기존의 서브쿼리는 재사용이 가능한 형태이지만 이것은 바로 Query를 거쳐 선택되기 때문에 관계된 테이블을 선택하는 것과 상고나없이 서브쿼리가 동작한다. 복잡해 보이지만 아주 쉽게 쓸 수 있다.


    from sqlalchemy.orm import subqueryload

    jack = session.query(User).\

            options(subqueryload(User.addresses)).\

            filter_by(name='jack').one()

    jack

    # <User('jack', 'Jack Bean', 'sadfjklas')>

    jack.address

    # <Address('test@test.com')>, <Address('test2@test.com')>]



    연결된 로딩(Joined Load)

    또 다른 자동 선행로딩 함수로 orm.joinedload()가 있다. join 할 때 사용할 수 있는 방법이나 관계된 객체나 컬렉션을 불러올 때 한번에 불러올 수 있다. (LEFT OUTER JOIN이 기본값) 앞서의 addressses를 동일한 방법으로 불러올 수 있다.


    from sqlalchemy.orm import joinedload

    jack = session.query(User).\

            options(joinedload(User.addresses)).\

            filter_by(name='jack').one()

    jack

    # <User('jack', 'Jack Bean', 'sadfjklas')>

    jack.addresses 

    # [<Address('test@test.com')>, <Address('test2@test.com')>]


    사실 OUTER JOIN 결과라면 두 행이 나타나야 하는데 여전히 User 하나만 얻을 수 있다. 이유는 Query는 엔티티를 반환할 때 객체 유일성을 위해 "유일하게 하기(uniquing)" 전략을 취한다.


    joinedload()는 오랫동안 써왔지만 subqueryload() 메소드가 더 새로운 형태의 선행로딩 형태다. 둘 다 한 행을 기준으로 관계된 객체를 가져오는 것은 동일하지만 subqueryload()는 적합한 관계 컬렉션을 가져오기에 적합하고 반면 joinedload()가 다대일 관계에 적합하다. 


    joinedload()는 join()의 대체재가 아니다 

    joinedload()으로  join을 생성하면 익명으로 aliased 되어 쿼리 결과에 영향을 미치지 않는다. Query.order_by()나 Query.filter() 호출로 이런 aliased 된 테이블을 참조할 수 없기 때문에 사용자 공간에서는 Query.join()을 사용해야 한다.

    joinedload()은 단지 관계된 객체 또는 콜렉션의 최적화된 내역을 불러오기 위해 사용하는 용도이기 때문에 추가하거나 제거해도 실제 결과엔 영향을 미치지 않는다. 더 궁금하면 선행로딩의 도를 참고.




    명시적 Join + 선행로딩


    세번째 스타일의 선행 로딩은 명시적 Join이 primary 행에 위치했을 때 추가적인 테이블에 관계된 객체나 컬렉션을 불러온다. 이 기능은 orm.contains_eager()를 통해 제공되는데 다대일 객체를 미리 불러와 동일 객체에 필터링할 경우에 유용하게 사용된다. 아래는 Address행에 연관된 User 객체를 가져오는 코드인데 "jack"이란 이름의 User를 orm.contains_eager()를 사용해 user 칼럼을 Address.user 속성으로 선행로딩한다.


    from sqlalchemy.orm import contains_eager

    jack_addresses = session.query(Address).\

        join(Address.user).\

        filter(User.name=='jack').\

        options(contains_eager(Address.user)).\

        all()

    jack_addresses

    # [<Address('test@test.com')>, <Address('test2@test.com')>]

    jack_address[0].user

    # <User('jack', 'Jack Bean', 'sadfjklas')>




    삭제하기


    jack을 삭제해보자. 삭제하고나면 count는 남은 행이 업삳고 표시한다.


    session.delete(jack)

    session.query(User).filter_by(name='jack').count()


    여기까진 좋다. Address객체는 어떤지 보자.


    session.query(Address).filter(

        Address.email_address.in_(['test@test.com', test2@test.com'])

    ).count()

    # 2


    여전히 남아있다. SQL 을 확인해보면 해당 Address의 user_id 칼럼은 모두 NULL로 되어 있지만 삭제되진 않았다. SQLAlchemy는 제거를 종속적으로(cascade) 하지 않는데 필요로 한다면 그렇게 할 수 있다.




    삭제/삭제-외톨이 종속처리 설정하기 


    cascade 옵션을 변경하기 위해서는 User.addresses의 관계에서 행동을 변경시켜야 한다. SQLAlchemy는 새 속성을 추가하는 것과 관계를 맵핑하는 것은 언제나 허용되지만 이 경우에는 존재하는 관계를 제거하는게 필요하므로 맵핑을 완전히 새로 시작해야 한다. 먼저 Session을 닫는다.


    session.close()


    그리고 새  declarative_base()를 사용하낟.


    Base = declarative_base()


    다음으로 User클래스를 선언하고 addresses 관계를 종속처리 설정과 함께 추가한다.


    class User(Base):

        __tablename__ = 'users'

        id = Column(Integer, primary_key=True)

        name = Column(String)

        fullname = Column(String)

        password = Column(String)

        addresses = relationship("Address", backref='user', cascade="all, delete, delete-orphan")


    그리고 Address도 다시 생성한다. 이 경우에는 이미 User에서 관계를 생성했기 때문에 Address.user는 따로 생성할 필요가 없다. 


    class Address(Base):

        __tablename__ = 'addresses'

        id = Column(Integer, primary_key=True)

        email_address = Column(String, nullable=False)

        user_id = Column(Integer, ForeignKey('users.id'))

        

    이제 Jack을 불러오고 삭제하면 Jack의 adresses 컬랙션은 Address에서 삭제도니다.


    jack = session.query(User).get(5)

    del jack.addresses[1]

    session.query(Address).filter(

        Address.email_address.in_(['test@test.com', 'test2@test.com'])

    ).count()

    # 1


    Jack을 지우면 Jack과 남은 Address도 삭제된다.


    session.delete(jack)

    session.query(User).filter_by(name='jack').count()

    # 0

    session.query(Address).filter(

        Address.email_address.in_(['test@test.com', 'test2@test.com'])

    ).count()

    # 0


    종속처리(cascade)에 대해 

    종속처리에 대한 더 자세한 설정은 Cascades 문서를 참고, 종속처리는 함수적으로 관련된 데이터베이스가 자연스럽게 ON  DELETE CASCADE 될 수 있도록 통합할 수 있다. 



    'Python & Flask & Django' 카테고리의 다른 글

    Python Garbage Collection  (0) 2017.02.10
    SQLAlchemy Tutorial  (0) 2017.02.02
    SQLAlchemy 시작하기 - Part 1  (0) 2017.01.31
    Python public, private, protected  (0) 2017.01.25
    WSGI (Web Server Gateway Interface)  (0) 2017.01.19
Designed by Tistory.