SQLAlchemy를 쓰면서 가장 혼란스러운 것 중 하나가 'Query 객체는 도대체 언제 SELECT 쿼리를 수행하는가?'였다. 마주치게 되는 상황은 이렇다.
query.all()
로 SELECT의 결과를 모두 가져올 수 있다고 한다. 이는 전형적인 반복자(iterator)로,for row in query.all()
처럼 for문에서 사용할 수 있다.- 그런데 그냥 query 자체를 for문에 넣는 예제도 보인다.
for row in query
를 해도 문제가 없다. - LIMIT 쿼리를 어떻게 표현하는지 검색했더니, 그냥 슬라이싱하라고 한다. 그래서
for row in query[:10].all()
로 했더니 에러가 발생하고,for row in query[:10]
처럼 표현하니 제대로 동작한다.
그냥 SQLAlchemy의 소스 코드를 GitHub에서 살펴보면, 그 비밀을 알 수 있다. sqlalchemy.orm.query.py 모듈을 보면 되고, 아래는 그들 중 일부를 가져온 snippet이다.
__iter__
Query 객체는 __iter__
메소드에서 실제로 SELECT를 수행한다고 요약할 수 있다. __iter__
메소드가 호출되는 타이밍 중 몇개를 예로 들면,
- iter 함수에 전달되었을 때
- for문에 사용되었을 때(for문은 in 우측에 전달된 객체를 iter 함수에 전달하므로)
- list 함수에 전달되었을 때
그럼 의문이 조금 풀린다.
for row in query
: for문에 의해 간접적으로__iter__
가 호출되므로 잘 동작한다.for row in query.all()
: 위 snippet을 보면 알 수 있듯,list(self)
의 반환을 iteration하므로 잘 동작한다.
__getitem__
__getitem__
메소드는 인덱싱과 슬라이싱 연산을 오버라이딩한다. Query.__getitem__
메소드는
- 슬라이스를 받았다면 현재 Query 객체에
LIMIT
쿼리를 추가하는self.slice
메소드를 호출한 후, 이를 통해 LIMIT이 반영된 Query 객체의 SELECT 결과를 반환하거나 - 인덱스를 받았고 그 인덱스가 -1이라면
list(self)[-1]
을 반환하고, 아니라면list(self[item:item + 1])[0]
으로__getitem__
을 재귀호출하여 해당 인덱스에 대한 row 하나만을 가져오도록 LIMIT 쿼리를 수행해서 경제적으로 row를 반환한다. 예를 들어query[15]
에 대해서는,LIMIT 15, 1
쿼리가 추가된다.
따라서,
for row in query[5:8]
:__getitem__
에 슬라이스가 전달되고,LIMIT 5, 3
쿼리가 추가된 Query 객체의 iterator가 반환된다.row = query[10]
:__getitem__
에 인덱스가 전달되고,LIMIT 10, 1
쿼리가 추가된 Query 객체의 결과 중 0번째 인덱스가 반환된다.
'Python 계열 > SQLAlchemy' 카테고리의 다른 글
Column.like, Column.ilike, not_, ~expr (0) | 2019.02.12 |
---|---|
특정 컬럼만 SELECT (0) | 2019.02.12 |
aliasing과 함수 (0) | 2019.02.12 |
limit (0) | 2019.02.12 |
Multiple Primary Key (0) | 2018.10.18 |