# SQLAlchemy 2.0(02)建置資料表 ###### tags: `python` `sqlalchemy` [官方文件](https://docs.sqlalchemy.org/en/20/index.html) 透過[SQLAlchemy 2.0(01)概觀](https://hackmd.io/@shaoeChen/B1CJAsHJ3)的簡單說明大致對SQLAlchemy有初步的觀念,接下來就要進入下一個議題,也就是Database Metadata。 :::info The foundation for these queries are Python objects that represent database concepts like tables and columns. These objects are known collectively as database metadata. ::: 官方文件說明來看,大致就是我們所查詢像是資料庫的資料表、欄位這種Python物件就統稱為database metadata。 ## 建置資料表(Core) Core的記錄是保存在由很多欄位所組成的資料表中,資料表則是放置在集合中,也就是`MetaData`: ```python= from sqlalchemy import MetaData metadata_obj = MetaData() ``` 有了這個`MetaData`,我們就可以開始建置資料表: ```python= from sqlalchemy import Table, Column, Integer, String user_table = Table( "user_account", metadata_obj, Column("id", Integer, primary_key=True), Column("name", String(30)), Column("fullname", String), ) ``` 上面的範例中我們定義一個`user_table`,它的資料表名稱為`user_account`,然後有三個欄位,`id, name, fullname`,其中以`id`做為pk值,也可以看的到剛剛所宣告的`metadata_obj`也做為定義這個`Table`的其中一個參數。 我們可以利用`Table.c`來觀察我們所設置的相關欄位的資訊: ```python= >>> user_table.c.name Column('name', String(length=30), table=<user_account>) >>> user_table.c.keys() ['id', 'name', 'fullname'] ``` 這可以讓我們觀察到所設置的資料表的欄位類型跟長度,回頭看上面的資料表定義,確實我們就是把`name`的長度設置為30。並且你也可以利用`metadata_obj`來取得該集名中的所有資料表資訊: ```python= >>> metadata_obj.sorted_tables [Table('user_account', MetaData(), Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False), Column('name', String(length=30), table=<user_account>), Column('fullname', String(), table=<user_account>), schema=None)] ``` 關聯式資料庫的資料表中也可以定義外來鍵,也就是Foregin Key: ```python= from sqlalchemy import ForeignKey address_table = Table( "address", metadata_obj, Column("id", Integer, primary_key=True), Column("user_id", ForeignKey("user_account.id"), nullable=False), Column("email_address", String, nullable=False), ) ``` 上面我們再定義一個資料表,設置`user_id`為fk,並且不允許為null。 定義好資料表之後就要實際的生成資料表,所有的資料表資訊都是保存在`MetaData`,也就是我們所宣告的資料集集合`metadata_obj`裡面,因此我們是可以直接利用這個物件來生成資料表的: ```python= metadata_obj.create_all(engine) ``` ## 建置資料表(ORM) 如果是ORM在定義資料表的話就有些許的不同,主要是利用`DeclarativeBase`,但這並不代表它就不存在`MetaData`: ```python= from sqlalchemy.orm import DeclarativeBase class Base(DeclarativeBase): pass ``` 事實上,`DeclarativeBase`的屬性中就存在著`metadata`,這就是`MetaData`。 :::info 可參考[官方文件](https://docs.sqlalchemy.org/en/20/orm/mapping_api.html#sqlalchemy.orm.DeclarativeBase.metadata)說明 ::: 現在就利用剛剛所設置的類別`Base`來為剛剛所設置的兩張資料表定義兩個ORM mapped classed: ```python= from typing import List from typing import Optional from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column from sqlalchemy.orm import relationship class User(Base): __tablename__ = "user_account" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(30)) fullname: Mapped[Optional[str]] addresses: Mapped[List["Address"]] = relationship(back_populates="user") def __repr__(self) -> str: return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})" class Address(Base): __tablename__ = "address" id: Mapped[int] = mapped_column(primary_key=True) email_address: Mapped[str] user_id = mapped_column(ForeignKey("user_account.id")) user: Mapped[User] = relationship(back_populates="addresses") def __repr__(self) -> str: return f"Address(id={self.id!r}, email_address={self.email_address!r})" ``` * 8、17行的`__tablename__`是建置過程中生成`Table`的依據,總之就是資料表名稱了 * 為了能夠指向`Table`內的欄位,利用`mapped_column`來建構,結合型別注釋的`Mapped`來生成`Column` * 可以利用python型別來對應資料庫型別,所以看的到`Mapped[str]`對應資料庫的string * 21行則是設置兩者之間的關聯,`Mapped[User]`指向另一個類別 * 13、22行就單純設置一個給人看的訊息 這兩個類別現在是ORM Mapped Classes,這樣的生成方式又稱為Declarative Table Configuration。可以看的出來整個設置的表達式跟1.4以前的版本有很大的差異。當然如果你習慣過去的寫法的話還是可以這麼做的: ```python= class User(Base): __tablename__ = "user_account" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(30), nullable=False) fullname = mapped_column(String) addresses = relationship("Address", back_populates="user") # ... definition continues ``` 值得注意的是,不是`Column`,而是`mapped_column`。 當我們整個架構都設置好要真正的生成資料表的時候就可以執行: ```python= Base.metadata.create_all(engine) ``` 不過因為我們剛剛的練習中已經有生成資料表了,所以這次的執行並不會再次的生成資料表。 ## Table Reflection 剛剛大致的操作都是一路的建置,如果有個什麼需求需要你拉出目前某個`Table`的狀況的話,那就可以利用這個table reflection的操作來處理: ```python= some_table = Table("some_table", metadata_obj, autoload_with=engine) ``` `some_table`是最一開始的範例所設置的資料表,裡面有`x, y`兩個欄位。這邊做的是,我們希望從`metadata_obj`這個集合中取得資料表為`some_table`的資料,結果如下: ``` Table('some_table', MetaData(), Column('x', INTEGER(), table=<some_table>), Column('y', INTEGER(), table=<some_table>), schema=None) ```