Запрос данных

При выполнении SQL-запроса, который возвращает данные, следует использовать один из методов Query, предоставляемых пакетом database/sql. Каждый из этих методов возвращает Row или Rows, данные которых можно скопировать в переменные с помощью метода Scan. Эти методы применяются, например, для выполнения операторов SELECT.

При выполнении оператора, который не возвращает данные, можно использовать методы Exec или ExecContext. Подробнее см. в разделе Выполнение операторов, не возвращающих данные.

Пакет database/sql предоставляет два способа выполнения запроса для получения результатов.

  • Запрос одной строкиQueryRow возвращает не более одной Row из базы данных. Подробнее см. в разделе Запрос одной строки.
  • Запрос нескольких строкQuery возвращает все совпадающие строки в виде структуры Rows, по которой можно выполнить итерацию в коде. Подробнее см. в разделе Запрос нескольких строк.

Если ваш код будет многократно выполнять один и тот же SQL-запрос, рассмотрите возможность использования подготовленного оператора. Подробнее см. в разделе Использование подготовленных операторов.

Внимание: Не используйте функции форматирования строк, такие как fmt.Sprintf, для построения SQL-запроса! Это может привести к уязвимости SQL-инъекций. Подробнее см. в разделе Предотвращение риска SQL-инъекций.

Запрос одной строки

QueryRow извлекает не более одной строки из базы данных, например, когда требуется найти данные по уникальному идентификатору. Если запрос возвращает несколько строк, метод Scan отбрасывает все строки, кроме первой.

QueryRowContext работает аналогично QueryRow, но с аргументом context.Context. Подробнее см. в разделе Отмена выполняющихся операций.

В следующем примере используется запрос для проверки наличия достаточного количества товара на складе для совершения покупки. SQL-запрос возвращает true, если товара достаточно, и false, если нет. Row.Scan копирует возвращаемое логическое значение в переменную enough через указатель.

<code>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
}
</code>

Примечание: Заполнители параметров в подготовленных операторах зависят от используемой СУБД и драйвера. Например, драйвер pq для Postgres требует заполнитель вида $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 для копирования значений столбцов в переменные, представленные указателями.

<code>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()
  // Срез альбомов для хранения данных из возвращённых строк.
  var albums []Album
  // Цикл по строкам, используя Scan для присвоения данных столбцов полям структуры.
  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
}
</code>

Обратите внимание на отложенную функцию rows.Close. Она освобождает все ресурсы, занятые строками, независимо от того, как завершится выполнение функции. Цикл по всем строкам также неявно закрывает их, но лучше использовать defer, чтобы убедиться, что rows закрывается в любом случае.

Примечание: Заполнители параметров в подготовленных операторах зависят от используемой СУБД и драйвера. Например, драйвер pq для Postgres требует заполнитель вида $1 вместо ?.

Обработка ошибок

Обязательно проверяйте наличие ошибки от sql.Rows после прохождения по результатам запроса. Если запрос завершился неудачей, именно так ваш код узнает об этом.

Функции для возврата нескольких строк

Функция Описание
DB.Query
DB.QueryContext
Выполнить запрос в изоляции.
Tx.Query
Tx.QueryContext
Выполнить запрос внутри более крупной транзакции. Подробнее см. Выполнение транзакций.
Stmt.Query
Stmt.QueryContext
Выполнить запрос, используя уже подготовленный оператор. Подробнее см. Использование подготовленных операторов.
Conn.QueryContext Используется с зарезервированными соединениями. Подробнее см. Управление соединениями.

Обработка значений столбцов, допускающих null

Пакет database/sql предоставляет несколько специальных типов, которые можно использовать в качестве аргументов для функции Scan, когда значение столбца может быть равно null. Каждый из них включает поле Valid, которое указывает, является ли значение ненулевым, и поле, хранящее значение, если оно ненулевое.

В следующем примере кода выполняется запрос к имени клиента. Если значение имени равно null, код подставляет другое значение для использования в приложении.

<code>var s sql.NullString
err := db.QueryRow("SELECT name FROM customer WHERE id = ?", id).Scan(&s)
if err != nil {
  log.Fatal(err)
}
// Найти имя клиента, используя заполнитель, если оно отсутствует.
name := "Valued Customer"
if s.Valid {
  name = s.String
}
</code>

Больше информации о каждом типе можно найти в справочнике по пакету 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.

<code>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)
}
</code>
GoRu.dev Golang на русском

На сайте представлена адаптированная под русский язык документация языка программирования Golang