MS Access and Forms – Create a Filtered Autonumber for Child Records

The following uses a function and the before update event of a form.

Sometimes it can be useful to have some kind of order field in the child records to indicate the order or version numbers of items. Although an incrementing Primary Key can be used child records may be in the thousands and if related to the parent you may want a simple almost ranking within the group. Which may be more meaningful when viewed filtered according to the parent.

A particular case may be where you are storing documents which have some kind of version.

 Public Function GetChildCount(OrderNo as Integer) As Integer

Dim intCount as Integer
intCount = DCount("FKID","[ParentTable]","[FKID]=" & OrderNo)
GetChildCount = IntCount + 1

End Function

This counts the number of records with the same FKID in the table called ParentTable with a FKID equal to OrderNo

Then within the before update event of the Sub_Form

Private Sub Form_BeforeUpdate(Cancel As Integer)

If Me.NewRecord Then
Me.Order = GetChildCount([SiteID])
End If

End Sub

The If statement just ensures that when you edit a record the order is not updated to the count of the child records if a count already exists in the field Order.

VBA Function Boolean Switch to test for specific character sets within a field

Boolean Switch to test for specific character sets within a field. This codes tests whether a field contains blanks or the specified characters only and will return -1 if true and 0 if false. If a character occurs that is not within the LValid_Values it will return 0 as false. This is different from identifying whether a field contains the listed characters. This can be useful for identifying characters in a field that you are wanting to alter the variable type. MS Access (and other databases) will delete field values that cannot be converted so if possible you want to identify values with illegal characters. This code can be used to identify this. Change the value of LValid_Values to represent the allowable characters and then you can reference the function in a query to identify illegal records and values. My primary use case is testing for numerical values in a string field which I am looking to alter so that I can change it into a long integer variable type. This is particularly useful for hunting down things like letters in house numbers or slashes in flat identities.

Function CharCheck(targetField) As Boolean

   Dim LPos As Integer
   Dim LChar As String
   Dim LValid_Values As String
   
   'Start at first character in strField
   LPos = 1
   
   LValid_Values = ".0123456789"
    'Test each character in strField
   While LPos <= Len(targetField)
      'Single character in strField
      LChar = Mid(targetField, LPos, 1)
    
      'If character is not LValid Value, return FALSE
      If InStr(LValid_Values, LChar) = 0 Then
         CharCheck = False
         Exit Function
      End If
    
         'Increment counter
       LPos = LPos + 1
       
   Wend
  

   'Value is LValid Value, return TRUE
   CharCheck = True
 
End Function    

Save Record before Event

If users are editing or creating a record and there is an option on the form to print out the form if they press it they will expect the information that they have just created to appear on the print preview. Normally MS Access does not save the information to the database until the record is exited or specifically instructed. This can lead to user confusion when they enter a record hit print preview expecting to see the record and see a blank preview.

Placing the following code before calling the print preview will ensure that the record is saved prior to the print preview being triggered resulting correct information being displayed in the print easy. An easy fix – you should always do the easy wins.!

Note order is important, put this before the event you are wishing to trigger (quite ubiquitous in my code)

DoCmd.DoMenuItem acFormBar, acRecordsMenu, acSaveRecord, , acMenuVer70

Its the constant small touches that make great applications

Forcing or Limiting Columns Using Cross Tab Queries

MS Access has a version of SQL that can be edited to enforce specific columns being shown in queries.

Within the query editor navigate to the SQL View

Normally when a cross-tab query is selected MS Access will generate columns for every value that exists. This is often very useful but if there are no values there may be gaps in the scale. Additionally you may wish to limit the number of columns that are displayed in the query or force limited numbers to tally with a report. Some reports will crash if certain columns are not found.

Generally the last line of SQL code within a query will follow a similar syntax to the following;

PIVOT [QueryName].YEAR;

Where YEAR is an actual field name that could alter in your circumstances

Altering this to the following will for instance generate years 2000 to 2010 with no gaps even if there are no values in certain columns.

PIVOT [QueryName].YEAR in ("2000","2001","2002","2003","2004","2005","2006","2007","2008","2009","2010");

Access Connection Strings – Link

Some notes on using SQL Server connection strings both using the default ODBC client drivers and also the native SQL Server driver.

Juan Soto of Access Experts explains connection strings from MS Access to SQL Server

Additionally I am hearing that it is better to use the SQL Server Native Client ODBC driver than the default SQL Server driver as a recent security update on SQL Server has caused issue with some applications using the default SQL driver.

Juan Soto of Access Experts explains issues with November 2014 SQL Server update for MS Access applications

OBJECT NAMING CONVENTIONS – My UI Design Patterns

I like to do several things.

Name my tables T001TextVariableInCamelCase incrementing the number
Name my queries Q001TextVariableInCamelCase incrementing the number
Name my forms F001TextVariableInCamelCase incrementing the number

This works well for me – some of my first applications only had 2 numeral positions. I now give new projects 3 digits as the more successful applications generally have more than 100 forms and queries and I like to have everything in order. I have yet to come close to more than 200 forms in an application so I feel confident that 999 is satisfactory. Of course its not a major problem if I go over 999 forms it just doesn’t order so nicely in the database window which tweaks my OCD.

All names have no spaces and are in camel case.

I like to have the forms tagged with this incremental Form Prefix – eg F001 or in the picture below F103. When you have lots of forms it is a lot easier to refer to forms by this unique key. Additionally if you are speaking to a user over the phone you can immediately identify the form that they are on that they may wish additional features on or on which they have found bugs.

These pre-fix conventions are only an implementation of the principles of database design. They are effectively primary keys for differing objects. If I was in the access team I would probably hard wire this feature into the IDE so that code referred to the primary key and text descriptions could be changed retrospectively without breaking links and if possible forms would start with the PKID placed on them. (maybe with the feature available to switch this off)

HIDE MENUS – My UI Design Patterns

Really simple – create a new module and create the following simple functions.

I usually name them TurnMenuOn and TurnMenuOff.

Public Function TurnMenuOn()

   Application.CommandBars("Menu Bar").Enabled = True

End Function

Public Function TurnMenuOff()

   Application.CommandBars("Menu Bar").Enabled = False

End Function

Then you can run at start up by creating a macro that is titled autoexec
And using the Runcode action call the TurnMenuOn() function alternatively you can call it from the form opening.

It is often useful to create a couple of straight Macros that run these functions as well so that when you are in design mode you can quickly run the functions.

A detailed explanation of the Algebraic modeling using SQL for a Race Timing System

Running-a-Race
I know I’ve given an overview of what I did to create the timing system but here is the kernel explanation of the algebra modeled in SQL that does the real heavy lifting in my system and probably most other timing systems as well.

A review of the problem firstly;
The data works on the principle of a chip passing a matt. As the chip passes the matt times are recorded in a timing box which will then be passed through to a database.

Problem is that the timing matt will give multiple readings every time a runner runs across the matt and he or she may be running across multiple times the results only need the first of those passes and not any of the other reads as they are duplicates of a single pass.

If we take a simple example.
Imagine a race with two runners Runner 1 and Runner 2.

Runner 1 and Runner 2 start at the same time 01:00:00 by both passing a matt

Runner 1 finishes across the matt first at 01:30:00
Runner 2 finishes second at 01:35:00

The timing box picks up all following times
ID RFIDtag Time
1 Runner1 01:00:00
2 Runner1 01:00:07
10 Runner2 01:00:00
11 Runner2 01:00:05
15 Runner1 01:30:00
16 Runner1 01:30:10
17 Runner2 01:35:00
18 Runner2 01:35:01
19 Runner2 01:35:02

The first step would be to sort these by the Runner

So that we get
MyTable (sorted first by RFIDtag then by time.
RFIDtag Time
Runner1 01:00:00
Runner1 01:00:07
Runner1 01:30:00
Runner1 01:30:10
Runner2 01:00:00
Runner2 01:00:05
Runner2 01:35:00
Runner2 01:35:01
Runner2 01:35:0

We then need to somehow programmatically or via a query identify times that are relevant against times that are not relevant. So how is this done? Well the trick here is to algebraically model this in SQL using alias tables. You compare the table above against itself and only select for those times where the runner is the same. Here we use a 20 second window or gate and ID field is added back into MyTable (ID field MUST be automatically incremented and unique ie a primary key)

SELECT T1.*
FROM MyTable T1
WHERE EXISTS
(SELECT T2.*
 FROM MyTable T2
 WHERE T2.RFIDtag = T1.RFIDtag
 AND T2.ID   <  T1.ID
 AND T2.Time <= T1.Time
 AND T2.Time >= T1.Time - TimeSerial(0, 0, 20));

In my database application I create a variable for the 20 number which in this example equates to 20 seconds and I have made it easy to alter this period through a simple user interface. I call this period the GATE and it means that for a given race an appropriate gate can be set. In this way the application easily copes with lapped races.

A really beautiful and simple pattern which must be used very widely.

The devil is always in the detail – Setting constants to Russian cyrillics in VB

With programming the devil is always in the detail. Just out of curiosity I was thinking about the code that I have posted that randomises information in a database and I was thinking why don’t I try to randomise the names using the Cyrillic alphabet?

Totally unnecessary I know but what I did discover is that Visual Basic for applications does not support Russian Cyrillics in the coding window and therefore constants cannot be statically set to Russian Cyrillic values.

As ever people have figured out how to get around this omission.

Here’s some code from the net that may help… (haven’t tried it yet)

strString = ChrW(decimal value) & ChrW(decimal value) & ChrW(decimal value) & ChrW(decimal value) & ChrW(decimal value) etc.

http://unicode-table.com/en/

Search for the characters you want then hover over the symbol to see the decimal number.

It of course raises lots of questions- what alphabet do coders in Russia use as a general standard? – I’m sure other IDEs will support non western alphabets but I suspect many programming languages are Latin alphabet centric. Come to think of it that must present quite a challenge for any individuals with a language not based on the latin alphabet wanting to be programmers. Full respect I guess they first need to learn English to really get to grips with programming.

VBA access code – Passing a selection of e-mail addresses to Outlook

This is similar to the previous post except uses the inbuilt SendObject object to pass the string sBcc directly to Outlook. Outlook picks up the variables and so there is no need to set up public variables.

Private Sub CommandGroupEmail_Click()
On Error GoTo Err_CommandGroupEmail_Click

DoCmd.DoMenuItem acFormBar, acRecordsMenu, acSaveRecord, , acMenuVer70

Dim MyDB As DAO.Database
Dim rsEmail As DAO.Recordset
Dim sBcc As String
Dim sSubject As String
Dim sMessageBody As String

Set MyDB = OpenDatabase("\\SERVERNAME\DIRECTORYPATH\" & "TARGET.MDB")
Set rsEmail = MyDB.OpenRecordset("SELECT ... STATEMENT", dbOpenSnapshot)

With rsEmail
.MoveFirst
Do Until rsEmail.EOF
If IsNull(![E-mail]) = False Then
sBcc = sBcc & rsEmail![E-mail] & ";"
sSubject = ""
sMessageBody = ""
End If
.MoveNext
Loop
End With

DoCmd.SendObject , , , , , sBcc , sSubject, sMessageBody, True

Set MyDB = Nothing
Set rsEmail = Nothing

 
Exit_CommandGroupEmail_Click:
    Exit Sub

Err_CommandGroupEmail_Click:
    If (Err = 2467) Or (Err = 91) Or (Err = 2483) Then
      Resume Next
    End If
    Resume Exit_CommandGroupEmail_Click
    
End Sub

VBA access code for pulling together a list of e-mails and passing to a form

Below some standard code that I use to loop through a selection of records and create a string from the individual [Email] s in the below case the e-mails are passed to a form (FORMTOOPEN) – if you are using this then all text in capitals will need to be replaced by application specific information. You want to set up a global variable that is made public which you can pass the combined rsemail (in this case SendBCC) string to between opening up forms.

I tend to have a module called modGlobalVariables and I would put this in

Public SendBcc as string

Remember to be careful with your variable definitions – If you have the same variable dimensioned locally within a command and publically over the whole project values may not appear as expected when you get them.

Private Sub CommandGroupEmail_Click()
On Error GoTo Err_CommandGroupEmail_Click

    DoCmd.DoMenuItem acFormBar, acRecordsMenu, acSaveRecord, , acMenuVer70
    MsgBox "Please note all available e-mails are placed in BCC section of a new form in alphabetical person name order. If a person doesn't have a listed e-mail address he/she will be omitted", , "APPLICATIONNAME"

    Dim MyDB As DAO.Database
    Dim rsEmail as DAO.Database

    Set MyDB = OpenDatabase("\\SERVERNAME\DIRECTORYPATH\" & "TARGET.MDB")
    Set rsEmail = MyDB.OpenRecordset("SELECT STATEMENT HERE"
   
    With rsEmail
    .MoveFirst
    Do Until rsEmail.EOF
    If IsNull(![Email]) = False Then
    SendBcc = SendBcc & rsEmail![Email] & ";"
    MessageSubject = ""
    End If
    .MoveNext
    Loop
    End With
    
    stDocName = "FORMTOOPEN"
    DoCmd.OpenForm stDocName, , , stLinkCriteria
    
    Set MyDB = Nothing
    Set rsEmail = Nothing
    SendBcc = ""

     
Exit_CommandGroupEmail_Click:
    Exit Sub

Err_CommandGroupEmail_Click:

    If Err.Number = 2501 Then
    MsgBox "The e-mail was cancelled without sending", , "APPLICATIONNAME"
    Exit Sub
    
    If Err.Number = 3734 Then
    MsgBox "There are no Records Cancelling", , "APPLICATIONNAME"
    Exit Sub
        
    Else
    
    MsgBox Err.Number
     
    End If
    
    End If
    
    Resume Exit_CommandGroupEmail_Click
    
End Sub

Note on loading of the new form you will need to pass SendBCC into whatever text box you wish to see it in where Me.Bcc is the name of the field receiving the SendBCC string.

Private Sub Form_Load()

 Me.Bcc = SendBcc

End Sub

MS Access SQL for randomising the results from a query

bullish_fractal

I was wanting to randomize the source of a form so that I could test myself on some Russian within a table. Turns out its very simple. Basing a Query on the following SQL will achieve it.

SELECT TableName.PKID, TableName.Field1, TableName.Field2
FROM TableName
ORDER BY rnd(INT(NOW*PKID)-NOW*PKID);

The nice thing about basing a form on this is that every time you open the query it will run a query and give you a complete random list but importantly if you want to navigate through the records in the form it will remember the random order and you can go back and forth in the list and it will be in the order as originally opened.

The interesting thing about this code is that it takes its seed from the time (the function NOW) the next thing I am thinking about doing is making that a definable variable that can be set automatically by the user. As I said I have used the above code to test myself on Russian Vocabulary. I have a dictionary of all the words that I have come across at present. I am given a phrase or word in English and I must type it out in Russian. This was good except when I opened the form it would test me on the table in the same order everytime. Great except I new the first part of the table well and steadily got worse as I went through the table. The table is now so big that I would never sit down and work through the whole table. Randomising the table prior to opening the form solved this but introduced a new problem. Having the same list was useful for building up knowledge of the words it effectively broke the table into a small subset. By having a variable that the user could define they or myself will be able to only move to a random list once I am confident of those words. My thinking is that this will break down what is now quite a large list into smaller parts to learn but I can still use repetition to improve my competency and rate of learning.

Wanting to demonstrate a database and need to scramble the data?

dice

Here’s a nice function I found that will completely randomize information within fields of a database. Data will not be recoverable from this process which of course is its strength.

Good if you are wanting to demonstrate a database to people that normally contains sensitive information but don’t have time to make up your own records.
Works on text fields and will randomize numbers as further numbers and letters as further letters.

Public Function ScrambleID(parmString) As String
    Dim lngLoop As Long
    Const cAlpha As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    Const cNum As String = "0123456789"
    Dim strNewChar As String
    Dim strThisChar As String
    For lngLoop = 1 To Len(parmString)
        strThisChar = Mid$(parmString, lngLoop, 1)
        Do
            Select Case strThisChar
                Case "A" To "Z"
                    strNewChar = Mid$(cAlpha, Int(Rnd * Len(cAlpha)) + 1, 1)
                Case "0" To "9"
                    strNewChar = Mid$(cNum, Int(Rnd * Len(cNum)) + 1, 1)
            End Select
        Loop While strNewChar = strThisChar
        ScrambleID = ScrambleID & strNewChar
    Next
End Function

Keyser Söze, Code and the World Cup

world-cup-fifa_1401359010

It seems appropriate given the name of this site and 2014 being a world cup year that I might post something on code that I found a while back now related to establishing fixtures in a league coded by, for me, a mythical character. Back then I was taking part in a squash league with some friends and we needed to figure out some way of organising the matches for everyone. Seemed simple enough everyone plays everyone else on consecutive weekends. Turns out its not quite so easy and there’s a whole branch of mathematics called combinatronics that specifically looks at the way of optimising this kind of problem. In that really humble syntax of mathematicians I heard this phrase being banded about – a non trivial problem. As ever I turned to the internet and found some code by someone called Dev Ashish. Now I don’t know about you but prior to the internet I never had access to this kinds of expert and the power of the code really blew me away. The code very neatly creates the required number of matches in a table and allows me to organise matches for individuals to play each other and from there I was able to keep scores on everyone. It was in a word a bit of genius coding.

That was approximately 2005 and come 2014 and my blog the world cup reminded me of this amazing piece of coding.
I have my suspicions of where Dev Ashish is now but I can’t tell for sure.

Keyser Söze / Woland / Dev Ashish – they’re out there…

Joking aside many thanks to Dev Ashish for posting an amazing piece of code…

Option Compare Database
Option Explicit

Public intLeagueno As Integer
Public strLeaguenme As String


Function CalculateFixtures(ByVal Age As Integer, ByVal startdate As Date, ByVal EndDate As Date) As Integer

'**************************************************
' Set Database connections and Recordsets Variables
' Coded by Dev Ashish
'**************************************************
Dim cnn As ADODB.Connection
Dim rstTeams As ADODB.Recordset
Dim rstFixtures As ADODB.Recordset
    
'****************************************
' Create Integer Variables
'****************************************
Dim NumberofFixtures As Integer     ' Number of Fixtures between teams
Dim NumberofMatches As Integer      ' Number of Matches to be played
Dim NumberofTeams As Integer        ' Number of Teams
Dim Week As Integer                 ' Week Number for Fixtures

Dim FirstTeam As Integer
Dim LastTeam As Integer

Dim StartPosition As Integer

    Dim strtdate As String
    Dim intMsgbox As Integer
    strtdate = InputBox("Enter the date you want the league to start", "Question?")
    If (strtdate = "") Then
    intMsgbox = MsgBox("Thanks anyway")
    startdate = 3500
    
    Else
    startdate = strtdate
    intMsgbox = MsgBox("Calculating the fixtures starting" & " " & startdate, vbOKOnly, "Result")
    
End If

Dim iCounter As Integer

'****************************************
' Create Player String Variables
'****************************************

Dim Player1 As String
Dim Player2 As String

'****************************************
' Create Team/GameSequence Variables based on Number of Teams
'****************************************
Dim Team(50) As String
Dim GameSequence(50) As String
Dim TeamNames(1 To 50) As String

Set cnn = CurrentProject.Connection
Set rstTeams = New ADODB.Recordset
Set rstFixtures = New ADODB.Recordset



    '*********************************************************
    'Open the Tables Teams and Fixtures
    '*********************************************************
    rstTeams.Open "SELECT * FROM tblTeams Where leagueno = " & intLeagueno & "", cnn, adOpenKeyset, adLockOptimistic
    'Where AgeGroup = 'u" & Age & "'"
    
    rstFixtures.Open "tblFixtures", cnn, adOpenKeyset, adLockOptimistic
    
    '****************************************************
    ' Read the Team Names into an Array
    '****************************************************
    iCounter = 1
    
    Do While Not rstTeams.EOF

        TeamNames(iCounter) = rstTeams.Fields("Team")
        iCounter = iCounter + 1
        rstTeams.MoveNext
    
    Loop
    
    '*****************************
    'Set Main constants
    '*****************************
    NumberofTeams = iCounter - 1
    NumberofFixtures = NumberofTeams - 1
    NumberofMatches = NumberofTeams / 2
    
    '*****************************************************
    ' Clear the Game Sequence Array
    '*****************************************************
    For iCounter = 1 To NumberofFixtures
        GameSequence(iCounter) = ""
    Next iCounter
    
    '*****************************************************
    ' Clear the Teams Array
    '*****************************************************
    For iCounter = 1 To NumberofTeams
        Team(iCounter) = iCounter
    Next iCounter
    
    FirstTeam = 0
    
    '*****************************************************
    ' Create the Game Sequence ready for the fixtures
    '*****************************************************
    For Week = 1 To NumberofFixtures
        FirstTeam = FirstTeam + 1
        
        For iCounter = FirstTeam To FirstTeam + NumberofFixtures - 1
            If iCounter > (NumberofFixtures) Then
                LastTeam = iCounter - NumberofFixtures
            Else
                LastTeam = iCounter
            End If
            GameSequence(Week) = GameSequence(Week) & " " & Format(Team(LastTeam), "00")
        Next iCounter
        GameSequence(Week) = Trim(GameSequence(Week)) + " " & Format(Team(NumberofTeams), "00")
    Next Week
    
    '***************************************************
    'Insert the new fixtures into the Table
    '***************************************************
    For Week = 1 To NumberofFixtures
        StartPosition = 1
        'Debug.Print "Week " & Week
        For iCounter = 1 To NumberofMatches
            Player1 = Mid(GameSequence(Week), StartPosition, 2)
            Player2 = Left(Right(GameSequence(Week), (StartPosition) + 1), 2)
            StartPosition = StartPosition + 3
            
            rstFixtures.AddNew
            rstFixtures.Fields("WeekNo") = Week
            'rstFixtures.Fields("HomeTeam") = TeamNames(HomeTeam)
            rstFixtures.Fields("Player1") = TeamNames(Player1)
            'rstFixtures.Fields("AwayTeam") = TeamNames(AwayTeam)
            rstFixtures.Fields("Player2") = TeamNames(Player2)
            'rstFixtures.Fields("Age") = Age
            rstFixtures.Fields("FixDate") = startdate
            rstFixtures.Fields("Leagueno") = intLeagueno
            rstFixtures.Update
        
        Next iCounter
    startdate = startdate + 7
    If startdate > EndDate Then Week = NumberofFixtures + 1
    Next Week

'****************************************
'Close the tables
'****************************************
rstTeams.Close
Set rstTeams = Nothing
rstFixtures.Close
Set rstFixtures = Nothing

End Function

Step through forms and alter properties.

A nice patch of code that will allow you to cycle through a series of forms and make them read only. Useful if you don’t have immediate access to make changes to the backend, SQL Server or active directory. If you have any programmatic save record commands you will have to deprecate those lines.

Public Sub turnOffFormProps()
Dim strForm As String, db As DAO.Database
Dim doc As DAO.Document
Set db = CurrentDb

For Each doc In db.Containers("Forms").Documents
strForm = doc.Name
DoCmd.OpenForm strForm, acDesign
Debug.Print Forms(strForm).Properties("AllowAdditions")
Forms(strForm).Properties("AllowAdditions") = False
Debug.Print Forms(strForm).Properties("AllowDeletions")
Forms(strForm).Properties("AllowDeletions") = False
DoCmd.Close acForm, strForm, acSaveYes
Next doc

Set doc = Nothing
db.Close
Set db = Nothing
End Sub

Using VBA to open URL in chrome

Haven’t tried this out but could be useful. I have a digital mapping web application that I link to from a database and it has issues with IE but works perfectly in Chrome…

shell("C:\Program Files (x86)\Google\Chrome\Application\chrome.exe -url http:google.ca")

Update : 24 June 2014 – just tested this out on a windows 8.1 device and seems to work

%localappdata%\google 

Code for altering tables on the fly

Most of the time when you are wanting to enter information automatically in fields as a result of a user interaction it is easiest to use some kind of event trigger from the form. Regularly you want to close down a form and alter a field value in a table on a form which is not presently open.

While the events associated with individual fields and the code therein on forms is generally very good at executing code consecutively making it very predictable, it doesn’t always like you calling another field on another form from a different form often producing an error.

In such cases it is better to alter data entry completely programmatically rather than relying on forms to be loaded before altering fields. The code can still be triggered by an event on a form however.
This code looks to a table of Attendees (T008Attendees) on Courses and selects an individual booking based on its reference booking (I have set this to ParameterID).
The letter sent and letter sent date fields are then updated. As many fields as you want could however be updated. Makes for a very nice user experience.

Dim db As DAO.Database
    Dim rst As DAO.Recordset
    Dim strSQL As String

    Set db = CurrentDb()

    strSQL = "SELECT T008Attendees.PKID, T008Attendees.LetterSentDate, T008Attendees.LetterSent FROM T008Attendees WHERE (((T008Attendees.PKID)=" & ParameterID & "));"

    Set rst = db.OpenRecordset(strSQL, dbOpenDynaset)

    With rst

    If .RecordCount > 0 Then
      .MoveFirst
      .Edit
      !LetterSent = 1
      !LetterSentDate = Date
      .Update
    End If

    End With

Attach DSN Less Connection to Link MS Access to SQL Server

link2

This code is generally available all over the tinternet nonetheless I list it here for my own personal use. I use an autoexec macro to trigger the code on open

With a Run Code action to trigger the AttachDSNLessTable…
So the code in the macro might look something like this
AttachDSNLessTable(“Table01Invoices”,”Table01Invoices”,”Server01″,”AccountingDatabase”,””,””)

Alternatively you could run it from the immediate window of the VBA module section.
Ctrl + G to get the immediate window up then create the function with the required parameters placing a question mark in front of the function eg
?AttachDSNLessTable(“Table01Invoices”,”Table01Invoices”,”Server01″,”AccountingDatabase”,””,””)

Pressing return will result in True result and when you go to the tables section Table01Invoices or your table should appear. Note if you have the tables section open of the database window then you will need to refresh.

Honestly works a treat and you can totally revolutionise processes if you are allowed to use the ease of front end design of something like MS Access with the scalability and power of SQL Server.

Needless to say vendors tend to be universally unwilling to give me details of their(/our!) backends.

'//Name     :   AttachDSNLessTable
'//Purpose  :   Create a linked table to SQL Server without using a DSN
'//Parameters
'//     stLocalTableName:Name of the table
'//     stRemoteTableName: Name of the table that you are linking to on the SQL Server database
'//     stServer: Name of the SQL Server that you are linking to
'//     stDatabase: Name of the SQL Server database that you are linking to
'//     stUsername: Name of the SQL Server user who can connect to SQL Server, leave blank to use a Trusted Connection
'//     stPassword: SQL Server user password
Function AttachDSNLessTable(stLocalTableName As String, stRemoteTableName As String, stServer As String, stDatabase As String, Optional stUsername As String, Optional stPassword As String)
    On Error GoTo AttachDSNLessTable_Err
    Dim td As TableDef
    Dim stConnect As String
    
    For Each td In CurrentDb.TableDefs
        If td.Name = stLocalTableName Then
            CurrentDb.TableDefs.Delete stLocalTableName
        End If
    Next
      
    If Len(stUsername) = 0 Then
        '//Use trusted authentication if stUsername is not supplied.
        stConnect = "ODBC;DRIVER=SQL Server;SERVER=" & stServer & ";DATABASE=" & stDatabase & ";Trusted_Connection=Yes"
    Else
        stConnect = "ODBC;DRIVER=SQL Server;SERVER=" & stServer & ";DATABASE=" & stDatabase & ";UID=" & stUsername & ";PWD=" & stPassword
    End If
    Set td = CurrentDb.CreateTableDef(stLocalTableName, dbAttachSavePWD, stRemoteTableName, stConnect)
    CurrentDb.TableDefs.Append td
    AttachDSNLessTable = True
    Exit Function

AttachDSNLessTable_Err:
    
    AttachDSNLessTable = False
    MsgBox "AttachDSNLessTable encountered an unexpected error: " & Err.Description

End Function