Quick Start: Go and TimescaleDB

Goal

This quick start guide is designed to get the Golang developer up and running with TimescaleDB as their database. In this tutorial, you’ll learn how to:

Prerequisites

To complete this tutorial, you need a cursory knowledge of the Structured Query Language (SQL). The tutorial walks you through each SQL command, but it is helpful if you’ve seen SQL before.

To start, install TimescaleDB. Once your installation is complete, we can proceed to ingesting or creating sample data and finishing the tutorial.

You also need:

Connect to database

Locate your TimescaleDB credentials in order to compose a connection string for PGX to use in order to connect to your TimescaleDB instance.

You’ll need the following credentials:

  • password
  • username
  • host URL
  • port number
  • database name

Next, compose your connection string variable, as a libpq connection string, using the following format:

  1. connStr := "postgres://username:[email protected]:port/dbname"

If you’re using a hosted version of TimescaleDB, or generally require an SSL connection, use this version instead:

  1. connStr := "postgres://username:[email protected]:port/dbname?sslmode=require"

Creating a single connection to your database

Here’s a hello world program that you can run to ensure you’re connected to your database

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "github.com/jackc/pgx/v4"
  7. )
  8. //connect to database using a single connection
  9. func main() {
  10. /***********************************************/
  11. /* Single Connection to TimescaleDB/ PostresQL */
  12. /***********************************************/
  13. ctx := context.Background()
  14. connStr := "yourConnectionStringHere"
  15. conn, err := pgx.Connect(ctx, connStr)
  16. if err != nil {
  17. fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
  18. os.Exit(1)
  19. }
  20. defer conn.Close(ctx)
  21. //run a simple query to check our connection
  22. var greeting string
  23. err = conn.QueryRow(ctx, "select 'Hello, Timescale!'").Scan(&greeting)
  24. if err != nil {
  25. fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
  26. os.Exit(1)
  27. }
  28. fmt.Println(greeting)
  29. }

If you’d like to specify your connection string as an environment variable, you can use the following syntax to access it in place of the variable connStr above:

os.Getenv("DATABASE_CONNECTION_STRING")

Using a connection pool (for multiple connections)

Connection pooling is useful to ensure you don’t waste resources and can lead to faster queries on your database.

To create a connection pool that can be used for concurrent connections to your database, use the pgxpool.Connect() function instead of pgx.Connect() as used in the example program below. Also note the import of github.com/jackc/pgx/v4/pgxpool, rather than pgx/v4 which was used to create a single connection.

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "github.com/jackc/pgx/v4/pgxpool"
  7. )
  8. func main() {
  9. ctx := context.Background()
  10. connStr := "yourConnectionStringHere"
  11. dbpool, err := pgxpool.Connect(ctx, connStr)
  12. if err != nil {
  13. fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
  14. os.Exit(1)
  15. }
  16. defer dbpool.Close()
  17. //run a simple query to check our connection
  18. var greeting string
  19. err = dbpool.QueryRow(ctx, "select 'Hello, Timescale (but concurrently)'").Scan(&greeting)
  20. if err != nil {
  21. fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
  22. os.Exit(1)
  23. }
  24. fmt.Println(greeting)
  25. }

Congratulations, you’ve successfully connected to TimescaleDB using Go.

Create a table

Note: For the rest of this tutorial, you use a connection pool, since having concurrent connections is the most common use case.

Step 1: Formulate your SQL statement

First, compose a string which contains the SQL state that you would use to create a relational table. In the example below, we create a table called sensors, with columns id, type and location:

  1. queryCreateTable := `CREATE TABLE sensors (id SERIAL PRIMARY KEY, type VARCHAR(50), location VARCHAR(50));`

Step 2: Execute the SQL statement and commit changes

Next, we execute our CREATE TABLE statement by calling the Exec() function on the dbpool object, using the arguments of the current context and our statement string, queryCreateTable formulated in step 1.

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "github.com/jackc/pgx/v4"
  7. "github.com/jackc/pgx/v4/pgxpool"
  8. )
  9. func main() {
  10. ctx := context.Background()
  11. connStr := "yourConnectionStringHere"
  12. dbpool, err := pgxpool.Connect(ctx, connStr)
  13. if err != nil {
  14. fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
  15. os.Exit(1)
  16. }
  17. defer dbpool.Close()
  18. /********************************************/
  19. /* Create relational table */
  20. /********************************************/
  21. //Create relational table called sensors
  1. queryCreateTable := `CREATE TABLE sensors (id SERIAL PRIMARY KEY, type VARCHAR(50), location VARCHAR(50));`
  2. `
  3. _, err = dbpool.Exec(ctx, queryCreateTable)
  4. if err != nil {
  5. fmt.Fprintf(os.Stderr, "Unable to create SENSORS table: %v\n", err)
  6. os.Exit(1)
  7. }
  8. fmt.Println("Successfully created relational table SENSORS")
  9. }
  10. ```
  11. ## Generate a hypertable
  12. In TimescaleDB, the primary point of interaction with your data is a hypertable, the abstraction of a single continuous table across all space and time intervals, such that one can query it via standard SQL.
  13. Virtually all user interactions with TimescaleDB are with hypertables. Creating tables and indexes, altering tables, inserting data, selecting data, etc. can (and should) all be executed on the hypertable.
  14. A hypertable is defined by a standard schema with column names and types, with at least one column specifying a time value.
  15. ### Step 1: Formulate CREATE TABLE SQL Statements for hypertable
  16. First, we create a variable which houses our `CREATE TABLE SQL` statement for our hypertable. Notice how the hypertable has the compulsory time column:
  17. Second, we formulate the SELECT statement to convert the table created into a hypertable. Note that we must specify the table name which we wish to convert to a hypertable and its time column name as the two arguments, as mandated by the [create\_hypertable docs]($810cce6f936ab8a7.md):

queryCreateHypertable := CREATE TABLE sensor_data ( time TIMESTAMPTZ NOT NULL,

  1. sensor_id INTEGER,
  2. temperature DOUBLE PRECISION,
  3. cpu DOUBLE PRECISION,
  4. FOREIGN KEY (sensor_id) REFERENCES sensors (id)
  5. );
  6. SELECT create_hypertable('sensor_data', 'time');
  1. `
  1. `
  1. ### Step 2: Execute SQL statement using .Exec()
  2. Next, we execute our `CREATE TABLE` statement and `SELECT` statement which converts the table created into a hypertable. We do this by calling the `Exec()` function on the dbpool object, using the arguments of the current context and our statement string `queryCreateHypertable` formulated in step 1 above.
  3. ```
  4. package main
  5. import (
  6. "context"
  7. "fmt"
  8. "os"
  9. "github.com/jackc/pgx/v4"
  10. "github.com/jackc/pgx/v4/pgxpool"
  11. )
  12. func main() {
  13. ctx := context.Background()
  14. connStr := "yourConnectionStringHere"
  15. dbpool, err := pgxpool.Connect(ctx, connStr)
  16. if err != nil {
  17. fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
  18. os.Exit(1)
  19. }
  20. defer dbpool.Close()
  21. /********************************************/
  22. /* Create Hypertable */
  23. /********************************************/
  24. // Create hypertable of time-series data called sensor_data
  25. //formulate statement
  26. ```

queryCreateHypertable := CREATE TABLE sensor_data ( time TIMESTAMPTZ NOT NULL,

  1. sensor_id INTEGER,
  2. temperature DOUBLE PRECISION,
  3. cpu DOUBLE PRECISION,
  4. FOREIGN KEY (sensor_id) REFERENCES sensors (id)
  5. );
  6. SELECT create_hypertable('sensor_data', 'time');
  1. `
  1. `

` //execute statement

_, err = dbpool.Exec(ctx, queryCreateHypertable)

if err != nil {

  1. fmt.Fprintf(os.Stderr, "Unable to create SENSOR_DATA hypertable: %v\n", err)
  2. os.Exit(1)

}

fmt.Println(“Successfully created hypertable SENSOR_DATA”)

}

  1. Congratulations, you've successfully created a hypertable in your Timescale database using Go.
  2. ## Insert a row into your Timescale database
  3. Here's a typical pattern you'd use to insert some data into a table. In the example below, we insert the relational data from the two arrays, `sensorTypes` and `sensorLocations`, into the relational table named `sensors`.
  4. First, we open a connection pool to the database, then using prepared statements formulate our `INSERT` SQL statement and then we execute that statement:

package main

import (

“context”

“fmt”

“os”

“github.com/jackc/pgx/v4”

“github.com/jackc/pgx/v4/pgxpool”

)

func main() {

ctx := context.Background()

connStr := “yourConnectionStringHere”

dbpool, err := pgxpool.Connect(ctx, connStr)

if err != nil {

  1. fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
  2. os.Exit(1)

}

defer dbpool.Close()

/**/

/ INSERT into relational table /

/**/

//Insert data into relational table

// Slices of sample data to insert

// observation i has type sensorTypes[i] and location sensorLocations[i]

sensorTypes := []string{“a”, “a”, “b”, “b”}

sensorLocations := []string{“floor”, “ceiling”, “floor”, “ceiling”}

for i := range sensorTypes {

  1. //INSERT statement in SQL
  1. `
  1. queryInsertMetadata := `INSERT INTO sensors (type, location) VALUES ($1, $2);`

` //Execute INSERT command

  1. _, err := dbpool.Exec(ctx, queryInsertMetadata, sensorTypes[i], sensorLocations[i])
  2. if err != nil {
  3. fmt.Fprintf(os.Stderr, "Unable to insert data into database: %v\n", err)
  4. os.Exit(1)
  5. }
  6. fmt.Printf("Inserted sensor (%s, %s) into database \n", sensorTypes[i], sensorLocations[i])

}

fmt.Println(“Successfully inserted all sensors into database”)

}

  1. ## Insert a batch of rows into your Timescale database
  2. We can insert a batch of rows into TimescaleDB in a couple of different ways. First, let's see what it looks like to insert a number of rows, but one at a time.
  3. ### Step 0: Generate sample time-series data to insert
  4. For simplicity's sake, we'll use PostgreSQL to generate some sample time-series data in order to insert into the `sensor_data` hypertable. To do this, we define the SQL statement to generate the data, called `queryDataGeneration`. Then we use the `.Query()` function to execute the statement and return our sample data.
  5. Then we store the data returned by our query in `results`, a slice of structs, which is then used as a source to insert data into our hypertable.

// Generate data to insert

//SQL query to generate sample data

  1. `

queryDataGeneration := SELECT generate_series(now() - interval ‘24 hour’, now(), interval ‘5 minute’) AS time,

  1. floor(random() * (3) + 1)::int as sensor_id,
  2. random()*100 AS temperature,
  3. random() AS cpu
  1. `
  1. `

` //Execute query to generate samples for sensor_data hypertable

rows, err := dbpool.Query(ctx, queryDataGeneration)

if err != nil {

  1. fmt.Fprintf(os.Stderr, "Unable to generate sensor data: %v\n", err)
  2. os.Exit(1)

}

defer rows.Close()

fmt.Println(“Successfully generated sensor data”)

//Store data generated in slice results

type result struct {

  1. Time time.Time
  2. SensorId int
  3. Temperature float64
  4. CPU float64

}

var results []result

for rows.Next() {

  1. var r result
  2. err = rows.Scan(&r.Time, &r.SensorId, &r.Temperature, &r.CPU)
  3. if err != nil {
  4. fmt.Fprintf(os.Stderr, "Unable to scan %v\n", err)
  5. os.Exit(1)
  6. }
  7. results = append(results, r)

}

// Any errors encountered by rows.Next or rows.Scan are returned here

if rows.Err() != nil {

  1. fmt.Fprintf(os.Stderr, "rows Error: %v\n", rows.Err())
  2. os.Exit(1)

}

// Check contents of results slice

fmt.Println(“Contents of RESULTS slice”)

for i := range results {

  1. var r result
  2. r = results[i]
  3. fmt.Printf("Time: %s | ID: %d | Temperature: %f | CPU: %f |\n", &r.Time, r.SensorId, r.Temperature, r.CPU)

}

  1. ### Step 1: Formulate INSERT SQL statement
  2. First we formulate a SQL insert statement for the sensor\_data hypertable.

//SQL query to generate sample data

  1. `

queryInsertTimeseriesData := INSERT INTO sensor_data (time, sensor_id, temperature, cpu) VALUES ($1, $2, $3, $4);

  1. `

`

  1. ### Step 2: Iterate over data and INSERT
  2. We then execute that SQL statement for each sample we have in our results slice:
  3. ```
  4. //Insert contents of results slice into TimescaleDB
  5. for i := range results {
  6. var r result
  7. r = results[i]
  8. _, err := dbpool.Exec(ctx, queryInsertTimeseriesData, r.Time, r.SensorId, r.Temperature, r.CPU)
  9. if err != nil {
  10. fmt.Fprintf(os.Stderr, "Unable to insert sample into Timescale %v\n", err)
  11. os.Exit(1)
  12. }
  13. defer rows.Close()
  14. }
  15. fmt.Println("Successfully inserted samples into sensor_data hypertable")
  16. ```
  17. Here's a sample `main.go` which generates sample data and inserts it into the `sensor_data` hypertable:
  18. ```
  19. package main
  20. import (
  21. "context"
  22. "fmt"
  23. "os"
  24. "time"
  25. "github.com/jackc/pgx/v4"
  26. "github.com/jackc/pgx/v4/pgxpool"
  27. )
  28. func main() {
  29. /********************************************/
  30. /* Connect using Connection Pool */
  31. /********************************************/
  32. ctx := context.Background()
  33. connStr := "yourConnectionStringHere"
  34. dbpool, err := pgxpool.Connect(ctx, connStr)
  35. if err != nil {
  36. fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
  37. os.Exit(1)
  38. }
  39. defer dbpool.Close()
  40. /********************************************/
  41. /* Insert data into hypertable */
  42. /********************************************/
  43. // Generate data to insert
  44. //SQL query to generate sample data
  45. ```

queryDataGeneration := SELECT generate_series(now() - interval ‘24 hour’, now(), interval ‘5 minute’) AS time,

  1. floor(random() * (3) + 1)::int as sensor_id,
  2. random()*100 AS temperature,
  3. random() AS cpu
  1. `
  1. `

` //Execute query to generate samples for sensor_data hypertable

rows, err := dbpool.Query(ctx, queryDataGeneration)

if err != nil {

  1. fmt.Fprintf(os.Stderr, "Unable to generate sensor data: %v\n", err)
  2. os.Exit(1)

}

defer rows.Close()

fmt.Println(“Successfully generated sensor data”)

//Store data generated in slice results

type result struct {

  1. Time time.Time
  2. SensorId int
  3. Temperature float64
  4. CPU float64

}

var results []result

for rows.Next() {

  1. var r result
  2. err = rows.Scan(&r.Time, &r.SensorId, &r.Temperature, &r.CPU)
  3. if err != nil {
  4. fmt.Fprintf(os.Stderr, "Unable to scan %v\n", err)
  5. os.Exit(1)
  6. }
  7. results = append(results, r)

}

// Any errors encountered by rows.Next or rows.Scan are returned here

if rows.Err() != nil {

  1. fmt.Fprintf(os.Stderr, "rows Error: %v\n", rows.Err())
  2. os.Exit(1)

}

// Check contents of results slice

fmt.Println(“Contents of RESULTS slice”)

for i := range results {

  1. var r result
  2. r = results[i]
  3. fmt.Printf("Time: %s | ID: %d | Temperature: %f | CPU: %f |\n", &r.Time, r.SensorId, r.Temperature, r.CPU)

}

//Insert contents of results slice into TimescaleDB

//SQL query to generate sample data

  1. `

queryInsertTimeseriesData := INSERT INTO sensor_data (time, sensor_id, temperature, cpu) VALUES ($1, $2, $3, $4);

  1. `

//Insert contents of results slice into TimescaleDB

for i := range results {

  1. var r result
  2. r = results[i]
  3. _, err := dbpool.Exec(ctx, queryInsertTimeseriesData, r.Time, r.SensorId, r.Temperature, r.CPU)
  4. if err != nil {
  5. fmt.Fprintf(os.Stderr, "Unable to insert sample into Timescale %v\n", err)
  6. os.Exit(1)
  7. }
  8. defer rows.Close()

}

fmt.Println(“Successfully inserted samples into sensor_data hypertable”)

}

  1. ## Batch insert data into TimescaleDB
  2. You'll notice that the method above executes as many insert statements as there are samples to be inserted. This can make ingestion of data slow. To speed up ingestion, we recommend batch inserting data.
  3. Here's a sample pattern for how to do so, using the sample data generated in Step 0 above, it uses the pgx `Batch` object:

package main

import (

“context”

“fmt”

“os”

“time”

“github.com/jackc/pgx/v4”

“github.com/jackc/pgx/v4/pgxpool”

)

func main() {

/**/

/ Connect using Connection Pool /

/**/

ctx := context.Background()

connStr := “yourConnectionStringHere”

dbpool, err := pgxpool.Connect(ctx, connStr)

if err != nil {

  1. fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
  2. os.Exit(1)

}

defer dbpool.Close()

// Generate data to insert

//SQL query to generate sample data

  1. `

queryDataGeneration := SELECT generate_series(now() - interval ‘24 hour’, now(), interval ‘5 minute’) AS time,

  1. floor(random() * (3) + 1)::int as sensor_id,
  2. random()*100 AS temperature,
  3. random() AS cpu
  1. `
  1. `

` //Execute query to generate samples for sensor_data hypertable

rows, err := dbpool.Query(ctx, queryDataGeneration)

if err != nil {

  1. fmt.Fprintf(os.Stderr, "Unable to generate sensor data: %v\n", err)
  2. os.Exit(1)

}

defer rows.Close()

fmt.Println(“Successfully generated sensor data”)

//Store data generated in slice results

type result struct {

  1. Time time.Time
  2. SensorId int
  3. Temperature float64
  4. CPU float64

}

var results []result

for rows.Next() {

  1. var r result
  2. err = rows.Scan(&r.Time, &r.SensorId, &r.Temperature, &r.CPU)
  3. if err != nil {
  4. fmt.Fprintf(os.Stderr, "Unable to scan %v\n", err)
  5. os.Exit(1)
  6. }
  7. results = append(results, r)

}

// Any errors encountered by rows.Next or rows.Scan are returned here

if rows.Err() != nil {

  1. fmt.Fprintf(os.Stderr, "rows Error: %v\n", rows.Err())
  2. os.Exit(1)

}

// Check contents of results slice

/*fmt.Println(“Contents of RESULTS slice”)

for i := range results {

  1. var r result
  2. r = results[i]
  3. fmt.Printf("Time: %s | ID: %d | Temperature: %f | CPU: %f |\n", &r.Time, r.SensorId, r.Temperature, r.CPU)

}*/

//Insert contents of results slice into TimescaleDB

//SQL query to generate sample data

  1. `

queryInsertTimeseriesData := INSERT INTO sensor_data (time, sensor_id, temperature, cpu) VALUES ($1, $2, $3, $4);

  1. `

/**/

/ Batch Insert into TimescaleDB /

/**/

//create batch

batch := &pgx.Batch{}

numInserts := len(results)

//load insert statements into batch queue

for i := range results {

  1. var r result
  2. r = results[i]
  3. batch.Queue(queryInsertTimeseriesData, r.Time, r.SensorId, r.Temperature, r.CPU)

}

batch.Queue(“select count(*) from sensor_data”)

//send batch to connection pool

br := dbpool.SendBatch(ctx, batch)

//execute statements in batch queue

_, err := br.Exec()

if err != nil {

  1. fmt.Fprintf(os.Stderr, "Unable to execute statement in batch queue %v\n", err)
  2. os.Exit(1)

}

fmt.Println(“Successfully batch inserted data”)

//Compare length of results slice to size of table

fmt.Printf(“size of results: %d\n”, len(results))

//check size of table for number of rows inserted

// result of last SELECT statement

var rowsInserted int

err = br.QueryRow().Scan(&rowsInserted)

fmt.Printf(“size of table: %d\n”, rowsInserted)

err = br.Close()

if err != nil {

  1. fmt.Fprintf(os.Stderr, "Unable to closer batch %v\n", err)
  2. os.Exit(1)

}

}

  1. ###### tip
  2. If you are inserting data from a CSV file, we recommend the [timescale-parallel-copy tool](https://github.com/timescale/timescaledb-parallel-copy), which is a command line program for parallelizing PostgreSQL's built-in `COPY` functionality for bulk inserting data into TimescaleDB.
  3. ## Execute a query on your Timescale database
  4. ### Step 1: Define the SQL query
  5. First, define the SQL query you'd like to run on the database. The example below contains a query which combines time-series and relational data. It returns the average cpu values for every 5 minute interval for sensors located on location `ceiling` and of type `a`.

// Formulate query in SQL

// Note the use of prepared statement placeholders $1 and $2

  1. `

queryTimebucketFiveMin := SELECT time_bucket(‘5 minutes’, time) AS five_min, avg(cpu)

  1. FROM sensor_data
  2. JOIN sensors ON sensors.id = sensor_data.sensor_id
  3. WHERE sensors.location = $1 AND sensors.type = $2
  4. GROUP BY five_min
  5. ORDER BY five_min DESC;
  1. `
  1. `
  1. Notice the use of placeholders for sensor location and type.
  2. ### Step 2: Execute the query
  3. Secondly, use the `.Query()` function to query your TimescaleDB database. Here we execute the query string from step 1, ensuring to specify the relevant placeholders.
  4. ```
  5. //Execute query on TimescaleDB
  6. rows, err := dbpool.Query(ctx, queryTimebucketFiveMin, "ceiling", "a")
  7. if err != nil {
  8. fmt.Fprintf(os.Stderr, "Unable to execute query %v\n", err)
  9. os.Exit(1)
  10. }
  11. defer rows.Close()
  12. fmt.Println("Successfully executed query")
  13. ```
  14. ### Step 3: Access results returned by the query
  15. We can access the rows returned by `.Query()` by using the following pattern. First we create a struct with fields representing the columns we expect to be returned.
  16. Then we use the `rows.Next()` function to iterate through the rows returned and fill up `results` our array of structs. To do this we use the rows.Scan() function, passing in pointers to the fields to which we want to scan the results.
  17. In the example below, we print out the results returned from our query, but you might want to use those results for some other purpose. Once you've acanned through all the rows returned you can then use the results array for your desired purpose.
  18. ```
  19. //Do something with the results of query
  20. // Struct for results
  21. type result2 struct {
  22. Bucket time.Time
  23. Avg float64
  24. }
  25. // Print rows returned and fill up results slice for later use
  26. var results []result2
  27. for rows.Next() {
  28. var r result2
  29. err = rows.Scan(&r.Bucket, &r.Avg)
  30. if err != nil {
  31. fmt.Fprintf(os.Stderr, "Unable to scan %v\n", err)
  32. os.Exit(1)
  33. }
  34. results = append(results, r)
  35. fmt.Printf("Time bucket: %s | Avg: %f\n", &r.Bucket, r.Avg)
  36. }
  37. // Any errors encountered by rows.Next or rows.Scan are returned here
  38. if rows.Err() != nil {
  39. fmt.Fprintf(os.Stderr, "rows Error: %v\n", rows.Err())
  40. os.Exit(1)
  41. }
  42. // use results here…
  43. ```
  44. ### Sample main.go for querying data in TimescaleDB
  45. Here's a sample program which combines steps 1, 2 and 3 above, to run a query on a TimescaleDB database and access the results of that query.
  46. ```
  47. package main
  48. import (
  49. "context"
  50. "fmt"
  51. "os"
  52. "time"
  53. "github.com/jackc/pgx/v4/pgxpool"
  54. )
  55. func main() {
  56. ctx := context.Background()
  57. connStr := "yourConnectionStringHere"
  58. dbpool, err := pgxpool.Connect(ctx, connStr)
  59. if err != nil {
  60. fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
  61. os.Exit(1)
  62. }
  63. defer dbpool.Close()
  64. /********************************************/
  65. /* Execute a query */
  66. /********************************************/
  67. // Formulate query in SQL
  68. // Note the use of prepared statement placeholders $1 and $2
  69. ```

queryTimebucketFiveMin := SELECT time_bucket(‘5 minutes’, time) AS five_min, avg(cpu)

  1. FROM sensor_data
  2. JOIN sensors ON sensors.id = sensor_data.sensor_id
  3. WHERE sensors.location = $1 AND sensors.type = $2
  4. GROUP BY five_min
  5. ORDER BY five_min DESC;
  1. `
  1. `

` //Execute query on TimescaleDB

rows, err := dbpool.Query(ctx, queryTimebucketFiveMin, “ceiling”, “a”)

if err != nil {

  1. fmt.Fprintf(os.Stderr, "Unable to execute query %v\n", err)
  2. os.Exit(1)

}

defer rows.Close()

fmt.Println(“Successfully executed query”)

//Do something with the results of query

// Struct for results

type result2 struct {

  1. Bucket time.Time
  2. Avg float64

}

// Print rows returned and fill up results slice for later use

var results []result2

for rows.Next() {

  1. var r result2
  2. err = rows.Scan(&r.Bucket, &r.Avg)
  3. if err != nil {
  4. fmt.Fprintf(os.Stderr, "Unable to scan %v\n", err)
  5. os.Exit(1)
  6. }
  7. results = append(results, r)
  8. fmt.Printf("Time bucket: %s | Avg: %f\n", &r.Bucket, r.Avg)

}

// Any errors encountered by rows.Next or rows.Scan are returned here

if rows.Err() != nil {

  1. fmt.Fprintf(os.Stderr, "rows Error: %v\n", rows.Err())
  2. os.Exit(1)

}

} ```

Congratulations, you’ve successfully executed a query on TimescaleDB using Go and pgx.

Next steps

Now that you’re able to connect, read, and write to a TimescaleDB instance from your Golang application, be sure to check out these advanced TimescaleDB tutorials: