Applies to VAST Cluster 5.5.
Inserts rows into a table, either as values or as the result of a SELECT. To import Parquet files with INSERT, see import Parquet files.
Syntax
INSERT INTO "<bucket>/<schema>".<table> VALUES (<value>, ...) [, (<value>, ...) ...]
INSERT INTO "<bucket>/<schema>".<table> (<column>, ...) VALUES (<value>, ...) [, (<value>, ...) ...]
INSERT INTO "<bucket>/<schema>".<table> [(<column>, ...)] SELECT ...
Parameters and rules
| Part | Meaning |
|---|---|
VALUES (...) |
One row per parenthesised list. Without a column list, give a value for every column, in table order. Several rows in one statement work. |
(<column>, ...) |
The columns to fill, in the order of the values. |
SELECT ... |
Inserts the rows the query returns. The query must itself be one the Query Engine supports: one table, no GROUP BY (SELECT). |
- The statement returns one row with one column,
Count: the number of rows inserted. - Write literals as you would in a
SELECT:'text',TIMESTAMP '2026-01-01 10:00:00', a list[1, 2], a struct{'lat': 38.7, 'lon': -9.1}. See data types in SQL. - The whole statement must fit the 5 MB statement size limit (limits). To load many rows, use ADBC bulk ingest (
cur.adbc_ingest()) instead of longVALUESlists; the profile a query guide loads 200,000 rows that way. Bound parameters (cur.execute(sql, params)) aren't supported (SQL reference). - There's no upsert:
INSERT OR REPLACE,INSERT OR IGNOREandON CONFLICTfail, because a table has no primary key. To replace rows,DELETEthem andINSERTthe new ones in one transaction. - You can't insert into a table in a snapshot, or into a partitioned table (not supported).
- On a sorted table, inserted rows are sorted later, in the background (write path).
What happens on the cluster
The Query Engine evaluates the values, or runs the SELECT, and writes the rows into the table on the cluster. With INSERT ... SELECT, the rows never leave the cluster: only the count comes back to your client (ingest). For what happens to the rows after the commit, see write path.
Example
The examples use the run() helper and the environment variables described in the SQL reference. This block connects, and creates the schema docs_sql_insert with two tables:
import os
import re
import adbc_driver_manager
import adbc_driver_manager.dbapi as dbapi
import adbc_driver_vastdb
BUCKET = os.environ["VASTDB_BUCKET"]
SCHEMA = f'"{BUCKET}/docs_sql_insert"'
def connect(autocommit=True):
return dbapi.connect(
driver=adbc_driver_vastdb.get_driver_path(),
db_kwargs={
"vast.db.endpoint": os.environ["QE_ENDPOINT"],
"vast.db.access_key": os.environ["AWS_ACCESS_KEY_ID"],
"vast.db.secret_key": os.environ["AWS_SECRET_ACCESS_KEY"],
},
autocommit=autocommit,
)
def server_message(e):
"""The server's message in a driver error, without the request ID or the echoed statement."""
text = re.sub(r"\[req=0x[0-9a-f]+\]: ", "", str(e))
found = (re.search(r'message: "(.*?)", request_id', text, re.S)
or re.search(r"description: (.*)\)", text, re.S))
message = found.group(1) if found else text.strip().splitlines()[-1]
message = re.sub(r"^Plan query: '.*?' failed with: ", "", message, flags=re.S)
message = re.split(r"\\n|\n", message)[0].replace('\\"', '"')
return message.replace(BUCKET, "<bucket>")
def run(sql):
"""Run one statement. Print the rows it returns, or the error."""
try:
with conn.cursor() as cur:
cur.execute(sql)
rows = cur.fetchall() if cur.description else []
except (adbc_driver_manager.Error, OSError) as e:
print(f"{type(e).__name__}: {server_message(e)}")
else:
print(rows)
conn = connect()
run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}")
run(f"CREATE TABLE IF NOT EXISTS {SCHEMA}.readings "
"(id BIGINT, city VARCHAR, temperature DOUBLE, tags INTEGER[], location STRUCT(lat DOUBLE, lon DOUBLE))")
run(f"CREATE TABLE IF NOT EXISTS {SCHEMA}.warm (id BIGINT, city VARCHAR)")
[]
[]
[]
Insert two rows with all columns, then one row with a column list:
run(f"""INSERT INTO {SCHEMA}.readings VALUES
(1, 'Lisbon', 21.5, [1, 2], {{'lat': 38.7, 'lon': -9.1}}),
(2, 'Oslo', 4.0, [3], {{'lat': 59.9, 'lon': 10.7}})""")
run(f"INSERT INTO {SCHEMA}.readings (id, city, temperature) VALUES (3, 'Cairo', 30.2)")
run(f"SELECT id, city, temperature, tags, location FROM {SCHEMA}.readings WHERE id < 3 ORDER BY id LIMIT 10")
[(2,)]
[(1,)]
[(1, 'Lisbon', 21.5, [1, 2], {'lat': 38.7, 'lon': -9.1}), (2, 'Oslo', 4.0, [3], {'lat': 59.9, 'lon': 10.7})]
Copy the result of a query into another table:
run(f"INSERT INTO {SCHEMA}.warm SELECT id, city FROM {SCHEMA}.readings WHERE temperature > 20")
run(f"SELECT id, city FROM {SCHEMA}.warm ORDER BY id LIMIT 10")
[(2,)]
[(1, 'Lisbon'), (3, 'Cairo')]
An upsert fails:
run(f"INSERT OR REPLACE INTO {SCHEMA}.warm VALUES (1, 'Lisboa')")
InternalError: Binder Error: There are no UNIQUE/PRIMARY KEY Indexes that refer to this table, ON CONFLICT is a no-op
Clean up:
run(f"DROP TABLE IF EXISTS {SCHEMA}.readings")
run(f"DROP TABLE IF EXISTS {SCHEMA}.warm")
run(f"DROP SCHEMA IF EXISTS {SCHEMA}")
conn.close()
[]
[]
[]
Errors
| You wrote | Message |
|---|---|
INSERT OR REPLACE, INSERT OR IGNORE, ... ON CONFLICT DO NOTHING |
InternalError: Binder Error: There are no UNIQUE/PRIMARY KEY Indexes that refer to this table, ON CONFLICT is a no-op |
| An insert into a snapshot path | InternalError: internal error: Access Denied (status 1) |
cur.execute(sql, params) |
ProgrammingError: INVALID_STATE: Cannot configure ingestion options on a statement configured for query |
For a vector-indexed table, see the insert rules in vector search SQL.
Trino and Spark
Trino and Spark insert with their own INSERT statements, and also copy query results with CREATE TABLE ... AS SELECT; Spark writes DataFrames too. See ingest.
See also: import Parquet files, ingest, UPDATE.