Insert data

This page shows how to insert data into QuestDB using different programming languages and tools.

InfluxDB Line Protocol is the recommended primary ingestion method in QuestDB and is recommended for high-performance applications.

For transactional data inserts, use the PostgreSQL wire protocol.

For operational (ad-hoc) data ingestion, the Web Console makes it easy to upload CSV files and insert via SQL statements. You can also perform these same actions via the HTTP REST API. For large CSV import (database migrations), use SQL COPY.

In summary, these are the different options:

InfluxDB Line Protocol#

The InfluxDB Line Protocol (ILP) is a text protocol over TCP on port 9009.

It is a one-way protocol to insert data, focusing on simplicity and performance.

Here is a summary table showing how it compares with other ways to insert data that we support:

ProtocolRecord Insertion ReportingData Insertion Performance
InfluxDB Line ProtocolServer logs; Disconnect on errorBest
CSV upload via HTTP RESTConfigurableVery Good
SQL INSERT statementsTransaction-levelGood
SQL COPY statementsTransaction-levelSuitable for one-off data migration

This interface is the preferred ingestion method as it provides the following benefits:

  • High-throughput ingestion
  • Robust ingestion from multiple sources into tables with dedicated systems for reducing congestion
  • Configurable commit-lag for out-of-order data via server configuration settings

With sufficient client-side validation, the lack of errors to the client and confirmation isn't necessarily a concern: QuestDB will log out any issues and disconnect on error. The database will process any valid lines up to that point and insert rows.

On the InfluxDB line protocol page, you may find additional details on the message format, ports and authentication.

The Telegraf guide helps you configure a Telegraf agent to collect and send metrics to QuestDB via ILP.


The ILP client libraries provide more user-friendly ILP clients for a growing number of languages.


These examples send a few rows of input. These use client libraries as well as raw TCP socket connections, when a client library is not available.

Python client library docs and repo.

python3 -m pip install questdb
from questdb.ingress import Sender, IngressError
import sys
import datetime
def example(host: str = 'localhost', port: int = 9009):
with Sender(host, port) as sender:
# Record with provided designated timestamp (using the 'at' param)
# Notice the designated timestamp is expected in Nanoseconds,
# but timestamps in other columns are expected in Microseconds.
# The API provides convenient functions
'pair': 'USDGBP',
'type': 'buy'},
'traded_price': 0.83,
'limit_price': 0.84,
'qty': 100,
'traded_ts': datetime.datetime(
2022, 8, 6, 7, 35, 23, 189062,
# If no 'at' param is passed, the server will use its own timestamp.
symbols={'pair': 'EURJPY'},
'traded_price': 135.97,
'qty': 400,
'limit_price': None}) # NULL columns can be passed as None,
# or simply be left out.
# We recommend flushing periodically, for example every few seconds.
# If you don't flush explicitly, the client will flush automatically
# once the buffer is reaches 63KiB and just before the connection
# is closed.
except IngressError as e:
sys.stderr.write(f'Got error: {e}\n')
if __name__ == '__main__':


Providing a timestamp is optional. If one isn't provided, the server will automatically assign the server's system time as the row's timestamp value.

Timestamps are interpreted as the number of nanoseconds from 1st Jan 1970 UTC, unless otherwise configured. See cairo.timestamp.locale and line.tcp.timestamp configuration options.

ILP Datatypes and Casts#

Strings vs Symbols#

Strings may be recorded as either the STRING type or the SYMBOL type.

Inspecting a sample ILP we can see how a space ' ' separator splits SYMBOL columns to the left from the rest of the columns.

table_name,col1=symbol_val1,col2=symbol_val2 col3="string val",col4=10.5
╰───────── separator

In this example, columns col1 and col2 are strings written to the database as SYMBOLs, whilst col3 is written out as a STRING.

SYMBOLs are strings with which are automatically interned by the database on a per-column basis. You should use this type if you expect the string to be re-used over and over, such as is common with identifiers.

For one-off strings use STRING columns which aren't interned.


QuestDB types are a superset of those supported by ILP. This means that when sending data you should be aware of the performed conversions.


Constructing well-formed messages#

Different library implementations will perform different degrees content validation upfront before sending messages out. To avoid encountering issues, follow these guidelines:

  • All strings must be UTF-8 encoded.

  • Columns should only appear once per row.

  • Symbol columns must be written out before other columns.

  • Table and column names can't have invalid characters. These should not contain ?, .,,, ', ", \, /, :, (, ), +, -, *, %, ~,' ' (space), \0 (nul terminator), ZERO WIDTH NO-BREAK SPACE.

  • Write timestamp column via designated API, or at the end of the message if you are using raw sockets. If you have multiple timestamp columns write additional ones as column values.

  • Don't change column type between rows.

  • Supply timestamps in order. These need to be at least equal to previous ones in the same table, unless using the out of order feature. This is not necessary if you use the out-of-order feature.

Errors in Server Logs#

QuestDB will always log any ILP errors in its server logs.

Here is an example error from the server logs caused when a line attempted to insert a STRING into a SYMBOL column.

2022-04-13T13:35:19.784654Z E i.q.c.l.t.LineTcpConnectionContext [3968] could not process line data [table=bad_ilp_example, msg=cast error for line protocol string [columnWriterIndex=0, columnType=SYMBOL], errno=0]
2022-04-13T13:35:19.784670Z I tcp-line-server scheduling disconnect [fd=3968, reason=0]

Inserting NULL values#

To insert a NULL value, skip the column (or symbol) for that row.

For example:

table1 a=10.5 1647357688714369403
table1 b=1.25 1647357698714369403

Will insert as:


If you don't immediately see data#

If you don't see your inserted data, this is usually down to one of two things:

  • You prepared the messages, but forgot to call .flush() or similar in your client library, so no data was sent.

  • The internal timers and buffers within QuestDB did not commit the data yet. For development (and development only), you may want to tweak configuration settings to commit data more frequently.


    Refer to ILP's commit strategy documentation for more on these configuration settings.


ILP can additionally provide authentication. This is an optional feature which is documented here.

Third-party Library Compatibility#

Use our own client libraries and/or protocol documentation: Clients intended to work with InfluxDB will not work with QuestDB.

PostgreSQL wire protocol#

QuestDB also supports the same wire protocol as PostgreSQL, allowing you to connect and query the database with various third-party pre-existing client libraries and tools.

You can connect to TCP port 8812 and use both INSERT and SELECT SQL queries.

PostgreSQL wire protocol is better suited for applications inserting via SQL programmatically as it provides parameterized queries, which avoid SQL injection issues.


InfluxDB Line Protocol is the recommended primary ingestion method in QuestDB. SQL INSERT statements over the PostgreSQL offer feedback and error reporting, but have worse overall performance.

Here are a few examples demonstrating SQL INSERT queries:

Create the table:

psql -h localhost -p 8812 -U admin -d qdb \

Insert row:

psql -h localhost -p 8812 -U admin -d qdb -c "INSERT INTO t1 VALUES('a', 42)"

Query back:

psql -h localhost -p 8812 -U admin -d qdb -c "SELECT * FROM t1"

Note that you can also run psql from Docker without installing the client locally:

docker run -it --rm --network=host -e PGPASSWORD=quest \
postgres psql ....

Web Console#

QuestDB ships with an embedded Web Console running by default on port 9000.

Creating a table and inserting some data
CREATE TABLE takeaway_order (ts TIMESTAMP, id SYMBOL, status SYMBOL)
INSERT INTO takeaway_order VALUES (now(), 'order1', 'placed');
INSERT INTO takeaway_order VALUES (now(), 'order2', 'placed');

SQL statements can be written in the code editor and executed by clicking the Run button. Note that the web console runs a single statement at a time.

For inserting bulk data or migrating data from other databases, see large CSV import.


QuestDB exposes a REST API for compatibility with a wide range of libraries and tools. The REST API is accessible on port 9000 and has the following insert-capable entrypoints:

EntrypointHTTP MethodDescriptionAPI Docs
/impPOSTImport CSV dataReference
/exec?query=..GETRun SQL Query returning JSON result setReference

For details such as content type, query parameters and more, refer to the REST API docs.

/imp: Uploading Tabular Data#


InfluxDB Line Protocol is the recommended primary ingestion method in QuestDB. CSV uploading offers insertion feedback and error reporting, but has worse overall performance.

See /imp's atomicity query parameter to customize behavior on error.

Let's assume you want to upload the following data via the /imp entrypoint:


You can do so via the command line using cURL or programmatically via HTTP APIs in your scripts and applications.

By default, the response is designed to be human-readable. Use the fmt=json query argument to obtain a response in JSON. You can also specify the schema explicitly. See the second example in Python for these features.

This example imports a CSV file with automatic schema detection.

Basic import with table name
curl -F data=@data.csv http://localhost:9000/imp?name=table_name

This example overwrites an existing table and specifies a timestamp format and a designated timestamp column. For more information on the optional parameters to specify timestamp formats, partitioning and renaming tables, see the REST API documentation.

Providing a user-defined schema
curl \
-F schema='[{"name":"ts", "type": "TIMESTAMP", "pattern": "yyyy-MM-dd - HH:mm:ss"}]' \
-F data=@weather.csv 'http://localhost:9000/imp?overwrite=true&timestamp=ts'

/exec: SQL INSERT Query#

The /exec entrypoint takes a SQL query and returns results as JSON.

We can use this for quick SQL inserts too, but note that there's no support for parameterized queries that are necessary to avoid SQL injection issues.


Prefer the PostgreSQL interface if you are generating sql programmatically.

Prefer ILP if you need high-performance inserts.

# Create Table
curl -G \
--data-urlencode "query=CREATE TABLE IF NOT EXISTS trades(name STRING, value INT)" \
# Insert a row
curl -G \
--data-urlencode "query=INSERT INTO trades VALUES('abc', 123456)" \