Question

First let me say I can't modify the database schema at all.

I do not have access to any of the existing code which interacts with the database. Its a compiled assembly and I can't decompile.

enter image description here

I have database tables with a value which functions as a primary key logically within the code but is not auto incremented.

Now here is the twist, sometimes the key increments positive numbers, sometimes negative numbers. :(

Can't find any evidence of a hilo code.

I have no idea how to deal with this besides doing a SELECT TOP 1, adding a value and hope the no overlaps occur.

Was it helpful?

Solution

If your table is part of a database container (*.DBC) and not a free table, then you may find code to auto-increment the key in the database container.

If you open the table and use the MODIFY STRUCTURE command, you can see the table's properties on the third tab. If the table is a member of a database container, the name of the database will be displayed, along with any rules including triggers and the record rule, which executes on every write to the table.

In the days before auto-incrementing fields were added I implemented auto-incrementing fields by adding a record rule as follows:

PROCEDURE TABLE_RR
    LOCAL nOldArea,nUnique

    IF PK=0     &&Only update primary key field when not already set   
        STORE SELECT() TO nOldArea

        &&Open table again in another work area, we can't move the record pointer in a Rule
        IF !USED("TABLE_RR")
            USE TABLE.DBF IN 0 NOUPDATE AGAIN ALIAS TABLE_RR
        ENDIF

        &&Find current maximum value of primary key field, add 1 as next primary key field
        SELECT TABLE_RR
        SET ORDER TO PKTable
        GO BOTT
        STORE 1+PK TO nUnique

        &&Update primary key field for our record
        SELECT(nOldArea)
        REPLACE PK WITH nUnique

     ENDIF

     &&Record rule must return true or changes are rejected
     RETURN .T.

ENDPROC

It's normal custom and practice to store such rules in the database container stored procedure code area, however, providing the running application can find the function, it will execute. Clearly, the above code sample assumes that the table is called TABLE, has a numeric (Int is preferable) field called PK with an index tag of PKTable.

Because the code is in the record rule, it executes on the record during any update and therefore whilst the record is locked, so there's no collision. Any record written without a value in PK gets 1 + the current highest value of that field. The record rule is intended to validate the record and reject invalid values, and must return true (.T.) for the write to be accepted. Other code can occur in the procedure including setting current values to timestamp fields etc.

The record rule can be applied to the table by the ALTER TABLE command:

ALTER TABLE 'TABLENAME' SET CHECK PROCEDURENAME()

Or through the interactive table editor from MODIFY STRUCTURE

OTHER TIPS

Dont know what you need, but if you need an additional PK, you can use the record number:

SELECT RECNO() FROM table1

Although Steve's solution is very good, especially utilizing the database container and assigning rules, you are obviously stuck (somewhat) with your situation. If you are trying to add records and need to create your own auto-increment, you may have to do it by just a procedure call and assign it. This is just an example, but may help you out.

FUNCTION AddMyNewRecord()
   */ localized variable so you don't accidentally mangle some otherwise 
   */ coincidental variable somewhere else.  Preserve work area at start
   local lnSelect, llSaveOk, lnAttempts, lnNewPKID
   lnSelect = select()
   llSaveOk = .f.    

   select YourAddressTable
   if NOT FLOCK()
      lnAttempts = 0
      do while lnAttempts < 10 AND NOT FLOCK()
         wait window "Attempting lock for address table..." timeout 1
      enddo
   endif 

   if NOT FLOCK()
      messagebox( "Unable to lock file to add next record primary key." )
   else
      */ We have a lock, now try to detect both the highest and lowest
      */ EXISTING key for the table
      use in select( "C_TryNextKey" )
      select MIN( YAT.PK ) as MinKey,;
             MAX( YAT.PK ) as MaxKey ;
         from ;
            YourAddressTable YAT;
         into ;
            cursor C_TryNextKey readwrite 

      */ Determine if you need higher or lower key sequence
      if CriteriaForPositiveKey
         lnNewPKID = C_TryNextKey.MaxKey +1
      else
         */ must be getting next LOWER sequence
         lnNewPKID = C_TryNextKey.MinKey -1
      endif 

      */ close temp cursor from getting respective high / low key
      use in select( "C_TryNextKey" )

      */ NOW, we can add the new record
      Select YourAddressTable
      APPEND BLANK
      */ replace PK and all the other fields you need to do too
      Replace PK with lnNewPKID,;
              Address1 with lcSomeVariableYouHaveForAddress1,;
              Address2 with lcSomeVariableForAddress2,;
              AnyOtherFields with lcOtherFieldsToBeSet

      */ Unlock the address table
      UNLOCK

      */ set flag so we know it worked vs not.
      llSaveOk = .t.
   endif  

   */ Return back where it started
   select( lnSelect )

   */ Return if the save was ok or not.
   return llSaveOk
endproc 

This does not modify the existing database, nor table structure. It will however try to get a lock on the table before inserting your new record content. Then look at lowest and highest ID. You will have to analyze the data to find out why some have negative vs positive and update the condition I "place-holder" put in the code above. At the end, unlock the table.

If this makes sense, great, if you need adjustments, let me know and I'll try to help guide you more.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top