コンテンツへスキップ

接続されたテーブルの作成

ここでは、異なるテーブルに格納された接続されたデータを扱います。

したがって、最初のステップは、複数のテーブルを作成し、それらを接続して、あるテーブルの各行が別のテーブルの別の行を参照できるようにすることです。

これまで、単一のheroテーブルでヒーローを扱ってきました。ここで、teamテーブルを追加しましょう。

チームテーブルは次のようになります。

id名前本部
1プリベンターズシャープタワー
2Zフォースシスターマーガレットのバー

これらを接続するために、team_idを使用してIDで各チームを指す別の列をヒーローテーブルに追加します。

id名前秘密の名前年齢team_id ✨
1デッドポンドダイブ・ウィルソンnull2 ✨
2スパイダーボーイペドロ・パルケドールnull1 ✨
3ラスティマントミー・シャープ481 ✨

このようにして、heroテーブルの各行はteamテーブルの行を指すことができます。

table relationships

一対多と多対一

ここでは、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と、定義したばかりのteamheroテーブルが作成されます。

$ 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()のフィールド(列)のみが必要です。

テーブルが作成および接続されたので、次の章でいくつかの行を作成しましょう。🚀