데이터 갱신 - UPDATE
데이터 갱신 - UPDATE (Update Data - UPDATE)
이제 SQLModel로 데이터를 갱신하는 법을 볼게요.
출처: 공식문서
이전 코드에서 이어가기
전처럼, 이전 코드에서 멈춘 지점부터 이어갈게요.
👀 전체 파일 미리 보기
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 = Field(index=True)
secret_name: str
age: int | None = Field(default=None, index=True)
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)
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()
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)
def main():
create_db_and_tables()
create_heroes()
select_heroes()
if __name__ == "__main__":
main()
같은 결과를 얻으려면 예제를 실행하기 전에 database.db 파일을 지워 주세요.
SQL로 갱신하기
SQL에서 데이터를 갱신하는 법을 빠르게 확인해 볼게요:
UPDATE hero
SET age=16
WHERE name = "Spider-Boy"
이건 대략 이렇게 말하는 거예요:
안녕 SQL 데이터베이스 👋,
hero라는 테이블을UPDATE(갱신)하고 싶어.
age열의 값을16으로SET(설정)해 줘......
name열의 값이"Spider-Boy"와 같은 각 행의WHERE에서요.
SELECT 문장과 비슷하게, 첫 부분은 작업할 열을 정의해요. 어떤 열을 어떤 값으로 갱신할지요. 나머지 열들은 그대로 있어요.
그리고 WHERE가 있는 두 번째 부분은 그 갱신을 어떤 행에 적용할지를 정의해요.
이 경우 "Spider-Boy"라는 이름의 히어로가 한 명뿐이니, 그 행에만 갱신이 적용돼요.
!!! note "참고"
`UPDATE`에서 등호 하나(`=`)는 **할당(assignment)** 을 뜻해요. 열을 어떤 값으로 설정하는 거죠.
그리고 `WHERE`에서는 같은 등호 하나(`=`)가 두 값 사이의 **비교(comparison)** 에 쓰여, 일치하는 행을 찾아요.
이건 파이썬이나 대부분의 프로그래밍 언어와 반대예요. 거기서는 등호 하나(`=`)는 할당에, 등호 두 개(`==`)는 비교에 쓰이니까요.
DB Browser for SQLite에서 직접 시도해 볼 수 있어요.

그 갱신 후에 테이블 데이터는 이렇게 보일 거예요. Spider-Boy의 새 나이가 반영된 상태로요:
| id | name | secret_name | age |
|---|---|---|---|
| 1 | Deadpond | Dive Wilson | null |
| 2 | Spider-Boy | Pedro Parqueador | 16 ✨ |
| 3 | Rusty-Man | Tommy Sharp | 48 |
!!! tip "팁"
갱신할 행을 `id`로 찾는 게 더 일반적일 거예요. 예:
```SQL
UPDATE hero
SET age=16
WHERE id = 2
```
다만 위 예제에서는 더 직관적이도록 `name`을 썼어요.
이제 SQLModel로 코드에서 똑같은 갱신을 해 볼게요.
같은 결과를 얻으려면 예제를 실행하기 전에 database.db 파일을 지워 주세요.
데이터베이스에서 읽기
갱신할 히어로인 "Spider-Boy"를 먼저 조회하는 것부터 시작할게요:
def update_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Spider-Boy")
results = session.exec(statement)
hero = results.one()
print("Hero:", hero)
커맨드 라인에서 실행할 때 호출되도록, 그 update_heroes() 함수를 main() 함수에 추가하는 것도 잊지 말아 주세요:
def main():
create_db_and_tables()
create_heroes()
update_heroes()
여기까지를 커맨드 라인에서 실행하면 이렇게 출력돼요:
$ 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.00017s] ('Spider-Boy',)
// 데이터베이스에서 가져온 히어로를 프린트
Hero: name='Spider-Boy' secret_name='Pedro Parqueador' age=None id=2
!!! tip "팁"
이 시점에 히어로는 아직 나이가 없어요.
필드 값 설정하기
이제 hero 객체가 있으니, 바꾸고 싶은 필드(열을 나타내는 속성)의 값을 그냥 설정하면 돼요.
이 경우에는 age를 16으로 설정할게요:
def update_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Spider-Boy")
results = session.exec(statement)
hero = results.one()
print("Hero:", hero)
hero.age = 16
히어로를 세션에 추가하기
메모리의 히어로 객체에 변경이 생겼으니(이 경우 age의 새 값), 그걸 세션에 추가해야 해요.
이건 새 히어로 인스턴스를 만들 때 했던 것과 똑같아요:
def update_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Spider-Boy")
results = session.exec(statement)
hero = results.one()
print("Hero:", hero)
hero.age = 16
session.add(hero)
세션 커밋하기
세션의 현재 변경 사항을 저장하려면 커밋(commit) 하세요.
그러면 갱신된 히어로가 데이터베이스에 저장돼요:
def update_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Spider-Boy")
results = session.exec(statement)
hero = results.one()
print("Hero:", hero)
hero.age = 16
session.add(hero)
session.commit()
세션에 추가됐던 다른 것들도 함께 저장돼요.
예를 들어 새 히어로를 만들면서 그 객체들을 이미 세션에 추가했다면, 이 한 번의 커밋으로 그것들도 함께 저장돼요.
이 커밋은 이런 출력을 만들어요:
$ uv run python app.py
// 부수적인 출력은 생략했어요 😉
// 이전 출력은 생략했어요 🙈
// 데이터베이스에서 히어로를 갱신하는 SQL
INFO Engine UPDATE hero SET age=? WHERE hero.id = ?
INFO Engine [generated in 0.00017s] (16, 2)
INFO Engine COMMIT
객체 새로고침하기
이 시점에서 히어로는 데이터베이스에 갱신되어 저장됐고, 새로운 데이터가 거기 있어요.
객체의 데이터는 hero.name 같은 속성에 접근하면 자동으로 새로고침돼요.
하지만 이 예제에서는 어떤 속성에도 접근하지 않고, 객체만 프린트할 거예요. 그리고 명시적으로 하고 싶으니 객체를 직접 .refresh() 할게요:
def update_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Spider-Boy")
results = session.exec(statement)
hero = results.one()
print("Hero:", hero)
hero.age = 16
session.add(hero)
session.commit()
session.refresh(hero)
이 새로고침은 속성에 접근할 때 자동으로 일어나는 것과 똑같은 SQL 쿼리를 실행해요. 그래서 이런 출력이 나와요:
$ uv run python app.py
// 부수적인 출력은 생략했어요 😉
// 이전 출력은 생략했어요 🙈
// 최신 히어로 데이터를 SELECT하는 SQL
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00018s] (2,)
갱신된 객체 프린트하기
이제 그냥 히어로를 프린트하면 돼요:
def update_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Spider-Boy")
results = session.exec(statement)
hero = results.one()
print("Hero:", hero)
hero.age = 16
session.add(hero)
session.commit()
session.refresh(hero)
print("Updated hero:", hero)
갱신 직후에 새로고침했으니, 방금 갱신한 새 age를 포함한 최신 데이터를 갖고 있어요.
그래서 프린트하면 새 age가 보여요:
$ uv run python app.py
// 부수적인 출력은 생략했어요 😉
// 이전 출력은 생략했어요 🙈
// 새 나이와 함께 히어로 프린트
Updated hero: name='Spider-Boy' secret_name='Pedro Parqueador' age=16 id=2
코드 살펴보기
이제 그 코드 전체를 한 번 살펴볼게요:
from sqlmodel import Field, Session, SQLModel, create_engine, select # (1)!
class Hero(SQLModel, table=True): # (2)!
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: int | None = Field(default=None, index=True)
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True) # (3)!
def create_db_and_tables():
SQLModel.metadata.create_all(engine) # (4)!
def create_heroes():
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson") # (5)!
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: # (6)!
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()
def update_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Spider-Boy") # (7)!
results = session.exec(statement) # (8)!
hero = results.one() # (9)!
print("Hero:", hero) # (10)!
hero.age = 16 # (11)!
session.add(hero) # (12)!
session.commit() # (13)!
session.refresh(hero) # (14)!
print("Updated hero:", hero) # (15)!
def main():
create_db_and_tables()
create_heroes()
update_heroes()
if __name__ == "__main__":
main()
- 작업할 히어로를 조회합니다.
- select 문장 객체로 쿼리를 실행합니다.
- 정확히 하나를 기대하며 히어로 객체 하나를 가져옵니다.
- 히어로 객체를 프린트합니다.
- 히어로의 age 필드를 새 값
16으로 설정합니다. - 히어로를 세션에 추가합니다.
- 세션을 커밋합니다.
- 방금 커밋한 age를 포함한 최신 데이터를 갖도록 히어로 객체를 새로고침합니다.
- 갱신된 히어로 객체를 프린트합니다.
!!! tip "팁"
코드에 있는 숫자 버블을 눌러 각 줄이 뭘 하는지 확인해 보세요. 👆
여러 개 갱신하기
SQLModel에서의 갱신 과정은 새 객체를 만들 때와 거의 같아요. 세션에 추가하고, 커밋하면 돼요.
즉 여러 필드(속성, 열)를 한 번에 갱신할 수도 있고, 여러 객체(히어로)를 한 번에 갱신할 수도 있어요:
# Code above omitted 👆
def update_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Spider-Boy") # (1)!
results = session.exec(statement) # (2)!
hero_1 = results.one() # (3)!
print("Hero 1:", hero_1) # (4)!
statement = select(Hero).where(Hero.name == "Captain North America") # (5)!
results = session.exec(statement) # (6)!
hero_2 = results.one() # (7)!
print("Hero 2:", hero_2) # (8)!
hero_1.age = 16 # (9)!
hero_1.name = "Spider-Youngster" # (10)!
session.add(hero_1) # (11)!
hero_2.name = "Captain North America Except Canada" # (12)!
hero_2.age = 110 # (13)!
session.add(hero_2) # (14)!
session.commit() # (15)!
session.refresh(hero_1) # (16)!
session.refresh(hero_2) # (17)!
print("Updated hero 1:", hero_1) # (18)!
print("Updated hero 2:", hero_2) # (19)!
# (20)!
def main():
create_db_and_tables()
create_heroes()
update_heroes()
if __name__ == "__main__":
main()
- 히어로
Spider-Boy를 조회합니다. - select 문장을 실행합니다.
Spider-Boy에 대해 있어야 할 유일한 히어로 객체 하나를 가져옵니다.- 이 히어로를 프린트합니다.
- 다른 히어로를 조회합니다.
- select 문장을 실행합니다. SQLAlchemy가 자동으로 트랜잭션을 시작합니다.
- 이 새 쿼리에 대한 히어로 객체 하나를 가져옵니다.
- 두 번째 히어로를 프린트합니다.
- 첫 번째 히어로의 age를 갱신합니다. (메모리만)
- 첫 번째 히어로의 name을 갱신합니다. (메모리만)
- 이 첫 번째 히어로를 세션에 추가합니다.
- 두 번째 히어로의 name을 갱신합니다. (메모리만)
- 두 번째 히어로의 age를 갱신합니다. (메모리만)
- 두 번째 히어로를 세션에 추가합니다.
- 세션에 추적된 모든 변경 사항을 한 번에 커밋합니다.
- 첫 번째 히어로를 새로고침합니다.
- 두 번째 히어로를 새로고침합니다.
- 이제 갱신된 첫 번째 히어로를 프린트합니다.
- 이제 갱신된 두 번째 히어로를 프린트합니다.
with블록 문장의 끝이라 세션이 종료 코드를 실행할 수 있습니다. 마지막 트랜잭션에서 커밋되지 않은 변경 사항은ROLLBACK(되돌림)됩니다.
!!! details "👀 전체 파일 미리 보기"
```python
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 = Field(index=True)
secret_name: str
age: int | None = Field(default=None, index=True)
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)
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()
def update_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Spider-Boy") # (1)!
results = session.exec(statement) # (2)!
hero_1 = results.one() # (3)!
print("Hero 1:", hero_1) # (4)!
statement = select(Hero).where(Hero.name == "Captain North America") # (5)!
results = session.exec(statement) # (6)!
hero_2 = results.one() # (7)!
print("Hero 2:", hero_2) # (8)!
hero_1.age = 16 # (9)!
hero_1.name = "Spider-Youngster" # (10)!
session.add(hero_1) # (11)!
hero_2.name = "Captain North America Except Canada" # (12)!
hero_2.age = 110 # (13)!
session.add(hero_2) # (14)!
session.commit() # (15)!
session.refresh(hero_1) # (16)!
session.refresh(hero_2) # (17)!
print("Updated hero 1:", hero_1) # (18)!
print("Updated hero 2:", hero_2) # (19)!
# (20)!
def main():
create_db_and_tables()
create_heroes()
update_heroes()
if __name__ == "__main__":
main()
```
!!! tip "팁"
각 줄이 뭘 하는지 숫자 버블을 눌러 확인해 보세요. 👆
요약
SQLModel 객체를 다른 파이썬 객체처럼 갱신하면 돼요. 🐍
그냥 세션에 add하고, commit하는 거 잊지 마세요. 필요하면 refresh도요.