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

Inside PgSelect's join creation (select.ts:248), Drizzle tracks every join alias and throws if a new join reuses an alias already present in config.joins. Postgres requires unique correlation names within a query, so duplicate aliases would produce ambiguous SQL; the check is performed by comparing getTableLikeName(table) against existing join aliases.

Source

Thrown at drizzle-orm/src/pg-core/query-builders/select.ts:249

		TIsLateral extends (TJoinType extends 'full' | 'right' ? false : boolean),
	>(
		joinType: TJoinType,
		lateral: TIsLateral,
	): 'cross' extends TJoinType ? PgSelectCrossJoinFn<this, TDynamic, TIsLateral>
		: PgSelectJoinFn<this, TDynamic, TJoinType, TIsLateral>
	{
		return ((
			table: TIsLateral extends true ? Subquery | SQL : PgTable | Subquery | PgViewBase | SQL,
			on?: ((aliases: TSelection) => SQL | undefined) | SQL | undefined,
		) => {
			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 occurrence using sql.alias(...) or a subquery with a distinct alias.
  2. For self-joins, create two aliased subqueries or use sql`<table> AS <alias>`.
  3. Track used aliases in dynamic builders and generate unique names (e.g., `${name}_${i}`).

Example fix

// before
db.select().from(users)
  .leftJoin(posts, eq(posts.userId, users.id))
  .leftJoin(posts, eq(posts.authorId, users.id)); // alias 'posts' reused

// after
const p1 = db.$with('p1').as(db.select().from(posts));
const p2 = db.$with('p2').as(db.select().from(posts));
db.with(p1, p2).select().from(users)
  .leftJoin(p1, eq(p1.userId, users.id))
  .leftJoin(p2, eq(p2.authorId, users.id));
Defensive patterns

Strategy: validation

Validate before calling

function buildJoin(db: Db, base: PgTable, joins: { table: PgTable | SQL; on: SQL }[]) {
  const used = new Set<string>([base[Table.Symbol.Name]]);
  let q = db.select().from(base);
  for (const j of joins) {
    const alias = getTableLikeName(j.table);
    if (typeof alias === 'string' && used.has(alias)) {
      throw new Error(`Alias "${alias}" already used; provide a unique alias`);
    }
    if (typeof alias === 'string') used.add(alias);
    q = q.leftJoin(j.table, j.on);
  }
  return q;
}

Type guard

function isUniqueAlias(alias: string, used: Set<string>): boolean {
  return !used.has(alias);
}

Prevention

When it happens

Trigger: Joining the same table twice without aliasing one: db.select().from(t).leftJoin(t2, ...).leftJoin(t2, ...); or joining a subquery whose derived alias collides with an earlier table/join alias. Also triggered by aliasing two subqueries to the same name.

Common situations: Self-joins where both sides use the default table name; reusing a table reference variable in multiple joins; dynamic query builders that append joins without tracking used aliases.

Related errors


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