查詢資料

當執行返回資料的 SQL 語句時,使用 database/sql 包中提供的 Query 方法之一。這些方法都會返回一個 RowRows,您可以使用 Scan 方法將其資料複製到變數中。例如,您可以使用這些方法來執行 SELECT 語句。

當執行不返回資料的語句時,可以使用 ExecExecContext 方法。有關更多資訊,請參閱執行不返回資料的語句

database/sql 包提供了兩種執行查詢以獲取結果的方式。

  • 查詢單行QueryRow 最多從資料庫返回單行 Row。有關更多資訊,請參閱查詢單行
  • 查詢多行Query 將所有匹配的行作為 Rows 結構體返回,您的程式碼可以對其進行迴圈。有關更多資訊,請參閱查詢多行

如果您的程式碼將重複執行相同的 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 和驅動程式而異。例如,Postgres 的 pq 驅動程式需要像 $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 和驅動程式而異。例如,Postgres 的 pq 驅動程式需要像 $1 這樣的佔位符而不是 ?

處理錯誤

在迴圈遍歷查詢結果後,務必檢查 sql.Rows 是否存在錯誤。如果查詢失敗,您的程式碼就是透過這種方式發現的。

返回多行的函式

函式 描述
DB.Query
DB.QueryContext
獨立執行查詢。
Tx.Query
Tx.QueryContext
在更大的事務中執行查詢。有關更多資訊,請參閱執行事務
Stmt.Query
Stmt.QueryContext
使用已準備好的語句執行查詢。有關更多資訊,請參閱使用預處理語句
Conn.QueryContext 用於保留連線。有關更多資訊,請參閱管理連線

處理可為空的列值

當列的值可能為 null 時,database/sql 包提供了幾種特殊型別,您可以將其用作 Scan 函式的引數。每種型別都包含一個 Valid 欄位,用於報告值是否非空,以及一個(如果非空)儲存值的欄位。

以下示例中的程式碼查詢客戶名稱。如果名稱值為 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 包參考中檢視每種型別的更多資訊

從列獲取資料

當迴圈遍歷查詢返回的行時,您使用 Scan 將行的列值複製到 Go 值中,如 Rows.Scan 參考中所述。

所有驅動程式都支援一組基本的資料轉換,例如將 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)
}