Skip to main content

Go SQL Operations

SQL operations in Go are performed using the database/sql package, which provides methods for executing SQL statements, handling results, and managing transactions. Understanding SQL operations is essential for building data-driven applications that can perform complex database operations efficiently. This comprehensive guide will teach you everything you need to know about SQL operations in Go.

Understanding SQL Operations​

What Are SQL Operations?​

SQL operations in Go involve executing SQL statements against databases to perform various data operations. They include:

  • CRUD Operations - Create, Read, Update, Delete operations
  • Prepared Statements - Pre-compiled SQL statements for performance
  • Transactions - Atomic operations that ensure data consistency
  • Complex Queries - Joins, aggregations, and advanced SQL features
  • Query Optimization - Techniques for improving query performance

SQL Operation Types​

Data Manipulation Language (DML)​

Operations that modify data: INSERT, UPDATE, DELETE.

Data Query Language (DQL)​

Operations that retrieve data: SELECT.

Transaction Control​

Operations that manage transactions: BEGIN, COMMIT, ROLLBACK.

Basic CRUD Operations​

Create Operations (INSERT)​

Simple INSERT Statements​

Inserting single records into database tables.

Batch INSERT Operations​

Inserting multiple records efficiently.

package main

import (
"database/sql"
"fmt"
"log"
"time"

_ "github.com/go-sql-driver/mysql"
)

func main() {
// Basic CRUD operations examples
fmt.Println("Basic CRUD operations examples:")

// Database setup
db, err := sql.Open("mysql", "root:password@tcp(localhost:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil {
log.Fatalf("Failed to open database connection: %v", err)
}
defer db.Close()

// Test connection
err = db.Ping()
if err != nil {
log.Fatalf("Failed to ping database: %v", err)
}

// Create table for examples
createTableSQL := `
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
age INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)`

_, err = db.Exec(createTableSQL)
if err != nil {
log.Fatalf("Failed to create table: %v", err)
}

fmt.Println("Table created successfully")

// Simple INSERT operation
func simpleInsert() {
insertSQL := "INSERT INTO users (name, email, age) VALUES (?, ?, ?)"

result, err := db.Exec(insertSQL, "John Doe", "[email protected]", 30)
if err != nil {
log.Printf("Failed to insert user: %v", err)
return
}

lastInsertID, err := result.LastInsertId()
if err != nil {
log.Printf("Failed to get last insert ID: %v", err)
return
}

rowsAffected, err := result.RowsAffected()
if err != nil {
log.Printf("Failed to get rows affected: %v", err)
return
}

fmt.Printf("User inserted successfully - ID: %d, Rows affected: %d\n", lastInsertID, rowsAffected)
}

simpleInsert()

// Batch INSERT operation
func batchInsert() {
insertSQL := "INSERT INTO users (name, email, age) VALUES (?, ?, ?)"

users := []struct {
Name string
Email string
Age int
}{
{"Jane Smith", "[email protected]", 25},
{"Bob Johnson", "[email protected]", 35},
{"Alice Brown", "[email protected]", 28},
}

stmt, err := db.Prepare(insertSQL)
if err != nil {
log.Printf("Failed to prepare statement: %v", err)
return
}
defer stmt.Close()

for _, user := range users {
result, err := stmt.Exec(user.Name, user.Email, user.Age)
if err != nil {
log.Printf("Failed to insert user %s: %v", user.Name, err)
continue
}

lastInsertID, _ := result.LastInsertId()
fmt.Printf("User %s inserted with ID: %d\n", user.Name, lastInsertID)
}
}

batchInsert()
}

Read Operations (SELECT)​

Simple SELECT Statements​

Retrieving data from database tables.

Complex SELECT with Joins​

Performing complex queries with multiple tables.

package main

import (
"database/sql"
"fmt"
"log"
"time"

_ "github.com/go-sql-driver/mysql"
)

func main() {
// Read operations examples
fmt.Println("Read operations examples:")

// Database setup
db, err := sql.Open("mysql", "root:password@tcp(localhost:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil {
log.Fatalf("Failed to open database connection: %v", err)
}
defer db.Close()

// Test connection
err = db.Ping()
if err != nil {
log.Fatalf("Failed to ping database: %v", err)
}

// Simple SELECT operation
func simpleSelect() {
selectSQL := "SELECT id, name, email, age, created_at FROM users WHERE age > ?"

rows, err := db.Query(selectSQL, 25)
if err != nil {
log.Printf("Failed to query users: %v", err)
return
}
defer rows.Close()

fmt.Println("Users with age > 25:")
for rows.Next() {
var id int
var name, email string
var age int
var createdAt time.Time

err := rows.Scan(&id, &name, &email, &age, &createdAt)
if err != nil {
log.Printf("Failed to scan row: %v", err)
continue
}

fmt.Printf("ID: %d, Name: %s, Email: %s, Age: %d, Created: %s\n",
id, name, email, age, createdAt.Format("2006-01-02 15:04:05"))
}

if err = rows.Err(); err != nil {
log.Printf("Error iterating rows: %v", err)
}
}

simpleSelect()

// SELECT with QueryRow for single result
func selectSingleRow() {
selectSQL := "SELECT id, name, email, age FROM users WHERE email = ?"

var id int
var name, email string
var age int

err := db.QueryRow(selectSQL, "[email protected]").Scan(&id, &name, &email, &age)
if err != nil {
if err == sql.ErrNoRows {
fmt.Println("No user found with that email")
return
}
log.Printf("Failed to query user: %v", err)
return
}

fmt.Printf("User found - ID: %d, Name: %s, Email: %s, Age: %d\n", id, name, email, age)
}

selectSingleRow()

// SELECT with aggregation
func selectWithAggregation() {
selectSQL := `
SELECT
COUNT(*) as total_users,
AVG(age) as average_age,
MIN(age) as min_age,
MAX(age) as max_age
FROM users`

var totalUsers int
var averageAge, minAge, maxAge sql.NullFloat64

err := db.QueryRow(selectSQL).Scan(&totalUsers, &averageAge, &minAge, &maxAge)
if err != nil {
log.Printf("Failed to query user statistics: %v", err)
return
}

fmt.Printf("User Statistics:\n")
fmt.Printf(" Total Users: %d\n", totalUsers)
if averageAge.Valid {
fmt.Printf(" Average Age: %.2f\n", averageAge.Float64)
}
if minAge.Valid {
fmt.Printf(" Minimum Age: %.0f\n", minAge.Float64)
}
if maxAge.Valid {
fmt.Printf(" Maximum Age: %.0f\n", maxAge.Float64)
}
}

selectWithAggregation()
}

Update Operations (UPDATE)​

Simple UPDATE Statements​

Updating existing records in database tables.

Conditional UPDATE Operations​

Updating records based on specific conditions.

package main

import (
"database/sql"
"fmt"
"log"
"time"

_ "github.com/go-sql-driver/mysql"
)

func main() {
// Update operations examples
fmt.Println("Update operations examples:")

// Database setup
db, err := sql.Open("mysql", "root:password@tcp(localhost:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil {
log.Fatalf("Failed to open database connection: %v", err)
}
defer db.Close()

// Test connection
err = db.Ping()
if err != nil {
log.Fatalf("Failed to ping database: %v", err)
}

// Simple UPDATE operation
func simpleUpdate() {
updateSQL := "UPDATE users SET age = ?, updated_at = CURRENT_TIMESTAMP WHERE email = ?"

result, err := db.Exec(updateSQL, 31, "[email protected]")
if err != nil {
log.Printf("Failed to update user: %v", err)
return
}

rowsAffected, err := result.RowsAffected()
if err != nil {
log.Printf("Failed to get rows affected: %v", err)
return
}

fmt.Printf("User updated successfully - Rows affected: %d\n", rowsAffected)
}

simpleUpdate()

// Conditional UPDATE operation
func conditionalUpdate() {
updateSQL := "UPDATE users SET age = age + 1 WHERE age < ? AND created_at < ?"

cutoffDate := time.Now().Add(-24 * time.Hour)
result, err := db.Exec(updateSQL, 30, cutoffDate)
if err != nil {
log.Printf("Failed to update users: %v", err)
return
}

rowsAffected, err := result.RowsAffected()
if err != nil {
log.Printf("Failed to get rows affected: %v", err)
return
}

fmt.Printf("Users updated successfully - Rows affected: %d\n", rowsAffected)
}

conditionalUpdate()

// UPDATE with JOIN
func updateWithJoin() {
// First, create a posts table for demonstration
createPostsTableSQL := `
CREATE TABLE IF NOT EXISTS posts (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
title VARCHAR(200) NOT NULL,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
)`

_, err := db.Exec(createPostsTableSQL)
if err != nil {
log.Printf("Failed to create posts table: %v", err)
return
}

// Insert some sample posts
insertPostSQL := "INSERT INTO posts (user_id, title, content) VALUES (?, ?, ?)"
_, err = db.Exec(insertPostSQL, 1, "Sample Post", "This is a sample post content")
if err != nil {
log.Printf("Failed to insert post: %v", err)
}

// UPDATE with JOIN
updateJoinSQL := `
UPDATE users u
JOIN posts p ON u.id = p.user_id
SET u.updated_at = CURRENT_TIMESTAMP
WHERE p.title LIKE ?`

result, err := db.Exec(updateJoinSQL, "%Sample%")
if err != nil {
log.Printf("Failed to update users with posts: %v", err)
return
}

rowsAffected, err := result.RowsAffected()
if err != nil {
log.Printf("Failed to get rows affected: %v", err)
return
}

fmt.Printf("Users with posts updated successfully - Rows affected: %d\n", rowsAffected)
}

updateWithJoin()
}

Delete Operations (DELETE)​

Simple DELETE Statements​

Deleting records from database tables.

Conditional DELETE Operations​

Deleting records based on specific conditions.

package main

import (
"database/sql"
"fmt"
"log"
"time"

_ "github.com/go-sql-driver/mysql"
)

func main() {
// Delete operations examples
fmt.Println("Delete operations examples:")

// Database setup
db, err := sql.Open("mysql", "root:password@tcp(localhost:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil {
log.Fatalf("Failed to open database connection: %v", err)
}
defer db.Close()

// Test connection
err = db.Ping()
if err != nil {
log.Fatalf("Failed to ping database: %v", err)
}

// Simple DELETE operation
func simpleDelete() {
deleteSQL := "DELETE FROM users WHERE email = ?"

result, err := db.Exec(deleteSQL, "[email protected]")
if err != nil {
log.Printf("Failed to delete user: %v", err)
return
}

rowsAffected, err := result.RowsAffected()
if err != nil {
log.Printf("Failed to get rows affected: %v", err)
return
}

fmt.Printf("User deleted successfully - Rows affected: %d\n", rowsAffected)
}

simpleDelete()

// Conditional DELETE operation
func conditionalDelete() {
deleteSQL := "DELETE FROM users WHERE age < ? AND created_at < ?"

cutoffDate := time.Now().Add(-7 * 24 * time.Hour) // 7 days ago
result, err := db.Exec(deleteSQL, 25, cutoffDate)
if err != nil {
log.Printf("Failed to delete users: %v", err)
return
}

rowsAffected, err := result.RowsAffected()
if err != nil {
log.Printf("Failed to get rows affected: %v", err)
return
}

fmt.Printf("Users deleted successfully - Rows affected: %d\n", rowsAffected)
}

conditionalDelete()

// DELETE with LIMIT
func deleteWithLimit() {
deleteSQL := "DELETE FROM users WHERE age > ? ORDER BY created_at ASC LIMIT ?"

result, err := db.Exec(deleteSQL, 30, 2)
if err != nil {
log.Printf("Failed to delete users: %v", err)
return
}

rowsAffected, err := result.RowsAffected()
if err != nil {
log.Printf("Failed to get rows affected: %v", err)
return
}

fmt.Printf("Users deleted successfully (with limit) - Rows affected: %d\n", rowsAffected)
}

deleteWithLimit()
}

Prepared Statements​

Understanding Prepared Statements​

Benefits of Prepared Statements​

Why prepared statements are important for performance and security.

Prepared Statement Lifecycle​

Managing prepared statements effectively.

package main

import (
"database/sql"
"fmt"
"log"
"time"

_ "github.com/go-sql-driver/mysql"
)

func main() {
// Prepared statements examples
fmt.Println("Prepared statements examples:")

// Database setup
db, err := sql.Open("mysql", "root:password@tcp(localhost:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil {
log.Fatalf("Failed to open database connection: %v", err)
}
defer db.Close()

// Test connection
err = db.Ping()
if err != nil {
log.Fatalf("Failed to ping database: %v", err)
}

// Prepared statement for INSERT
func preparedInsert() {
insertSQL := "INSERT INTO users (name, email, age) VALUES (?, ?, ?)"

stmt, err := db.Prepare(insertSQL)
if err != nil {
log.Printf("Failed to prepare statement: %v", err)
return
}
defer stmt.Close()

users := []struct {
Name string
Email string
Age int
}{
{"Prepared User 1", "[email protected]", 25},
{"Prepared User 2", "[email protected]", 30},
{"Prepared User 3", "[email protected]", 35},
}

for _, user := range users {
result, err := stmt.Exec(user.Name, user.Email, user.Age)
if err != nil {
log.Printf("Failed to execute prepared statement: %v", err)
continue
}

lastInsertID, _ := result.LastInsertId()
fmt.Printf("Prepared insert successful - ID: %d\n", lastInsertID)
}
}

preparedInsert()

// Prepared statement for SELECT
func preparedSelect() {
selectSQL := "SELECT id, name, email, age FROM users WHERE age BETWEEN ? AND ?"

stmt, err := db.Prepare(selectSQL)
if err != nil {
log.Printf("Failed to prepare statement: %v", err)
return
}
defer stmt.Close()

rows, err := stmt.Query(25, 35)
if err != nil {
log.Printf("Failed to execute prepared query: %v", err)
return
}
defer rows.Close()

fmt.Println("Users with age between 25 and 35:")
for rows.Next() {
var id, age int
var name, email string

err := rows.Scan(&id, &name, &email, &age)
if err != nil {
log.Printf("Failed to scan row: %v", err)
continue
}

fmt.Printf("ID: %d, Name: %s, Email: %s, Age: %d\n", id, name, email, age)
}
}

preparedSelect()

// Prepared statement for UPDATE
func preparedUpdate() {
updateSQL := "UPDATE users SET age = ? WHERE email = ?"

stmt, err := db.Prepare(updateSQL)
if err != nil {
log.Printf("Failed to prepare statement: %v", err)
return
}
defer stmt.Close()

updates := []struct {
Email string
Age int
}{
{"[email protected]", 26},
{"[email protected]", 31},
{"[email protected]", 36},
}

for _, update := range updates {
result, err := stmt.Exec(update.Age, update.Email)
if err != nil {
log.Printf("Failed to execute prepared update: %v", err)
continue
}

rowsAffected, _ := result.RowsAffected()
fmt.Printf("Prepared update successful - Rows affected: %d\n", rowsAffected)
}
}

preparedUpdate()
}

Transactions​

Understanding Transactions​

Transaction Properties​

ACID properties of database transactions.

Transaction Management​

Managing transactions in Go applications.

package main

import (
"database/sql"
"fmt"
"log"
"time"

_ "github.com/go-sql-driver/mysql"
)

func main() {
// Transactions examples
fmt.Println("Transactions examples:")

// Database setup
db, err := sql.Open("mysql", "root:password@tcp(localhost:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil {
log.Fatalf("Failed to open database connection: %v", err)
}
defer db.Close()

// Test connection
err = db.Ping()
if err != nil {
log.Fatalf("Failed to ping database: %v", err)
}

// Create accounts table for transaction example
createAccountsTableSQL := `
CREATE TABLE IF NOT EXISTS accounts (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
balance DECIMAL(10,2) DEFAULT 0.00,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
)`

_, err = db.Exec(createAccountsTableSQL)
if err != nil {
log.Printf("Failed to create accounts table: %v", err)
return
}

// Simple transaction
func simpleTransaction() {
tx, err := db.Begin()
if err != nil {
log.Printf("Failed to begin transaction: %v", err)
return
}

// Insert account
insertSQL := "INSERT INTO accounts (user_id, balance) VALUES (?, ?)"
result, err := tx.Exec(insertSQL, 1, 1000.00)
if err != nil {
tx.Rollback()
log.Printf("Failed to insert account: %v", err)
return
}

accountID, _ := result.LastInsertId()
fmt.Printf("Account created with ID: %d\n", accountID)

// Update user
updateSQL := "UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = ?"
_, err = tx.Exec(updateSQL, 1)
if err != nil {
tx.Rollback()
log.Printf("Failed to update user: %v", err)
return
}

// Commit transaction
err = tx.Commit()
if err != nil {
log.Printf("Failed to commit transaction: %v", err)
return
}

fmt.Println("Transaction committed successfully")
}

simpleTransaction()

// Transaction with rollback
func transactionWithRollback() {
tx, err := db.Begin()
if err != nil {
log.Printf("Failed to begin transaction: %v", err)
return
}

// Insert account
insertSQL := "INSERT INTO accounts (user_id, balance) VALUES (?, ?)"
result, err := tx.Exec(insertSQL, 2, 500.00)
if err != nil {
tx.Rollback()
log.Printf("Failed to insert account: %v", err)
return
}

accountID, _ := result.LastInsertId()
fmt.Printf("Account created with ID: %d\n", accountID)

// Simulate an error
fmt.Println("Simulating an error...")
tx.Rollback()
fmt.Println("Transaction rolled back successfully")
}

transactionWithRollback()

// Complex transaction with multiple operations
func complexTransaction() {
tx, err := db.Begin()
if err != nil {
log.Printf("Failed to begin transaction: %v", err)
return
}

// Transfer money between accounts
transferAmount := 100.00
fromAccountID := 1
toAccountID := 2

// Check if from account has sufficient balance
var balance float64
err = tx.QueryRow("SELECT balance FROM accounts WHERE id = ?", fromAccountID).Scan(&balance)
if err != nil {
tx.Rollback()
log.Printf("Failed to check balance: %v", err)
return
}

if balance < transferAmount {
tx.Rollback()
fmt.Println("Insufficient balance for transfer")
return
}

// Deduct from source account
_, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", transferAmount, fromAccountID)
if err != nil {
tx.Rollback()
log.Printf("Failed to deduct from source account: %v", err)
return
}

// Add to destination account
_, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", transferAmount, toAccountID)
if err != nil {
tx.Rollback()
log.Printf("Failed to add to destination account: %v", err)
return
}

// Commit transaction
err = tx.Commit()
if err != nil {
log.Printf("Failed to commit transaction: %v", err)
return
}

fmt.Printf("Transfer of $%.2f completed successfully\n", transferAmount)
}

complexTransaction()
}

What You've Learned​

Congratulations! You now have a comprehensive understanding of Go's SQL operations capabilities:

Basic CRUD Operations​

  • Understanding INSERT, SELECT, UPDATE, and DELETE operations
  • Working with single and batch operations
  • Handling query results and affected rows
  • Implementing conditional operations

Prepared Statements​

  • Understanding the benefits of prepared statements
  • Creating and managing prepared statements
  • Using prepared statements for performance and security
  • Handling prepared statement lifecycle

Transactions​

  • Understanding transaction properties and benefits
  • Managing transaction lifecycle
  • Implementing rollback and commit operations
  • Handling complex multi-operation transactions

Query Optimization​

  • Writing efficient SQL queries
  • Using appropriate indexes and constraints
  • Optimizing query performance
  • Handling large result sets

Key Concepts​

  • CRUD Operations - Create, Read, Update, Delete operations
  • Prepared Statements - Pre-compiled SQL statements
  • Transactions - Atomic database operations
  • Query Results - Handling database query results
  • Error Handling - Managing database operation errors

Next Steps​

You now have a solid foundation in Go's SQL operations capabilities. In the next section, we'll explore ORM mapping, which will help you work with databases using object-relational mapping techniques.

Understanding SQL operations is crucial for building data-driven applications. These concepts form the foundation for all the more advanced database operations we'll cover in the coming sections.


Ready to learn about ORM mapping? Let's explore object-relational mapping and learn how to work with databases using ORM libraries like GORM!