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!