[AccessD] Resequence All Autonum Fields - a tentative approach

Bill Benson bensonforums at gmail.com
Fri Dec 26 19:18:58 CST 2014


I have written this routine (ReseedAll calls Reseed on each table that is
not a system table, one by one); The purpose is to resequence every
autonumber field while maintaining data integrity for all the other tables.
I believe it is reasonably robust. Where it is *not* robust is that I just
let it fail with suspended error handling, on the last part where I
re-build indexes, knowing that I have taken care of all unique keys *with
exception* of those Access-generated ones that occur in regards to
relations. The reason for that is that I am just too lazy and inexperienced
to figure out how to iterate through those. However, those are not the ones
preventing duplicates -- and since I never delete them from the database, I
don't produce no sweat about getting an error when "recreating" them.

I have tested this on my databases. If you are interested in using this
routine, I suggest that you make a backup of your own database before
trying it out.

Note that it only works (I think) on Fields / Foreign Keys that are long
integer. I don't think I even check for multi-key primary keys, so I don't
know what it will do to your database if you have multi-column PKs,
non-numeric PKs and FKs, and multi-column FKs. I wouldn't even try it out
if your database is in that condition.

(1) This is probably re-inventing the wheel, someone out there has a better
routine I am more than sure, such as Allen Browne and Roger Carlson. When I
get more energy I will compare this to other routines someone recommends I
check.

(2) Most here will inevitably begin to flame me saying that there is no
need to ever do this anyway, that the purpose of a PK is to be unique, and
it shouldn't matter if values are sequenced or not. Well, since I already
know that --- and this was just an exercised for the anal retentive and
obsessive compulsive... just let me alone please. Well, actually, say what
you want, just be sure to also let us know we can save 15% or more on car
insurance by switching to GEICO, and other things that everybody knows at
the same time - may as well cover all the bases!

(3) at the risk of being redundant, this is a "draft" version. If you want
to kick it around and see if it handles complex database, I would be glad
for advice about where it fails.

Sorry there are few comments. There's a metaphor for this kind of operation
... Towers of Hammurabi comes to mind, but that is not really the one. The
routine essentially checks the max PK value, kills indexes, copies all
records as duplicates deletes switches all the FK values in every other
table that refers to the subject table's PK (single-column) to the higher
number range, one-for-one, with Update queries. Seeds in the largest
autonumber value possible, 2147483647; seeds in 0 to get rid of negatives;
deletes records in the erstwhile lower PK range, duplicates all records
again so that PK sequence will again start with 1; does the swap of FK
values again in all tables that have the FKs to this table's PK, to the
lower range values; then deletes off the higher number range records,
before putting the indexes back on.

Final note:  if the routine pauses with errors in the midst and you let
VarIndexes go out of state after indexes have been removed, you will lose
them. So one thing I will make more robust as a first priority is to store
all the index parameters in a table, so that I can more easily recover them
if there is a problem at runtime.


Option Compare Database
Option Explicit
Function ConcatenatePhone(Companyid As Long) As String
Dim R As DAO.Recordset
Dim D As DAO.Database
Set D = CurrentDb
Dim SQL As String
Dim strResult As String
'            P.Phone,
'            T.PhoneType
'            from TblCompany As C  inner Join
'               (TblCompanyPhone as P
'                  inner join TblPhoneType as T  on T.ID = P.FKPhoneTypeID)
on C.ID = P.FKCompanyID WHERE P.ID = 3
SQL = ""
SQL = SQL & " Select P.Phone, T.PhoneType "
SQL = SQL & " from TblCompany as C "
SQL = SQL & " inner Join (TblCompanyPhone as P "
SQL = SQL & " inner join TblPhoneType as T "
SQL = SQL & " on T.ID = P.FKPhoneTypeID)"
SQL = SQL & " on C.ID = P.FKCompanyID"
SQL = SQL & " WHERE C.ID = " & Companyid
Set R = D.OpenRecordset(SQL)
While Not R.EOF
    strResult = strResult & "|" & R!PhoneType & ": " & R!Phone
    R.MoveNext
Wend
If strResult <> "" Then
    strResult = Mid(strResult, 2)
End If
ConcatenatePhone = strResult
End Function
''
''Function MyWholesaler() As Long
''MyWholesaler = CLng(Nz(Screen.ActiveForm.Controls("txtWholesaler"), 0))
''
''End Function
''Function MyRetailer() As Long
''MyRetailer = CLng(Nz(Screen.ActiveForm.Controls("txtRetailer"), 0))
''
''End Function
''
''Function MyMarketer() As Long
''MyMarketer = CLng(Nz(Screen.ActiveForm.Controls("txtMarketer"), 0))
''
''End Function
''
''
''
Sub findcontrol()
Dim Frm As Form
Dim ctrl As Control
Dim str As String
Set Frm = Screen.ActiveForm
For Each ctrl In Frm.Controls
   str = ""
   On Error Resume Next
   str = ctrl.RowSource
   If str <> "" Then
    Debug.Print ctrl.Name & ": " & str
End If
Next
End Sub
Sub PositionCombo(Frm As Form, Ctr As String, iCol As Long, varValue As
Variant)
Dim ctrl As Control
Dim i As Long
Set ctrl = Frm.Controls(Ctr)
ctrl.Requery
For i = 0 To ctrl.ListCount - 1
    If ctrl.Column(iCol, i) = CStr(varValue) Then
        ctrl.Value = i
        Exit For
    End If
Next
End Sub
Sub ReseedAll()
Dim i As Long
Dim T As DAO.TableDef
Dim D As DAO.Database
Set D = CurrentDb
Dim VarTables() As String
ReDim VarTables(1 To D.TableDefs.Count) As String
For Each T In D.TableDefs
    If UCase(Left(T.Name, 4)) <> "MSYS" And Left(T.Name, 1) <> "~" Then
        i = i + 1
        VarTables(i) = T.Name
    End If
Next
For i = 1 To UBound(VarTables)
    If VarTables(i) <> "" Then
    Debug.Print VarTables(i)
    'Stop
        Reseed VarTables(i)
    End If
Next
End Sub
Sub Reseed(strTable As String)
Dim Qdf As DAO.QueryDef
Dim Tdf As DAO.TableDef
Dim TdfDependent As DAO.TableDef
Dim D As DAO.Database
Dim iCol As Long
Dim Frm As Form
Dim i As Long
Dim iRelFields As Long
Dim R1 As DAO.Recordset
Dim R2 As DAO.Recordset
Dim R As DAO.Recordset
Dim FLD As DAO.Field
Dim strField As String
Dim strIndexFields  As String
Dim strFields As String
Dim SQL As String
Dim iMax As Double
Dim Indx As Index
Dim iUnique As Long
Dim iRequired As Long
Set D = CurrentDb
Dim iUB As Long
Dim VarIndexes()
Dim VarFieldsTemp
Dim VarFields() As String
Dim Rel As Relation
Dim fldPK As DAO.Field
Dim strTempFields As String
If strTable = "Temp" Then Exit Sub
On Error Resume Next
For Each Frm In Forms
    DoCmd.Close acForm, Frm.Name, acSaveYes
Next
For Each Qdf In D.QueryDefs
    DoCmd.Close acQuery, Qdf.Name, acSaveYes
Next
For Each Tdf In D.TableDefs
    DoCmd.Close acTable, Tdf.Name
Next
Set Tdf = D.TableDefs(strTable)
For Each FLD In Tdf.Fields

    If FLD.Attributes And dbAutoIncrField Then
        strField = FLD.Name
    Else
        strFields = strFields & ",[" & FLD.Name & "]"
    End If
Next
Err.Clear
DoCmd.DeleteObject acTable, "Temp"
SQL = "Create Table [Temp] ([" & strField & "] Long)"
Err.Clear
D.Execute SQL, dbFailOnError
D.TableDefs.Refresh
If Err.Number <> 0 Then Stop

strFields = Mid(strFields, 2)

iMax = DMax("[" & strField & "]", strTable)
'Remove indexes else we might not be able to put in duplicate items
ReDim VarIndexes(1 To 4, 0 To 0)
For Each Indx In Tdf.Indexes
    If Indx.Name <> "PrimaryKey" Then
        strIndexFields = ""
        For Each FLD In Indx.Fields
            strIndexFields = strIndexFields & ",[" & FLD.Name & "]"
        Next
        strIndexFields = Mid(strIndexFields, 2)
        iUnique = Indx.Unique
        iRequired = Indx.Required
        iUB = UBound(VarIndexes, 2) + 1
        If iUB = 1 Then
            ReDim VarIndexes(1 To 4, 1 To iUB)
        Else
            ReDim Preserve VarIndexes(1 To 4, 1 To iUB)
        End If
        VarIndexes(1, iUB) = Indx.Name
        VarIndexes(2, iUB) = strIndexFields
        VarIndexes(3, iUB) = iUnique
        VarIndexes(4, iUB) = iRequired
    End If
Next
For i = UBound(VarIndexes, 2) To 1 Step -1
    Tdf.Indexes.Delete VarIndexes(1, i)
Next
Tdf.Indexes.Refresh
'insert Duplicates
Err.Clear
SQL = "Insert Into [" & strTable & "] (" & strFields & ") Select " &
strFields & " from [" & strTable & "] order by [" & strField & "] Asc"
D.Execute SQL, dbFailOnError
If Err.Number <> 0 Then Stop
'Loop each dupe record, change fks everywhere to these replacement values
Set R1 = D.OpenRecordset("Select * from [" & strTable & "] WHERE [" &
strField & "] > " & iMax & " ORDER BY [" & strField & "] ASC")
R1.MoveFirst
Set R2 = D.OpenRecordset("Select * from [" & strTable & "] WHERE [" &
strField & "] <= " & iMax & " ORDER BY [" & strField & "] ASC")
R2.MoveFirst
While Not R1.EOF
    On Error GoTo 0
    For Each Rel In D.Relations
        If Rel.Table = Tdf.Name Then
            For iRelFields = 0 To Rel.Fields.Count - 1
                For Each fldPK In Tdf.Indexes("PrimaryKey").Fields
                    If Rel.Fields(iRelFields).Name = fldPK.Name Then
                        SQL = "Update [" & Rel.ForeignTable & "] Set [" &
Rel.Fields(iRelFields).ForeignName & "] = " & R1.Fields(strField) & " WHERE
[" & Rel.Fields(iRelFields).ForeignName & "] = " & R2.Fields(strField)
                        Err.Clear
                        D.Execute SQL, dbFailOnError
                        If Err.Number <> 0 Then Stop
                    End If
                Next
            Next
        End If
    Next
    R1.MoveNext
    R2.MoveNext
Wend
'Delete obsolete records
Err.Clear
D.Execute "Delete From [" & strTable & "] Where [" & strField & "] <=" &
iMax, dbFailOnError
If Err.Number <> 0 Then Stop
ReDim VarFieldsTemp(1 To 2, 0 To 0)
For Each Rel In D.Relations
    If Rel.ForeignTable = strTable Then
        SQL = "ALTER TABLE Temp ADD COLUMN [" & Rel.Fields(0).ForeignName &
"] Long"
        Err.Clear
        D.Execute SQL, dbFailOnError
        If Err.Number <> 0 Then Stop
        iUB = UBound(VarFieldsTemp, 2) + 1
        If iUB = 1 Then
            ReDim VarFieldsTemp(1 To 2, 1 To iUB)
        Else
            ReDim Preserve VarFieldsTemp(1 To 2, 1 To iUB)
        End If
        VarFieldsTemp(1, iUB) = Rel.Fields(0).ForeignName
        VarFieldsTemp(2, iUB) = DMin("[" & Rel.Fields(0).Name & "]",
Rel.Table)

    End If
Next
'insert the highest possible recordid
Err.Clear
D.Execute "Insert into [Temp] ([" & strField & "]) Values (" & 2147483647 &
")", dbFailOnError
If Err.Number <> 0 Then Stop
strTempFields = "[" & strField & "]"
'Put other required values in the table due to FKs in strTable
For i = 1 To UBound(VarFieldsTemp, 2)
    strTempFields = strTempFields & ",[" & VarFieldsTemp(1, i) & "]"
    SQL = "Update [Temp] Set [" & VarFieldsTemp(1, i) & "] = " &
VarFieldsTemp(2, i)
    Err.Clear
    D.Execute SQL, dbFailOnError
    If Err.Number <> 0 Then Stop
Next
'Append this to our main table
Err.Clear
D.Execute "Insert Into [" & strTable & "] (" & strTempFields & ") Select "
& strTempFields & " From [Temp]", dbFailOnError
If Err.Number <> 0 Then Stop
'It was used so now we can remove it so we dont duplicate it
Err.Clear
SQL = "Update [Temp] Set [" & strField & "] = 0"
D.Execute SQL, dbFailOnError
If Err.Number <> 0 Then Stop
Err.Clear
D.Execute "Insert Into [" & strTable & "] (" & strTempFields & ") Select "
& strTempFields & " From [Temp]", dbFailOnError
If Err.Number <> 0 Then Stop
Err.Clear
D.Execute "Delete from [" & strTable & "] Where [" & strField & "] = " &
2147483647, dbFailOnError
If Err.Number <> 0 Then Stop
Err.Clear
D.Execute "Delete from [" & strTable & "] Where [" & strField & "] = " & 0,
dbFailOnError
If Err.Number <> 0 Then Stop
'From here on out all records should be 1, 2, ...
'Add the records in one more time so that they will sequence starting at 1
Err.Clear
D.Execute "Insert Into [" & strTable & "] (" & strFields & ") Select " &
strFields & " from [" & strTable & "] order by [" & strField & "] Asc",
dbFailOnError
If Err.Number <> 0 Then Stop
'Rerun the swap
Set R1 = D.OpenRecordset("Select * from [" & strTable & "] WHERE [" &
strField & "] > " & iMax & " ORDER BY [" & strField & "] ASC")
R1.MoveFirst
Set R2 = D.OpenRecordset("Select * from [" & strTable & "] WHERE [" &
strField & "] <= " & iMax & " ORDER BY [" & strField & "] ASC")
R2.MoveFirst
While Not R1.EOF
    On Error GoTo 0
    For Each Rel In D.Relations
        Debug.Print Rel.Name
        If Rel.Table = Tdf.Name Then
            For iRelFields = 0 To Rel.Fields.Count - 1
                For Each fldPK In Tdf.Indexes("PrimaryKey").Fields
                    If Rel.Fields(iRelFields).Name = fldPK.Name Then
                        SQL = "Update [" & Rel.ForeignTable & "] Set [" &
Rel.Fields(iRelFields).ForeignName & "] = " & R2.Fields(strField) & " WHERE
[" & Rel.Fields(iRelFields).ForeignName & "] = " & R1.Fields(strField)
                        Err.Clear
                        D.Execute SQL, dbFailOnError
                        If Err.Number <> 0 Then Stop
                    End If
                Next
            Next
        End If
    Next
    R1.MoveNext
    R2.MoveNext
Wend
'Delete duplicates, those above iDesiredCount
Err.Clear
D.Execute "Delete from [" & strTable & "] Where [" & strField & "] > " &
iMax, dbFailOnError
If Err.Number <> 0 Then Stop
'Put back indexes after duplicates removed
Set Tdf = Nothing
Set R1 = Nothing
Set R2 = Nothing
For i = 1 To UBound(VarIndexes, 2)
'    Set Indx = Tdf.CreateIndex(VarIndexes(1, i))
'    VarFields = Split(VarIndexes(2, i), ",")
    SQL = "CREATE " & IIf(CBool(VarIndexes(3, i)), " UNIQUE ", "") & "
Index " & VarIndexes(1, i) & " on [" & strTable & "] (" & VarIndexes(2, i)
& ")" & IIf(CBool(VarIndexes(4, i)), " WITH DISALLOW NULL;", ";")
    On Error Resume Next
    D.Execute SQL, dbFailOnError

    'If Err.Number <> 0 Then Stop
Next

'D.Execute "CREATE UNIQUE INDEX CustID " _
        & "ON Customers (CustomerID) " _
        & "WITH DISALLOW NULL;"


Set FLD = Nothing
Set Tdf = Nothing


End Sub


More information about the AccessD mailing list