In short, the primary key is the value the application uses to identify a row in the table (for example in joins).  The %ID (IDKEY) is the value the systems uses internally (the "row address") to identify a row in the table.

Thus the system always maintains an index on the primary key, so the application can use it, in addition to the "master map", which is an index on %ID.

Often the primary key is a value generated by the application, while the %ID is a system-generated integer.

Obviously the duality of having 2 different ways and indexes to identify rows might not necessarily be a good thing, and you can overcome that in 2 ways:

  1. Use the application-generated primary-key value as the IDKEY.  You can do that by identifying the primary key index in the class definition with both keywords PrimaryKey and IdKey (you can also do that from DDL if you correctly set the special flag for this purpose).  This will make the primary key index be the table's "master map", i.e. the primary key will be used as the main internal address for the rows.
  2. Do not use an application-generated primary key value, and instead use the system-generated %ID integer within the application as the application-used primary key (for example in joins).

Note that solution 1 above could be a problem, esp. if the primary key consists of more than one field or is not an integer, since the system-generated %ID lends itself better to more efficient processing, including usage of bitmaps.

So, depending on the nature of the application, sometimes it's better to have separate indexes for the application-generated primary key and the system-generated %ID.