---
# System prepended metadata

title: Flask Extension(03)\_Flask-SQLAlchemy(3)
tags: [flask 2.x, flask extension, book, flask, python, sqlalchemy]

---

# Flask Extension(03)\_Flask-SQLAlchemy(3)
###### tags: `book` `flask` `flask 2.x` `flask extension` `sqlalchemy` `python` 

[Flask-SQLAlchemy](https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/)
[SQLAlchemy](https://docs.sqlalchemy.org/)
[Relationship Configuration](https://docs.sqlalchemy.org/en/14/orm/relationships.html)

關聯式資料庫中最重要的就是關聯的設置，當我們把資料庫當做物件之後應該如何來設置，就是這一邊要說明的部份。比較有趣的是，改版之後的說明文件並沒有看到關聯設置的範例，所以這部份不是看舊版文件就是要看[SQLAlchemy](https://docs.sqlalchemy.org/)，個人是建議看SQLAlchemy的文件就是了。

## 關聯
關聯式資料庫的關聯方式有很多種，一對一、一對多、多對多，我們就把SQLAlchemy的[範例](https://docs.sqlalchemy.org/en/14/orm/relationships.html)調整成用Flask-SQLAlchemy來做說明：
```python=
class Parent(db.Model):
    __tablename__ = "parent_table"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    children = db.relationship("Child")


class Child(db.Model):
    __tablename__ = "child_table"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    parent_id = db.Column(db.Integer, db.ForeignKey("parent_table.id"))
```
* 5：利用`db.relationship`來做關聯設置，參數中放的是關聯目標的類別名稱，案例中為`Child`
* 12：關聯設置需同時設置外來鍵，也就是`db.ForeignKey`，參數中放的是目標資料表中的鍵值，案例中為`parent_table.id` 


上面是根據SQLAlchemy的範例調整過來的，不難發現它的關聯邏輯就是父表對子表，屬一對多，主要利用`db.relationship`來做關聯的設定。

:::info
事實上，如果你想設置一對一的關聯的話，只需要調整為`db.relationship("Child", uselist=False)`即可。
:::

下面我們來做幾個操作，快速瞭解orm帶給我們的一些便利：
```python=
pp = Parent(name='A')
pp.children
[]
```
* 1：新增一筆記錄
* 2：觀察我們設置的關聯是否正確

因為我們的資料表是新建的，而且裡面都沒有任何資料，所以當我們去看`pp.children`的時候會得到一個空的`list`

```python=
cc = Child(name='A_C')
pp.children.append(cc)
with app.app_context():
    db.session.add(pp)
    db.session.commit()
```
* 1：新增一筆記錄
* 2：將新增的資料寫入`Partent`
* 3：資料寫入資料表，並完成交易記錄

這邊我們把產生的`Child`做為一個物件寫入`Parent`設置的關聯屬性，也就是上面我們說的那個空的`list`，然後完成這筆交易記錄，記得，flask-sqlalchemy的交易記錄只能寫在flask的context之中。然後回頭看一下我們的資料表：
![](https://hackmd.io/_uploads/r18QvqjLi.png)

可以先看一下，基本上`parent_table`有資料不稀奇，這很正常，不過如果你去看`child_table`的話就會發現：
![](https://hackmd.io/_uploads/rkKJdciUi.png)

經過我們剛剛在`Parent`把`Child`給`append`進去那個關聯設置之後，一次性的`add`可以在兩張表同時生成資料，而且連關聯用的鍵值也同時記錄了。這就是orm給我們的一個便利性。

上面的設置是一對多，反過來就是多對一，互相認識一下，這只需要做一個小調整：
```python=
class Parent(db.Model):
    __tablename__ = "parent_table"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    children = db.relationship("Child", back_populates='parent')


class Child(db.Model):
    __tablename__ = "child_table"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    parent_id = db.Column(db.Integer, db.ForeignKey("parent_table.id"))
    parent = db.relationship('Parent', back_populates='children')
```

上面程式碼可以看的出來關聯設置上的差異，利用`back_populates.back_populates`來做為雙邊關聯的設置。

:::info
官方文件中有提到，`backref`是屬於遺留下來的接口，建議還是用上面的設置方式較為直觀。
:::

一樣的，你可以利用`select Child`的方式來確認是不是能取得它的相對應的`Parent`：
```python=
stms = db.select(Child)
results = db.session.execute(stms)
result = results.fetchone()[0]
result.parent.name
>>> 'A'
result.name
>>> 'A_C'
```

:::info
這邊可能要稍微注意一下，使用`fetchone`與`scalar`得到的回傳是不一樣的，這部份可以參考官方[文件說明](https://docs.sqlalchemy.org/en/14/orm/queryguide.html#selecting-orm-entities-and-attributes)。最主要是`fetchone`、`fetchall`回傳的是sqlalchemy的row；而`scalar`、`scalars`則是回傳實例，親手做一次就會有感覺。
:::

## 結論
大致上我們已經知道如何設置一對一、一對多、多對一的關聯，當然還有多對多的關聯，不過這部份不急，後續實作過程中如果有緣就會使用到，一篇文章如果有太多資訊就會造成消化不良以及閱讀障礙。

實務上在資料庫的操作中還會有刪除、更新的需求，刪除的話我們可能會希望當`Child`沒有資料的時候就刪除相對應的`Parent`，這一樣的，後續實作中我們就會明白如何處理。