Mail merge query results

garywood84

Registered User.
Local time
Today, 19:47
Joined
Apr 12, 2006
Messages
168
I have a form on which users select fields to include in a query and can select their parameters. The query itself is then created using an SQL statement in the VBA behind the form.

I want to be able to use the records which this query selects through mail merge. With a standard query, this is easy because from within Word I can choose the query through the mail merge settings. However, since the query doesn't exist in this case (it is created every time it's run using VBA) I can't figure out how to do it.

Ideally, I want the form to come up during the mail merge process so that the user can specify which records to use, but I can't select a form as a mail merge source.

Can anyone help?

Gary
 
I don't know much about mailmerge.
Are you using a wizard in Word and browsing for the access database and then selecting a a query from a list.

But you cant select the query cos it's run from the VBA and not saved as a query.

Is that where you are at?
 
Cuttsy,

Yes, that's sort of the problem I've got.

I say "sort of" because there is actually a saved query it's just that there's a problem with using it! Let me explain how what I'm doing works.

There's a basic query saved in my database called "BaseQuery". There's also a form which lets users specify which fields they want in a query, set parameters (or not) for any or all of these fields, and also define how they want the resultant records to be sorted. The "Run" button on this form creates a query string in SQL and feeds this into "BaseQuery", then opens BaseQuery up so the user sees the results.

If the user closes all the windows and then manually opens "BaseQuery" they can still see the results they asked for through the form - unless they have run the form again to define a new set of results.

So, it is entirely possible that the user could define the set of records they want to mail merge, then go into Word and, using the mail merge wizard and selecting BaseQuery as the record source, achieve the results they want.

However, the reason this is not satisfactory is that there will be serveral people using the database. This means that a situation could arise where another user sets up a query in the time period between the first user defining theirs and them running the mail merge - in which case, BaseQuery will be updated to the second users dataset.

Now, explaining this has just made me realise that there could potentially be another issue here, because I don't know what will happen if two users try to create queries using this system at the same time? Such usage may create a problem.

So, there are basically two issues I need to resolve:

1) How can I run the mail merge from the SQL statement in the form (as opposed to selecting the saved query)?

2) How can I ensure that my system will work for multiple users at the same time?

I hope this makes sense and apologise for its length!

Gary
 
Alrighty, Hows about this for an of the top of my head dirty solution

Make a new Module in your VBA called modGetNames and put this code in it.

Code:
Private Declare Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA" (ByVal lpBuffer As String, nSize As Long) As Long

Public Function GetUserName() As String
  
    Dim lpBuff As String * 25, retval As Long
    retval = GetUserName(lpBuff, 25)
    GetUserName= Left(lpBuff, InStr(lpBuff, Chr(0)) - 1)

End Function

Now when you call GetUserName you get a string of the current users windows logon name.

This will allow you to save the quey as the current users name instead of BaseQuery. Thus everyone can have their own result set.

Nasty or what. :eek:
 
Cuttsy,

Why is that nasty? It sounds like a very workable solution to me... I'll have a play with it and let you know when I get stuck!!

One other thing I'm having problems with, which is a related issue -
At the moment, the "Run" button on my form opens the query in a datasheet view. So that the whole thing matches the rest of my database, I want this datasheet to show up as a subform on another form which has our logo on and navigation buttons for the database.

I can create the subform from the saved query and it works, but when the query is updated the subform stops working if the query then has different fields in it, because the fields it still tries to display the old fields, not the ones in the new query.

Is there a way around this?

Gary
 
Cuttsy,

I've been looking at doing what you suggested and have some questions I wonder if anyone can answer:

1) How do I create the new module? (The only way I've created code so far is where I've done it by setting event properties of a button etc).

2) How do I call the appropriate module when I want to name the query in my code which creates the query.

3) To keep the database tidy, I'd like a system to run when the database is closed which checks to see if there are any queries with the current user's name that were created before today's date and, if so, deletes them (without any user prompting).

Thanks in advance for your help.

Cheers,

Gary
 
1) When you are in the code window click on the Insert menu at the top and then click Module

2)If you put the code in that I gave you then you have a function called GetUserName.
To call this function you would do something like this

Code:
Dim sUserName as String
sUserName = GetUserName

That gives you a string variable with the current user name as its value.
 
Last edited:
garywood84 said:
I can create the subform from the saved query and it works, but when the query is updated the subform stops working

Do you requery the subform after you update the query?
 
Cuttsy,

I have now followed your instructions to create the module. I've not yet made for form use the module to find a name for the query but have added the references to the top of the VBA code to get the username. For some reason, when I try and run the VBA now, I get an error:
Compile Error
Ambiguous name detected: GetUserName​

At the moment, the VBA references the saved query which it updates. This means that using my current code, I'll have to ensure there's a saved query named the same as every username in my database, and, whenever I add a new user, create a query for them as well as their user account. Is there any way I can add code to check if a query called the current users' name exists and, if not, create one at the start of the code (possibly by copying and renaming a "template" query if necessary)?

Re requerying the subform after the query has changed - I think so, in that I'm not leaving the form open but am closing it then reopening it (and I assume that when it's opened it's requeried?) That said, the way this works will change if there's a query for each user. I'll need a way to create a subform which displays a datasheet view of the query results for the current user... any ideas how I can do that?!!

Thanks again for your assistance with this; I greatly appreciate it and am sorry to ask so many questions: VBA = very steep learning curve!!!

Cheers,

Gary
 
Last edited:
garywood84 said:
Ambiguous name detected: GetUserName

My fault.
I have given you code that contains 2 function which both have the same name and it is confusing things somewhat.

Replace the code in the module with this:

Code:
Private Declare Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA" (ByVal lpBuffer As String, nSize As Long) As Long

Public Function CurrentUserName() As String
  
    Dim lpBuff As String * 25, retval As Long
    retval = GetUserName(lpBuff, 25)
    CurrentUserName= Left(lpBuff, InStr(lpBuff, Chr(0)) - 1)

End Function
 
garywood84 said:
This means that using my current code, I'll have to ensure there's a saved query named the same as every username in my database, and, whenever I add a new user, create a query for them as well as their user account.

no no no no no

garywood84 said:
Is there any way I can add code to check if a query called the current users' name exists and, if not, create one at the start of the code (possibly by copying and renaming a "template" query if necessary)?

oh yes... sort of



Show me the code you have now for running the base query.
 
Cuttsy,

Here's the code I have at the moment:

Code:
Private Sub cmdOK_Click()
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim varItem As Variant
Dim strCriteria As String
Dim strCriteriaCtr As String
Dim strSortOrder As String
Dim strFieldList As String
Dim strSQL As String

Set db = CurrentDb()
Set qdf = db.QueryDefs("Test")


'Build Criteria String
If Me!lstAB.ItemsSelected.Count > 0 Then
    For Each varItem In Me!lstAB.ItemsSelected
        strCriteria = strCriteria & "Centres.[Area Board] = " & Chr(34) _
            & Me!lstAB.ItemData(varItem) & Chr(34) & "OR "
    Next varItem
    strCriteria = Left(strCriteria, Len(strCriteria) - 3)
Else
    strCriteria = "Centres.[Area Board] Like '*'"
End If

If Me!lstCtrType.ItemsSelected.Count > 0 Then
    For Each varItem In Me!lstCtrType.ItemsSelected
        strCriteriaCtr = strCriteriaCtr & "Centres.[Centre Type] = " & Chr(34) _
            & Me!lstCtrType.ItemData(varItem) & Chr(34) & "OR "
    Next varItem
    strCriteriaCtr = Left(strCriteriaCtr, Len(strCriteriaCtr) - 3)
Else
    strCriteriaCtr = "Centres.[Centre Type] Like '*'"
End If


'Build sort order code
If Me.cboSort1.Value <> "Not sorted" Then
    strSortOrder = " ORDER BY Centres.[" & Me.cboSort1.Value & "]"
    If Me.cboSort2.Value <> "Not sorted" Then
        strSortOrder = strSortOrder & ",centres.[" & Me.cboSort2.Value & "]"
        If Me.cboSort3.Value <> "Not sorted" Then
            strSortOrder = strSortOrder & ",centres.[" & Me.cboSort3.Value & "]"
        End If
    End If
Else
    strSortOrder = ""
End If
              
              
'Build Field List
strFieldList = "Centres."
If Me!lstFieldList.ItemsSelected.Count > 0 Then
    For Each varItem In Me!lstFieldList.ItemsSelected
        strFieldList = strFieldList & "[" & Me!lstFieldList.ItemData(varItem) & "], "
    Next varItem
    strFieldList = Left(strFieldList, Len(strFieldList) - 2)
Else
    strFieldList = "*"
End If

     
strSQL = "SELECT " & strFieldList & " FROM Centres " & _
    "Where " & strCriteria & _
    " And " & strCriteriaCtr & strSortOrder & ";"
    

qdf.SQL = strSQL
DoCmd.OpenQuery "Test"

Set db = Nothing
Set qdf = Nothing

End Sub

Another possibility which I'm wondering about is splitting the database into a front and back end - so the the actual data could be on the network drive and all users could have their own front end. This would then let them modify the base query, but only their copy.

What do you think of this as an idea? I wonder if it woud be easier and provide more flexibility for changes in the future as the database grows?

Thanks,

Gary
 
Okay give this the once over

Most of your code stays the same
I have removed the line
Code:
Set qdf = db.QueryDefs("Test")
And I have added the On error line at the top.

Code:
Private Sub cmdOK_Click()

    On Error GoTo Err_BuildQuery

    Dim db As DAO.Database
    Dim qdf As DAO.QueryDef
    Dim varItem As Variant
    Dim strCriteria As String
    Dim strCriteriaCtr As String
    Dim strSortOrder As String
    Dim strFieldList As String
    Dim strSQL As String

    Set db = CurrentDb()

    'Build Criteria String


Now keep your string building part of the code unchanged but replace this code

Code:
qdf.SQL = strSQL
DoCmd.OpenQuery "Test"

Set db = Nothing
Set qdf = Nothing

End Sub

With this code.

Code:
    On Error Resume Next
    db.QueryDefs.Delete CurrentUserName

    On Error GoTo Err_BuildQuery
    Set qdf = db.CreateQueryDef(CurrentUserName, strSQL)
    
Exit_BuildQuery:
    Set qdf = Nothing
    Set db = Nothing
    Exit Sub
    
Err_BuildQuery:
    MsgBox Err.Description, vbExclamation, "Error #" & Err.Number
    Resume Exit_BuildQuery

End Sub

Give that a whirl and tell me what error you get ;)
 
Thanks Cuttsy and apologies for the late reply. I have been very busy with other work and not looked at my database for a few days. I've copied your code and will try it out over the weekend.

Please watch this space for further questions when I get stuck!!

Cheers,

Gary
 
Cuttsy,

I have just spent some time looking at the code you posted. Unfortunately, a change I made to the code after posting the above code for you to look at has left me confused about how to implement your code. I'm really sorry for this but would be grateful if you could advise me further as once I get this working, my database will be finished!

I needed to change the code because I didn't actually want the results of the query created by the form to appear in a standard query datasheet. Instead, I need the datasheet to be displayed as a subform on a main form, so that I can have a logo and database navigation buttons above the results datasheet (by having them on the main form).

I have successfully achieved this, but doing so has changed the code so that I'm not sure now how to integrate your code. The code I now have is below and I'm also attaching a cut down version of my database, with sample data, so you can have a look at it if that helps.

Many thanks in advance for your help and I'm sorry to have moved the goal posts before you posted back!

Cheers,

Gary

Code now in place:
Code:
Private Sub cmdOK_Click()
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim varItem As Variant
Dim strCriteria As String
Dim strCriteriaCtr As String
Dim strSortOrder As String
Dim strFieldList As String
Dim strSQL As String
Dim frm As Form
Dim subfrm As Control

Set db = CurrentDb()
Set qdf = db.QueryDefs("BaseQuery")


'Build Criteria String
If Me!lstAB.ItemsSelected.Count > 0 Then
    For Each varItem In Me!lstAB.ItemsSelected
        strCriteria = strCriteria & "Centres.[Area Board] = " & Chr(34) _
            & Me!lstAB.ItemData(varItem) & Chr(34) & "OR "
    Next varItem
    strCriteria = Left(strCriteria, Len(strCriteria) - 3)
Else
    strCriteria = "Centres.[Area Board] Like '*'"
End If

If Me!lstCtrType.ItemsSelected.Count > 0 Then
    For Each varItem In Me!lstCtrType.ItemsSelected
        strCriteriaCtr = strCriteriaCtr & "Centres.[Centre Type] = " & Chr(34) _
            & Me!lstCtrType.ItemData(varItem) & Chr(34) & "OR "
    Next varItem
    strCriteriaCtr = Left(strCriteriaCtr, Len(strCriteriaCtr) - 3)
Else
    strCriteriaCtr = "Centres.[Centre Type] Like '*'"
End If


'Build sort order code
If Me.cboSort1.Value <> "Not sorted" Then
    strSortOrder = " ORDER BY Centres.[" & Me.cboSort1.Value & "]"
    If Me.cboSort2.Value <> "Not sorted" Then
        strSortOrder = strSortOrder & ",centres.[" & Me.cboSort2.Value & "]"
        If Me.cboSort3.Value <> "Not sorted" Then
            strSortOrder = strSortOrder & ",centres.[" & Me.cboSort3.Value & "]"
        End If
    End If
Else
    strSortOrder = ""
End If
              
              
'Build Field List
strFieldList = "Centres."
If Me!lstFieldList.ItemsSelected.Count > 0 Then
    For Each varItem In Me!lstFieldList.ItemsSelected
        strFieldList = strFieldList & "[" & Me!lstFieldList.ItemData(varItem) & "], "
    Next varItem
    strFieldList = Left(strFieldList, Len(strFieldList) - 2)
Else
    strFieldList = "*"
End If

     
strSQL = "SELECT " & strFieldList & " FROM Centres " & _
    "Where " & strCriteria & _
    " And " & strCriteriaCtr & strSortOrder & ";"
    

qdf.SQL = strSQL




Call fDelete_Form                                       'If subform already exists, delete it
Set frm = CreateForm()                                  'Create subform in memory
With frm
    .Caption = "My Form"
    .RecordSource = "BaseQuery"
    .DefaultView = 2                                    'Datasheet View
    .RecordSelectors = False
   
End With
Call fGet_Result_Columns                                'Determine which fields will be populating the subform
DoCmd.Save
DoCmd.Close acForm, "form1", acSaveYes                  'Needs to be unloaded to place it on the parent form
Set frm = Nothing
DoCmd.OpenForm "Query Results", acNormal                 'Open parent form
Set subfrm = Forms![Query Results]!subfrmResults
subfrm.SourceObject = "form1"
DoCmd.Save
Set subfrm = Nothing




Set db = Nothing
Set qdf = Nothing

End Sub

Private Sub Form_Current()

End Sub



Public Function fGet_Result_Columns()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim qry As DAO.QueryDef
Dim fldcount, i As Long
Set db = CurrentDb
Dim ctl As Control
Set rs = db.OpenRecordset("SELECT TOP 1 * FROM BaseQuery")       'Get a small recordset
fldcount = rs.Fields.Count                                       'so that we can Count
For Each qry In db.QueryDefs                                     'Column Headings
    If qry.Name = "BaseQuery" Then                               'Find the query, get the Column Names
        For i = 0 To (fldcount - 1)
            Set ctl = CreateControl("form1", acTextBox, acDetail, , _
                db.QueryDefs("BaseQuery").Fields(i).Name)        'Create controls equal to
            ctl.Name = db.QueryDefs("BaseQuery").Fields(i).Name  'Number of query fields
            ctl.Visible = True
        Next
    End If
Next
Set ctl = Nothing
Set rs = Nothing
Set db = Nothing
End Function


Public Function fDelete_Form()
'Find subform if it exists by looping through the forms collection and then delete it
Dim frm As Object
For Each frm In Application.CurrentProject.AllForms
    If frm.Name = "form1" Then
        DoCmd.DeleteObject acForm, "form1"
        Exit For
    End If
Next
Set frm = Nothing
End Function
 

Attachments

Oh, and a quick update to find the User Name - as GHudson has posted several times - this is much shorter and easier to use than an API:
Code:
strUserName = Environ("username")
 
I've been playing with this some more today and now have code that, instead of updating the SQL in a saved query, creates a query, saves it with the same name as the current user and then opens this as a subform on the results form.

The problem I now have is that, for the same reason I needed the query to be called the current users' name, I need the subform to be called that too. So far, the code calls every subform it makes "form1", overwriting "form1" if it already exists.

The code I'm using is below, but also a sample database is attached. Can anyone advise me how I can modify this so that the subform created to show results is called frmCurrentUser (i.e. frmFatherChristmas if the current user is FatherChristmas)?

Many thanks in advance,

Gary

Code:
Option Compare Database

Private Sub Command3_Click()

End Sub


Private Sub cboSort1_BeforeUpdate(Cancel As Integer)
'Check if sort field has already been chosen
    If Me.cboSort1.Value <> "Not sorted" Then
        If Me.cboSort1.Value = Me.cboSort2.Value _
        Or Me.cboSort1.Value = Me.cboSort3.Value Then
            MsgBox "You have already chosen that item."
            Cancel = True
            Me.cboSort1.Dropdown
        End If
    End If

End Sub

Private Sub cboSort1_Change()
'Disable following sort options if "Not sorted" is chosen
    If Me.cboSort1.Value = "Not sorted" Then
        With Me.cboSort2
            .Enabled = False
            .Value = "Not sorted"
        End With
        With Me.cboSort3
            .Enabled = False
            .Value = "Not sorted"
        End With
    Else
        Me.cboSort2.Enabled = True
    End If
End Sub

Private Sub cboSort2_BeforeUpdate(Cancel As Integer)
'Check if sort field has already been chosen
    If Me.cboSort1.Value <> "Not sorted" Then
        If Me.cboSort1.Value = Me.cboSort2.Value _
        Or Me.cboSort1.Value = Me.cboSort3.Value Then
            MsgBox "You have already chosen that item."
            Cancel = True
            Me.cboSort2.Dropdown
        End If
    End If
End Sub

Private Sub cboSort2_Change()
'Disable following sort options if "Not sorted" is chosen
    If Me.cboSort2.Value = "Not sorted" Then
        With Me.cboSort3
            .Enabled = False
            .Value = "Not sorted"
        End With
    Else
        Me.cboSort3.Enabled = True
    End If
End Sub

Private Sub cboSort3_BeforeUpdate(Cancel As Integer)
'Check if sort  field has already been chosen
    If Me.cboSort1.Value <> "Not sorted" Then
        If Me.cboSort1.Value = Me.cboSort3.Value _
        Or Me.cboSort2.Value = Me.cboSort3.Value Then
            MsgBox "You have already chosen that item."
            Cancel = True
            Me.cboSort3.Dropdown
        End If
    End If

End Sub

Private Sub cmdCancel_Click()
DoCmd.Close acForm, "DIAQRY_BaseQuery"
End Sub

Private Sub cmdOK_Click()

On Error Resume Next

Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim varItem As Variant
Dim strCriteria As String
Dim strCriteriaCtr As String
Dim strSortOrder As String
Dim strFieldList As String
Dim strSQL As String
Dim frm As Form
Dim subfrm As Control
Dim strQueryName As String
Dim strUserName As String

Set db = CurrentDb()
'Set qdf = db.QueryDefs("BaseQuery")
strUserName = Environ("username")
strQueryName = strUserName


'Build Criteria String
If Me!lstAB.ItemsSelected.Count > 0 Then
    For Each varItem In Me!lstAB.ItemsSelected
        strCriteria = strCriteria & "Centres.[Area Board] = " & Chr(34) _
            & Me!lstAB.ItemData(varItem) & Chr(34) & "OR "
    Next varItem
    strCriteria = Left(strCriteria, Len(strCriteria) - 3)
Else
    strCriteria = "Centres.[Area Board] Like '*'"
End If

If Me!lstCtrType.ItemsSelected.Count > 0 Then
    For Each varItem In Me!lstCtrType.ItemsSelected
        strCriteriaCtr = strCriteriaCtr & "Centres.[Centre Type] = " & Chr(34) _
            & Me!lstCtrType.ItemData(varItem) & Chr(34) & "OR "
    Next varItem
    strCriteriaCtr = Left(strCriteriaCtr, Len(strCriteriaCtr) - 3)
Else
    strCriteriaCtr = "Centres.[Centre Type] Like '*'"
End If


'Build sort order code
If Me.cboSort1.Value <> "Not sorted" Then
    strSortOrder = " ORDER BY Centres.[" & Me.cboSort1.Value & "]"
    If Me.cboSort2.Value <> "Not sorted" Then
        strSortOrder = strSortOrder & ",centres.[" & Me.cboSort2.Value & "]"
        If Me.cboSort3.Value <> "Not sorted" Then
            strSortOrder = strSortOrder & ",centres.[" & Me.cboSort3.Value & "]"
        End If
    End If
Else
    strSortOrder = ""
End If
              
              
'Build Field List
strFieldList = "Centres."
If Me!lstFieldList.ItemsSelected.Count > 0 Then
    For Each varItem In Me!lstFieldList.ItemsSelected
        strFieldList = strFieldList & "[" & Me!lstFieldList.ItemData(varItem) & "], "
    Next varItem
    strFieldList = Left(strFieldList, Len(strFieldList) - 2)
Else
    strFieldList = "*"
End If

     
strSQL = "SELECT " & strFieldList & " FROM Centres " & _
    "Where " & strCriteria & _
    " And " & strCriteriaCtr & strSortOrder & ";"
    
db.QueryDefs.Delete strQueryName
qdf.SQL = strSQL
Set qdf = db.CreateQueryDef(strUserName, strSQL)

Call fDelete_Form                                       'If subform already exists, delete it
Set frm = CreateForm()                                  'Create subform in memory
With frm
    .Caption = "My Form"
    .RecordSource = "BaseQuery"
    .DefaultView = 2                                    'Datasheet View
    .RecordSelectors = False
End With
Call fGet_Result_Columns                                'Determine which fields will be populating the subform
DoCmd.Save
DoCmd.Close acForm, "form1", acSaveYes                  'Needs to be unloaded to place it on the parent form
Set frm = Nothing
DoCmd.OpenForm "Query Results", acNormal                 'Open parent form
Set subfrm = Forms![Query Results]!subfrmResults
subfrm.SourceObject = "form1"
DoCmd.Save
Set subfrm = Nothing




Set db = Nothing
Set qdf = Nothing

End Sub

Private Sub Form_Current()

End Sub
Public Function fGet_Result_Columns()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim qry As DAO.QueryDef
Dim fldcount, i As Long
Set db = CurrentDb
Dim ctl As Control
Set rs = db.OpenRecordset("SELECT TOP 1 * FROM BaseQuery")       'Get a small recordset
fldcount = rs.Fields.Count                                       'so that we can Count
For Each qry In db.QueryDefs                                     'Column Headings
    If qry.Name = "BaseQuery" Then                               'Find the query, get the Column Names
        For i = 0 To (fldcount - 1)
            Set ctl = CreateControl("form1", acTextBox, acDetail, , _
                db.QueryDefs("BaseQuery").Fields(i).Name)        'Create controls equal to
            ctl.Name = db.QueryDefs("BaseQuery").Fields(i).Name  'Number of query fields
            ctl.Visible = True
        Next
    End If
Next
Set ctl = Nothing
Set rs = Nothing
Set db = Nothing
End Function


Public Function fDelete_Form()
'Find subform if it exists by looping through the forms collection and then delete it
Dim frm As Object
For Each frm In Application.CurrentProject.AllForms
    If frm.Name = "form1" Then
        DoCmd.DeleteObject acForm, "form1"
        Exit For
    End If
Next
Set frm = Nothing
End Function
 

Attachments

Users who are viewing this thread

Back
Top Bottom