查詢資料
當執行返回資料的 SQL 語句時,使用 database/sql
包中提供的 Query
方法之一。這些方法都會返回一個 Row
或 Rows
,您可以使用 Scan
方法將其資料複製到變數中。例如,您可以使用這些方法來執行 SELECT
語句。
當執行不返回資料的語句時,可以使用 Exec
或 ExecContext
方法。有關更多資訊,請參閱執行不返回資料的語句。
database/sql
包提供了兩種執行查詢以獲取結果的方式。
- 查詢單行 –
QueryRow
最多從資料庫返回單行Row
。有關更多資訊,請參閱查詢單行。 - 查詢多行 –
Query
將所有匹配的行作為Rows
結構體返回,您的程式碼可以對其進行迴圈。有關更多資訊,請參閱查詢多行。
如果您的程式碼將重複執行相同的 SQL 語句,請考慮使用預處理語句。有關更多資訊,請參閱使用預處理語句。
注意:不要使用像 fmt.Sprintf
這樣的字串格式化函式來組裝 SQL 語句!您可能會引入 SQL 注入風險。有關更多資訊,請參閱避免 SQL 注入風險。
查詢單行
QueryRow
最多檢索單個數據庫行,例如當您想透過唯一 ID 查詢資料時。如果查詢返回多行,Scan
方法會丟棄除第一行之外的所有行。
QueryRowContext
的工作方式與 QueryRow
類似,但帶有一個 context.Context
引數。有關更多資訊,請參閱取消正在進行的操作。
以下示例使用查詢來查詢是否有足夠的庫存來支援購買。如果庫存足夠,SQL 語句返回 true
,否則返回 false
。Row.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
|
用於保留連線。有關更多資訊,請參閱管理連線。 |
查詢多行
您可以使用 Query
或 QueryContext
查詢多行,它們返回一個表示查詢結果的 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 CHAR
、VARCHAR
和 TEXT
轉換為 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)
}