데이터 갱신 - 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에서 직접 시도해 볼 수 있어요.

DB Browser for SQLite에서 UPDATE 사용하기

그 갱신 후에 테이블 데이터는 이렇게 보일 거예요. 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 객체가 있으니, 바꾸고 싶은 필드(열을 나타내는 속성)의 값을 그냥 설정하면 돼요.

이 경우에는 age16으로 설정할게요:

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()
  1. 작업할 히어로를 조회합니다.
  2. select 문장 객체로 쿼리를 실행합니다.
  3. 정확히 하나를 기대하며 히어로 객체 하나를 가져옵니다.
  4. 히어로 객체를 프린트합니다.
  5. 히어로의 age 필드를 새 값 16으로 설정합니다.
  6. 히어로를 세션에 추가합니다.
  7. 세션을 커밋합니다.
  8. 방금 커밋한 age를 포함한 최신 데이터를 갖도록 히어로 객체를 새로고침합니다.
  9. 갱신된 히어로 객체를 프린트합니다.

!!! 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()
  1. 히어로 Spider-Boy를 조회합니다.
  2. select 문장을 실행합니다.
  3. Spider-Boy에 대해 있어야 할 유일한 히어로 객체 하나를 가져옵니다.
  4. 이 히어로를 프린트합니다.
  5. 다른 히어로를 조회합니다.
  6. select 문장을 실행합니다. SQLAlchemy가 자동으로 트랜잭션을 시작합니다.
  7. 이 새 쿼리에 대한 히어로 객체 하나를 가져옵니다.
  8. 두 번째 히어로를 프린트합니다.
  9. 첫 번째 히어로의 age를 갱신합니다. (메모리만)
  10. 첫 번째 히어로의 name을 갱신합니다. (메모리만)
  11. 이 첫 번째 히어로를 세션에 추가합니다.
  12. 두 번째 히어로의 name을 갱신합니다. (메모리만)
  13. 두 번째 히어로의 age를 갱신합니다. (메모리만)
  14. 두 번째 히어로를 세션에 추가합니다.
  15. 세션에 추적된 모든 변경 사항을 한 번에 커밋합니다.
  16. 첫 번째 히어로를 새로고침합니다.
  17. 두 번째 히어로를 새로고침합니다.
  18. 이제 갱신된 첫 번째 히어로를 프린트합니다.
  19. 이제 갱신된 두 번째 히어로를 프린트합니다.
  20. 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도요.

더 알아보기 (Learn more)