MS Access: how to compact current database in VBA
Asked Answered
T

16

23

Pretty simple question, I know.

Tympanites answered 22/9, 2009 at 13:33 Comment(1)
of course, just would like stackOverflow to have all the answers!Tympanites
M
43

If you want to compact/repair an external mdb file (not the one you are working in just now):

Application.compactRepair sourecFile, destinationFile

If you want to compact the database you are working with:

Application.SetOption "Auto compact", True

In this last case, your app will be compacted when closing the file.

My opinion: writting a few lines of code in an extra MDB "compacter" file that you can call when you want to compact/repair an mdb file is very usefull: in most situations the file that needs to be compacted cannot be opened normally anymore, so you need to call the method from outside the file.

Otherwise, the autocompact shall by default be set to true in each main module of an Access app.

In case of a disaster, create a new mdb file and import all objects from the buggy file. You will usually find a faulty object (form, module, etc) that you will not be able to import.

Monazite answered 22/9, 2009 at 14:42 Comment(7)
COMPACT ON CLOSE is worthless in any properly designed Access app (it's only the back end that needs compacting, and you never have that open in the front end), and downright dangerous, since you don't get a chance to skip it (compacts can cause certain kinds of corrupt but still accessible data to be permanently lost).Calyx
When you use your client database to hold temporary data (such as local tables for example), compacting the file on close makes sense.Monazite
interesting stuff David. I have compact on close set, but might have to review that. Had a moment where it hung while compacting yesterday, killed the process and wondered if it had corrupted my db. Sighed in relief as I realised it compacts to a temp file, but still.Tympanites
When does one open a temporary database? Don't you have it linked to your application database, rather than opening it directly? Or are you storing all your UI objects in your temp database? I just don't see the point of COMPACT ON CLOSE. If I've been hammering a datastore with a bunch of UPDATE/APPEND/DELETE operations, I know to back it up and compact it. You do back up before a compact, right?Calyx
I was talking of temporary tables, not temporary databases. I have noticed that, when you regularly create temporary tables on your front end (being imported data from you back end, or runtime data such as a menu table, built at loggin time out of a global menu table with options granted depending on the user, or local 'available connections' table uploaded from an xml file somewhere in the network, or ...) the client side file has a tendance to grow, which is limited by setting the compactOnClause option. We do not back up before compacting, as we have no "permanent" data on the client side.Monazite
THe very best option, for temp tables, is to avoid them. Second best: create programmatically a temp db on the %TEMP% folder of the userand use that db, which can be destroyed when closing the app.Aeroballistics
#iDevlop: I loved your proposal. Anyway we are now using on our client side a combination of ADODB recordsets as recordsources, and xml files holding temp data. We thus do not have to manage any temp table in front end.Monazite
T
3

If you have the database with a front end and a back end. You can use the following code on the main form of your front end main navigation form:

Dim sDataFile As String, sDataFileTemp As String, sDataFileBackup As String
Dim s1 As Long, s2 As Long

sDataFile = "C:\MyDataFile.mdb"
sDataFileTemp = "C:\MyDataFileTemp.mdb"
sDataFileBackup = "C:\MyDataFile Backup " & Format(Now, "YYYY-MM-DD HHMMSS") & ".mdb"
    
DoCmd.Hourglass True

'get file size before compact
Open sDataFile For Binary As #1
s1 = LOF(1)
Close #1

'backup data file
FileCopy sDataFile, sDataFileBackup

'only proceed if data file exists
If Dir(sDataFileBackup, vbNormal) <> "" Then
    
        'compact data file to temp file
        On Error Resume Next
        Kill sDataFileTemp
        On Error GoTo 0
        DBEngine.CompactDatabase sDataFile, sDataFileTemp
        
        If Dir(sDataFileTemp, vbNormal) <> "" Then
            'delete old data file data file
            Kill sDataFile
        
            'copy temp file to data file
            FileCopy sDataFileTemp, sDataFile
            
            'get file size after compact
            Open sDataFile For Binary As #1
            s2 = LOF(1)
            Close #1
        
            DoCmd.Hourglass False
            MsgBox "Compact complete. " & vbCrLf & vbCrLf _
                & "Size before: " & Round(s1 / 1024 / 1024, 2) & "MB" & vbCrLf _
                & "Size after:    " & Round(s2 / 1024 / 1024, 2) & "MB", vbInformation
        Else
            DoCmd.Hourglass False
            MsgBox "ERROR: Unable to compact data file."
        End If

Else
        DoCmd.Hourglass False
        MsgBox "ERROR: Unable to backup data file."
End If

DoCmd.Hourglass False
Toggery answered 20/6, 2012 at 1:16 Comment(0)
F
2

Try adding this module, pretty simple, just launches Access, opens the database, sets the "Compact on Close" option to "True", then quits.

Syntax to auto-compact:

acCompactRepair "C:\Folder\Database.accdb", True

To return to default*:

acCompactRepair "C:\Folder\Database.accdb", False

*not necessary, but if your back end database is >1GB this can be rather annoying when you go into it directly and it takes 2 minutes to quit!

EDIT: added option to recurse through all folders, I run this nightly to keep databases down to a minimum.

'accCompactRepair
'v2.02 2013-11-28 17:25

'===========================================================================
' HELP CONTACT
'===========================================================================
' Code is provided without warranty and can be stolen and amended as required.
'   Tom Parish
'   [email protected]
'   http://baldywrittencod.blogspot.com/2013/10/vba-modules-access-compact-repair.html
'   DGF Help Contact: see BPMHelpContact module
'=========================================================================

'includes code from
'http://www.ammara.com/access_image_faq/recursive_folder_search.html
'tweaked slightly for improved error handling

'   v2.02   bugfix preventing Compact when bAutoCompact set to False
'           bugfix with "OLE waiting for another application" msgbox
'           added "MB" to start & end sizes of message box at end
'   v2.01   added size reduction to message box
'   v2.00   added recurse
'   v1.00   original version

Option Explicit

Function accSweepForDatabases(ByVal strFolder As String, Optional ByVal bIncludeSubfolders As Boolean = True _
    , Optional bAutoCompact As Boolean = False) As String
'v2.02 2013-11-28 17:25
'sweeps path for .accdb and .mdb files, compacts and repairs all that it finds
'NB: leaves AutoCompact on Close as False unless specified, then leaves as True

'syntax:
'   accSweepForDatabases "path", [False], [True]

'code for ActiveX CommandButton on sheet module named "admin" with two named ranges "vPath" and "vRecurse":
'   accSweepForDatabases admin.Range("vPath"), admin.Range("vRecurse") [, admin.Range("vLeaveAutoCompact")]

Application.DisplayAlerts = False

Dim colFiles As New Collection, vFile As Variant, i As Integer, j As Integer, sFails As String, t As Single
Dim SizeBefore As Long, SizeAfter As Long
t = Timer
RecursiveDir colFiles, strFolder, "*.accdb", True  'comment this out if you only have Access 2003 installed
RecursiveDir colFiles, strFolder, "*.mdb", True

    For Each vFile In colFiles
        'Debug.Print vFile
        SizeBefore = SizeBefore + (FileLen(vFile) / 1048576)
On Error GoTo CompactFailed
    If InStr(vFile, "Geographical Configuration.accdb") > 0 Then MsgBox "yes"
        acCompactRepair vFile, bAutoCompact
        i = i + 1  'counts successes
        GoTo NextCompact
CompactFailed:
On Error GoTo 0
        j = j + 1   'counts failures
        sFails = sFails & vFile & vbLf  'records failure
NextCompact:
On Error GoTo 0
        SizeAfter = SizeAfter + (FileLen(vFile) / 1048576)

    Next vFile

Application.DisplayAlerts = True

'display message box, mark end of process
    accSweepForDatabases = i & " databases compacted successfully, taking " & CInt(Timer - t) & " seconds, and reducing storage overheads by " & Int(SizeBefore - SizeAfter) & "MB" & vbLf & vbLf & "Size Before: " & Int(SizeBefore) & "MB" & vbLf & "Size After: " & Int(SizeAfter) & "MB"
    If j > 0 Then accSweepForDatabases = accSweepForDatabases & vbLf & j & " failures:" & vbLf & vbLf & sFails
    MsgBox accSweepForDatabases, vbInformation, "accSweepForDatabases"

End Function

Function acCompactRepair(ByVal pthfn As String, Optional doEnable As Boolean = True) As Boolean
'v2.02 2013-11-28 16:22
'if doEnable = True will compact and repair pthfn
'if doEnable = False will then disable auto compact on pthfn

On Error GoTo CompactFailed

Dim A As Object
Set A = CreateObject("Access.Application")
With A
    .OpenCurrentDatabase pthfn
    .SetOption "Auto compact", True
    .CloseCurrentDatabase
    If doEnable = False Then
        .OpenCurrentDatabase pthfn
        .SetOption "Auto compact", doEnable
    End If
    .Quit
End With
Set A = Nothing
acCompactRepair = True
Exit Function
CompactFailed:
End Function


'source: http://www.ammara.com/access_image_faq/recursive_folder_search.html
'tweaked slightly for error handling

Private Function RecursiveDir(colFiles As Collection, _
                             strFolder As String, _
                             strFileSpec As String, _
                             bIncludeSubfolders As Boolean)

    Dim strTemp As String
    Dim colFolders As New Collection
    Dim vFolderName As Variant

    'Add files in strFolder matching strFileSpec to colFiles
    strFolder = TrailingSlash(strFolder)
On Error Resume Next
    strTemp = ""
    strTemp = Dir(strFolder & strFileSpec)
On Error GoTo 0
    Do While strTemp <> vbNullString
        colFiles.Add strFolder & strTemp
        strTemp = Dir
    Loop

    If bIncludeSubfolders Then
        'Fill colFolders with list of subdirectories of strFolder
On Error Resume Next
        strTemp = ""
        strTemp = Dir(strFolder, vbDirectory)
On Error GoTo 0
        Do While strTemp <> vbNullString
            If (strTemp <> ".") And (strTemp <> "..") Then
                If (GetAttr(strFolder & strTemp) And vbDirectory) <> 0 Then
                    colFolders.Add strTemp
                End If
            End If
            strTemp = Dir
        Loop

        'Call RecursiveDir for each subfolder in colFolders
        For Each vFolderName In colFolders
            Call RecursiveDir(colFiles, strFolder & vFolderName, strFileSpec, True)
        Next vFolderName
    End If

End Function

Private Function TrailingSlash(strFolder As String) As String
    If Len(strFolder) > 0 Then
        If Right(strFolder, 1) = "\" Then
            TrailingSlash = strFolder
        Else
            TrailingSlash = strFolder & "\"
        End If
    End If
End Function
Farouche answered 8/10, 2013 at 14:4 Comment(0)
O
1

For Access 2013, you could just do

Sendkeys "%fic"

This is the same as typing ALT, F, I, C on your keyboard.

It's probably a different sequence of letters for different versions, but the "%" symbol means "ALT", so keep that in the code. you may just need to change the letters, depending on what letters appear when you press ALT

Letters that appear when pressing ALT in Access 2013

Officeholder answered 21/3, 2018 at 9:33 Comment(2)
or Sendkeys "%yc"Bentlee
Do not rely on keypresses when you have native methods available. This can lead to undefined behavior as keypresses will be sent to whatever is active (Possibly not your intended target).Undervest
P
1

In response to the excellent post by jdawgx:

Please be aware of a flaw in the code for CompactDB() above.

If the database's "AppTitle" property is defined (as happens when an "Application title" is defined in the database properties), this invalidates the "default window title" logic shown, which can cause the script to fail, or "behave unpredictably". So, adding code to check for an AppTitle property - or using API calls to read the Window title text from the Application.hWndAccessApp window could both be much more reliable.

Additionally, in Access 2019, we have observed that: SendKeys "multi-key-string-here" ... may also not work reliably, needing to be replaced with:

SendKey (single-character)

'put a DoEvents or Sleep 150 here

SendKey (single-character)

'put a DoEvents or Sleep 150 here

SendKey (single-character)

'put a DoEvents or Sleep 150 here

SendKey (single-character)

...to get proper responses from the Access UI.

ALSO for Access 2019:

Sendkeys "%yc" ( <-- works for Access 2016)

is no longer correct.

it is now:

Sendkeys "%y1c"

...and if that little change wasn't enough - try to determine (in code) how to tell the difference between Access 2016 and 2019 - Good Luck!! because Application.Version alone won't help, and even combining Application.Version and Application.Build is not a guarantee (unless you are in a controlled-release enterprise environment, and then it may work as the possible version/build #s in circulation should be more limited).

Progesterone answered 30/6, 2021 at 17:0 Comment(0)
M
0

Yes it is simple to do.

Sub CompactRepair()
  Dim control As Office.CommandBarControl
  Set control = CommandBars.FindControl( Id:=2071 )
  control.accDoDefaultAction
End Sub

Basically it just finds the "Compact and repair" menuitem and clicks it, programatically.

Mosstrooper answered 22/9, 2009 at 13:40 Comment(3)
I guess the difference between my version and yours is that this answer appears to be for Access 2007 and mine for Access 2003 and lower.Sclerenchyma
not a fan of virtually clicking command bars - reminds me of SendKeys!Tympanites
I get an error "You cannot compact the open database by running a macro or Visual Basic code."Giana
I
0

I did this many years back on 2003 or possibly 97, yikes!

If I recall you need to use one of the subcommands above tied to a timer. You cannot operate on the db with any connections or forms open.

So you do something about closing all forms, and kick off the timer as the last running method. (which will in turn call the compact operation once everything closes)

If you haven't figured this out I could dig through my archives and pull it up.

Ihs answered 22/9, 2009 at 14:25 Comment(0)
C
0

When the user exits the FE attempt to rename the backend MDB preferably with todays date in the name in yyyy-mm-dd format. Ensure you close all bound forms, including hidden forms, and reports before doing this. If you get an error message, oops, its busy so don't bother. If it is successful then compact it back.

See my Backup, do you trust the users or sysadmins? tips page for more info.

Crankshaft answered 22/9, 2009 at 18:37 Comment(0)
T
0

DBEngine.CompactDatabase source, dest

Tympanites answered 30/9, 2009 at 11:5 Comment(0)
M
0

Application.SetOption "Auto compact", False '(mentioned above) Use this with a button caption: "DB Not Compact On Close"

Write code to toggle the caption with "DB Compact On Close" along with Application.SetOption "Auto compact", True

AutoCompact can be set by means of the button or by code, ex: after importing large temp tables.

The start up form can have code that turns off Auto Compact, so that it doesn't run every time.

This way, you are not trying to fight Access.

Merrymaker answered 25/2, 2015 at 1:50 Comment(0)
B
0

If you don't wish to use compact on close (eg, because the front-end mdb is a robot program that runs continually), and you don't want to create a separate mdb just for compacting, consider using a cmd file.

I let my robot.mdb check its own size:

FileLen(CurrentDb.Name))

If its size exceeds 1 GB, it creates a cmd file like this ...

Dim f As Integer
Dim Folder As String
Dim Access As String
    'select Access in the correct PF directory (my robot.mdb runs in 32-bit MSAccess, on 32-bit and 64-bit machines)
    If Dir("C:\Program Files (x86)\Microsoft Office\Office\MSACCESS.EXE") > "" Then
        Access = """C:\Program Files (x86)\Microsoft Office\Office\MSACCESS.EXE"""
    Else
        Access = """C:\Program Files\Microsoft Office\Office\MSACCESS.EXE"""
    End If
    Folder = ExtractFileDir(CurrentDb.Name)
    f = FreeFile
    Open Folder & "comrep.cmd" For Output As f
    'wait until robot.mdb closes (ldb file is gone), then compact robot.mdb
    Print #f, ":checkldb1"
    Print #f, "if exist " & Folder & "robot.ldb goto checkldb1"
    Print #f, Access & " " & Folder & "robot.mdb /compact"
    'wait until the robot mdb closes, then start it
    Print #f, ":checkldb2"
    Print #f, "if exist " & Folder & "robot.ldb goto checkldb2"
    Print #f, Access & " " & Folder & "robot.mdb"
    Close f

... launches the cmd file ...

Shell ExtractFileDir(CurrentDb.Name) & "comrep.cmd"

... and shuts down ...

DoCmd.Quit

Next, the cmd file compacts and restarts robot.mdb.

Businesswoman answered 29/6, 2015 at 14:41 Comment(1)
Oops, ExtractFileDir is not a standard VBA function... Left(CurrentDb.Name, InStrRev(CurrentDb.Name, "\")) will also do the trick.Businesswoman
D
0

Try this. It works on the same database in which the code resides. Just call the CompactDB() function shown below. Make sure that after you add the function, you click the Save button in the VBA Editor window prior to running for the first time. I only tested it in Access 2010. Ba-da-bing, ba-da-boom.

Public Function CompactDB()

    Dim strWindowTitle As String

    On Error GoTo err_Handler

    strWindowTitle = Application.Name & " - " & Left(Application.CurrentProject.Name, Len(Application.CurrentProject.Name) - 4)
    strTempDir = Environ("Temp")
    strScriptPath = strTempDir & "\compact.vbs"
    strCmd = "wscript " & """" & strScriptPath & """"

    Open strScriptPath For Output As #1
    Print #1, "Set WshShell = WScript.CreateObject(""WScript.Shell"")"
    Print #1, "WScript.Sleep 1000"
    Print #1, "WshShell.AppActivate " & """" & strWindowTitle & """"
    Print #1, "WScript.Sleep 500"
    Print #1, "WshShell.SendKeys ""%yc"""
    Close #1

    Shell strCmd, vbHide
    Exit Function

    err_Handler:
    MsgBox "Error " & Err.Number & ": " & Err.Description
    Close #1

End Function
Diplocardiac answered 9/7, 2015 at 14:28 Comment(0)
P
0

Please Note the following - all of you who favor doing a "Compact on Close" solution for MS-Access.

I used to prefer that option too, until one day, when I received the WORST error message possible from the DBEngine during a Compress & Repair operation:

"Table MSysObjects is corrupt - Table Truncated."

Now, you have probably never realized that THAT error is even a possibility.

Well, it is. And if you ever see it, your ENTIRE DATABASE, and EVERYTHING IN IT is now simply GONE. poof!

What is funny about that is that Access will let you actually reopen the "fixed" database, only, the Access window and menu items are all now utterly useless (except to close the DB and exit access again) because ALL the tables (including the other MSYS* tables, forms, queries, reports, code modules, & macros) are simply gone - and with the disk space previously allocated to them released to the tender mercies of the Windows OS - unless you have additional protection than the bog-standard recycle bin, which won't help you either.

So, if you REALLY want to accept the risk of Compact on Close completely clobbering your database - with NO POSSIBILITY of recovering it, then please...do carry on.

If, OTOH, like me you find that risk an unacceptable one, well, don't enable C&R-on-Close - ever again.

Progesterone answered 21/9, 2021 at 17:20 Comment(0)
J
0

You can compact the current database via code (VBA) by using the following command: DoCmd.RunCommand acCmdCompactDatabase1.

This command is equivalent to clicking the Compact and Repair Database button on the Database Tools tab2.

Jaehne answered 8/6, 2023 at 3:21 Comment(0)
S
-1

Check out this solution VBA Compact Current Database.

Basically it says this should work

Public Sub CompactDB() 
    CommandBars("Menu Bar").Controls("Tools").Controls ("Database utilities"). _
    Controls("Compact and repair database...").accDoDefaultAction 
End Sub 
Sclerenchyma answered 22/9, 2009 at 13:39 Comment(1)
I'd strongly suggest using the direct VBA method.Crankshaft
C
-1

There's also Michael Kaplan's SOON ("Shut One, Open New") add-in. You'd have to chain it, but it's one way to do this.

I can't say I've had much reason to ever want to do this programatically, since I'm programming for end users, and they are never using anything but the front end in the Access user interface, and there's no reason to regularly compact a properly-designed front end.

Calyx answered 23/9, 2009 at 1:44 Comment(2)
nice strategic thought. Sometimes though, you just want it to be a self contained file. a portable tool.Tympanites
Highly suspect: "There's no reason to regularly compact a properly-designed front end." First, front-ends aren't compacted; second, compacting and repairing absolutely should be done even on "properly-designed front ends" because internal Access fragmentation is not always the result of a front-end that suffers from some sort of design flaw.Riotous

© 2022 - 2024 — McMap. All rights reserved.