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

Thrown by createJoin inside MySqlSelectQueryBuilderBase (line 273) when a join is added whose alias already exists among the previously registered joins. Drizzle prevents duplicate aliases because they'd produce ambiguous SQL.

Source

Thrown at drizzle-orm/src/mysql-core/query-builders/select.ts:273

				: 'Index hint configuration is allowed only for MySqlTable and not for subqueries or views',
		) => {
			const isCrossJoin = joinType === 'cross';
			let on = (isCrossJoin ? undefined : a) as (
				| ((aliases: TSelection) => SQL | undefined)
				| SQL
				| undefined
			);
			const onIndex = (isCrossJoin ? a : b) as TJoinedTable extends MySqlTable ? IndexConfig
				: 'Index hint configuration is allowed only for MySqlTable and not for subqueries or views';

			const baseTableName = this.tableName;
			const tableName = getTableLikeName(table);

			// store all tables used in a query
			for (const item of extractUsedTable(table)) this.usedTables.add(item);

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

			if (!this.isPartialSelect) {
				// If this is the first join and this is not a partial select and we're not selecting from raw SQL, "move" the fields from the main table to the nested object
				if (Object.keys(this.joinsNotNullableMap).length === 1 && typeof baseTableName === 'string') {
					this.config.fields = {
						[baseTableName]: this.config.fields,
					};
				}
				if (typeof tableName === 'string' && !is(table, SQL)) {
					const selection = is(table, Subquery)
						? table._.selectedFields
						: is(table, View)
						? table[ViewBaseConfig].selectedFields
						: table[Table.Symbol.Columns];
					this.config.fields[tableName] = selection;
				}
			}

View on GitHub (pinned to b7862528fd)

Solutions

  1. Alias the second instance distinctly: .leftJoin(pets.as('pet1'), ...).leftJoin(pets.as('pet2'), ...).
  2. Remove the duplicate join if it was added by mistake.
  3. For self-joins, create two aliased references to the same table up front and join each once.

Example fix

// before
db.select().from(users)
  .leftJoin(pets, eq(users.id, pets.ownerId))
  .leftJoin(pets, eq(users.id, pets.coOwnerId)); // duplicate 'pets'

// after - alias the second
const pet1 = pets.as('pet1');
const pet2 = pets.as('pet2');
db.select().from(users)
  .leftJoin(pet1, eq(users.id, pet1.ownerId))
  .leftJoin(pet2, eq(users.id, pet2.coOwnerId));
Defensive patterns

Strategy: validation

Validate before calling

const usedAliases = new Set<string>();
function assertAlias(alias: string) {
  if (usedAliases.has(alias)) throw new Error(`Alias ${alias} already used`);
  usedAliases.add(alias);
}

Type guard

function aliasAvailable(existing: string[], alias: string): boolean {
  return !existing.includes(alias);
}

Prevention

When it happens

Trigger: Calling two joins with the same table/alias, e.g. .leftJoin(pets, ...).leftJoin(pets, ...) or joining an aliased table whose alias was already used. The check scans config.joins for a matching alias.

Common situations: Self-joins done without distinct aliases; joining the same relation twice (e.g. owner + co-owner); copy-pasting a join without renaming the alias.

Related errors


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