O outro cara estava errado como eu suspeitava, então vou responder minha própria pergunta.
No SQL Server 2012 (e provavelmente em versões anteriores), descobri que, se eu chamar 'set showplan_xml on;' em uma consulta arbitrária como:
select * from (select * from Lessons a) a inner join (select * from LessonTypes b) b on a.LessonTypeID = b.ID;
O XML retornado inclui o seguinte como seu OutputList de primeiro nível/nível superior, que mostra claramente todas as colunas e não apenas suas tabelas de origem, mas o alias de sua subconsulta de origem.
<OutputList>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[a]" Column="ID"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[a]" Column="Name"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[a]" Column="Description"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[a]" Column="Enabled"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[a]" Column="LessonTypeID"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[LessonTypes]" Alias="[b]" Column="ID"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[LessonTypes]" Alias="[b]" Column="Name"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[LessonTypes]" Alias="[b]" Column="Description"/>
</OutputList>
Portanto, na minha API de banco de dados, como armazeno todas as strings de consulta ordenadamente em um dicionário, posso realmente executar um aquecimento de cache no início do aplicativo que executa todas elas com xml_showplan ativado, analisa as colunas de saída de nível superior dos XMLs e, em seguida, têm um mapeamento completo de alias de tabela e nomes de coluna para seu ordinal de saída.
Na verdade, eu usei um pequeno truque aqui. Na verdade, não é o alias da subconsulta, mas o alias da tabela base. Se você não incluir um alias na tabela base, o atributo Alias não estará presente no XML. No entanto, é muito simples garantir que toda vez que você fizer referência a uma tabela real, você forneça um alias, que deve fazer com que ela seja enviada para o XML, e pronto.
Apenas para direcionar o ponto para casa, os aliases permanecem, mesmo ao unir a tabela a si mesma em uma consulta como:
select * from Lessons a, Lessons b, Lessons c
, que produz:<OutputList>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[a]" Column="ID"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[a]" Column="Name"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[a]" Column="Description"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[a]" Column="Enabled"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[a]" Column="LessonTypeID"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[b]" Column="ID"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[b]" Column="Name"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[b]" Column="Description"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[b]" Column="Enabled"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[b]" Column="LessonTypeID"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[c]" Column="ID"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[c]" Column="Name"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[c]" Column="Description"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[c]" Column="Enabled"/>
<ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[c]" Column="LessonTypeID"/>
</OutputList>
Então, como você pode ver, os aliases estão de fato intactos e recuperáveis.