drizzle-team/drizzle-orm · error · Error

Alias "${tableName}" is already used in this query

Error message

Alias "${tableName}" is already used in this query

What it means

PgUpdate.createJoin (update.ts:414) mirrors the select alias guard: it compares getTableLikeName(table) against existing join aliases in config.joins and throws on a duplicate. UPDATE...FROM in Postgres needs unique aliases for each referenced relation, so a repeat alias is rejected before SQL is built.

Source

Thrown at drizzle-orm/src/pg-core/query-builders/update.ts:415

		if (is(table, PgTable)) {
			return table[Table.Symbol.Columns];
		} else if (is(table, Subquery)) {
			return table._.selectedFields;
		}
		return table[ViewBaseConfig].selectedFields;
	}

	private createJoin<TJoinType extends JoinType>(
		joinType: TJoinType,
	): PgUpdateJoinFn<this, TDynamic, TJoinType> {
		return ((
			table: PgTable | Subquery | PgViewBase | SQL,
			on: ((updateTable: TTable, from: TFrom) => SQL | undefined) | SQL | undefined,
		) => {
			const tableName = getTableLikeName(table);

			if (typeof tableName === 'string' && this.config.joins.some((join) => join.alias === tableName)) {
				throw new Error(`Alias "${tableName}" is already used in this query`);
			}

			if (typeof on === 'function') {
				const from = this.config.from && !is(this.config.from, SQL)
					? this.getTableLikeFields(this.config.from)
					: undefined;
				on = on(
					new Proxy(
						this.config.table[Table.Symbol.Columns],
						new SelectionProxyHandler({ sqlAliasedBehavior: 'sql', sqlBehavior: 'sql' }),
					) as any,
					from && new Proxy(
						from,
						new SelectionProxyHandler({ sqlAliasedBehavior: 'sql', sqlBehavior: 'sql' }),
					) as any,
				);
			}

View on GitHub (pinned to b7862528fd)

Solutions

  1. Alias the second reference differently via a subquery (db.$with('alias').as(...)).
  2. For self-joins on the target table, use a distinct alias for the secondary reference.
  3. Track used aliases in dynamic builders and synthesize unique names.

Example fix

// before
db.update(t).set({...}).from(lookup).where(...)
  .leftJoin(lookup, eq(lookup.id, t.lookupId)); // 'lookup' twice

// after
const l2 = db.$with('l2').as(db.select().from(lookup));
db.update(t).set({...}).with(l2).from(lookup).leftJoin(l2, eq(l2.id, t.otherId)).where(...);
Defensive patterns

Strategy: validation

Validate before calling

function buildUpdateJoin(db: Db, target: PgTable, joins: { table: PgTable | SQL; on: SQL }[]) {
  const used = new Set<string>();
  let q = db.update(target).set({}).from(joins[0].table);
  used.add(String(getTableLikeName(joins[0].table)));
  for (const j of joins.slice(1)) {
    const alias = getTableLikeName(j.table);
    if (typeof alias === 'string' && used.has(alias)) {
      throw new Error(`Alias "${alias}" already used in update`);
    }
    if (typeof alias === 'string') used.add(alias);
    q = (q as any).leftJoin(j.table, j.on);
  }
  return q;
}

Prevention

When it happens

Trigger: Calling .from()/leftJoin()/innerJoin() on an update with a table or subquery whose alias was already used by the base table or a previous join; self-referential updates referencing the same table twice.

Common situations: Update-with-join patterns referencing the same lookup table twice; dynamic update builders appending joins without alias tracking; aliasing a subquery to collide with the target table.

Related errors


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