-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
Discussed in #12892
Originally posted by e2ne0 October 1, 2025
Hi everyone,
While using alembic, I noticed that indexes which were already correctly created kept showing up as changes and got re-created every time I ran autogenerate.
Looking into it, I found that when reflecting indexes from mssql, the columns are returned in the order of sys.columns.column_id rather than the actual order defined in the index sys.index_columns.key_ordinal.
I'm posting this in the SQLAlchemy repo because the issue comes from how SQLAlchemy reflects indexes, not from Alembic itself.
Example
Database index definition:
CREATE NONCLUSTERED INDEX [Index_Example]
ON [Table1]([Column3] DESC, [Column1] ASC, [Column2] DESC);What get_indexes() returns:
{
'name': 'Index_Example',
'column_names': ['Column1', 'Column2', 'Column3']
# sorted by column_id
}What it should return to make alembic autogenerate correctly:
{
'name': 'Index_Example',
'column_names': ['Column3', 'Column1', 'Column2']
# sorted by key_ordinal
}Reproducible test case
from sqlalchemy import create_engine, inspect, MetaData, Table, Column, Integer, Index
engine = create_engine('mssql+pyodbc://...')
metadata = MetaData()
test_table = Table(
'Table1',
metadata,
Column('Column1', Integer, primary_key=True),
Column('Column2', Integer),
Column('Column3', Integer),
)
# create test index
idx = Index(
'Index_Example',
test_table.c.Column3.desc(),
test_table.c.Column1.asc(),
test_table.c.Column2.desc(),
)
metadata.create_all(engine)
inspector = inspect(engine)
indexes = inspector.get_indexes('Index_Example')
print(indexes[0]['column_names'])
# output shows wrong order
metadata.drop_all(engine)MSSQL Verification
Running this query shows the correct order:
SELECT
COL_NAME(ic.object_id, ic.column_id) AS ColumnName,
ic.key_ordinal
FROM sys.indexes i
INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
WHERE i.object_id = OBJECT_ID('Table1') AND i.name = 'Index_Example'
ORDER BY ic.key_ordinal; -- This ORDER BY is missing in get_indexes()Root cause
In sqlalchemy/dialects/mssql/base.py, the get_indexes() method's second query doesn't order by key_ordinal:
# Current query
rp = connection.execution_options(future_result=True).execute(
sql.text(
"""
select
ind_col.index_id,
col.name,
ind_col.is_included_column
from
sys.columns as col
join sys.tables as tab on tab.object_id = col.object_id
join sys.index_columns as ind_col on
ind_col.column_id = col.column_id
and ind_col.object_id = tab.object_id
join sys.schemas as sch on sch.schema_id = tab.schema_id
where
tab.name = :tabname
and sch.name = :schname
"""
)
# missing: ORDER BY ind_col.index_id, ind_col.key_ordinal
)Proposed fix
Add ORDER BY ind_col.index_id, ind_col.key_ordinal to ensure columns are returned in the correct order.
My questions
- Is this considered a bug?
- If yes, would you accept a PR with the above fix?
- Any known backward compatibility concerns?
Environment
- SQLAlchemy: 2.0.x
- db: Microsoft SQL Server 2016+
- This affects Alembic autogenerate significantly