SQLAlchemy ORM in Depth

rang-dev·2020년 3월 17일
0

Overview

  • SQLAlchemy와 SQLAlchemy ORM은 파이썬 환경에서 모델을 만들거나 상호작용하기 위해 사용하는 가장 인기있는 라이브러리 중 하나이다.
  • It's a very well-developed library that offers flexibility and versatility(ability to adapt or be adapted to many different functions or activities) and how we can work with models in our web applications.

Model.query

  • Query는 SQLAlchemy에서 생성되는 모든 SELECT문의 근원지이다.
  • SQLAlchemy application에서 정의했던 model에 존재하는 object이다.

다양한 쿼리 메소드들

  • Person.query.filter_by(name='Amy)
    • SELECT * FROM Person WHERE name = 'Amy';과 동일
  • Person.query.all()
    • SELECT * FROM Person;
  • Person.query.count()
    • SQL에서의 Group by count와 동일
  • Person.query.filter(Person.name == 'Amy')
    • .filter_by()보다 더 flexible한 방법
    • 모델의 attribute를 결정할 수 있다. 이것은 조인 모델에서 쿼리를 할 때 다양한 모델들 중에서 attribute를 골라서 필터를 할 수 있는 장점이 있다.
    • +)common filter operators
  • Person.query.filter(Person.name == 'Amy', Tema.name == 'Udacity')
    • 모델의 attribute의 이름을 사용할 수도 있고 다른 테이블의 모델을 이용 할 수도 있다.
  • Person.query.get(1)
    • 기본키(primary key)로 object를 얻는다.(get(model_id))

Bulk operations

Product.query.filter_by(category='Misc').delete()

bulk operation이란 large scale로 수행되는 action들을 말한다.

위의 코드는 카테고리가 'Misc'인 모든 레코드들을 한꺼번에 삭제한다.

Ordering

  • order by
MyModel.order_by(MyModel.created_at)
MyMode.order_by(db.desc(MyModel.created_at))   # db.desc: descending order
  • limit
Order.query.limit(100).all()    # limit(max_num_rows)

Model.query method chaining

query의 메소드들은 first()all(), count(), delete()처럼 non-query object를 리턴하는 terminal method를 사용하기 전까지는 무한으로 계속 엮일 수 있다.

Person.query.filter(Person.name == 'Amy').frist()도 가능하고 Person.query.filter(Person.name == 'Amy').flter(Tema.name == 'Udacity').first()도 가능하다. SQL문에서 여러개의 WHERE절이 함께 쓰이는 것과 같은 의미이다.

method chaining은 join이나 join을 해야할때 필수적이다.

Driver.query.join('vehicles').filter_by(driver_id=3).all()는 Driver table과 Vehicle table을 조인하고 driver_id=3인 레코드들을 리턴한다.


Question. SQL에서는 조인할때 ON절이 필요하지만 여기서는 에트리뷰트 이름만 써준다. 그럼 기준키를 어떻게 하는걸까?

  • Query API에 의하면

    Consider a mapping between two classes User and Address, with a relationship User.addresses representing a collection of Address objects associated with each User. The most common usage of join() is to create a JOIN along this relationship, using the User.addresses attribute as an indicator for how this should occur:

    q = session.query(User).join(User.addresses)

내가 이해한 게 맞다면 SQL에서는 ON절을 사용해서 테이블끼리 연결이 되지만 query에서는 만약 User라는 테이블과 Address라는 테이블이 있을 때, User 테이블 안에 user와 관련된 주소가 해당 에트리뷰트(addresses) 내에 존재한다면 (Address 테이블의 주소에 들어간 내용과 같겠지) 자동적으로 ON user.id = address.user_id 처리가 된다. 또한 User에서만 선택할 거라면 .join("addresses")와 같이 클래스명을 제외하고 string name만 적어도 된다. ~~SQL으로만 생각하다가 이렇게 이해하려니 뭔가 내가 잘 이해하고 있는건지 잘 모르겠다. 계속 읽어보니 addresses -> Address 이렇게 이름으로 찾아가는게 아닌가 싶다.~~ ==> 이 내용에 대해 멘토에게 물어보니 다음과 같은 대답을 얻었다.

If it’s the prior then you can call it(attribute) whatever you want as long as the model is linked to it. --> relationship

Here are two models Driver and Vehicle, so if you want to join them together and access the data, it should look something like this:

class Driver(db.Model):
    __tablename__ = 'driver'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
ride = db.relationship('Vehicle')
class Vehicle(db.Model):
    __tablename__ = 'vehicle'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
drivers = Driver.query.join(Vehicle).all()
for driver in drivers:
    for ride in driver:
        print(ride.name)

Model을 만들때 relationship()을 통해 애트리뷰트와 클래스를 연결했었기 때문에 바로 join이 가능했던 것이었다. relationship이 무슨 의미인지 파악하고 나니 위의 Query API에 있는 설명이 이해가 되었다.

foriegn key가 설정된 경우에는 두 테이블 명을 적어서 join하면 된다. 설정하지 않은 채로 아래와 같이 실행한다면 오류가 난다.

q = session.query(User).join(Address)

모델의 쿼리 오브젝트에 접근하는 두가지 방식

  • Person.query
  • db.session.query(Person)

The reason why we might want to do it this way instead of using an individual model is that every once in ah while you may want to do a joint query instead. --> session.query(Person).join(Team)

So since session.query is agnostic(=>~와 관계없는, 종속적이지 않은) to the type of model that you pass in, you can do things like session.query of person.join on another model Team.

--> 잘 이해가 안됨...

+) SQLALchemy Query API

Practice

Let's say you created a Users tale with a name attribute.
1. Implement a query to filter all users by name 'Bob'.

User.query.filter_by(name='Bob').all()
  1. Implement a LIKE query to filter the users for records with a name that includes the letter "b".
User.query.filter(User.name.like('%b%')).all()
  1. Return only the first 5 records of the query above.
User.query.limit(5).all()
  1. Re-implement the Like query using case-insesitive search.
User.query.filter(or_(User.name.like('%B%'), User.name.like('%b%'))).all()
  1. Return the number of records of users with name 'Bob'.
User.query.filter_by(name='Bob').count()

SQLALchemy Object Lifecycle

Stages

  1. Transient
  • 객체는 생성되었지만 아직 세션에 포함되지 않았다.
  1. Pending
    (awaiting decision or settlement - 미결정의, 대기중인)
  • Sesssion object에 포함됨
  • session.rollback()을 통해 undo가능
  • 나중에 execute하고 싶은 INSERT, UPDATE, DELETE관련 트랜잭션 발생
  • flush가 발생하기 전까지는 pending 상태
  1. Flushed
  • 데이터베이스에 커밋될 준비를 함
  • Engine을 위해 액션들을 SQL command statements로 변환함
  1. Committed
  • 영구적으로 데이터베이스에 적용됨
  • 세션의 트랜젝션은 빈 상태로 초기화됨

Flush가 발생하는 경우

  • Query를 호출할 때
    • 이 경우에는 db를 확인했을때 변화가 적용되지 않은 것을 확인 할 수 있다. commit이 필요하다.
    • Question: 예제에서 Person.query는 flush가 발생하지 않는다고 했는데 이것도 Query를 호출한 것 같은데 왜 flush가 일어나지 않는걸까?
      • Person.query는 object에 불과하다(Person.query()처럼 사용할 수 없음). 강의에서는 설명이 부족했지만 Query를 호출할 때는 BaseQuery에 사용 가능한 메소드(like .all())를 이용할 때를 의미한 것이다.
  • db.session.commit() --> flush & commit


    When a statement has been flushed already, SQLAlchemy knows not to do the work again of translating actions to SQL statements.

lifecycle

profile
지금 있는 곳에서, 내가 가진 것으로, 할 수 있는 일을 하기 🐢

0개의 댓글