drizzle-team/drizzle-orm · error · Error

values() must be called with at least one value

Error message

values() must be called with at least one value

What it means

Thrown by MySqlInsertBuilder.values (line 76) when the values argument resolves to an empty array. MySQL INSERT VALUES requires at least one row; Drizzle fails fast at query construction rather than emitting invalid SQL.

Source

Thrown at drizzle-orm/src/mysql-core/query-builders/insert.ts:76

	constructor(
		private table: TTable,
		private session: MySqlSession,
		private dialect: MySqlDialect,
	) {}

	ignore(): this {
		this.shouldIgnore = true;
		return this;
	}

	values(value: MySqlInsertValue<TTable>): MySqlInsertBase<TTable, TQueryResult, TPreparedQueryHKT>;
	values(values: MySqlInsertValue<TTable>[]): MySqlInsertBase<TTable, TQueryResult, TPreparedQueryHKT>;
	values(
		values: MySqlInsertValue<TTable> | MySqlInsertValue<TTable>[],
	): MySqlInsertBase<TTable, TQueryResult, TPreparedQueryHKT> {
		values = Array.isArray(values) ? values : [values];
		if (values.length === 0) {
			throw new Error('values() must be called with at least one value');
		}
		const mappedValues = values.map((entry) => {
			const result: Record<string, Param | SQL> = {};
			const cols = this.table[Table.Symbol.Columns];
			for (const colKey of Object.keys(entry)) {
				const colValue = entry[colKey as keyof typeof entry];
				result[colKey] = is(colValue, SQL) ? colValue : new Param(colValue, cols[colKey]);
			}
			return result;
		});

		return new MySqlInsertBase(this.table, mappedValues, this.shouldIgnore, this.session, this.dialect);
	}

	select(
		selectQuery: (qb: QueryBuilder) => MySqlInsertSelectQueryBuilder<TTable>,
	): MySqlInsertBase<TTable, TQueryResult, TPreparedQueryHKT>;
	select(selectQuery: (qb: QueryBuilder) => SQL): MySqlInsertBase<TTable, TQueryResult, TPreparedQueryHKT>;

View on GitHub (pinned to b7862528fd)

Solutions

  1. Guard before calling values: if (rows.length) await db.insert(table).values(rows);
  2. Ensure the source array is populated; log its length if inserts are unexpectedly empty.
  3. Skip the insert entirely when there's nothing to insert rather than relying on the error.

Example fix

// before
const rows = pendingOrders.filter(o => o.ready); // []
await db.insert(orders).values(rows); // throws

// after - skip when empty
const rows = pendingOrders.filter(o => o.ready);
if (rows.length > 0) {
  await db.insert(orders).values(rows);
}
Defensive patterns

Strategy: validation

Validate before calling

if (!Array.isArray(rows) || rows.length === 0) {
  // nothing to insert; skip
  return;
}
await db.insert(table).values(rows);

Type guard

function hasRows<T>(rows: T[]): rows is [T, ...T[]] {
  return rows.length > 0;
}

Prevention

When it happens

Trigger: Calling db.insert(table).values([]) directly, or passing an array variable that happens to be empty at runtime (e.g. a bulk-insert loop with no items).

Common situations: Batch insert where the source array is empty due to upstream filtering; processing user-uploaded rows that yielded zero records; forgetting to guard an array before insert.

Related errors


AI-assisted analysis of drizzle-team/drizzle-orm@b7862528fd (2026-08-03). Data as JSON: /data/errors/b46bc6681a36d476.json. Report an issue: GitHub.