Question

Hello I have a table called (Details) hold information about machines (each machine has only one record):

MachineId    PersonId
---------------------
MacA         PersY
MacB         PersX

I have another table (Calls) that holds the Calls for maintenance:

CallId        MachineId
-----------------------
Call01       MacA
Call02       MacB
Call03       MacA

And a third table (Visit) that hold the person the who responded for the call:

CallId        PersonId       Date
--------------------------------
Call01       PersA          15/05/2013
Call02       PersB          20/05/2013
Call03       PersC          25/08/2013

What I need is create one query to update the Details table with the last person who responded to the call, which in this example for MacA is PersC and MacB is PersB

My problem is that I need to do it in Access 2003. I have tried to do a subquery with SELECT TOP 1 PersonId ORDER BY Date DESC and creating a relation between tables (it works in sql server but not here)

Thanks. Have tried:

UPDATE Details SET Details.PersonId = t.Person 
FROM Details INNER JOIN
(SELECT TOP 1 Call.MachineId, Visit.PersonId AS Person 
 FROM Call INNER JOIN Visit ON Call.CallId = Visit.CallId 
 ORDER BY Visit.Date DESC) AS t ON Details.MachineId = t.MachineId;
Was it helpful?

Solution

You could use a SELECT query with a correlated subquery to retrieve the appropriate PersonId for each MachineId.

SELECT
    d.MachineId,
    (
        SELECT TOP 1 v.PersonId
        FROM
            Visit AS v
            INNER JOIN Calls AS c
            ON v.CallId = c.CallId
        WHERE c.MachineId = d.MachineId
        ORDER BY v.Date DESC
    ) AS person_id
FROM Details AS d;

The advantages of this approach are that it is fairly simple and the result would always be consistent whenever the data changes in the Visit and/or Calls tables. In other words, you wouldn't need to execute an UPDATE to see the latest result.

If you have a need to change stored values in Details.PersonId, you can't simply use the correlated subquery directly because Access will complain "Operation must use an updateable query."

OTHER TIPS

The MS Access Jet/ACE db engine does not support UPDATEs via subqueries (among other limitations).

You have three primary options:

Option 1: Temporary Table

Run your subquery as a "Make Table" query first:

SELECT Call.MachineId, Visit.PersonId AS Person INTO temp_T 
FROM Call INNER JOIN Visit ON Call.CallId = Visit.CallId 
ORDER BY Visit.Date DESC

Then use the resulting temporary table to do your update:

UPDATE Details SET Details.PersonId = t.Person 
FROM Details INNER JOIN temp_T AS t ON Details.MachineId = t.MachineId

Option 2: Select Query and Domain Aggregate Function

Create and save a select query and then use a DLookup when setting the value in your UPDATE query. You need to save the select query because the stock DLookup function does not allow you to specify a sort order.

SELECT Call.MachineId, Visit.PersonId 
FROM Call INNER JOIN Visit ON Call.CallId = Visit.CallId 
ORDER BY Visit.Date DESC

Save the above to a query named LastCalledTech. Then change your UPDATE query to:

UPDATE Details SET Details.PersonId = 
    DLookup("PersonID", "LastCalledTech", "MachineID=" & Details.MachineID)

Option 3: Modified DLookup() Custom Function

Allen Browne wrote an "extended" DLookup function that allows you to specify a sort order. I further modified the function slightly to allow passing in an arbitrary SELECT statement (not just a table or query name). We don't actually need the sort parameter in this case because it is more efficient to simply include it in the SELECT SQL string. Using this function (which I've posted below), you would execute the following UPDATE query:

UPDATE Details SET Details.PersonId = 
    ELookup("PersonID", "SELECT Call.MachineId, Visit.PersonId 
                         FROM Call INNER JOIN Visit ON 
                              Call.CallId = Visit.CallId
                         ORDER BY Visit.Date DESC", 
            "MachineID=" & Details.MachineID)

And here is the modified function:

Public Function ELookup(Expr As String, Domain As String, Optional Criteria As Variant, _
    Optional OrderClause As Variant) As Variant
On Error GoTo Err_ELookup
    'Purpose:   Faster and more flexible replacement for DLookup()
    'Arguments: Same as DLookup, with additional Order By option.
    'Return:    Value of the Expr if found, else Null.
    '           Delimited list for multi-value field.
    'Author:    Allen Browne. allen@allenbrowne.com
    'Updated:   December 2006, to handle multi-value fields (Access 2007 and later.)
    '           {by mwolfe02} Add parentheses to allow passing arbitrary SELECT statements
    'Examples:
    '           1. To find the last value, include DESC in the OrderClause, e.g.:
    '               ELookup("[Surname] & [FirstName]", "tblClient", , "ClientID DESC")
    '           2. To find the lowest non-null value of a field, use the Criteria, e.g.:
    '               ELookup("ClientID", "tblClient", "Surname Is Not Null" , "Surname")
    'Note:      Requires a reference to the DAO library.
    Dim db As DAO.Database          'This database.
    Dim rs As DAO.Recordset         'To retrieve the value to find.
    Dim rsMVF As DAO.Recordset      'Child recordset to use for multi-value fields.
    Dim varResult As Variant        'Return value for function.
    Dim strSql As String            'SQL statement.
    Dim strOut As String            'Output string to build up (multi-value field.)
    Dim lngLen As Long              'Length of string.
    Const strcSep = ","             'Separator between items in multi-value list.

    'Initialize to null.
    varResult = Null

    'Build the SQL string.
    strSql = "SELECT TOP 1 " & Expr & " FROM (" & Domain & ")"
    If Not IsMissing(Criteria) Then
        strSql = strSql & " WHERE " & Criteria
    End If
    If Not IsMissing(OrderClause) Then
        strSql = strSql & " ORDER BY " & OrderClause
    End If
    strSql = strSql & ";"

    'Lookup the value.
    Set db = DBEngine(0)(0)
    Set rs = db.OpenRecordset(strSql, dbOpenForwardOnly)
    If rs.RecordCount > 0 Then
        'Will be an object if multi-value field.
        If VarType(rs(0)) = vbObject Then
            Set rsMVF = rs(0).Value
            Do While Not rsMVF.EOF
                If rs(0).Type = 101 Then        'dbAttachment
                    strOut = strOut & rsMVF!FileName & strcSep
                Else
                    strOut = strOut & rsMVF![Value].Value & strcSep
                End If
                rsMVF.MoveNext
            Loop
            'Remove trailing separator.
            lngLen = Len(strOut) - Len(strcSep)
            If lngLen > 0& Then
                varResult = Left(strOut, lngLen)
            End If
            Set rsMVF = Nothing
        Else
            'Not a multi-value field: just return the value.
            varResult = rs(0)
        End If
    End If
    rs.Close

    'Assign the return value.
    ELookup = varResult

Exit_ELookup:
    Set rs = Nothing
    Set db = Nothing
    Exit Function

Err_ELookup:
    MsgBox Err.Description, vbExclamation, "ELookup Error " & Err.number
    Resume Exit_ELookup
End Function
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top