INSERT

The INSERT statement inserts one or more rows into a table. In cases where inserted values conflict with uniqueness constraints, the ON CONFLICT clause can be used to update rather than insert rows.

Performance best practices

  • To bulk-insert data into an existing table, batch multiple rows in one multi-row INSERT statement and do not include the INSERT statements within a transaction. Experimentally determine the optimal batch size for your application by monitoring the performance for different batch sizes (10 rows, 100 rows, 1000 rows).
  • To bulk-insert data into a brand new table, the IMPORT statement performs better than INSERT.
  • In traditional SQL databases, generating and retrieving unique IDs involves using INSERT with SELECT. In CockroachDB, use RETURNING clause with INSERT instead. See Insert and Return Values for more details.

Required privileges

The user must have the INSERT privilege on the table.To use ON CONFLICT, the user must also have the SELECT privilege on the table.To use ON CONFLICT DO UPDATE, the user must additionally have the UPDATE privilege on the table.

Synopsis

WITHcommon_table_expr,INSERTINTOtable_nameAStable_alias_name(column_name,)select_stmtDEFAULTVALUESon_conflictRETURNINGtarget_elem,NOTHING

Parameters

ParameterDescription
common_table_exprSee Common Table Expressions.
table_nameThe table you want to write data to.
AS table_alias_nameAn alias for the table name. When an alias is provided, it completely hides the actual table name.
column_nameThe name of a column to populate during the insert.
select_stmtA selection query. Each value must match the data type of its column. Also, if column names are listed after INTO, values must be in corresponding order; otherwise, they must follow the declared order of the columns in the table.
DEFAULT VALUESTo fill all columns with their default values, use DEFAULT VALUES in place of select_stmt. To fill a specific column with its default value, leave the value out of the select_stmt or use DEFAULT at the appropriate position. See the Insert Default Values examples below.
RETURNING target_listReturn values based on rows inserted, where target_list can be specific column names from the table, * for all columns, or computations using scalar expressions. See the Insert and Return Values example below.

ON CONFLICT clause

ONCONFLICT(name,)WHEREa_exprDOUPDATESETcolumn_name=a_expr(column_name,)=(select_stmta_expr,),WHEREa_exprNOTHING

Normally, when inserted valuesconflict with a UNIQUE constraint on one or more columns, CockroachDBreturns an error. To update the affected rows instead, use an ON
CONFLICT
clause containing the column(s) with the unique constraintand the DO UPDATE SET expression set to the column(s) to be updated(any SET expression supported by the UPDATEstatement is also supported here, including those with WHEREclauses). To prevent the affected rows from updating while allowingnew rows to be inserted, set ON CONFLICT to DO NOTHING. See theUpdate Values ON CONFLICT and Do NotUpdate Values ON CONFLICTexamples below.

If the values in the SET expression cause uniqueness conflicts,CockroachDB will return an error.

As a short-hand alternative to the ON
CONFLICT
clause, you can use the UPSERTstatement. However, UPSERT does not let you specify the column(s) withthe unique constraint; it always uses the column(s) from the primarykey. Using ON CONFLICT is therefore more flexible.

Examples

All of the examples below assume you've already created a table accounts:

  1. > CREATE TABLE accounts(
  2. id INT DEFAULT unique_rowid(),
  3. balance DECIMAL
  4. );

Insert a single row

  1. > INSERT INTO accounts (balance, id) VALUES (10000.50, 1);
  1. > SELECT * FROM accounts;
  1. +----+---------+
  2. | id | balance |
  3. +----+---------+
  4. | 1 | 10000.5 |
  5. +----+---------+

If you do not list column names, the statement will use the columns of the table in their declared order:

  1. > SHOW COLUMNS FROM accounts;
  1. +-------------+-----------+-------------+----------------+-----------------------+---------+
  2. | column_name | data_type | is_nullable | column_default | generation_expression | indices |
  3. +-------------+-----------+-------------+----------------+-----------------------+---------+
  4. | id | INT | true | unique_rowid() | | {} |
  5. | balance | DECIMAL | true | NULL | | {} |
  6. +-------------+-----------+-------------+----------------+-----------------------+---------+
  7. (2 rows)
  1. > INSERT INTO accounts VALUES (2, 20000.75);
  1. > SELECT * FROM accounts;
  1. +----+----------+
  2. | id | balance |
  3. +----+----------+
  4. | 1 | 10000.50 |
  5. | 2 | 20000.75 |
  6. +----+----------+

Insert multiple rows into an existing table

Tip:
Multi-row inserts are faster than multiple single-row INSERT statements. As a performance best practice, we recommend batching multiple rows in one multi-row INSERT statement instead of using multiple single-row INSERT statements. Experimentally determine the optimal batch size for your application by monitoring the performance for different batch sizes (10 rows, 100 rows, 1000 rows).

  1. > INSERT INTO accounts (id, balance) VALUES (3, 8100.73), (4, 9400.10);
  1. > SELECT * FROM accounts;
  1. +----+----------+
  2. | id | balance |
  3. +----+----------+
  4. | 1 | 10000.50 |
  5. | 2 | 20000.75 |
  6. | 3 | 8100.73 |
  7. | 4 | 9400.10 |
  8. +----+----------+

Insert multiple rows into a new table

The IMPORT statement performs better than INSERT when inserting rows into a new table.

Insert from a SELECT statement

  1. > SHOW COLUMS FROM other_accounts;
  1. +-------------+-----------+-------------+----------------+-----------------------+---------+
  2. | column_name | data_type | is_nullable | column_default | generation_expression | indices |
  3. +-------------+-----------+-------------+----------------+-----------------------+---------+
  4. | number | INT | true | NULL | | {} |
  5. | amount | DECIMAL | true | NULL | | {} |
  6. +-------------+-----------+-------------+----------------+-----------------------+---------+
  7. (2 rows)
  1. > INSERT INTO accounts (id, balance) SELECT number, amount FROM other_accounts WHERE id > 4;
  1. > SELECT * FROM accounts;
  1. +----+----------+
  2. | id | balance |
  3. +----+----------+
  4. | 1 | 10000.5 |
  5. | 2 | 20000.75 |
  6. | 3 | 8100.73 |
  7. | 4 | 9400.1 |
  8. | 5 | 350.1 |
  9. | 6 | 150 |
  10. | 7 | 200.1 |
  11. +----+----------+

Insert default values

  1. > INSERT INTO accounts (id) VALUES (8);
  1. > INSERT INTO accounts (id, balance) VALUES (9, DEFAULT);
  1. > SELECT * FROM accounts WHERE id in (8, 9);
  1. +----+---------+
  2. | id | balance |
  3. +----+---------+
  4. | 8 | NULL |
  5. | 9 | NULL |
  6. +----+---------+
  1. > INSERT INTO accounts DEFAULT VALUES;
  1. > SELECT * FROM accounts;
  1. +--------------------+----------+
  2. | id | balance |
  3. +--------------------+----------+
  4. | 1 | 10000.5 |
  5. | 2 | 20000.75 |
  6. | 3 | 8100.73 |
  7. | 4 | 9400.1 |
  8. | 5 | 350.1 |
  9. | 6 | 150 |
  10. | 7 | 200.1 |
  11. | 8 | NULL |
  12. | 9 | NULL |
  13. | 142933248649822209 | NULL |
  14. +--------------------+----------+

Insert and return values

In this example, the RETURNING clause returns the id values of the rows inserted, which are generated server-side by the unique_rowid() function. The language-specific versions assume that you have installed the relevant client drivers.

Tip:
This use of RETURNING mirrors the behavior of MySQL's last_insert_id() function.

Note:
When a driver provides a query() method for statements that return results and an exec() method for statements that do not (e.g., Go), it's likely necessary to use the query() method for INSERT statements with RETURNING.

  1. > INSERT INTO accounts (id, balance)
  2. VALUES (DEFAULT, 1000), (DEFAULT, 250)
  3. RETURNING id;
  1. +--------------------+
  2. | id |
  3. +--------------------+
  4. | 190018410823680001 |
  5. | 190018410823712769 |
  6. +--------------------+
  7. (2 rows)
  1. # Import the driver.
  2. import psycopg2
  3. # Connect to the "bank" database.
  4. conn = psycopg2.connect(
  5. database='bank',
  6. user='root',
  7. host='localhost',
  8. port=26257
  9. )
  10. # Make each statement commit immediately.
  11. conn.set_session(autocommit=True)
  12. # Open a cursor to perform database operations.
  13. cur = conn.cursor()
  14. # Insert two rows into the "accounts" table
  15. # and return the "id" values generated server-side.
  16. cur.execute(
  17. 'INSERT INTO accounts (id, balance) '
  18. 'VALUES (DEFAULT, 1000), (DEFAULT, 250) '
  19. 'RETURNING id'
  20. )
  21. # Print out the returned values.
  22. rows = cur.fetchall()
  23. print('IDs:')
  24. for row in rows:
  25. print([str(cell) for cell in row])
  26. # Close the database connection.
  27. cur.close()
  28. conn.close()

The printed values would look like:

  1. IDs:
  2. ['190019066706952193']
  3. ['190019066706984961']
  1. # Import the driver.
  2. require 'pg'
  3. # Connect to the "bank" database.
  4. conn = PG.connect(
  5. user: 'root',
  6. dbname: 'bank',
  7. host: 'localhost',
  8. port: 26257
  9. )
  10. # Insert two rows into the "accounts" table
  11. # and return the "id" values generated server-side.
  12. conn.exec(
  13. 'INSERT INTO accounts (id, balance) '\
  14. 'VALUES (DEFAULT, 1000), (DEFAULT, 250) '\
  15. 'RETURNING id'
  16. ) do |res|
  17. # Print out the returned values.
  18. puts "IDs:"
  19. res.each do |row|
  20. puts row
  21. end
  22. end
  23. # Close communication with the database.
  24. conn.close()

The printed values would look like:

  1. IDs:
  2. {"id"=>"190019066706952193"}
  3. {"id"=>"190019066706984961"}
  1. package main
  2. import (
  3. "database/sql"
  4. "fmt"
  5. "log"
  6. _ "github.com/lib/pq"
  7. )
  8. func main() {
  9. //Connect to the "bank" database.
  10. db, err := sql.Open(
  11. "postgres",
  12. "postgresql://root@localhost:26257/bank?sslmode=disable"
  13. )
  14. if err != nil {
  15. log.Fatal("error connecting to the database: ", err)
  16. }
  17. // Insert two rows into the "accounts" table
  18. // and return the "id" values generated server-side.
  19. rows, err := db.Query(
  20. "INSERT INTO accounts (id, balance) " +
  21. "VALUES (DEFAULT, 1000), (DEFAULT, 250) " +
  22. "RETURNING id",
  23. )
  24. if err != nil {
  25. log.Fatal(err)
  26. }
  27. // Print out the returned values.
  28. defer rows.Close()
  29. fmt.Println("IDs:")
  30. for rows.Next() {
  31. var id int
  32. if err := rows.Scan(&id); err != nil {
  33. log.Fatal(err)
  34. }
  35. fmt.Printf("%d\n", id)
  36. }
  37. }

The printed values would look like:

  1. IDs:
  2. 190019066706952193
  3. 190019066706984961
  1. var async = require('async');
  2. // Require the driver.
  3. var pg = require('pg');
  4. // Connect to the "bank" database.
  5. var config = {
  6. user: 'root',
  7. host: 'localhost',
  8. database: 'bank',
  9. port: 26257
  10. };
  11. pg.connect(config, function (err, client, done) {
  12. // Closes communication with the database and exits.
  13. var finish = function () {
  14. done();
  15. process.exit();
  16. };
  17. if (err) {
  18. console.error('could not connect to cockroachdb', err);
  19. finish();
  20. }
  21. async.waterfall([
  22. function (next) {
  23. // Insert two rows into the "accounts" table
  24. // and return the "id" values generated server-side.
  25. client.query(
  26. `INSERT INTO accounts (id, balance)
  27. VALUES (DEFAULT, 1000), (DEFAULT, 250)
  28. RETURNING id;`,
  29. next
  30. );
  31. }
  32. ],
  33. function (err, results) {
  34. if (err) {
  35. console.error('error inserting into and selecting from accounts', err);
  36. finish();
  37. }
  38. // Print out the returned values.
  39. console.log('IDs:');
  40. results.rows.forEach(function (row) {
  41. console.log(row);
  42. });
  43. finish();
  44. });
  45. });

The printed values would look like:

  1. IDs:
  2. { id: '190019066706952193' }
  3. { id: '190019066706984961' }

Update values ON CONFLICT

When a uniqueness conflict is detected, CockroachDB stores the row in a temporary table called excluded. This example demonstrates how you use the columns in the temporary excluded table to apply updates on conflict:

  1. > INSERT INTO accounts (id, balance)
  2. VALUES (8, 500.50)
  3. ON CONFLICT (id)
  4. DO UPDATE SET balance = excluded.balance;
  1. > SELECT * FROM accounts WHERE id = 8;
  1. +----+---------+
  2. | id | balance |
  3. +----+---------+
  4. | 8 | 500.50 |
  5. +----+---------+

You can also update the row using an existing value:

  1. > INSERT INTO accounts (id, balance)
  2. VALUES (8, 500.50)
  3. ON CONFLICT (id)
  4. DO UPDATE SET balance = accounts.balance + excluded.balance;
  1. > SELECT * FROM accounts WHERE id = 8;
  1. +----+---------+
  2. | id | balance |
  3. +----+---------+
  4. | 8 | 1001.00 |
  5. +----+---------+

You can also use a WHERE clause to apply the DO UPDATE SET expression conditionally:

  1. > INSERT INTO accounts (id, balance)
  2. VALUES (8, 700)
  3. ON CONFLICT (id)
  4. DO UPDATE SET balance = excluded.balance
  5. WHERE excluded.balance > accounts.balance;
  1. > SELECT * FROM accounts WHERE id = 8;
  1. +----+---------+
  2. | id | balance |
  3. +----+---------+
  4. | 8 | 800 |
  5. +----+---------+
  6. (1 row)

Do not update values ON CONFLICT

In this example, we get an error from a uniqueness conflict:

  1. > SELECT * FROM accounts WHERE id = 8;
  1. +----+---------+
  2. | id | balance |
  3. +----+---------+
  4. | 8 | 500.5 |
  5. +----+---------+
  1. > INSERT INTO accounts (id, balance) VALUES (8, 125.50);
  1. pq: duplicate key value (id)=(8) violates unique constraint "primary"

In this example, we use ON CONFLICT DO NOTHING to ignore the uniqueness error and prevent the affected row from being updated:

  1. > INSERT INTO accounts (id, balance)
  2. VALUES (8, 125.50)
  3. ON CONFLICT (id)
  4. DO NOTHING;
  1. > SELECT * FROM accounts WHERE id = 8;
  1. +----+---------+
  2. | id | balance |
  3. +----+---------+
  4. | 8 | 500.5 |
  5. +----+---------+

In this example, ON CONFLICT DO NOTHING prevents the first row from updating while allowing the second row to be inserted:

  1. > INSERT INTO accounts (id, balance)
  2. VALUES (8, 125.50), (10, 450)
  3. ON CONFLICT (id)
  4. DO NOTHING;
  1. > SELECT * FROM accounts WHERE id in (8, 10);
  1. +----+---------+
  2. | id | balance |
  3. +----+---------+
  4. | 8 | 500.5 |
  5. | 10 | 450 |
  6. +----+---------+

Import data containing duplicate rows using ON CONFLICT and DISTINCT ON

If the input data for INSERT ON CONFLICT contains duplicate rows,you must use DISTINCT
ON
to remove theseduplicates.

For example:

  1. > WITH
  2. -- the following data contains duplicates on the conflict column "id":
  3. inputrows AS (VALUES (8, 130), (8, 140))
  4. INSERT INTO accounts (id, balance)
  5. (SELECT DISTINCT ON(id) id, balance FROM inputrows) -- de-duplicate the input rows
  6. ON CONFLICT (id)
  7. DO NOTHING;

The DISTINCT ON clause does not guarantee which of the duplicates isconsidered. To force the selection of a particular duplicate, use anORDER BY clause:

  1. > WITH
  2. -- the following data contains duplicates on the conflict column "id":
  3. inputrows AS (VALUES (8, 130), (8, 140))
  4. INSERT INTO accounts (id, balance)
  5. (SELECT DISTINCT ON(id) id, balance
  6. FROM inputrows
  7. ORDER BY balance) -- pick the lowest balance as value to update in each account
  8. ON CONFLICT (id)
  9. DO NOTHING;

Note:

Using DISTINCT ON incurs a performance cost to search and eliminate duplicates.For best performance, avoid using it when the input is known to not contain duplicates.

See also

Was this page helpful?
YesNo