데이터 조회하기
데이터 조회하기 (Querying for data)
데이터를 반환하는 SQL 문을 실행할 때는 database/sql 패키지가 제공하는 Query 메서드 중 하나를 사용하세요. 이 메서드들은 각각 Row 또는 Rows를 반환하며, 그 데이터는 Scan 메서드로 변수에 복사할 수 있어요. 예를 들어 SELECT 문을 실행할 때 이런 메서드들을 쓰게 됩니다.
데이터를 반환하지 않는 문을 실행할 때는 대신 Exec나 ExecContext 메서드를 사용할 수 있어요. 자세한 내용은 Executing statements that don't return data를 참고하세요.
출처: Go 공식 문서
database/sql 패키지는 결과를 조회하는 두 가지 방법을 제공합니다.
- 단일 행 조회 –
QueryRow는 데이터베이스에서 최대 한 개의Row를 반환해요. 자세한 내용은 Querying for a single row 참고. - 여러 행 조회 –
Query는 일치하는 모든 행을Rows구조체로 반환하며, 코드가 그것을 반복(loop)할 수 있어요. 자세한 내용은 Querying for multiple rows 참고.
코드가 같은 SQL 문을 반복해서 실행한다면 준비된 문(prepared statement)을 사용하는 걸 고려해 보세요. 자세한 내용은 Using prepared statements 참고.
주의: fmt.Sprintf 같은 문자열 형식 함수로 SQL 문을 조립하지 마세요! SQL 인젝션 위험을 만들 수 있어요. 자세한 내용은 Avoiding SQL injection risk 참고.
단일 행 조회하기
QueryRow는 최대 한 개의 데이터베이스 행을 가져옵니다. 예를 들어 고유한 ID로 데이터를 찾을 때처럼요. 쿼리가 여러 행을 반환하면 Scan 메서드는 첫 행만 남기고 나머지를 버립니다.
QueryRowContext는 QueryRow와 비슷하게 동작하지만 context.Context 인자를 받습니다. 자세한 내용은 Canceling in-progress operations 참고.
다음 예시는 구매를 지원할 만큼 재고가 있는지 알아보는 쿼리를 사용합니다. SQL 문은 재고가 충분하면 true, 부족하면 false를 반환해요. Row.Scan은 포인터를 통해 bool 반환값을 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.QueryRowDB.QueryRowContext |
단독으로 단일 행 쿼리 실행 |
Tx.QueryRowTx.QueryRowContext |
더 큰 트랜잭션 안에서 단일 행 쿼리 실행. 자세한 내용은 Executing transactions 참고 |
Stmt.QueryRowStmt.QueryRowContext |
이미 준비된 문으로 단일 행 쿼리 실행. 자세한 내용은 Using prepared statements 참고 |
Conn.QueryRowContext |
예약된 연결과 함께 사용. 자세한 내용은 Managing connections 참고 |
여러 행 조회하기
Query나 QueryContext로 여러 행을 조회할 수 있는데, 이들은 쿼리 결과를 나타내는 Rows를 반환합니다. 코드는 Rows.Next로 반환된 행들을 반복합니다. 각 반복마다 Scan을 호출해서 열 값을 변수에 복사해요.
QueryContext는 Query와 비슷하게 동작하지만 context.Context 인자를 받습니다. 자세한 내용은 Canceling in-progress operations 참고.
다음 예시는 지정된 아티스트의 앨범을 반환하는 쿼리를 실행합니다. 앨범은 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에 대한 지연(deferred) 호출을 주목하세요. 이는 함수가 어떻게 반환되든 행이 보유한 리소스를 해제합니다. 행을 끝까지 반복하면 암시적으로 닫히기도 하지만, 어떤 경우든 rows가 닫히도록 defer를 쓰는 게 더 좋아요.
참고: 준비된 문의 파라미터 자리 표시자는 사용하는 DBMS와 드라이버에 따라 달라져요. 예를 들어 Postgres용 pq 드라이버는 ? 대신 $1 같은 자리 표시자를 요구합니다.
에러 처리
쿼리 결과를 반복한 뒤에는 반드시 sql.Rows의 에러를 확인하세요. 쿼리가 실패했다면 이렇게 해야 코드가 그 사실을 알 수 있어요.
여러 행을 반환하는 함수
| 함수 | 설명 |
|---|---|
DB.QueryDB.QueryContext |
단독으로 쿼리 실행 |
Tx.QueryTx.QueryContext |
더 큰 트랜잭션 안에서 쿼리 실행. 자세한 내용은 Executing transactions 참고 |
Stmt.QueryStmt.QueryContext |
이미 준비된 문으로 쿼리 실행. 자세한 내용은 Using prepared statements 참고 |
Conn.QueryContext |
예약된 연결과 함께 사용. 자세한 내용은 Managing connections 참고 |
nullable 열 값 처리하기
열 값이 null일 수 있을 때 Scan 함수의 인자로 쓸 수 있는 특별한 타입을 database/sql 패키지가 몇 가지 제공합니다. 각 타입은 값이 null이 아닌지 보고하는 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라면 int 같은 숫자 Go 타입을 지정해서 값을 받을 수 있고, Scan이 strconv.Atoi로 변환해 줍니다.
Scan 함수가 수행하는 변환에 대한 자세한 내용은 Rows.Scan 레퍼런스를 참고하세요.
여러 결과 집합 다루기
데이터베이스 연산이 여러 결과 집합을 반환할 수 있다면, Rows.NextResultSet으로 그 결과들을 가져올 수 있어요. 예를 들어 여러 테이블을 따로 조회하는 SQL을 보내서 각각의 결과 집합을 반환받는 경우에 유용합니다.
Rows.NextResultSet은 다음 결과 집합을 준비해서, Rows.Next 호출이 그 다음 집합의 첫 행을 가져오도록 합니다. 다음 결과 집합이 아예 있는지 여부를 나타내는 bool을 반환해요.
다음 예시의 코드는 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)
}
더 알아보기 (Learn more)
- Using prepared statements — 반복 실행용으로 SQL 문 미리 준비하기
- Avoiding SQL injection risk — SQL 인젝션 위험 피하기
- Executing statements that don't return data — 데이터를 반환하지 않는 문 실행
database/sql—sql패키지 전체 레퍼런스