Truy vấn dữ liệu

Khi thực thi một câu lệnh SQL trả về dữ liệu, hãy dùng một trong các phương thức Query được cung cấp trong gói database/sql. Mỗi phương thức trả về một Row hoặc Rows mà bạn có thể sao chép dữ liệu vào các biến bằng phương thức Scan. Bạn sẽ dùng các phương thức này để thực thi các câu lệnh SELECT chẳng hạn.

Khi thực thi một câu lệnh không trả về dữ liệu, bạn có thể dùng phương thức Exec hoặc ExecContext thay thế. Để tìm hiểu thêm, xem Thực thi các câu lệnh không trả về dữ liệu.

Gói database/sql cung cấp hai cách để thực thi truy vấn lấy kết quả.

  • Truy vấn một hàng duy nhất - QueryRow trả về tối đa một Row từ cơ sở dữ liệu. Để tìm hiểu thêm, xem Truy vấn một hàng duy nhất.
  • Truy vấn nhiều hàng - Query trả về tất cả các hàng khớp dưới dạng struct Rows mà mã của bạn có thể lặp qua. Để tìm hiểu thêm, xem Truy vấn nhiều hàng.

Nếu mã của bạn sẽ thực thi cùng một câu lệnh SQL nhiều lần, hãy cân nhắc dùng prepared statement. Để tìm hiểu thêm, xem Sử dụng prepared statement.

Chú ý: Đừng dùng các hàm định dạng chuỗi như fmt.Sprintf để tạo câu lệnh SQL! Bạn có thể tạo ra nguy cơ SQL injection. Để tìm hiểu thêm, xem Tránh nguy cơ SQL injection.

Truy vấn một hàng duy nhất

QueryRow lấy tối đa một hàng cơ sở dữ liệu, chẳng hạn khi bạn muốn tra cứu dữ liệu theo một ID duy nhất. Nếu truy vấn trả về nhiều hàng, phương thức Scan sẽ loại bỏ tất cả trừ hàng đầu tiên.

QueryRowContext hoạt động giống QueryRow nhưng nhận thêm tham số context.Context. Để tìm hiểu thêm, xem Hủy các thao tác đang thực hiện.

Ví dụ dưới đây dùng truy vấn để kiểm tra xem có đủ hàng tồn kho để hỗ trợ một giao dịch mua hàng không. Câu lệnh SQL trả về true nếu có đủ, false nếu không. Row.Scan sao chép giá trị boolean trả về vào biến enough thông qua một con trỏ.

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
}

Lưu ý: Các placeholder tham số trong prepared statement thay đổi tùy theo DBMS và driver bạn đang dùng. Ví dụ, pq driver cho Postgres yêu cầu placeholder dạng $1 thay vì ?.

Xử lý lỗi

Bản thân QueryRow không trả về lỗi. Thay vào đó, Scan báo cáo bất kỳ lỗi nào từ quá trình tra cứu và quét kết hợp. Nó trả về sql.ErrNoRows khi truy vấn không tìm thấy hàng nào.

Các hàm trả về một hàng duy nhất

Hàm Mô tả
DB.QueryRow
DB.QueryRowContext
Thực thi truy vấn một hàng độc lập.
Tx.QueryRow
Tx.QueryRowContext
Thực thi truy vấn một hàng trong phạm vi một transaction lớn hơn. Để tìm hiểu thêm, xem Thực thi transaction.
Stmt.QueryRow
Stmt.QueryRowContext
Thực thi truy vấn một hàng bằng prepared statement đã có sẵn. Để tìm hiểu thêm, xem Sử dụng prepared statement.
Conn.QueryRowContext Dùng với các kết nối dành riêng. Để tìm hiểu thêm, xem Quản lý kết nối.

Truy vấn nhiều hàng

Bạn có thể truy vấn nhiều hàng bằng Query hoặc QueryContext, trả về Rows đại diện cho kết quả truy vấn. Mã của bạn lặp qua các hàng trả về bằng Rows.Next. Mỗi lần lặp gọi Scan để sao chép các giá trị cột vào các biến.

QueryContext hoạt động giống Query nhưng nhận thêm tham số context.Context. Để tìm hiểu thêm, xem Hủy các thao tác đang thực hiện.

Ví dụ dưới đây thực thi truy vấn để trả về các album của một nghệ sĩ cụ thể. Các album được trả về trong sql.Rows. Mã dùng Rows.Scan để sao chép các giá trị cột vào các biến được đại diện bởi con trỏ.

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
}

Lưu ý lời gọi defer đến rows.Close. Điều này giải phóng mọi tài nguyên được giữ bởi các hàng dù hàm trả về theo cách nào. Lặp qua toàn bộ các hàng cũng đóng nó một cách ngầm định, nhưng tốt hơn là dùng defer để đảm bảo rows được đóng bất kể điều gì xảy ra.

Lưu ý: Các placeholder tham số trong prepared statement thay đổi tùy theo DBMS và driver bạn đang dùng. Ví dụ, pq driver cho Postgres yêu cầu placeholder dạng $1 thay vì ?.

Xử lý lỗi

Hãy đảm bảo kiểm tra lỗi từ sql.Rows sau khi lặp qua kết quả truy vấn. Nếu truy vấn thất bại, đây là cách mã của bạn phát hiện ra điều đó.

Các hàm trả về nhiều hàng

Hàm Mô tả
DB.Query
DB.QueryContext
Thực thi truy vấn độc lập.
Tx.Query
Tx.QueryContext
Thực thi truy vấn trong phạm vi một transaction lớn hơn. Để tìm hiểu thêm, xem Thực thi transaction.
Stmt.Query
Stmt.QueryContext
Thực thi truy vấn bằng prepared statement đã có sẵn. Để tìm hiểu thêm, xem Sử dụng prepared statement.
Conn.QueryContext Dùng với các kết nối dành riêng. Để tìm hiểu thêm, xem Quản lý kết nối.

Xử lý giá trị cột có thể null

Gói database/sql cung cấp một số kiểu đặc biệt bạn có thể dùng làm tham số cho hàm Scan khi giá trị của cột có thể là null. Mỗi kiểu bao gồm một trường Valid báo cáo xem giá trị có khác null không, và một trường chứa giá trị nếu có.

Đoạn mã trong ví dụ dưới đây truy vấn tên khách hàng. Nếu giá trị tên là null, mã thay thế bằng một giá trị khác để dùng trong ứng dụng.

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
}

Xem thêm về từng kiểu trong tài liệu gói sql:

Lấy dữ liệu từ các cột

Khi lặp qua các hàng được trả về bởi truy vấn, bạn dùng Scan để sao chép các giá trị cột của một hàng vào các giá trị Go, như mô tả trong tài liệu Rows.Scan.

Có một tập hợp chuyển đổi dữ liệu cơ bản được tất cả driver hỗ trợ, chẳng hạn chuyển đổi SQL INT thành Go int. Một số driver mở rộng tập hợp chuyển đổi này; xem tài liệu của từng driver để biết chi tiết.

Như bạn có thể mong đợi, Scan sẽ chuyển đổi từ kiểu cột sang kiểu Go tương tự. Ví dụ, Scan sẽ chuyển đổi từ SQL CHAR, VARCHARTEXT sang Go string. Tuy nhiên, Scan cũng thực hiện chuyển đổi sang kiểu Go khác phù hợp với giá trị cột. Ví dụ, nếu cột là VARCHAR luôn chứa một số, bạn có thể chỉ định kiểu số Go, chẳng hạn int, để nhận giá trị, và Scan sẽ chuyển đổi bằng strconv.Atoi cho bạn.

Để biết thêm chi tiết về các chuyển đổi mà hàm Scan thực hiện, xem tài liệu Rows.Scan.

Xử lý nhiều tập kết quả

Khi thao tác cơ sở dữ liệu của bạn có thể trả về nhiều tập kết quả, bạn có thể lấy chúng bằng cách dùng Rows.NextResultSet. Điều này có thể hữu ích, ví dụ, khi bạn gửi SQL truy vấn riêng biệt nhiều bảng, trả về một tập kết quả cho mỗi bảng.

Rows.NextResultSet chuẩn bị tập kết quả tiếp theo để lời gọi Rows.Next lấy hàng đầu tiên từ tập đó. Nó trả về một giá trị boolean cho biết có tập kết quả tiếp theo hay không.

Đoạn mã trong ví dụ dưới đây dùng DB.Query để thực thi hai câu lệnh SQL. Tập kết quả đầu tiên từ truy vấn đầu tiên trong thủ tục, lấy tất cả các hàng trong bảng album. Tập kết quả tiếp theo từ truy vấn thứ hai, lấy các hàng từ bảng 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)
}