DuckDB:Golang操作DuckDB实战案例
DuckDB是一个嵌入式SQL数据库引擎。它与众所周知的SQLite非常相似,但它是为olap风格的工作负载设计的。DuckDB支持各种数据类型和SQL特性。凭借其在以内存为中心的环境中处理高速分析的能力,它迅速受到数据科学家和分析师的欢迎。在这篇博文中,我们将探索在Go中使用DuckDB。
DuckDB的主要优点
- 内存内执行:DuckDB主要在内存中操作,但也支持内存外执行。这使得它能够非常快速有效地执行计算。
- 完整的SQL支持:DuckDB支持广泛的SQL特性,这使得它对于各种类型的数据操作非常灵活。
- 事务支持:DuckDB支持事务,这是在许多应用程序中维护数据完整性和一致性的关键特性。
- 向量化执行:DuckDB使用向量化查询执行,从而提高CPU利用率和性能。
- 易于集成:DuckDB为多种编程语言提供api,包括Python、R、c++、Rust、Java和Go。这使得将DuckDB集成到现有工作流和系统中变得更加容易。
- 开源:DuckDB是开源的,这意味着它的源代码可以免费修改或增强。这允许社区驱动的改进和对特定用例的适应性。
环境准备
在开始使用DuckDB和Go之前,需要安装DuckDB Go驱动程序。你可以使用Go的包管理器下载。在终端上运行以下命令:
github.com/marcboeker/go-duckdb
- 连接数据库
package main
import (
"database/sql"
"log"
_ "github.com/marcboeker/go-duckdb"
)
func main() {
// Empty datasource means, that DB will be solely in-memory, otherwise you could specify a filename here
db, err := sql.Open("duckdb", "")
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer db.Close()
}
安装了驱动程序后,现在可以从Go应用程序建立到DuckDB的连接。sql.Open() 函数用于连接到DuckDB,空数据源名称表示我们正在使用内存中的数据库,你也可以指定数据库文件名称,实现数据持久化,相对于当前项目所在目录。
初始化表和数据
现在连接已经建立,你可以执行各种数据库操作了。我们首先创建表,然后插入初始化数据进行测试:
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/marcboeker/go-duckdb"
)
var db *sql.DB
var err error
func main() {
// Empty datasource means, that DB will be solely in-memory, otherwise you could specify a filename here
db, err = sql.Open("duckdb", "data.db")
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer db.Close()
init_data()
}
func init_data() {
// Create table
_, err := db.Exec(`
CREATE TABLE employee (
id INTEGER,
name VARCHAR(20),
start_dt TIMESTAMP,
is_remote BOOLEAN
)`)
if err != nil {
log.Fatal(err)
}
// Insert some data in table
_, err = db.Exec(`
INSERT INTO employee (id, name, start_dt, is_remote)
VALUES
(1, 'John Doe', '2022-01-01 09:00:00', true),
(2, 'Jane Smith', '2023-03-15 10:00:00', false)`)
if err != nil {
log.Fatal(err)
}
}
在处理较大的数据集时,考虑使用事务和预处理语句以提高效率和安全性。记住,总是处理错误并在完成后关闭连接。
查询单行或多行
要获取数据,可以使用QueryRow() 函数来选择单行:
func query_one() {
// Variables to store query result
var id int
var name string
var startDt time.Time
var isRemote bool
// Query single row
if err := db.QueryRow("SELECT id, name, start_dt, is_remote FROM employee WHERE id = ?", 1).Scan(&id, &name, &startDt, &isRemote); err != nil {
if err == sql.ErrNoRows {
log.Println("No rows found.")
} else {
log.Fatalf("unable to execute query: %v", err)
}
} else {
fmt.Println("Select 1 row result:\nID:", id, "Name:", name, "Start Datetime:", startDt, "Is Remote:", isRemote)
}
}
不要忘记处理任何错误并正确关闭连接和结果集,如上所示。
要选择多行,可以使用Query() 函数:
func query_all() {
// Variables to store query result
var id int
var name string
var startDt time.Time
var isRemote bool
// Query multiple rows
rows, err := db.Query("SELECT id, name, start_dt, is_remote FROM employee")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// Print the results
fmt.Println("Results:")
for rows.Next() {
err = rows.Scan(&id, &name, &startDt, &isRemote)
if err != nil {
log.Fatal(err)
}
fmt.Println("ID:", id, "Name:", name, "Start Datetime:", startDt, "Is Remote:", isRemote)
}
err = rows.Err()
if err != nil {
log.Fatal(err)
}
}
我们用SQL命令调用Query()函数,从employee表中选择所有记录。
- 然后使用rows.Next()进入循环,该循环遍历查询返回的每一行。
- 在循环中,我们使用Scan()函数将当前行的列复制到id、name、startDt和isRemote变量中。
- 然后使用fmt.Println()函数打印这些变量。
- 循环结束后,使用rows.Err()检查迭代过程中的错误。如果有错误,我们使用log.Fatal(err)打印它。
错误处理和事务
在现实世界中,Go代码必须准备好处理错误和处理事务。SQL包提供了所有必要的工具:
func trans_insert() {
// Error handling and transactions
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
defer tx.Rollback()
_, err = tx.Exec(`
INSERT INTO employee (id, name, start_dt, is_remote)
VALUES
(3000000000, 'id int64 instead of int32', '2022-06-17 11:00:00', true)`)
if err != nil {
log.Printf("ERROR: %s\n", err.Error()) // Do not fail, just print the error in output
}
err = tx.Commit()
if err != nil {
log.Fatal(err)
}
}
此代码开始事务,尝试执行插入语句,然后提交事务。如果在执行期间发生错误,它将回滚在该事务中所做的任何更改。
完整代码
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/marcboeker/go-duckdb"
)
var db *sql.DB
var err error
func main() {
// Empty datasource means, that DB will be solely in-memory, otherwise you could specify a filename here
db, err = sql.Open("duckdb", "data.db")
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer db.Close()
// init_data()
query_one()
// trans_insert()
query_all()
}
func init_data() {
// Create table
_, err = db.Exec(`
CREATE TABLE employee (
id INTEGER,
name VARCHAR(20),
start_dt TIMESTAMP,
is_remote BOOLEAN
)`)
if err != nil {
log.Fatal(err)
}
// Insert some data in table
_, err = db.Exec(`
INSERT INTO employee (id, name, start_dt, is_remote)
VALUES
(1, 'John Doe', '2022-01-01 09:00:00', true),
(2, 'Jane Smith', '2023-03-15 10:00:00', false)`)
if err != nil {
log.Fatal(err)
}
}
func trans_insert() {
// Error handling and transactions
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
defer tx.Rollback()
_, err = tx.Exec(`
INSERT INTO employee (id, name, start_dt, is_remote)
VALUES
(3000000000, 'id int64 instead of int32', '2022-06-17 11:00:00', true)`)
if err != nil {
log.Printf("ERROR: %s\n", err.Error()) // Do not fail, just print the error in output
}
err = tx.Commit()
if err != nil {
log.Fatal(err)
}
}
func query_one() {
// Variables to store query result
var id int
var name string
var startDt time.Time
var isRemote bool
// Query single row
if err := db.QueryRow("SELECT id, name, start_dt, is_remote FROM employee WHERE id = ?", 1).Scan(&id, &name, &startDt, &isRemote); err != nil {
if err == sql.ErrNoRows {
log.Println("No rows found.")
} else {
log.Fatalf("unable to execute query: %v", err)
}
} else {
fmt.Println("Select 1 row result:\nID:", id, "Name:", name, "Start Datetime:", startDt, "Is Remote:", isRemote)
}
}
func query_all() {
// Variables to store query result
var id int
var name string
var startDt time.Time
var isRemote bool
// Query multiple rows
rows, err := db.Query("SELECT id, name, start_dt, is_remote FROM employee")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// Print the results
fmt.Println("Results:")
for rows.Next() {
err = rows.Scan(&id, &name, &startDt, &isRemote)
if err != nil {
log.Fatal(err)
}
fmt.Println("ID:", id, "Name:", name, "Start Datetime:", startDt, "Is Remote:", isRemote)
}
err = rows.Err()
if err != nil {
log.Fatal(err)
}
}
最后总结
DuckDB对Go的支持允许开发人员直接从他们的Go应用程序中执行强大的数据分析操作。强大的数据管理系统和通用高效的编程语言之间的这种集成为更先进的数据处理应用打开了大门。有了本文提供的基础知识,你就可以开始探索这些可能性了。