연결된 테이블 만들기
연결된 테이블 만들기 (Create Connected Tables)
이번 장에서는 서로 다른 테이블에 있는 연결된 데이터를 다루기 위해, 테이블을 두 개 이상 만들고 서로 연결하는 방법을 배워요. 한 테이블의 각 행이 다른 테이블의 행을 참조할 수 있게 하는 거죠.
출처: 공식문서
테이블 두 개를 만들어 보자
지금까지 우리는 hero 테이블 하나로 히어로들을 다뤘어요. 이제 team 테이블을 추가해 볼게요.
team 테이블은 이렇게 생겼어요.
| id | name | headquarters |
|---|---|---|
| 1 | Preventers | Sharp Tower |
| 2 | Z-Force | Sister Margaret's Bar |
두 테이블을 연결하기 위해, hero 테이블에 팀의 ID를 가리키는 team_id라는 컬럼을 하나 더 추가할 거예요.
| id | name | secret_name | age | team_id ✨ |
|---|---|---|---|---|
| 1 | Deadpond | Dive Wilson | null | 2 ✨ |
| 2 | Spider-Boy | Pedro Parqueador | null | 1 ✨ |
| 3 | Rusty-Man | Tommy Sharp | 48 | 1 ✨ |
이렇게 hero 테이블의 각 행이 team 테이블의 행을 가리킬 수 있게 됩니다.
일대다(One-to-Many)와 다대일(Many-to-One)
여기서 우리는 한 팀이 여러 히어로를 가질 수 있는 관계를 만드는 거예요. 그래서 흔히 일대다(one-to-many) 또는 다대일(many-to-one) 관계라고 불러요.
다대일 부분은 히어로 쪽에서 보면 보여요. 여러 히어로가 한 팀에 속할 수 있죠.
아마 이것이 가장 많이 쓰는 관계 유형일 거예요. 그래서 여기서부터 시작할게요. 물론 다대다(many-to-many) 나 일대일(one-to-one) 관계도 있어요.
코드로 테이블 만들기
team 테이블 만들기
코드로 테이블을 만드는 것부터 시작할게요.
sqlmodel에서 필요한 것들을 import 하고 새로운 Team 모델을 만들어요.
# Code above omitted 👆
class Team(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
# Code below omitted 👇
이것은 우리가 Hero 모델로 해왔던 것과 아주 비슷해요.
Team 모델은 자동으로 "team"이라는 이름의 테이블이 되고, 다음 컬럼들을 갖게 돼요.
id, 기본 키, 데이터베이스가 자동 생성name, 팀 이름- 이 컬럼에 SQLModel이 인덱스(index)를 만들어 달라고도 했죠
headquarters, 팀의 본부
마지막으로 모델을 설정(config)에서 테이블로 표시했습니다.
새 hero 테이블 만들기
이제 hero 테이블을 만들 차례예요.
이것은 지금까지 쓰던 모델과 같지만, team_id라는 새 컬럼만 추가했어요.
# Code above omitted 👆
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)
team_id: int | None = Field(default=None, foreign_key="team.id")
# Code below omitted 👇
대부분은 이미 익숙할 거예요.
컬럼 이름은 team_id가 될 거고, 정수(integer)예요. 그리고 데이터베이스에서는 NULL(파이썬에서는 None)이 될 수 있어요. 어떤 팀에도 속하지 않는 히어로가 있을 수 있으니까요.
히어로를 만들 때마다 명시적으로 team_id=None을 넘기지 않아도 되도록 Field()에 기본값 None을 추가했어요.
이제 새로 나온 부분이에요.
Field()에서 foreign_key="team.id" 인자를 전달했죠. 이건 이 team_id 컬럼이 team 테이블을 가리키는 외래 키(foreign key) 라는 걸 데이터베이스에 알려주는 거예요. "외래 키"는 간단히 말해, 이 컬럼이 다른(foreign) 테이블의 행을 식별하는 키(key) 를 담겠다는 뜻입니다.
이 team_id 컬럼의 값은 team 테이블의 id 컬럼에 있는 어떤 행과 같은 정수예요. 이게 바로 두 테이블을 연결하는 지점이에요.
foreign_key의 값
foreign_key가 문자열(string)이라는 점을 눈여겨보세요.
그 안에는 테이블 이름, 점(dot), 그리고 컬럼 이름이 들어가요.
여기서 테이블 이름은 데이터베이스 안의 테이블 이름이므로 "team"이에요. 모델 클래스 이름인 Team(대문자 T)이 아니죠.
만약 커스텀 테이블 이름을 쓴다면 그 이름을 쓰면 됩니다.
참고
모델의 커스텀 테이블 이름을 설정하는 방법은 Advanced User Guide에서 배울 수 있어요.
테이블 만들기
이제 이전과 같은 코드를 추가해 엔진(engine)과 테이블을 만드는 함수를 만들면 됩니다.
# Code above omitted 👆
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)
# Code below omitted 👇
그리고 이전처럼, 이 함수를 다른 함수 main()에서 호출하고, 파일의 main 블록에 그 main() 함수를 추가할 거예요.
# Code above omitted 👆
def main():
create_db_and_tables()
if __name__ == "__main__":
main()
코드 실행하기
팁
코드를 실행하기 전에
database.db파일을 삭제해서 처음부터 시작하는 걸 확실히 하세요.
지금까지의 코드를 실행하면 database.db 파일과 그 안에 우리가 방금 정의한 team과 hero 테이블을 만들어요.
$ uv run python app.py
// Automatically start a new transaction
INFO Engine BEGIN (implicit)
// Check if the tables exist already
INFO Engine PRAGMA main.table_info("team")
INFO Engine [raw sql] ()
INFO Engine PRAGMA temp.table_info("team")
INFO Engine [raw sql] ()
INFO Engine PRAGMA main.table_info("hero")
INFO Engine [raw sql] ()
INFO Engine PRAGMA temp.table_info("hero")
INFO Engine [raw sql] ()
// Create the tables
INFO Engine
CREATE TABLE team (
id INTEGER,
name VARCHAR NOT NULL,
headquarters VARCHAR NOT NULL,
PRIMARY KEY (id)
)
INFO Engine [no key 0.00010s] ()
INFO Engine
CREATE TABLE hero (
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
team_id INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(team_id) REFERENCES team (id)
)
INFO Engine [no key 0.00026s] ()
INFO Engine COMMIT
SQL로 테이블 만들기
이렇게 생성된 SQL 코드를 살펴볼게요.
앞서 봤듯이, 이 실험에서 쓰는 SQLite에서는 VARCHAR 컬럼이 TEXT로 변환돼요.
그래서 첫 번째 SQL은 이렇게도 쓸 수 있어요.
CREATE TABLE team (
id INTEGER,
name TEXT NOT NULL,
headquarters TEXT NOT NULL,
PRIMARY KEY (id)
)
두 번째 테이블은 이렇게 쓸 수 있어요.
CREATE TABLE hero (
id INTEGER,
name TEXT NOT NULL,
secret_name TEXT NOT NULL,
age INTEGER,
team_id INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(team_id) REFERENCES team (id)
)
새로 추가된 부분은 FOREIGN KEY 줄뿐이에요. 보시다시피, 이 줄은 데이터베이스에 이 테이블에서 어떤 컬럼이 외래 키인지(team_id), 어떤 (외부) 테이블을 참조하는지(team), 그리고 그 테이블의 어떤 컬럼이 연결할 행을 정하는 키인지(id)를 알려줘요.
DB Browser for SQLite에서 직접 실험해 보셔도 좋아요.
요약 (Recap)
SQLModel에서 대부분의 경우, 두 테이블을 연결하려면 Field() 안에 다른 테이블과 컬럼을 가리키는 문자열 foreign_key를 가진 필드(컬럼) 하나면 충분해요.
이제 테이블을 만들고 연결했으니, 다음 장에서 행들을 만들어 볼게요. 🚀