데이터 필터링 - WHERE
데이터 필터링 - WHERE (Filter Data - WHERE)
이전 장에서 우리는 데이터베이스에서 데이터를 SELECT(조회)하는 법을 봤어요.
순수한 SQL로도, SQLModel로도 해봤죠.
하지만 항상 모든 행, 테이블 전체를 가져왔어요.
| id | name | secret_name | age |
|---|---|---|---|
| 1 | Deadpond | Dive Wilson | null |
| 2 | Spider-Boy | Pedro Parqueador | null |
| 3 | Rusty-Man | Tommy Sharp | 48 |
대부분의 경우 우리는 행 하나만, 또는 일부 행만 가져오길 원할 거예요.
이제 그걸 어떻게 하는지 볼게요. 조건이 참(where) 인 행만 골라내도록 데이터를 필터링하는 거죠.
출처: 공식문서
이전 코드에서 이어가기
지금까지 챕터들에서 데이터를 만들고 조회할 때 쓰던 예제를 그대로 이어서 쓸게요.
이제 select_heroes()를 데이터를 필터링하도록 수정할 거예요.
👀 전체 파일 미리 보기
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
secret_name: str
age: int | None = None
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def create_heroes():
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
with Session(engine) as session:
session.add(hero_1)
session.add(hero_2)
session.add(hero_3)
session.commit()
def select_heroes():
with Session(engine) as session:
statement = select(Hero)
results = session.exec(statement)
for hero in results:
print(hero)
def main():
create_db_and_tables()
create_heroes()
select_heroes()
if __name__ == "__main__":
main()
혹시 앞선 예제를 이미 실행해서 데이터가 있는 데이터베이스가 있다면, 각 예제를 실행하기 전에 데이터베이스 파일을 지워주세요. 그래야 데이터가 중복되지 않고 같은 결과를 얻을 수 있어요.
SQL로 데이터 필터링하기
먼저 SQL에서 WHERE 키워드로 데이터를 필터링하는 법을 볼게요.
SELECT id, name, secret_name, age
FROM hero
WHERE name = "Deadpond"
첫 부분은 아까와 똑같은 뜻이에요:
안녕 SQL 데이터베이스 👋, 나를 위해 데이터를 좀
SELECT(조회)해 줘.먼저 원하는 열을 알려줄게:
idnamesecret_nameage그리고 이 열들을
"hero"라는 테이블에서FROM(가져와) 줘.
그다음 WHERE 키워드가 이렇게 덧붙여져요:
자 SQL 데이터베이스, 어떤 열을
SELECT하고 어디서FROM할지 이미 알려줬잖아. 그런데 모든 행을 가져오지 말고,name열이"Deadpond"인 행만 가져와 줘.WHERE그name열이"Deadpond"인 행만 말이야.
그러면 데이터베이스가 이런 테이블을 가져와요.
| id | name | secret_name | age |
|---|---|---|---|
| 1 | Deadpond | Dive Wilson | null |
!!! tip "팁"
결과가 행 하나뿐이라도, 데이터베이스는 항상 **테이블**을 반환해요.
이 경우엔 행이 하나뿐인 테이블이죠.
DB Browser for SQLite에서 직접 시도해 볼 수 있어요.

WHERE와 FROM는 "절(clause)"이에요
SELECT(또는 다른 키워드) 뒤에 오는 WHERE나 FROM 같은 몇몇 키워드·구간에는 기술적인 이름이 있어요. 절(clause) 이라고 불러요.
자기만의 SQL 키워드를 가진 다른 절들도 있어요.
여기서 절이라는 용어를 많이 쓰진 않을게요. 하지만 알아두면 좋아요. 나중에 공부할 다른 튜토리얼에도 자주 나올 테니까요. 🤓
SELECT와 WHERE
생각을 정리하는 데 도움 되는 짧은 팁을 드릴게요.
SELECT는 SQL 데이터베이스에게 어떤 열을 반환할지 알려줘요.WHERE는 SQL 데이터베이스에게 어떤 행을 반환할지 알려줘요.
테이블의 두 차원(가로·세로) 크기는 대부분 이 두 키워드에 달려 있어요.
SELECT 영역
열이 너무 많거나 적으면, 그건 SELECT 부분에서 바꿔요.
어떤 테이블에서 시작해서:
| id | name | secret_name | age |
|---|---|---|---|
| 1 | Deadpond | Dive Wilson | null |
| 2 | Spider-Boy | Pedro Parqueador | null |
| 3 | Rusty-Man | Tommy Sharp | 48 |
...열의 수를 바꾸는 것:
| name |
|---|
| Deadpond |
| Spider-Boy |
| Rusty-Man |
...은 전부 SELECT 영역이에요.
WHERE 영역
행이 너무 많거나 적으면, 그건 WHERE 부분에서 바꿔요.
어떤 테이블에서 시작해서:
| id | name | secret_name | age |
|---|---|---|---|
| 1 | Deadpond | Dive Wilson | null |
| 2 | Spider-Boy | Pedro Parqueador | null |
| 3 | Rusty-Man | Tommy Sharp | 48 |
...행의 수를 바꾸는 것:
| id | name | secret_name | age |
|---|---|---|---|
| 2 | Spider-Boy | Pedro Parqueador | null |
...은 전부 WHERE 영역이에요.
SQLModel로 SELECT 복습하기
SQLModel로 데이터를 읽을 때 썼던 코드를 잠깐 돌아볼게요.
특히 select 문장(statement) 이 중요해요:
def select_heroes():
with Session(engine) as session:
statement = select(Hero)
results = session.exec(statement)
for hero in results:
print(hero)
SQLModel로 WHERE를 이용해 행 필터링하기
이제 SQL 문장에 WHERE를 붙여 행을 필터링하듯이, SQLModel의 select() 문장에 .where()를 붙이면 반환되는 객체들을 필터링할 수 있어요:
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Deadpond")
results = session.exec(statement)
for hero in results:
print(hero)
아주 작은 변화지만, 세부 내용은 정말 많아요. 하나씩 살펴볼게요.
select() 객체
select(Hero)가 반환하는 객체는 몇몇 메서드를 가진 특별한 종류의 객체예요.
그 메서드 중 하나가 .where()인데, (놀랍지 않게도) 그 select 객체 안의 SQL 문장에 WHERE를 추가하는 데 써요.
나중에 살펴볼 다른 메서드들도 있어요. 💡
이런 메서드 대부분은 객체를 수정한 뒤 같은 객체를 다시 반환해요.
그래서 하나씩 차례로 호출할 수 있어요:
statement = select(Hero).where(Hero.name == "Deadpond").where(Hero.age == 48)
.where() 호출하기
이 .where() 메서드는 특별하고 아주 강력해요. SQLModel(사실은 SQLAlchemy)과 긴밀하게 통합되어서, 아주 익숙한 파이썬 문법과 코드를 쓸 수 있게 해 주죠.
우리가 등호 하나(=)가 아니라 이렇게 호출했다는 점을 눈여겨보세요:
# 지원하지 않아요 🚨
select(Hero).where(name="Deadpond")
물론 그게 더 짧긴 했겠지만, 오류가 훨씬 더 잘 나고 제한적일 거예요. 잠시 후 그 이유를 보여드릴게요.
대신 우리는 등호 두 개(==)를 썼죠:
select(Hero).where(Hero.name == "Deadpond")
그럼 거기서 무슨 일이 벌어지는 걸까요?
.where()와 표현식(Expressions)
위 예제에서 우리는 등호 두 개(==)를 쓰고 있어요. 이걸 "동등 연산자(equality operator)"라고 불러요.
!!! tip "팁"
**연산자(operator)** 는 값 하나 옆이나 값 두 개 사이에 놓아 뭔가를 처리하게 하는 기호예요.
`==`는 두 값이 **같은지** 확인하기 때문에 **동등(equality)** 연산자라고 불러요.
파이썬에서 이 동등 연산자(==)를 쓴 코드를 쓰면, 예:
some_name == "Deadpond"
...그걸 동등 "비교(comparison)"라고 하고, 보통 이런 값을 결과로 냅니다:
True
...또는:
False
!!! tip "팁"
`<`, `>`, `==`, `>=`, `<=`, `!=` 는 모두 **비교**에 쓰이는 **연산자**예요.
하지만 SQLAlchemy는 모델 클래스 안의 열/필드에 약간의 마법을 더해서, 그 파이썬 비교들이 초능력을 갖게 해요.
그래서 이렇게 쓰면:
Hero.name == "Deadpond"
...그건 True나 False 값이 안 돼요. 🤯
대신 특별한 종류의 객체가 돼요. 인터랙티브 파이썬 세션에서 해보면 이렇게 보일 거예요:
>>> Hero.name == "Deadpond"
<sqlalchemy.sql.elements.BinaryExpression object at 0x7f4aec0d6c90>
그러니까 그 결과 값은 표현식(expression) 객체예요. 💡
그리고 .where()는 이런 표현식 객체를 하나(또는 여러 개) 받아서 SQL 문장을 갱신해요.
모델 클래스 속성, 표현식, 인스턴스
잠깐 멈추고, 아주 중요하면서도 놓치기 쉬운 구분을 확실히 하고 갈게요.
각 열/필드에 대한 모델 클래스 속성은 특별해서 표현식에 쓸 수 있어요.
하지만 그건 모델 클래스 속성에만 해당돼요. 🚨
인스턴스 속성은 평범한 파이썬 값처럼 동작해요. ✅
그래서 파이썬 비교에 클래스(Hero, 대문자 H)를 쓰면:
Hero.name == "Deadpond"
....where()에 쓸 표현식 객체 중 하나가 나와요:
<sqlalchemy.sql.elements.BinaryExpression object at 0x7f4aec0d6c90>
하지만 인스턴스를 하나 만들어서:
some_hero = Hero(name="Deadpond", secret_name="Dive Wilson")
...그걸 비교에 쓰면:
some_hero.name == "Deadpond"
...그건 파이썬 값이 나와요:
True
...또는 다른 이름을 가진 다른 객체였더라면 이랬을 수도 있고요:
False
차이점은 하나는 모델 클래스를 쓰고, 다른 하나는 인스턴스를 쓴다는 거예요.
클래스일까 인스턴스일까
아마 소문자 h로 된 hero라는 변수를 쓰게 될 가능성이 높아요:
hero = Hero(name="Deadpond", secret_name="Dive Wilson")
이제 클래스는 Hero(대문자 H)이고, 인스턴스는 hero(소문자 h)예요.
그래서 Hero.name과 hero.name은 아주 비슷해 보이지만, 전혀 다른 두 가지예요:
>>> Hero.name == "Deadpond"
<sqlalchemy.sql.elements.BinaryExpression object at 0x7f4aec0d6c90>
>>> hero.name == "Deadpond"
True
그냥 주의해서 볼 일이에요. 🤓
하지만 클래스와 인스턴스의 차이를 이해하고 나면 자연스러워지고, 아주 강력한 일을 할 수 있어요. 🚀
예를 들어 hero.name은 str처럼 동작하고 Hero.name은 비교용 특별 객체처럼 동작하니까, 이런 코드를 쓸 수 있어요:
select(Hero).where(Hero.name == hero.name)
그건 이런 뜻이에요:
안녕 SQL 데이터베이스 👋, 모든 열을
SELECT(조회)해 줘모델 클래스
Hero에 대한 테이블(테이블"hero")에서FROM(가져와) 줘
"name"열이 지금 내가 가진 이 히어로 인스턴스의 이름hero.name(위 예제에서는 값"Deadpond")과 같은 행WHERE만.
키워드 인자 대신 .where()와 표현식
이제 SQL 데이터베이스를 다루는 이 용도에서 왜 표현식이 더 나은지 제 생각을 말씀드릴게요:
# 표현식 ✨
select(Hero).where(Hero.name == "Deadpond")
...이런 키워드 인자 대신 말이죠:
# 지원하지 않아요, 키워드 인자 🚨
select(Hero).where(name="Deadpond")
물론 키워드 인자가 조금 더 짧았을 거예요.
하지만 표현식을 쓰면 에디터가 자동완성과 인라인 오류 검사로 많은 도움을 줘요. ✨
예를 하나 들어볼게요. SQLModel에서 키워드 인자가 지원된다고 상상해 보고, Spider-Boy의 비밀 정체로 필터링한다고 해 봐요.
이렇게 쓸 수 있을 거예요:
# 이건 복사하지 마세요 🚨
select(Hero).where(secret_identity="Pedro Parqueador")
에디터가 이 코드를 보면, 어떤 키워드 인자가 허용되는지에 대한 정보가 전혀 없으니 오류를 감지할 방법이 없어요.
심지어 코드가 실행되고 다 괜찮아 보일 수도 있어요. 그러다 몇 달 뒤, 확실히 "Pedro Parqueador"가 있는데 왜 앱이 행을 절대 못 찾지? 하고 헤매게 될 거예요. 😱
그리고 마지막에야 깨닫게 되겠죠. 우리가 테이블에 없는 열인 secret_identity로 코드를 짰다는 걸요. secret_name이라고 썼어야 했는데 말이에요.
이제 표현식을 쓰면, 이렇게 시도하는 순간 에디터가 바로 오류를 보여줘요:
# 표현식 ✨
select(Hero).where(Hero.secret_identity == "Pedro Parqueador")
더 좋게는, 올바른 걸 자동완성해 주기도 해요:
select(Hero).where(Hero.secret_name == "Pedro Parqueador")
에디터 지원이 더 좋아지고 자동완성과 인라인 오류가 생기는 것만으로도, 키워드 인자 대신 표현식을 쓸 가치가 충분하다고 생각해요. ✨
!!! tip "팁"
**표현식**은 아래에서 보여드릴 다른 종류의 비교 기능도 더 제공해요. 👇
문장 실행하기
이제 .where()가 어떻게 동작하는지 알았으니, 코드를 마무리해 볼게요.
사실 이전 장들에서 데이터를 조회할 때와 똑같아요:
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Deadpond")
results = session.exec(statement)
for hero in results:
print(hero)
이제 WHERE가 포함된 그 문장을 가져다 exec()로 실행해서 결과를 얻어요.
이 경우 결과는 하나뿐일 거예요:
$ uv run python app.py
// 부수적인 출력은 생략했어요 😉
// 이제 중요한 부분, WHERE가 있는 SELECT 💡
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.name = ?
INFO Engine [no key 0.00014s] ('Deadpond',)
// 프린트된 유일한 히어로
secret_name='Dive Wilson' age=None id=1 name='Deadpond'
!!! tip "팁"
`results` 객체는 `for` 루프에 쓸 수 있는 이터러블(iterable)이에요.
행이 하나뿐이어도 우리는 그 `results` 객체를 순회해요. 마치 요소가 하나인 리스트인 것처럼요.
데이터를 가져오는 다른 방법은 나중에 볼게요.
다른 비교들
.where()에 전달하는 이 특별한 표현식들의 또 다른 큰 장점이 여기 있어요.
위에서는 "동등" 비교(== 사용)만 해서, 두 값이 같은 값인지만 확인했죠.
하지만 다른 표준 파이썬 비교도 쓸 수 있어요. ✨
같지 않음 (Not Equal)
!=를 사용해서 열이 어떤 값과 같지 않은 행들을 가져올 수 있어요:
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name != "Deadpond")
results = session.exec(statement)
for hero in results:
print(hero)
그러면 이렇게 출력돼요:
secret_name='Pedro Parqueador' age=None id=2 name='Spider-Boy'
secret_name='Tommy Sharp' age=48 id=3 name='Rusty-Man'
데이터 추가하기
비교 예제들을 더 명확하게 보여주기 위해, create_heroes() 함수를 수정해 행을 몇 개 더 추가할게요:
def create_heroes():
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
hero_4 = Hero(name="Tarantula", secret_name="Natalia Roman-on", age=32)
hero_5 = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
hero_6 = Hero(name="Dr. Weird", secret_name="Steve Weird", age=36)
hero_7 = Hero(name="Captain North America", secret_name="Esteban Rogelios", age=93)
with Session(engine) as session:
session.add(hero_1)
session.add(hero_2)
session.add(hero_3)
session.add(hero_4)
session.add(hero_5)
session.add(hero_6)
session.add(hero_7)
session.commit()
이제 나이가 서로 다른 히어로가 여럿 있으니, 다음 비교들이 뭘 하는지 훨씬 명확해질 거예요.
초과 (More Than)
이제 >를 사용해서 열이 어떤 값 보다 큰 행들을 가져와 볼게요:
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.age > 35)
results = session.exec(statement)
for hero in results:
print(hero)
그러면 이렇게 출력돼요:
age=48 id=3 name='Rusty-Man' secret_name='Tommy Sharp'
age=36 id=6 name='Dr. Weird' secret_name='Steve Weird'
age=93 id=7 name='Captain North America' secret_name='Esteban Rogelios'
!!! tip "팁"
`Black Lion`은 선택되지 않았어요. 나이가 `35`보다 *엄밀히* 크지 않으니까요.
이상 (More Than or Equal)
이번엔 >=로 다시 해서, 열이 어떤 값 보다 크거나 같은 행들을 가져와 볼게요:
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.age >= 35)
results = session.exec(statement)
for hero in results:
print(hero)
>=를 쓰니까 이제 나이 35도 출력에 포함돼요:
age=48 id=3 name='Rusty-Man' secret_name='Tommy Sharp'
age=35 id=5 name='Black Lion' secret_name='Trevor Challa'
age=36 id=6 name='Dr. Weird' secret_name='Steve Weird'
age=93 id=7 name='Captain North America' secret_name='Esteban Rogelios'
!!! tip "팁"
이번엔 `Black Lion`도 들어왔어요. 나이가 `35`보다 *엄밀히* 크진 않지만 `35`와 *같기* 때문이에요.
미만 (Less Than)
비슷하게 <를 써서 열이 어떤 값 보다 작은 행들을 가져올 수 있어요:
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.age < 35)
results = session.exec(statement)
for hero in results:
print(hero)
그리고 데이터베이스에서 나이가 가장 어린 히어로를 얻게 돼요:
age=32 id=4 name='Tarantula' secret_name='Natalia Roman-on'
!!! tip "팁"
**Spider-Boy**가 더 **어리다고** 상상해 볼 수 있겠죠. 하지만 나이를 모르니 데이터베이스에서 `NULL`(`None`)이에요. 그래서 숫자로 하는 어떤 나이 비교에도 걸리지 않아요.
이하 (Less Than or Equal)
또한 <=를 써서 열이 어떤 값 보다 작거나 같은 행들을 가져올 수 있어요:
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.age <= 35)
results = session.exec(statement)
for hero in results:
print(hero)
그리고 어린 쪽, 35 이하를 얻게 돼요:
age=32 id=4 name='Tarantula' secret_name='Natalia Roman-on'
age=35 id=5 name='Black Lion' secret_name='Trevor Challa'
!!! tip "팁"
`Black Lion`도 여기 들어와요. 나이가 `35`보다 *엄밀히* 작진 않지만 `35`와 *같기* 때문이에요.
포함 (In)
마지막으로 in_를 써서 열이 값 컬렉션의 구성원인 행들을 가져올 수 있어요:
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(col(Hero.name).in_(["Deadpond", "Ratman"]))
results = session.exec(statement)
for hero in results:
print(hero)
이 경우, 이름 컬렉션에 속해 있으니 Deadpond가 일치해요.
Ratman이라는 히어로는 없으니 매칭되는 히어로는 없어요.
!!! tip "팁"
`in_`를 쓰려면 속성을 `col()`로 감싸야 해요.
자세한 내용은 [타입 어노테이션과 오류](#타입-어노테이션과-오류) 섹션에서 읽을 수 있어요.
표현식의 이점
순수 파이썬 표현식을 키워드 인자 대신 쓸 수 있다는 게 얼마나 도움이 되는지 볼 좋은 순간이에요. ✨
<, <=, >, >=, == 같은 표준 파이썬 비교 연산자를 똑같이 쓸 수 있어요.
여러 개의 .where()
.where()는 같은 특별한 select 객체를 다시 반환하니까, 거기에 .where() 호출을 더 붙일 수 있어요:
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.age >= 35).where(Hero.age < 40)
results = session.exec(statement)
for hero in results:
print(hero)
이건 age가 35 보다 크거나 같고, AND 동시에 age가 40 보다 작은 행들을 선택해요.
같은 뜻의 SQL은 이래요:
SELECT id, name, secret_name, age
FROM hero
WHERE age >= 35 AND age < 40
두 비교를 AND로 묶는 거죠.
그러고 나서 실행해서 프로그램의 출력을 볼 수 있어요:
$ uv run python app.py
// 부수적인 출력은 생략했어요 😉
// AND를 사용한 WHERE가 있는 SELECT 문장
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.age >= ? AND hero.age < ?
INFO Engine [no key 0.00014s] (35, 40)
// 프린트된 두 히어로
age=35 id=5 name='Black Lion' secret_name='Trevor Challa'
age=36 id=6 name='Dr. Weird' secret_name='Steve Weird'
표현식 여러 개를 .where()에 넣기
.where()를 여러 번 쓰는 대신, 하나의 .where()에 표현식을 여러 개 넘길 수도 있어요:
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.age >= 35, Hero.age < 40)
results = session.exec(statement)
for hero in results:
print(hero)
위와 같은 뜻이고, 두 히어로가 나오는 같은 출력이 돼요:
age=35 id=5 name='Black Lion' secret_name='Trevor Challa'
age=36 id=6 name='Dr. Weird' secret_name='Steve Weird'
OR로 표현식 여러 개 쓰기
마지막 예제들은 .where()에 표현식 여러 개를 썼어요. 그러면 최종 SQL에서 그게 AND로 묶이죠. 즉 한 행이 결과에 포함되려면 표현식이 전부 참이어야 해요.
하지만 OR로 표현식을 묶을 수도 있어요. 이 경우엔 행에 표현식 중 하나라도(반드시 전부일 필요 없이) 참이면 포함돼요.
그렇게 하려면 or_를 import 하면 돼요:
from sqlmodel import Field, Session, SQLModel, create_engine, or_, select
그리고 두 표현식을 or_()에 넘기고 .where() 안에 넣으면 돼요.
예를 들어 여기서는 가장 어린 히어로 OR 가장 나이 많은 히어로를 선택해요:
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(or_(Hero.age <= 35, Hero.age > 90))
results = session.exec(statement)
for hero in results:
print(hero)
실행하면 이렇게 출력돼요:
$ uv run python app.py
// 부수적인 출력은 생략했어요 😉
// OR도 쓰는 WHERE가 있는 SELECT 문장 🔍
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.age <= ? OR hero.age > ?
INFO Engine [no key 0.00021s] (35, 90)
// 결과에는 가장 어린 히어로와 가장 나이 많은 히어로가 포함돼요 ✨
secret_name='Natalia Roman-on' age=32 id=4 name='Tarantula'
secret_name='Trevor Challa' age=35 id=5 name='Black Lion'
secret_name='Esteban Rogelios' age=93 id=7 name='Captain North America'
타입 어노테이션과 오류
이런 비교를 사용할 때 에디터가 오류를 표시할 가능성이 있어요. 예:
Hero.age > 35
이런 오류가 나올 거예요:
Hero.age는 잠재적으로None인데,None을>로 비교할 수 없어요
필드에 순수하고 평범한 파이썬 어노테이션을 쓰고 있기 때문이에요. age는 실제로 int | None으로 어노테이션되어 있거든요.
이렇게 단순하고 표준적인 파이썬 타입 어노테이션을 쓰면, 인스턴스를 만들거나 사용할 때의 단순함과 인라인 오류 검사라는 이점을 얻어요. ✨
그리고 .where()에서 이런 특별한 클래스 속성을 쓰면, 프로그램 실행 중에 그 특별한 클래스 속성이 데이터베이스에서 NULL이 아닌 값에만 비교가 적용된다는 걸 알고, 올바르게 동작해요.
하지만 에디터는 그게 특별한 클래스 속성인지 모르니까, (이 경우엔 오탐지인) 오류를 막으려고 도와주려는 거예요.
그래도 쉽게 고칠 수 있어요. 🎉
에디터에게 이 클래스 속성이 사실 특별한 SQLModel 열(평범한 값을 가진 인스턴스 속성이 아니라)이라는 걸 알려줄 수 있어요.
그렇게 하려면 col()을 import 하면 돼요("column"의 줄임말이에요):
from sqlmodel import Field, Session, SQLModel, col, create_engine, select
그리고 .where()에서 쓸 때 클래스 속성을 col() 안에 넣으면 돼요:
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(col(Hero.age) >= 35)
results = session.exec(statement)
for hero in results:
print(hero)
이제 비교가 이게 아니라:
Hero.age > 35
...이거죠:
col(Hero.age) > 35
그리고 이제 에디터는 이 코드가 실제로 괜찮다는 걸 알아요. 특별한 SQLModel 열이니까요.
!!! tip "팁"
그 `col()`은 나중에 여러모로 유용해져요. 열에 대한 이 특별한 **클래스 속성**들로 할 수 있는 여러 다른 일들에 자동완성도 주고요.
하지만 그건 나중에 볼게요.
요약
SQLModel 열(특별한 클래스 속성)로 만든 강력한 표현식과 함께 .where()를 써서 원하는 행들을 필터링할 수 있어요. 🚀
지금까지는 데이터베이스가 각 레코드(행)를 하나하나 훑어 원하는 것과 일치하는 걸 찾고 있었어요. 레코드가 수천, 수백만 개라면 아주 느릴 수 있죠. 😱
다음 섹션에서는 데이터베이스에 인덱스를 추가하는 법을 알려드릴게요. 그게 쿼리를 아주 효율적으로 만들어 줄 거예요. 😎