Quick Start: Python and TimescaleDB

Goal

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

Prerequisites

Before you start, make sure you have:

Connect Python to TimescaleDB

Step 1: Import psycopg2 library

  1. import psycopg2

Step 2: Compose a connection string

Locate your TimescaleDB credentials. You need them to compose a connection string for psycopg2.

You’ll need the following credentials:

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

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

  1. CONNECTION = "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. CONNECTION = "postgres://username:[email protected]:port/dbname?sslmode=require"

Alternatively you can specify each parameter in the connection string as follows

  1. CONNECTION = "dbname =tsdb user=tsdbadmin password=secret host=host.com port=5432 sslmode=require"
warning

The above method of composing a connection string is for test or development purposes only, for production purposes be sure to make sensitive details like your password, hostname, and port number environment variables.

Step 3: Connect to TimescaleDB using the psycopg2 connect function

Use the psycopg2 connect function to create a new database session and create a new cursor object to interact with the database.

In your main function, add the following lines:

  1. CONNECTION = "postgres://username:[email protected]:port/dbname"
  2. def main():
  3. with psycopg2.connect(CONNECTION) as conn:
  4. cursor = conn.cursor()
  5. # use the cursor to interact with your database
  6. # cursor.execute("SELECT * FROM table")

Alternatively, you can create a connection object and pass the object around as needed, like opening a cursor to perform database operations:

  1. CONNECTION = "postgres://username:[email protected]:port/dbname"
  2. def main():
  3. conn = psycopg2.connect(CONNECTION)
  4. cursor = conn.cursor()
  5. # use the cursor to interact with your database
  6. cursor.execute("SELECT 'hello world'")
  7. print(cursor.fetchone())

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

Create a relational table

Step 1: Formulate your SQL statement

First, compose a string which contains the SQL statement 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. query_create_sensors_table = "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 the CREATE TABLE statement by opening a cursor, executing the query from Step 1 and committing the query we executed in order to make the changes persistent. Afterward, we close the cursor to clean up:

  1. cursor = conn.cursor()
  2. # see definition in Step 1
  3. cursor.execute(query_create_sensors_table)
  4. conn.commit()
  5. cursor.close()

Congratulations, you’ve successfully created a relational table in TimescaleDB using Python.

Create hypertable

In TimescaleDB, the primary point of interaction with your data is a hypertable. It provides an abstraction of a single continuous table across all space and time intervals. You can query it via standard SQL.

Virtually all user interactions with TimescaleDB are with hypertables. Creating tables and indexes, altering tables, inserting data, selecting data, and most other tasks can and should all be executed on the hypertable.

A hypertable is defined by a standard schema with column names and types, with at least one column specifying a time value. Learn more about using hypertables in the API documentation.

Step 1: Formulate the CREATE TABLE SQL statement for your hypertable

First, create a string variable which houses the CREATE TABLE SQL statement for your hypertable. Notice how the hypertable has the compulsory time column:

  1. # create sensor data hypertable
  2. query_create_sensordata_table = """CREATE TABLE sensor_data (
  3. time TIMESTAMPTZ NOT NULL,
  4. sensor_id INTEGER,
  5. temperature DOUBLE PRECISION,
  6. cpu DOUBLE PRECISION,
  7. FOREIGN KEY (sensor_id) REFERENCES sensors (id)
  8. );"""

Step 2: Formulate the SELECT statement to create your hypertable

Next, formulate a SELECT statement that converts the sensor_data table to a hypertable. Note that you must specify the table name which you wish to convert to a hypertable and its time column name as the two arguments, as mandated by the create_hypertable docs:

  1. query_create_sensordata_hypertable = "SELECT create_hypertable('sensor_data', 'time');"

Step 3: Execute statements from Step 1 and Step 2 and commit changes

Now bring it all together by opening a cursor with our connection, executing the statements from step 1 and step 2 and committing your changes and closing the cursor:

  1. cursor = conn.cursor()
  2. cursor.execute(query_create_sensordata_table)
  3. cursor.execute(query_create_sensordata_hypertable)
  4. # commit changes to the database to make changes persistent
  5. conn.commit()
  6. cursor.close()

Congratulations, you’ve successfully created a hypertable in your Timescale database using Python!

Insert rows into TimescaleDB

How to insert rows using Psycopg2

Here’s a typical pattern you’d use to insert data into a table. In the example below, insert a list of tuples (relational data) called sensors, into the relational table named sensors.

First, we open a cursor with our connection to the database, then using prepared statements formulate our INSERT SQL statement and then execute that statement.

  1. sensors = [('a', 'floor'), ('a', 'ceiling'), ('b', 'floor'), ('b', 'ceiling')]
  2. cursor = conn.cursor()
  3. for sensor in sensors:
  4. try:
  5. cursor.execute("INSERT INTO sensors (type, location) VALUES (%s, %s);",
  6. (sensor[0], sensor[1]))
  7. except (Exception, psycopg2.Error) as error:
  8. print(error.pgerror)
  9. conn.commit()

A cleaner way to pass variables to the cursor.execute function is to separate the formulation of our SQL statement, SQL, from the data being passed with it into the prepared statement, data:

  1. SQL = "INSERT INTO sensors (type, location) VALUES (%s, %s);"
  2. sensors = [('a', 'floor'), ('a', 'ceiling'), ('b', 'floor'), ('b', 'ceiling')]
  3. cursor = conn.cursor()
  4. for sensor in sensors:
  5. try:
  6. data = (sensor[0], sensor[1])
  7. cursor.execute(SQL, data)
  8. except (Exception, psycopg2.Error) as error:
  9. print(error.pgerror)
  10. conn.commit()

Congratulations, you’ve successfully inserted data into TimescaleDB using Python.

How to insert rows fast using pgcopy

While using psycopg2 by itself may be sufficient for you to insert rows into your hypertable, if you need quicker performance, you can use pgcopy. To do this, install pgcopy using pip and then add this line to your list of import statements:

  1. from pgcopy import CopyManager
note

This section provides step-by-step instructions to insert rows using pgcopy. The full sample code to insert data into TimescaleDB using pgcopy, with the example of sensor data from four sensors, is provided in Step 3.

Step 1: Get data to insert into database

First we generate random sensor data using the generate_series function provided by PostgreSQL. This example inserts a total of 480 rows of data (4 readings, every 5 minutes, for 24 hours). In your application, this would be the query that saves your time-series data into the hypertable.

  1. # for sensors with ids 1-4
  2. for id in range(1, 4, 1):
  3. data = (id,)
  4. # create random data
  5. simulate_query = """SELECT generate_series(now() - interval '24 hour', now(), interval '5 minute') AS time,
  6. %s as sensor_id,
  7. random()*100 AS temperature,
  8. random() AS cpu
  9. """
  10. cursor.execute(simulate_query, data)
  11. values = cursor.fetchall()

Step 2: Define columns of table you’re inserting data into

Then we define the column names of the table we want to insert data into. In this case, we’re using the sensor_data hypertable that we created in the “Generate a Hypertable” section above. This hypertable consists of the columns named time, sensor_id, temperature and cpu. We define these column names in a list of strings called cols.

  1. cols = ['time', 'sensor_id', 'temperature', 'cpu']

Step 3: Instantiate a CopyManager with your target table and column definition

Lastly we create an instance of the pgcopy CopyManager, mgr, and pass our connection variable, hypertable name, and list of column names. Then we use the copy function of the CopyManager to insert the data into the database quickly using pgcopy.

  1. mgr = CopyManager(conn, 'sensor_data', cols)
  2. mgr.copy(values)

Finally, commit to persist changes:

  1. conn.commit()

Full sample code to insert data into TimescaleDB using pgcopy, using the example of sensor data from four sensors:

  1. # insert using pgcopy
  2. def fast_insert(conn):
  3. cursor = conn.cursor()
  4. # for sensors with ids 1-4
  5. for id in range(1, 4, 1):
  6. data = (id,)
  7. # create random data
  8. simulate_query = """SELECT generate_series(now() - interval '24 hour', now(), interval '5 minute') AS time,
  9. %s as sensor_id,
  10. random()*100 AS temperature,
  11. random() AS cpu
  12. """
  13. cursor.execute(simulate_query, data)
  14. values = cursor.fetchall()
  15. # column names of the table you're inserting into
  16. cols = ['time', 'sensor_id', 'temperature', 'cpu']
  17. # create copy manager with the target table and insert
  18. mgr = CopyManager(conn, 'sensor_data', cols)
  19. mgr.copy(values)
  20. # commit after all sensor data is inserted
  21. # could also commit after each sensor insert is done
  22. conn.commit()

You can also check if the insertion worked:

  1. cursor.execute("SELECT * FROM sensor_data LIMIT 5;")
  2. print(cursor.fetchall())

Congratulations, you’ve successfully inserted time-series data into TimescaleDB using Python and the pgcopy library.

Execute a query

Step 1: Define your query in SQL

First, define the SQL query you’d like to run on the database. The example below is a simple SELECT statement querying each row from the previously created sensor_data table.

  1. query = "SELECT * FROM sensor_data;"

Step 2: Execute the query

Next, open a cursor from our existing database connection, conn, and then execute the query you defined in Step 1:

  1. cursor = conn.cursor()
  2. query = "SELECT * FROM sensor_data;"
  3. cursor.execute(query)

Step 3: Access results returned by the query

To access all resulting rows returned by your query, use one of pyscopg2‘s results retrieval methods, such as fetchall() or fetchmany(). In the example below, we’re simply printing the results of our query, row by row. Note that the result of fetchall() is a list of tuples, so you can handle them accordingly:

  1. cursor = conn.cursor()
  2. query = "SELECT * FROM sensor_data;"
  3. cursor.execute(query)
  4. for row in cursor.fetchall():
  5. print(row)
  6. cursor.close()

If you want a list of dictionaries instead, you can define the cursor using DictCursor:

  1. cursor = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)

Using this cursor, cursor.fetchall() returns a list of dictionary-like objects.

Executing queries using prepared statements

For more complex queries than a simple SELECT *, we can use prepared statements to ensure our queries are executed safely against the database. We write our query using placeholders as shown in the sample code below. For more information about properly using placeholders in psycopg2, see the basic module usage document.

  1. # query with placeholders
  2. cursor = conn.cursor()
  3. query = """
  4. SELECT time_bucket('5 minutes', time) AS five_min, avg(cpu)
  5. FROM sensor_data
  6. JOIN sensors ON sensors.id = sensor_data.sensor_id
  7. WHERE sensors.location = %s AND sensors.type = %s
  8. GROUP BY five_min
  9. ORDER BY five_min DESC;
  10. """
  11. location = "floor"
  12. sensor_type = "a"
  13. data = (location, sensor_type)
  14. cursor.execute(query, data)
  15. results = cursor.fetchall()

Congratulations, you’ve successfully executed a query on TimescaleDB using Python! For more information on how to execute more complex queries, see the psycopg2 documentation

Next steps

Now that you’re able to connect, read, and write to a TimescaleDB instance from your Python application, and generate the scaffolding necessary to build a new application from an existing TimescaleDB instance, be sure to check out these advanced TimescaleDB tutorials: