데이터 삭제 - DELETE

데이터 삭제 - DELETE (Delete Data - DELETE)

이번 장에서는 SQLModel로 데이터를 삭제하는 방법을 배워요. session.delete()로 세션에서 삭제하고, commit()으로 데이터베이스에 반영하는 식이에요.

출처: 공식문서

이전 코드에서 이어가기

이전처럼, 앞서 만든 코드에서 이어서 진행할게요.

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")
        results = session.exec(statement)
        hero_1 = results.one()
        print("Hero 1:", hero_1)

        statement = select(Hero).where(Hero.name == "Captain North America")
        results = session.exec(statement)
        hero_2 = results.one()
        print("Hero 2:", hero_2)

        hero_1.age = 16
        hero_1.name = "Spider-Youngster"
        session.add(hero_1)

        hero_2.name = "Captain North America Except Canada"
        hero_2.age = 110
        session.add(hero_2)

        session.commit()
        session.refresh(hero_1)
        session.refresh(hero_2)

        print("Updated hero 1:", hero_1)
        print("Updated hero 2:", hero_2)


def main():
    create_db_and_tables()
    create_heroes()
    update_heroes()


if __name__ == "__main__":
    main()

같은 결과를 얻으려면 예제를 실행하기 전에 database.db 파일을 삭제하는 걸 잊지 마세요.

SQL로 삭제하기

Spider-Youngster는 너무 이상해졌으니, 그냥 삭제할게요.

하지만 걱정 마세요. 나중에 새로운 스토리로 다시 부활시킬 거예요. 😅

SQL로 어떻게 삭제하는지 볼게요.

DELETE
FROM hero
WHERE name = "Spider-Youngster"

이건 대략 이렇게 말하는 것과 같아요.

안녕 SQL 데이터베이스 👋, hero라는 테이블 FROM에서 행들을 DELETE 하고 싶어.

name 컬럼의 값이 "Spider-Youngster"와 같은 모든 행 WHERE을 삭제해 줘.

SELECT 문을 쓸 때는 이런 형태를 가진다는 걸 기억하나요?

SELECT [some stuff here]
FROM [name of a table here]
WHERE [some condition here]

DELETE는 아주 비슷해요. 역시 FROM으로 작업할 테이블을 말하고, WHERE로 삭제하고 싶은 행들을 매칭할 조건을 말해요.

DB Browser for SQLite에서 직접 시도해 볼 수 있어요.

DELETE는 행 하나의 특정 값이 아니라 행 전체를 삭제한다는 점을 명심하세요.

만약 행은 유지하면서 컬럼의 특정 값 하나만 "삭제"하고 싶다면, 이전 장에서 설명한 대로 그 행을 update 하여 그 컬럼의 값을 NULL(파이썬에서는 None)로 설정하면 돼요.

이제 SQLModel로 삭제해 볼게요.

데이터베이스에서 읽기

이전 장에서 업데이트한 히어로 "Spider-Youngster"를 선택하는 것부터 시작할게요. 이게 우리가 삭제할 히어로예요.

# Code above omitted 👆

def delete_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.name == "Spider-Youngster")
        results = session.exec(statement)
        hero = results.one()
        print("Hero: ", hero)

        # Code below omitted 👇

# Code below omitted 👇

새 함수 delete_heroes()이므로, 명령줄에서 프로그램을 실행할 때 호출되도록 main() 함수에도 추가할 거예요.

# Code above omitted 👆

def main():
    create_db_and_tables()
    create_heroes()
    update_heroes()
    delete_heroes()

# Code below omitted 👇

그러면 기존의 히어로 Spider-Youngster를 출력할 거예요.

$ uv run python app.py

// Some boilerplate and previous output omitted 😉

// The SELECT with WHERE
INFO Engine BEGIN (implicit)
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.name = ?
INFO Engine [no key 0.00011s] ('Spider-Youngster',)

// Print the hero as obtained from the database
Hero:  name='Spider-Youngster' secret_name='Pedro Parqueador' age=16 id=2

세션에서 히어로 삭제하기

이제 session.add()로 히어로를 추가하거나 업데이트했던 것과 아주 비슷하게, session.delete()로 히어로를 세션에서 삭제할 수 있어요.

# Code above omitted 👆

def delete_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.name == "Spider-Youngster")
        results = session.exec(statement)
        hero = results.one()
        print("Hero: ", hero)

        session.delete(hero)

        # Code below omitted 👇

# Code below omitted 👇

세션 커밋하기

세션의 현재 변경 사항을 저장하려면 commit 하세요.

이러면 삭제된 히어로처럼 세션에 저장된 모든 변경 사항이 저장돼요.

# Code above omitted 👆

def delete_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.name == "Spider-Youngster")
        results = session.exec(statement)
        hero = results.one()
        print("Hero: ", hero)

        session.delete(hero)
        session.commit()

        # Code below omitted 👇

# Code below omitted 👇

이전에 본 것과 마찬가지로, .commit()은 세션에 추가된 다른 모든 것도 저장해요. 업데이트나 새로 만든 히어로까지요.

히어로를 삭제한 후 이렇게 commit 하면 이 출력이 생겨요.

$ uv run python app.py

// Some boilerplate output omitted 😉

// Previous output omitted 🙈

// The SQL to update the hero in the database
INFO Engine DELETE FROM hero WHERE hero.id = ?
INFO Engine [generated in 0.00020s] (2,)
INFO Engine COMMIT

삭제된 객체 출력하기

이제 히어로는 데이터베이스에서 삭제됐어요.

그것으로 session.refresh()를 하려고 하면, 데이터베이스에 이 히어로에 대한 데이터가 없으므로 예외(exception)가 발생할 거예요.

그럼에도 불구하고, 객체는 여전히 데이터와 함께 사용할 수 있어요. 하지만 이제 세션에 연결되어 있지 않고, 데이터베이스에도 더 이상 존재하지 않아요.

객체가 세션에 연결되어 있지 않으니 "expired(만료)"로 표시되지도 않아요. 세션은 이 객체에 대해 더 이상 신경 쓰지 않죠.

그래서 객체는 여전히 그 데이터를 담은 속성들을 갖고 있어서, 우리가 출력할 수 있어요.

# Code above omitted 👆

def delete_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.name == "Spider-Youngster")
        results = session.exec(statement)
        hero = results.one()
        print("Hero: ", hero)

        session.delete(hero)
        session.commit()

        print("Deleted hero:", hero)

        # Code below omitted 👇

# Code below omitted 👇

이러면 이렇게 출력돼요.

$ uv run python app.py

// Some boilerplate output omitted 😉

// Previous output omitted 🙈

// Print the deleted hero
Deleted hero: name='Spider-Youngster' secret_name='Pedro Parqueador' age=16 id=2

같은 행을 데이터베이스에서 조회하기

삭제됐는지 확인하려면, 이제 같은 "Spider-Youngster" 이름으로 데이터베이스를 다시 조회해 볼게요.

# Code above omitted 👆

def delete_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.name == "Spider-Youngster")
        results = session.exec(statement)
        hero = results.one()
        print("Hero: ", hero)

        session.delete(hero)
        session.commit()

        print("Deleted hero:", hero)

        statement = select(Hero).where(Hero.name == "Spider-Youngster")
        results = session.exec(statement)
        hero = results.first()

        # Code below omitted 👇

# Code below omitted 👇

여기서 results.first()를 사용해 찾은 첫 번째 객체를 얻고 있어요(여러 개를 찾으면 첫 번째를, 아무것도 못 찾으면 None을). 만약 results.one()을 썼다면 정확히 하나의 결과를 기대하므로 예외가 발생했을 거예요.

그리고 방금 그 히어로를 삭제했으니, 아무것도 찾지 못하고 None을 받아야 해요.

이러면 데이터베이스에서 SQL을 실행하고 이렇게 출력돼요.

$ uv run python app.py

// Some boilerplate output omitted 😉

// Previous output omitted 🙈

// Automatically start a new transaction
INFO Engine BEGIN (implicit)

// SQL to search for the hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.name = ?
INFO Engine [no key 0.00013s] ('Spider-Youngster',)

삭제 확인하기

이제 실제로 그 이름을 가진 히어로가 데이터베이스에 없는지 확인할게요.

results의 "첫 번째" 항목이 None인지 확인해서 하면 돼요.

# Code above omitted 👆

def delete_heroes():
    with Session(engine) as session:
        # Code here omitted 👈

        statement = select(Hero).where(Hero.name == "Spider-Youngster")
        results = session.exec(statement)
        hero = results.first()

        if hero is None:
            print("There's no hero named Spider-Youngster")

# Code below omitted 👇

이러면 이렇게 출력돼요.

$ uv run python app.py

// Some boilerplate output omitted 😉

// Previous output omitted 🙈

// Indeed, the hero was deleted 🔥
There's no hero named Spider-Youngster

// Cleanup after the with block
INFO Engine ROLLBACK

코드 전체 보기

이제 그 모든 코드를 다시 볼게요.

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")
        results = session.exec(statement)
        hero_1 = results.one()
        print("Hero 1:", hero_1)

        statement = select(Hero).where(Hero.name == "Captain North America")
        results = session.exec(statement)
        hero_2 = results.one()
        print("Hero 2:", hero_2)

        hero_1.age = 16
        hero_1.name = "Spider-Youngster"
        session.add(hero_1)

        hero_2.name = "Captain North America Except Canada"
        hero_2.age = 110
        session.add(hero_2)

        session.commit()
        session.refresh(hero_1)
        session.refresh(hero_2)

        print("Updated hero 1:", hero_1)
        print("Updated hero 2:", hero_2)


def delete_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.name == "Spider-Youngster")
        results = session.exec(statement)
        hero = results.one()
        print("Hero: ", hero)

        session.delete(hero)
        session.commit()

        print("Deleted hero:", hero)

        statement = select(Hero).where(Hero.name == "Spider-Youngster")
        results = session.exec(statement)
        hero = results.first()

        if hero is None:
            print("There's no hero named Spider-Youngster")


def main():
    create_db_and_tables()
    create_heroes()
    update_heroes()
    delete_heroes()


if __name__ == "__main__":
    main()

각 줄이 무슨 일을 하는지 보려면 코드의 숫자 풍선을 확인해 보세요.

요약 (Recap)

SQLModel로 행을 삭제하려면, session으로 .delete() 하고, 그리고 항상 그렇듯 변경 사항을 데이터베이스에 저장하려면 .commit() 하면 돼요. 🔥

더 알아보기 (Learn more)