데이터 삭제 - 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() 하면 돼요. 🔥