{"id":"036884c29322c7a7","repo":"drizzle-team/drizzle-orm","slug":"alias-tablename-is-already-used-in-this-query-036884","errorCode":null,"errorMessage":"Alias \"${tableName}\" is already used in this query","messagePattern":"Alias \"(.+?)\" is already used in this query","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"drizzle-orm/src/pg-core/query-builders/select.ts","lineNumber":249,"sourceCode":"\t\tTIsLateral extends (TJoinType extends 'full' | 'right' ? false : boolean),\n\t>(\n\t\tjoinType: TJoinType,\n\t\tlateral: TIsLateral,\n\t): 'cross' extends TJoinType ? PgSelectCrossJoinFn<this, TDynamic, TIsLateral>\n\t\t: PgSelectJoinFn<this, TDynamic, TJoinType, TIsLateral>\n\t{\n\t\treturn ((\n\t\t\ttable: TIsLateral extends true ? Subquery | SQL : PgTable | Subquery | PgViewBase | SQL,\n\t\t\ton?: ((aliases: TSelection) => SQL | undefined) | SQL | undefined,\n\t\t) => {\n\t\t\tconst baseTableName = this.tableName;\n\t\t\tconst tableName = getTableLikeName(table);\n\n\t\t\t// store all tables used in a query\n\t\t\tfor (const item of extractUsedTable(table)) this.usedTables.add(item);\n\n\t\t\tif (typeof tableName === 'string' && this.config.joins?.some((join) => join.alias === tableName)) {\n\t\t\t\tthrow new Error(`Alias \"${tableName}\" is already used in this query`);\n\t\t\t}\n\n\t\t\tif (!this.isPartialSelect) {\n\t\t\t\t// 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\n\t\t\t\tif (Object.keys(this.joinsNotNullableMap).length === 1 && typeof baseTableName === 'string') {\n\t\t\t\t\tthis.config.fields = {\n\t\t\t\t\t\t[baseTableName]: this.config.fields,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (typeof tableName === 'string' && !is(table, SQL)) {\n\t\t\t\t\tconst selection = is(table, Subquery)\n\t\t\t\t\t\t? table._.selectedFields\n\t\t\t\t\t\t: is(table, View)\n\t\t\t\t\t\t? table[ViewBaseConfig].selectedFields\n\t\t\t\t\t\t: table[Table.Symbol.Columns];\n\t\t\t\t\tthis.config.fields[tableName] = selection;\n\t\t\t\t}\n\t\t\t}","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/drizzle-team/drizzle-orm/blob/b7862528fd8fc39bc2653a6c18dad7c1f4e68d10/drizzle-orm/src/pg-core/query-builders/select.ts#L231-L267","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Alias the second occurrence using sql.alias(...) or a subquery with a distinct alias.","For self-joins, create two aliased subqueries or use sql`<table> AS <alias>`.","Track used aliases in dynamic builders and generate unique names (e.g., `${name}_${i}`)."],"exampleFix":"// before\ndb.select().from(users)\n  .leftJoin(posts, eq(posts.userId, users.id))\n  .leftJoin(posts, eq(posts.authorId, users.id)); // alias 'posts' reused\n\n// after\nconst p1 = db.$with('p1').as(db.select().from(posts));\nconst p2 = db.$with('p2').as(db.select().from(posts));\ndb.with(p1, p2).select().from(users)\n  .leftJoin(p1, eq(p1.userId, users.id))\n  .leftJoin(p2, eq(p2.authorId, users.id));","handlingStrategy":"validation","validationCode":"function buildJoin(db: Db, base: PgTable, joins: { table: PgTable | SQL; on: SQL }[]) {\n  const used = new Set<string>([base[Table.Symbol.Name]]);\n  let q = db.select().from(base);\n  for (const j of joins) {\n    const alias = getTableLikeName(j.table);\n    if (typeof alias === 'string' && used.has(alias)) {\n      throw new Error(`Alias \"${alias}\" already used; provide a unique alias`);\n    }\n    if (typeof alias === 'string') used.add(alias);\n    q = q.leftJoin(j.table, j.on);\n  }\n  return q;\n}","typeGuard":"function isUniqueAlias(alias: string, used: Set<string>): boolean {\n  return !used.has(alias);\n}","tryCatchPattern":null,"preventionTips":["Track used aliases in dynamic query builders.","Alias self-joins and repeated tables via subqueries ($with/as).","Use distinct, descriptive alias names per join."],"tags":["select","join","alias","validation"],"analyzedSha":"b7862528fd8fc39bc2653a6c18dad7c1f4e68d10","analyzedAt":"2026-08-03T18:11:14.318Z","schemaVersion":2}