接続されたテーブルの作成¶
ここでは、異なるテーブルに格納された接続されたデータを扱います。
したがって、最初のステップは、複数のテーブルを作成し、それらを接続して、あるテーブルの各行が別のテーブルの別の行を参照できるようにすることです。
これまで、単一のheroテーブルでヒーローを扱ってきました。ここで、teamテーブルを追加しましょう。
チームテーブルは次のようになります。
| id | 名前 | 本部 |
|---|---|---|
| 1 | プリベンターズ | シャープタワー |
| 2 | Zフォース | シスターマーガレットのバー |
これらを接続するために、team_idを使用してIDで各チームを指す別の列をヒーローテーブルに追加します。
| id | 名前 | 秘密の名前 | 年齢 | team_id ✨ |
|---|---|---|---|---|
| 1 | デッドポンド | ダイブ・ウィルソン | null | 2 ✨ |
| 2 | スパイダーボーイ | ペドロ・パルケドール | null | 1 ✨ |
| 3 | ラスティマン | トミー・シャープ | 48 | 1 ✨ |
このようにして、heroテーブルの各行はteamテーブルの行を指すことができます。
一対多と多対一¶
ここでは、1つのチームが複数のヒーローを持つ可能性があるリレーションシップで、接続されたデータを作成しています。そのため、一般に一対多または多対一のリレーションシップと呼ばれます。
ヒーローから開始すると、多対一の部分を見ることができます。複数のヒーローが1つのチームに所属する可能性があります。
これはおそらく最も一般的なタイプのリレーションシップであるため、これから始めます。ただし、多対多および一対一のリレーションシップもあります。
コードでテーブルを作成¶
teamテーブルの作成¶
まず、コードでテーブルを作成することから始めましょう。
sqlmodelから必要なものをインポートし、新しいTeamモデルを作成します。
from sqlmodel import Field, SQLModel, create_engine
class Team(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
# Code below omitted 👇
from typing import Optional
from sqlmodel import Field, SQLModel, create_engine
class Team(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
# Code below omitted 👇
👀 ファイルの完全プレビュー
from sqlmodel import Field, SQLModel, create_engine
class Team(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
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")
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 main():
create_db_and_tables()
if __name__ == "__main__":
main()
from typing import Optional
from sqlmodel import Field, SQLModel, create_engine
class Team(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Optional[int] = Field(default=None, index=True)
team_id: Optional[int] = Field(default=None, foreign_key="team.id")
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 main():
create_db_and_tables()
if __name__ == "__main__":
main()
これは、Heroモデルで行ってきたことと非常によく似ています。
Teamモデルは、自動的に"team"という名前のテーブルに格納され、次の列が含まれます。
id、データベースによって自動的に生成される主キーname、チームの名前- また、この列のインデックスを作成するようにSQLModelに指示します。
headquarters、チームの本部
そして最後に、構成でテーブルとしてマークします。
新しいheroテーブルの作成¶
次に、heroテーブルを作成しましょう。
これはこれまで使用してきたモデルと同じですが、新しい列team_idを追加するだけです。
from sqlmodel import Field, SQLModel, create_engine
class Team(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
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 👇
from typing import Optional
from sqlmodel import Field, SQLModel, create_engine
class Team(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Optional[int] = Field(default=None, index=True)
team_id: Optional[int] = Field(default=None, foreign_key="team.id")
# Code below omitted 👇
👀 ファイルの完全プレビュー
from sqlmodel import Field, SQLModel, create_engine
class Team(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
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")
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 main():
create_db_and_tables()
if __name__ == "__main__":
main()
from typing import Optional
from sqlmodel import Field, SQLModel, create_engine
class Team(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Optional[int] = Field(default=None, index=True)
team_id: Optional[int] = Field(default=None, foreign_key="team.id")
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 main():
create_db_and_tables()
if __name__ == "__main__":
main()
そのほとんどは見覚えがあるはずです。
列の名前はteam_idになります。これは整数であり、データベースではNULLになる可能性があります(またはPythonではNone)。これは、どのチームにも所属していないヒーローがいる可能性があるためです。
ヒーローを作成するときにteam_id=Noneを明示的に渡す必要がないように、Field()にデフォルトのNoneを追加します。
さて、ここが新しい部分です。
Field()で、引数foreign_key="team.id"を渡します。これにより、この列team_idがテーブルteamへの外部キーであることをデータベースに伝えます。「外部キー」とは、この列に外部テーブルの行を識別するためのキーがあることを意味します。
この列team_idの値は、teamテーブルのid列にある行と同じ整数になります。それが2つのテーブルを接続するものです。
foreign_keyの値¶
foreign_keyは文字列であることに注意してください。
その中には、テーブルの名前、次にドット、そして列の名前があります。
これはデータベース内のテーブルの名前なので、モデルクラスTeam(大文字のTを使用)の名前ではなく、"team"です。
カスタムテーブル名がある場合は、そのカスタムテーブル名を使用します。
情報
モデルのカスタムテーブル名の設定については、高度なユーザーガイドで学ぶことができます。
テーブルを作成¶
これで、以前と同じコードを追加して、エンジンを作成し、テーブルを作成する関数を作成できます。
# 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 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)
👀 ファイルの完全プレビュー
from sqlmodel import Field, SQLModel, create_engine
class Team(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
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")
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 main():
create_db_and_tables()
if __name__ == "__main__":
main()
from typing import Optional
from sqlmodel import Field, SQLModel, create_engine
class Team(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Optional[int] = Field(default=None, index=True)
team_id: Optional[int] = Field(default=None, foreign_key="team.id")
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 main():
create_db_and_tables()
if __name__ == "__main__":
main()
そして、以前と同様に、この関数を別の関数main()から呼び出し、その関数main()をファイルのメインブロックに追加します。
# Code above omitted 👆
def main():
create_db_and_tables()
if __name__ == "__main__":
main()
# Code above omitted 👆
def main():
create_db_and_tables()
if __name__ == "__main__":
main()
👀 ファイルの完全プレビュー
from sqlmodel import Field, SQLModel, create_engine
class Team(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
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")
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 main():
create_db_and_tables()
if __name__ == "__main__":
main()
from typing import Optional
from sqlmodel import Field, SQLModel, create_engine
class Team(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Optional[int] = Field(default=None, index=True)
team_id: Optional[int] = Field(default=None, foreign_key="team.id")
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 main():
create_db_and_tables()
if __name__ == "__main__":
main()
コードを実行¶
ヒント
コードを実行する前に、database.dbファイルを削除して、最初からやり直すようにしてください。
ここまでのコードを実行すると、データベースファイルdatabase.dbと、定義したばかりのteamとheroテーブルが作成されます。
$ 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コードを見てみましょう。
前に見たように、これらのVARCHAR列は、これらの実験で使用しているデータベースであるSQLiteではTEXTに変換されます。
したがって、最初のSQLは次のように記述することもできます。
CREATE TABLE team (
id INTEGER,
name TEXT NOT NULL,
headquarters TEXT NOT NULL,
PRIMARY KEY (id)
)
そして、2番目のテーブルは次のように記述できます。
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で自由に試してください。
まとめ¶
SQLModelを使用すると、ほとんどの場合、2つのテーブルを接続するために、別のテーブルと列を指す文字列を持つforeign_keyを持つField()のフィールド(列)のみが必要です。
テーブルが作成および接続されたので、次の章でいくつかの行を作成しましょう。🚀