суббота, 4 сентября 2021 г.

Запрос данных из базы данных с пакетом database/sql в Golang

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

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

Пакет database/sql предоставляет два способа выполнить запрос, возвращающий данные.

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

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

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

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

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

QueryRowContext работает как QueryRow, но с аргументом context.Context.

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

func canPurchase(id int, quantity int) (bool, error) {
    var enough bool
    // Запрос значения на основе одной строки.
    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)
    }
    return enough, nil
}

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

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, album)
    }
    if err = rows.Err(); err != nil {
        return albums, err
    }
    return albums, nil
}

Обратите внимание на отложенный вызов rows.Close. Он освобождает любые ресурсы, удерживаемые rows, независимо от того, что функция возвращает. Цикл по всем строкам также неявно закрывает rows, но лучше использовать 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, которое сообщает, является ли значение ненулевым, и поле, содержащее значение, если это так.

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

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
}

Вот некоторые типы, предназначенные для полей, имеющих null значения:

  • NullBool
  • NullFloat64
  • NullInt32
  • NullInt64
  • NullString
  • NullTime

Получение данных из столбцов

При просмотре строк, возвращаемых запросом, вы используете Scan для копирования значений столбца строки в значения Go.

Существует базовый набор преобразований данных, поддерживаемых всеми драйверами, например преобразование SQL INT в Go int.

Как и следовало ожидать, Scan преобразует типы столбцов в похожие типы Go. Например, Scan преобразует из SQL CHAR, VARCHAR и TEXT в строку Go. Однако Scan также выполнит преобразование в другой тип Go, который хорошо подходит для значения столбца. Например, если столбец представляет собой VARCHAR, который всегда будет содержать число, вы можете указать числовой тип Go, например int, для получения значения, и Scan преобразует его с помощью strconv.Atoi за вас.

Обработка нескольких наборов результатов

Когда операция с базой данных может возвращать несколько наборов результатов, вы можете получить их с помощью 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()

// Проходим в цикле по первому набору результатов.
for rows.Next() {
    // Обрабатываем набор результатов.
}

// Переход к следующему набору результатов.
rows.NextResultSet()

// Проходим в цикле по второму набору результатов.
for rows.Next() {
    // Обрабатываем второй набор результатов.
}

// Проверяем наличие ошибок в любом наборе результатов.
if err := rows.Err(); err != nil {
    log.Fatal(err)
}


Читайте также:


Купить gopher

Комментариев нет:

Отправить комментарий