SQL Server 2017 – CU25 – sp_pkeys – Wrong order

After our customers have installed CU25 there is a problem with the stored procedure sp_pkeys. It may now return the wrong order if a primary key has multiple columns.

It can be tracked down in the code itself. This is the 2017 CU25 variant:

create procedure sys.sp_pkeys (     @table_name      sysname,     @table_owner     sysname = null,     @table_qualifier sysname = null ) as     declare @table_id           int     -- quotename() returns up to 258 chars     declare @full_table_name    nvarchar(517) -- 258 + 1 + 258          if @table_qualifier is not null     begin         if db_name() <> @table_qualifier         begin   -- If qualifier doesn't match current database             raiserror (15250, -1,-1)             return         end     end          if @table_owner is null     begin   -- If unqualified table name         select @full_table_name = quotename(@table_name)     end     else     begin   -- Qualified table name         if @table_owner = ''         begin   -- If empty owner name             select @full_table_name = quotename(@table_owner)         end         else         begin             select @full_table_name = quotename(@table_owner) + '.' + quotename(@table_name)         end     end          select @table_id = object_id(@full_table_name)          select         TABLE_QUALIFIER = convert(sysname,db_name()),         TABLE_OWNER = convert(sysname,schema_name(o.schema_id)),         TABLE_NAME = convert(sysname,o.name),         COLUMN_NAME = convert(sysname,c.name),         KEY_SEQ = (SELECT convert(smallint, index_column_id)                              FROM sys.index_columns                               WHERE object_id = @table_id AND index_id = i.index_id and column_id = c.column_id),         PK_NAME = convert(sysname,k.name)     from         sys.indexes i,         sys.all_columns c,         sys.all_objects o,         sys.key_constraints k     where         o.object_id = @table_id and         o.object_id = c.object_id and         o.object_id = i.object_id and         k.parent_object_id = o.object_id and          k.unique_index_id = i.index_id and          i.is_primary_key = 1 and               c.column_id IN                (SELECT column_id                FROM sys.index_columns                WHERE object_id = @table_id AND index_id = i.index_id)     order by 1, 2, 3, 5 

And this is the SQL Server 2017 CU24 and SQL Server 2019 variant:

create procedure sys.sp_pkeys   (       @table_name      sysname,       @table_owner     sysname = null,       @table_qualifier sysname = null   )   as       declare @table_id           int       -- quotename() returns up to 258 chars       declare @full_table_name    nvarchar(517) -- 258 + 1 + 258          if @table_qualifier is not null       begin           if db_name() <> @table_qualifier           begin   -- If qualifier doesn't match current database               raiserror (15250, -1,-1)               return           end       end          if @table_owner is null       begin   -- If unqualified table name           select @full_table_name = quotename(@table_name)       end       else       begin   -- Qualified table name           if @table_owner = ''           begin   -- If empty owner name               select @full_table_name = quotename(@table_owner)           end           else           begin               select @full_table_name = quotename(@table_owner) + '.' + quotename(@table_name)           end       end          select @table_id = object_id(@full_table_name)          select           TABLE_QUALIFIER = convert(sysname,db_name()),           TABLE_OWNER = convert(sysname,schema_name(o.schema_id)),           TABLE_NAME = convert(sysname,o.name),           COLUMN_NAME = convert(sysname,c.name),           KEY_SEQ = convert (smallint,               case                   when c.name = index_col(@full_table_name, i.index_id,  1) then 1                   when c.name = index_col(@full_table_name, i.index_id,  2) then 2                   when c.name = index_col(@full_table_name, i.index_id,  3) then 3                   when c.name = index_col(@full_table_name, i.index_id,  4) then 4                   when c.name = index_col(@full_table_name, i.index_id,  5) then 5                   when c.name = index_col(@full_table_name, i.index_id,  6) then 6                   when c.name = index_col(@full_table_name, i.index_id,  7) then 7                   when c.name = index_col(@full_table_name, i.index_id,  8) then 8                   when c.name = index_col(@full_table_name, i.index_id,  9) then 9                   when c.name = index_col(@full_table_name, i.index_id, 10) then 10                   when c.name = index_col(@full_table_name, i.index_id, 11) then 11                   when c.name = index_col(@full_table_name, i.index_id, 12) then 12                   when c.name = index_col(@full_table_name, i.index_id, 13) then 13                   when c.name = index_col(@full_table_name, i.index_id, 14) then 14                   when c.name = index_col(@full_table_name, i.index_id, 15) then 15                   when c.name = index_col(@full_table_name, i.index_id, 16) then 16               end),           PK_NAME = convert(sysname,k.name)       from           sys.indexes i,           sys.all_columns c,           sys.all_objects o,           sys.key_constraints k       where           o.object_id = @table_id and           o.object_id = c.object_id and           o.object_id = i.object_id and           k.parent_object_id = o.object_id and            k.unique_index_id = i.index_id and            i.is_primary_key = 1 and           (c.name = index_col (@full_table_name, i.index_id,  1) or            c.name = index_col (@full_table_name, i.index_id,  2) or            c.name = index_col (@full_table_name, i.index_id,  3) or            c.name = index_col (@full_table_name, i.index_id,  4) or            c.name = index_col (@full_table_name, i.index_id,  5) or            c.name = index_col (@full_table_name, i.index_id,  6) or            c.name = index_col (@full_table_name, i.index_id,  7) or            c.name = index_col (@full_table_name, i.index_id,  8) or            c.name = index_col (@full_table_name, i.index_id,  9) or            c.name = index_col (@full_table_name, i.index_id, 10) or            c.name = index_col (@full_table_name, i.index_id, 11) or            c.name = index_col (@full_table_name, i.index_id, 12) or            c.name = index_col (@full_table_name, i.index_id, 13) or            c.name = index_col (@full_table_name, i.index_id, 14) or            c.name = index_col (@full_table_name, i.index_id, 15) or            c.name = index_col (@full_table_name, i.index_id, 16))                   order by 1, 2, 3, 5 

The crucial part is how KEY_SEQ is determined. It would work correctly if the second variant would be used.

Alternatively even the first variant would work if the sub-select would be done like this:

SELECT convert(smallint, key_ordinal) FROM sys.index_columns  WHERE object_id = @table_id AND index_id = i.index_id and column_id = c.column_id 

So key_ordinal instead index_column_id needs to be used.

However as it is a system stored procedure it seemingly is not possible to change it. At least not without very critical and unadvised steps.

Are there any alternatives than downgrading or waiting for a fix from Microsoft (workarounds)? What would be the best way to contact Microsoft to receive a fix as quickly as possible?

Edit: Forgot to mention. They even have written about a change to sp_pkeys in their release notes. See https://support.microsoft.com/en-us/topic/kb5003830-cumulative-update-25-for-sql-server-2017-357b80dc-43b5-447c-b544-7503eee189e9#bkmk_13975159.

Edit2: There is no difference with CU26 which was just released.