> ## Documentation Index
> Fetch the complete documentation index at: https://datagen.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# 從程式碼查詢資料庫

> 以唯讀角色、存在連線保險庫中的 PG* 密鑰與 pandas，從 Agent 或應用（App）的程式碼直接查詢 Postgres

已發布的 [分析資料](/zh/data/overview) 是提供資料給 Agent 的建議方式。當程式碼需要直接查詢資料庫時 -- 例如臨時 SQL、尚未建模的資料表、長時間執行的應用 -- 請從沙盒以**唯讀**角色連線。本指南說明角色、承載連線資訊的 `PG*` 密鑰、所需套件與查詢程式碼。

本指南建立在兩個基礎設定頁面之上：

* [連線保險庫](/zh/foundation/vault) -- 密鑰如何變成環境變數，以及工作區共用與個人的差別。
* [沙箱環境](/zh/foundation/sandbox-environments) -- 如何把套件預先建置進映像檔。

## 前置需求

<Note>
  本指南專門針對 **PostgreSQL**（本機、Neon、RDS 或任何相容 Postgres 的資料庫）。唯讀角色的 SQL、`psycopg2` 驅動程式與連線 URL 格式都是 Postgres 專屬 -- 其他資料庫引擎（MySQL、SQL Server、BigQuery 等）使用不同的角色、驅動程式與語法。
</Note>

你需要：

* 一個 **PostgreSQL** 資料庫，以及可建立角色的超級使用者或擁有者帳號。
* 執行 SQL 的方式（`psql`、`pgAdmin` 或供應商的 SQL 編輯器）。
* DataGen 中一個會執行查詢程式碼的 Agent 或應用。

<Note>
  託管資料庫（Neon、RDS、Supabase）本來就能透過 TLS 連線，不需要通道 -- 任何具有對外網路的沙盒都能直接連線。只有要對外開放你自己電腦上的資料庫時，才需要通道。
</Note>

***

## 1. 建立唯讀使用者

Agent 以只能 `SELECT` 的角色連線 -- 永遠不能寫入、刪除或修改結構。這能限制影響範圍：憑證外洩時最多只會暴露資料，絕不會變更或破壞任何東西。

<Tabs>
  <Tab title="SQL（任何 Postgres）">
    以**超級使用者或資料庫擁有者**身分，連到 `<DB>` 後執行：

    ```sql create_readonly_role.sql theme={null}
    -- 建立唯讀登入角色。
    CREATE ROLE <RO_USER> WITH LOGIN PASSWORD '<RO_PASSWORD>';

    -- 授予現有資料表的唯讀權限。
    GRANT CONNECT ON DATABASE <DB> TO <RO_USER>;
    GRANT USAGE ON SCHEMA public TO <RO_USER>;
    GRANT SELECT ON ALL TABLES IN SCHEMA public TO <RO_USER>;

    -- 選用：也能讀取之後建立的資料表（例如未來的 migration）。
    ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO <RO_USER>;
    ```

    <Tip>
      最後一行為選用。`GRANT SELECT ON ALL TABLES` 只涵蓋目前已存在的資料表；`ALTER DEFAULT PRIVILEGES` 會把讀取權限延伸到之後建立的資料表。請以擁有這些資料表的角色執行此腳本。
    </Tip>

    執行腳本：

    <CodeGroup>
      ```bash Local Docker theme={null}
      docker exec -i <POSTGRES_CONTAINER> \
        psql -U postgres -d <DB> < create_readonly_role.sql
      ```

      ```bash Cloud (psql) theme={null}
      psql "postgresql://<OWNER_ROLE>:<owner_pw>@<HOST>:<PORT>/<DB>?sslmode=require" \
        -f create_readonly_role.sql
      ```

      ```text Neon theme={null}
      把 SQL 貼到 Neon Console -> SQL Editor 並執行。
      ```
    </CodeGroup>
  </Tab>

  <Tab title="pgAdmin">
    <Steps>
      <Step title="建立登入角色">
        在左側樹狀清單中，對 **Login/Group Roles**（位於*伺服器*下，而非資料庫下）按右鍵，選擇 **Create -> Login/Group Role...**

        * **General** 分頁 -> *Name*：`<RO_USER>`
        * **Definition** 分頁 -> *Password*：`<RO_PASSWORD>`
        * **Privileges** 分頁 -> *Can login?* = **Yes**（其他保持關閉）
        * **Save**
      </Step>

      <Step title="授予讀取權限">
        在 Query Tool 中執行 `GRANT` 陳述式：對你的 **`<DB>`** 資料庫按右鍵 -> **Query Tool**，貼上另一個分頁中的授權語句並執行。
      </Step>
    </Steps>
  </Tab>
</Tabs>

**確認它真的是唯讀：**

```bash theme={null}
# 讀取成功：
psql "<connection-url>" -c 'SELECT count(*) FROM "SomeTable";'
# 寫入被拒絕：
psql "<connection-url>" -c 'CREATE TABLE nope (id int);'
#   -> ERROR: permission denied for schema public
```

***

## 2. 把連線資訊存成 PG\* 密鑰

把連線資訊存成個別的 **libpq `PG*` 變數**。Postgres 驅動程式會自動讀取它們，因此你的程式碼不需要組合或解析 URL，含特殊字元的密碼也不需要編碼。

在 Agent 或應用綁定的 [連線保險庫](/zh/foundation/vault) 中，以 **新增憑證** -> **密鑰** 逐一新增下列各列，**目標** 設為變數名稱，**類型** 依範圍設定：

| 目標（環境變數） | 類型 | 範例值 |
| - | - | - |
| `PGHOST` | **與工作區共用** | `10.10.0.15`（私有 IP 或內部 DNS） |
| `PGPORT` | **與工作區共用** | `5432` |
| `PGDATABASE` | **與工作區共用** | `analytics` |
| `PGSSLMODE` | **與工作區共用** | `prefer`（若伺服器有 TLS 請用 `require`） |
| `PGUSER` | **個人** | `jsmith_ro` -- 每位成員自己的角色 |
| `PGPASSWORD` | **個人** | 每位成員自己的密碼 |

主機、埠號、資料庫與 sslmode 對每個人都一樣，因此由管理員以**工作區共用**設定一次。使用者與密碼屬於每位成員，因此設為**個人**：每位成員在該列點選 **連接** 並輸入自己的值。

<Note>
  請完全使用 libpq 的名稱。DataGen 會把目標轉為大寫，所以 `pghost` 也會變成 `PGHOST`；但像 `PSQL_PASSWORD` 這種不同的名稱，libpq 永遠不會讀取。
</Note>

**主機**會隨程式碼執行的位置而改變：

| 程式碼執行位置 | `PGHOST` | `PGPORT` |
| - | - | - |
| 你的電腦 -> 本機 Docker Postgres | `localhost` | `5432`（對外發布的埠號；用 `docker ps` 確認） |
| 同一個 Docker 網路中的容器 | `postgres` | `5432` |
| 公司私有網路中的資料庫 | 私有 IP / 內部 DNS（例如 `10.10.0.15`） | `5432` |
| 任何地方 -> Neon / RDS / 雲端 | 供應商主機名稱（例如 `ep-...neon.tech`） | 供應商埠號 |

<Tip>
  Neon 的連線資訊位於 **Console -> 你的專案 -> Connection Details**。Agent 與沙盒工作負載請使用 **pooled**（`-pooler`）主機。
</Tip>

<Warning>
  **可連線性。** 像 `10.10.0.15` 這樣的私有位址，只有在**同一個網路**中的沙盒才能解析。地端版 DataGen 在你的網路內執行沙盒，因此能直接連到資料庫 -- 不需要通道。從雲端沙盒連線時，資料庫必須透過防火牆允許清單、VPC peering 或通道才能連到。
</Warning>

<AccordionGroup>
  <Accordion title="替代方案：單一 DATABASE_URL 密鑰">
    想只用一個值？新增一個 **密鑰**，**目標** 設為 `DATABASE_URL`，值為由相同元素組成的完整 URL：

    ```text theme={null}
    postgresql://<RO_USER>:<RO_PASSWORD>@<HOST>:<PORT>/<DB>
    ```

    託管資料庫會加上 TLS 參數，例如 Neon：

    ```text theme={null}
    postgresql://<RO_USER>:<RO_PASSWORD>@<HOST>/<DB>?sslmode=require&channel_binding=require
    ```

    以 `os.environ["DATABASE_URL"]` 讀取。若 URL 內含個人憑證，**類型** 設為 **個人**，否則設為 **與工作區共用**。

    <Warning>
      若密碼含有 URL 特殊字元（`@ : / ? # %`），請進行 URL 編碼 -- `@` 會變成 `%40`。`PG*` 做法完全不會遇到這個問題，因為 libpq 會照字面讀取每個值。
    </Warning>
  </Accordion>
</AccordionGroup>

***

## 3. 預先建置查詢套件

透過 [沙箱環境](/zh/foundation/sandbox-environments) 為沙盒提供 `pandas`、`sqlalchemy` 與 `psycopg2-binary`。內建的 **Data Engineering** 範本已包含這些套件（另含 `duckdb`、`polars` 與 `pyarrow`）。以它建立環境，等待 **就緒** 標籤出現後，再於 Agent 或應用上選取它。

| 套件管理工具 | 套件 | 原因 |
| - | - | - |
| `pip` | `pandas`、`sqlalchemy`、`psycopg2-binary` | 查詢工具組 -- `-binary` wheel 內含 `libpq` |
| `apt` | `libpq-dev` | 只有改用非 binary 的 `psycopg2` 時才需要 |

<Note>
  **Alpine / musl 基礎映像檔：** `psycopg2-binary` 只提供 glibc wheel。在 musl 映像檔上，請改用 `psycopg[binary]`（psycopg 3 提供 musl wheel），並以 `postgresql+psycopg://` URL 連線，而非 `postgresql+psycopg2://`。
</Note>

***

## 4. 查詢

```python theme={null}
import pandas as pd
from sqlalchemy import create_engine

# 連線資訊來自連線保險庫中設定的 PG* 環境變數 -- 沒有任何寫死的值。
# （若改用單一密鑰的做法，請用 create_engine(os.environ["DATABASE_URL"])。）
engine = create_engine(
    "postgresql+psycopg2://",
    pool_pre_ping=True,   # 在閒置空檔間保持連線池中的連線可用
)

df = pd.read_sql('SELECT * FROM "SomeTable" LIMIT 100;', engine)
print(df.shape)

engine.dispose()   # 完成後釋放連線池
```

### 快速端到端檢查

```python theme={null}
import psycopg2
with psycopg2.connect(connect_timeout=15) as c:   # PGHOST/PGUSER/PGPASSWORD 來自環境變數
    cur = c.cursor()
    cur.execute("SELECT current_user, current_database();")
    print(cur.fetchone())   # 確認你是「誰」、連到「哪裡」-- 能抓出用錯角色的問題
```

***

## 疑難排解

<AccordionGroup>
  <Accordion title="執行失敗並顯示「Vault not ready」">
    執行身分的成員尚未填入自己的個人 `PGUSER` / `PGPASSWORD`。請他在連線保險庫中對每一列點選 **連接**。請見 [連線保險庫常見問題](/zh/foundation/vault#常見問題)。
  </Accordion>

  <Accordion title="ModuleNotFoundError: sqlalchemy / psycopg2">
    Agent 或應用沒有綁定含查詢套件的環境，或該環境仍在建置中。請到 [沙箱環境](/zh/foundation/sandbox-environments) 檢查標籤。
  </Accordion>

  <Accordion title="為什麼要 pool_pre_ping=True">
    連線池中的連線可能在兩次查詢之間失效 -- 伺服器的閒置逾時把它斷開、資料庫重新啟動，或無伺服器後端（Neon）進入暫停。`pool_pre_ping` 會在重複使用連線前送出一個成本很低的 `SELECT 1`，把失效連線造成的錯誤變成無感的重新連線。對任何長時間執行或會間歇閒置的沙盒來說，都是划算的保險。
  </Accordion>

  <Accordion title="第一次查詢很慢（1-3 秒）">
    Neon 資料庫閒置一段時間後的第一次連線，會在運算資源喚醒時觸發冷啟動。請設定寬鬆的 `connect_timeout` -- 這不是網路故障，下一次查詢就會很快。
  </Accordion>

  <Accordion title="出現資料表「does not exist」錯誤">
    在原始 SQL 中，PascalCase 的資料表名稱要加引號：`'SELECT * FROM "Workspace"'`。沒有引號時，Postgres 會把識別字轉為小寫，並回報資料表不存在。
  </Accordion>

  <Accordion title="連線逾時">
    沙盒連不到該主機。私有 IP 只能從同一網路中的沙盒連線；從雲端沙盒連線時，請把資料庫加入允許清單，或使用通道或 peering。
  </Accordion>
</AccordionGroup>

***

## 安全檢查清單

* 沙盒以**唯讀**角色連線，絕不使用擁有者角色。
* 連線資訊存放在**連線保險庫密鑰**（`PG*` 或 `DATABASE_URL`）中，絕不放在程式碼或 git 裡。
* 已設定 `ALTER DEFAULT PRIVILEGES`，讓之後建立的資料表也能讀取。
* （Postgres 14 及更早版本）已從 `PUBLIC` 撤銷 `CREATE`，讓此角色無法寫入。
* 雲端資料庫使用 `sslmode=require`（支援時加上 `channel_binding=require`）。
* 只要密碼曾被貼到聊天、日誌或工單中，就[輪替](/zh/foundation/vault)它。

## 下一步

<CardGroup cols={2}>
  <Card title="檔案" icon="folder" href="/zh/files/overview">
    上傳 Agent 可讀取的文件與試算表。
  </Card>

  <Card title="在 Agent 中使用資料" icon="robot" href="/zh/data/use">
    把已發布的分析資料掛載到 Agent 與應用，取代原始 SQL。
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.