查詢資料

當執行會傳回資料的 SQL 語句時,請使用 database/sql 套件中提供的其中一個 Query 方法。它們每個會傳回一個 RowRows,你的程式碼可以使用 Scan 方法將其資料複製到變數。例如,你可以使用這些方法來執行 SELECT 語句。

當執行不會傳回資料的語句時,你可以改用 ExecExecContext 方法。如需更多資訊,請參閱 執行不會傳回資料的語句

database/sql 套件提供兩種執行可取得結果的查詢的方法。

如果您的程式碼將重複執行相同的 SQL 語句,請考慮使用已準備好的語句。關於更多資訊,請參閱 使用已準備好的語句

請注意:不要使用例如 fmt.Sprintf 之類的字串格式化函數組裝 SQL 語句!您可能會引入 SQL 內嵌風險。關於更多資訊,請參閱 避免 SQL 內嵌風險

查詢單一列

QueryRow 最多擷取單一資料庫列,例如您想要透過唯一 ID 查詢資料時。如果查詢回傳多列,Scan 方法會捨棄第一列以外的所有資料。

QueryRowContext 的運作方式與 QueryRow 相同,但多一個 context.Context 參數。關於更多資訊,請參閱 取消進行中的作業

下列範例使用查詢找出是否有足夠庫存來支援購買。如果庫存足夠,SQL 語句會回傳 true,否則會回傳 falseRow.Scan 會透過指標將布林回傳值複製到 enough 變數中。

func canPurchase(id int, quantity int) (bool, error) {
    var enough bool
    // Query for a value based on a single row.
    if err := db.QueryRow("SELECT (quantity >= ?) from album where id = ?",
        quantity, id).Scan(&enough); err != nil {
        if err == sql.ErrNoRows {
            return false, fmt.Errorf("canPurchase %d: unknown album", id)
        }
        return false, fmt.Errorf("canPurchase %d: %v", id, err)
    }
    return enough, nil
}

請注意:已準備好語句中的參數佔位符會根據您使用的 DBMS 和驅動程式而有所不同。例如,pq 驅動程式 for Postgres 需要類似於 $1 而不是 ? 的佔位符。

處理錯誤

QueryRow 本身不會回傳錯誤。相反地,Scan 會回報結合查詢和掃描所得的任何錯誤。當查詢找不到列時,它會回傳 sql.ErrNoRows

用於回傳單一列的函數

函數 說明
DB.QueryRow
DB.QueryRowContext
獨立執行單一列查詢。
Tx.QueryRow
Tx.QueryRowContext
在較大範圍的交易中執行單一列查詢。關於更多資訊,請參閱 執行交易
Stmt.QueryRow
Stmt.QueryRowContext
使用已準備好的語句執行單一列查詢。關於更多資訊,請參閱 使用已準備好的語句
Conn.QueryRowContext 供保留的連線使用。關於更多資訊,請參閱 管理連線

查詢多列

您可以使用 QueryQueryContext 來查詢多列,這兩個方法會回傳代表查詢結果的 Rows。您的程式碼會使用 Rows.Next 來反覆執行回傳的列。每一次反覆執行都會呼叫 Scan 將欄位值複製到變數中。

QueryContext 的運作方式與 Query 相似,但附帶 context.Context 參數。更多資訊,請參閱 取消進行中的操作

下列範例執行查詢,以傳回特定藝術家的專輯。專輯會傳回至 sql.Rows 中。程式碼使用 Rows.Scan 將欄位值複製到指標表示的變數中。

func albumsByArtist(artist string) ([]Album, error) {
    rows, err := db.Query("SELECT * FROM album WHERE artist = ?", artist)
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    // An album slice to hold data from returned rows.
    var albums []Album

    // Loop through rows, using Scan to assign column data to struct fields.
    for rows.Next() {
        var alb Album
        if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist,
            &alb.Price, &alb.Quantity); err != nil {
            return albums, err
        }
        albums = append(albums, alb)
    }
    if err = rows.Err(); err != nil {
        return albums, err
    }
    return albums, nil
}

請注意遞延呼叫 rows.Close。這會釋放由該列所保持的任何資源,不論函式如何傳回。循序巡覽所有列也會暗中關閉,但最好使用 defer 來確保 rows 確實會關閉。

請注意:已準備好語句中的參數佔位符會根據您使用的 DBMS 和驅動程式而有所不同。例如,pq 驅動程式 for Postgres 需要類似於 $1 而不是 ? 的佔位符。

處理錯誤

循序巡覽查詢結果後,請務必檢查 sql.Rows 的錯誤。如果查詢失敗,您的程式碼會透過此方式找出原因。

用於傳回多個列的函式

函數 說明
DB.Query
DB.QueryContext
獨立執行查詢。
Tx.Query
Tx.QueryContext
在較大型交易內執行查詢。更多資訊,請參閱 執行交易
Stmt.Query
Stmt.QueryContext
使用已準備好的陳述式執行查詢。更多資訊,請參閱 使用已準備好的陳述式
Conn.QueryContext 供保留的連線使用。關於更多資訊,請參閱 管理連線

處理可為 Null 的欄位值

database/sql 套件提供許多特殊類型,可於欄位值可能為 Null 時,用作 Scan 函式的參數。每個類型都包含 Valid 欄位,顯示值是否非 Null,如果是非 Null 則包含該值。

下列範例中的程式碼查詢客戶名稱。如果名稱值為 Null,程式碼則以其他值取代,以供應用程式使用。

var s sql.NullString
err := db.QueryRow("SELECT name FROM customer WHERE id = ?", id).Scan(&s)
if err != nil {
    log.Fatal(err)
}

// Find customer name, using placeholder if not present.
name := "Valued Customer"
if s.Valid {
    name = s.String
}

sql 套件參考中,進一步了解各個類型

從欄位取得資料

在循序巡覽查詢傳回的列時,Rows.Scan 說明將列的欄位值複製到 Go 值中。

基本資料轉換設定由所有驅動程式支援,例如轉換 SQL INT 成 Go int。部分驅動程式會延伸這個轉換設定;詳情請參閱各個別驅動程式的文件。

正如你所預期的那樣,Scan 會將資料欄類型轉換為類似的 Go 類型。例如,Scan 會將 SQL CHARVARCHARTEXT 轉換為 Go string。然而,Scan 也會轉換為另一個與資料欄值很吻合的 Go 類型。例如,如果資料欄是將永遠包含一個數字的 VARCHAR,你可以指定一個數值 Go 類型,例如 int,來接收值,而且 Scan 會使用 strconv.Atoi 為你轉換。

如需有關由 Scan 函式做出的轉換的更多資訊,請參閱 Rows.Scan 參考。

處理多個結果集合

當你的資料庫操作可能會傳回多個結果集合時,你可以使用 Rows.NextResultSet 來擷取這些集合。例如,當你傳送 SQL 分別查詢多個資料表並為每個資料表傳回結果集合時,會很有用。

Rows.NextResultSet 準備下一個結果集合,這樣就可以呼叫 Rows.Next 來擷取該下一個集合的第一列。它傳回一個布林值,指出是否還有任何下一個結果集合。

以下範例中的程式碼使用 DB.Query 來執行兩個 SQL 陳述式。第一個結果集合來自該程序中的第一個查詢,擷取 album 資料表中的所有列。下一個結果集合來自第二個查詢,擷取 song 資料表中的列。

rows, err := db.Query("SELECT * from album; SELECT * from song;")
if err != nil {
    log.Fatal(err)
}
defer rows.Close()

// Loop through the first result set.
for rows.Next() {
    // Handle result set.
}

// Advance to next result set.
rows.NextResultSet()

// Loop through the second result set.
for rows.Next() {
    // Handle second set.
}

// Check for any error in either result set.
if err := rows.Err(); err != nil {
    log.Fatal(err)
}